Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,13 @@ Organized by domain (model line / subsystem / playbook / lesson) instead of by l
| `models/qwen3/serving-perf-5090.md` | Tuning history behind the serving numbers: unified-step attention fusion, batched step tail (#345), chunked prefill, cuBLAS 12.9 N=1025 cliff, cublasLt per-shape tuning, split-KV ≤bs32. Latest data lives in `serving-performance.md`. |
| `models/qwen3/decode-attention.md` | Decode attention path (`NonPartition` vs `SplitKv`) is chosen by **batch (CTA-vs-SM), not context**: the old `max_seq_len>=1024` gate stranded bs=1 mid-context decode on the SM-starved NonPartition kernel — a tpot hump peaking ~ctx800, cliff-dropping at ctx1024. Removing it flattens bs=1 tpot (5090 −16% / 5070 Ti −7.5% @ctx800); kept `padded_bs<=32` (bs≤8 wins big, bs16 even, bs32 <1% loss). Also records the SplitKv chunk-size/grid policy (`Tuned` adaptive vs `Pin`/`PerToken` fixed-split batch-invariance, #435/#438). Two-card A/B + CUDA-graph capture + golden-gate verified. |
| `models/qwen3/green-ctx-sm-partition.md` | Green Context SM partition (`OPENINFER_SM_PARTITION=20`) runs prefill/decode on disjoint SMs so decode stops stalling behind co-scheduled prefill: 5090 mid-band ITL p99 ~halved, TPOT down (−22% @QPS12), but TTFT 2–4× worse (prefill deferred + fewer SMs) — a TTFT↔ITL/TPOT trade, not a free win. Two-graph change (decode CUDA graph captured on the green decode stream) adds ~5% ITL p99 / 1–4% TPOT on top. Mechanism, A/B table, Xid-31/gemm_lt pitfalls. |
| `models/qwen3/roadmap.md` | Qwen3-4B roadmap (2026-06 review): line is the maturity bar; #220 RoPE OOB, batched greedy sampling (#307), mixed greedy/non-greedy sampling (#284), and pegaflow KV offload (#316) are landed; open set is zero TP coverage, zero-adapter-only LoRA gate, dropped prefix-cache observability, stale docs, and YaRN #8 follow-up. |
| `models/qwen3/model-crate.md` | `openinfer-qwen3` owns Qwen3 config/weights/executor/scheduler/tests/kernel plan; root sees generic `EngineHandle`; split-K decode gated on `padded_bs<=32` (64-token `Tuned` floor, cap 64 chunks; `Pin`/`PerToken` fixed 160-token split), 4k/64 serving TPOT p50 `6.46ms` on RTX 5090. |
| `models/qwen3/roadmap.md` | Qwen3-4B maturity roadmap: real-adapter LoRA accuracy and per-request cached-token propagation are gated; open qualification work is TP numerical parity, aggregate prefix-cache telemetry, LoRA cache-salt isolation, and YaRN #8. |
| `models/qwen3/model-crate.md` | Current Qwen3 crate layout and ownership: model policy/execution in `openinfer-qwen3`, reusable GPU operations in `openinfer-kernels`, generic engine contracts below, and HTTP/model selection in `openinfer-server`. |
| `models/qwen3/prefix-cache.md` | Prefix caching on by default for Qwen3-4B: full-block kvbm radix matching at the executor, suffix-only prefill. Repeated ~1900-token prompt TTFT 141.8 → 16.3ms p50 (8.7×); warm TTFT ≈ TPOT + ~5ms setup. Includes the RoPE scalar-path corruption fix and the drain-the-stream TTFT measurement pitfall. |
| `models/qwen3/dspark-integration.md` | DeepSeek **DSpark** Phase 1 is implemented for Qwen3-4B: DFlash backbone + rank-256 Markov head, anchor-first DeepSpec layout, one strided argmax-with-bias kernel, PDL polish, and one D2H per draft block. Greedy losslessness passes; 5090 block7 A/B vs matched DFlash shows DSpark +3.6% geomean output tok/s overall (+3–16% on text/code, random synthetic exception) and better accepted-draft distribution (2.52 vs 2.30 draft tokens/round). |
| `models/qwen3/dflash-speculative-decoding.md` | DFlash speculative decoding behind `--dflash-draft-model-path`, modelled as an optimistic transaction (propose K → verify K+1 span → accept longest argmax prefix + 1 bonus → commit/roll back KV). Lossless up to bf16 tie-flips (bit-identical multi-token accepts; lm-eval gsm8k strict-match identical spec on/off). Single-stream decode 1.82× on 5070 Ti, 1.56× on 5090. Concurrent throughput fixed by batching the draft forward, then a piecewise verify CUDA Graph (dense ops captured, attention eager) closed single-stream: 5090 greedy c1 274 ≈ vLLM 278, c8 1525 > 1240, c16 1834 ≈ 1846 — all batch sizes now ≥ vLLM. Accept measured equal (9.1% vs 8.85%, same drafter); draft-side piecewise graph tracked next. Proposer trait deferred to EAGLE. |
| `models/qwen3/accuracy-gate.md` | Qwen3-4B instance of the logits golden gate (`tests/hf_golden_gate.rs`): 48 teacher-forced sequences / 816 positions vs a stored HF bf16 golden, replayed over bs=1 / batched eager / CUDA-graph. Strict guards: regret check + mean ≤ 0.06 + p99 ≤ 0.20; absolute max printed but not asserted (coverage-unstable). Methodology in `subsystems/correctness/`. |
| `models/qwen3/kernels-crate.md` | Phase 1 split implemented and 5090-verified: Qwen3-4B kernel surface lives in `openinfer-kernels`; release build, test-target compile, accuracy gate, and bench snapshot pass. |
| `models/qwen3/tp-design.md` | Qwen3 tensor-parallel design: `TP=2` milestone scope plus the controller/worker broadcast execution model, request identity, and coarse-grained step protocol for future TP/MoE work. |
| `models/qwen3/kv-pressure-hang.md` | Issue #85 Qwen3-4B KV pressure hang fixed by full-lifetime scheduler KV admission, waiting-queue deferral, cleanup on disconnect/error, impossible-request errors, scheduler/bridge gates, and real `vllm bench serve` QPS=2 `500/500` pass with post-pressure completion healthy. |
| `models/qwen3/tp-design.md` | Implemented Qwen3 TP runtime: rank-local workers, controller-broadcast steps, sharded attention/MLP with NCCL reductions, startup CUDA Graph pre-capture, current coverage, and remaining numerical/vocab-parallel work. |
| `models/qwen3/pd-disaggregation-m2.md` | P/D 分离 M2 **已端到端验证**(单机 2×H200 + 400G IB):Qwen3-8B 1P+1D,KV 经 pegaflow metaserver P2P(RDMA READ)流转,greedy 输出与单实例逐 token 一致,杀 metaserver/P 优雅退化;多轮并发压测已过(含 router `max_completion_tokens` 坑)。openinfer `feat/pd-pegaflow-p2p` + pegaflow PR #381。RemoteFetch 状态机单测欠账;M3 layer-wise push 延后。 |

## models / qwen35
Expand Down Expand Up @@ -181,6 +179,7 @@ Organized by domain (model line / subsystem / playbook / lesson) instead of by l
| `lessons/moe-dplb-decode-imbalance.md` | DPLB lesson for future PegaFlow/WiDeep MoE+EP serving: decode-side DP imbalance is a sticky KV-state problem; engines should emit raw progress while external router/proxy derive load and routing. |
| `lessons/moe-zero-prefill-long-prefill.md` | ZeRO-Prefill lesson for future long-prefill MoE serving: once a router selects long-P work, maximize batch throughput by preserving compute-bound execution, hiding expert-weight movement, respecting KV handoff boundaries, and measuring bottlenecks before committing to an AsyncEP-style backend. |
| `lessons/exact-match-gate-thread-cublas.md` | Two durable lessons from a Qwen3.5 e2e gibberish bug: worker threads that run a model must rebind the CUDA context and init thread-local cuBLAS handles, and exact-match greedy gates are sensitive to equal-logit top1 choices (keep a single FlashInfer selector). |
| `lessons/kv-full-lifetime-admission.md` | Scheduler invariant for paged KV without preemption: reserve each admitted request's peak block-manager footprint, defer temporary pressure, explicitly reject impossible requests, clean up every terminal path, and prove recovery with a post-pressure completion. |
| `lessons/kimi-bringup-numerics.md` | Three MoE+TP greedy-parity / reporting lessons from Kimi-K2 bring-up, reusable on any MoE+TP decode engine gated on token-id parity: reduce hidden states in F32 not BF16 (BF16 bulk all-reduce silently breaks greedy); don't merge shared+routed expert reduce into one collective (breaks cold-batch greedy); always report p50+p99, never just mean (tail dominates on barrier-synced MoE+EP decode). |
| `lessons/cuda-green-contexts.md` | Local mirror of NVIDIA CUDA 13.1+ Green Contexts guide (§4.6): static SM/workqueue partitioning via runtime execution contexts; host-only changes, no kernel edits. Generated by `scripts/html_to_md.py`. |

Expand Down
62 changes: 62 additions & 0 deletions docs/lessons/kv-full-lifetime-admission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Full-Lifetime KV Admission

> **TL;DR:** Without preemption, admitting a request means reserving its peak physical KV footprint across the block manager's full request lifecycle, not only its prompt or final committed token count. Temporarily over-budget requests wait; requests that can never fit are rejected explicitly; every terminal path releases request-owned state. Validate both pressure behavior and a post-pressure completion, because a server can remain reachable while generation is permanently wedged.
>
> **Last touched:** 2026-07

This lesson was extracted from the Qwen3 issue #85 KV-pressure hang, but the invariant applies to any paged-KV scheduler without preemption.

## The invariant

A scheduler must not admit more potential KV growth than the pool can satisfy:

```text
reserved_peak_footprint(active requests)
+ peak_kv_footprint(new request)
<= usable_pool_capacity
```

Prefill-only admission is unsafe. Several requests can all fit their prompts, enter decode, cross page boundaries together, and then fail to allocate. If failure cleanup is incomplete, pages remain pinned and later requests wait forever.

Full-lifetime reservation is conservative: a request may stop early and use less than its reservation. Until the scheduler supports preemption or another recoverable overcommit policy, that lost concurrency is the cost of guaranteeing progress.

## Measure the block-manager peak

Derive the budget from both the model's state transition and the block manager's allocation lifecycle. Do not assume every sampled token is immediately present in KV, or that the final committed-token count is the peak number of physical pages held.

For Qwen3, prefill writes the prompt and returns the first sampled output. The request commits at most `P + N - 1` KV positions for prompt length `P` and completion limit `N`, but kvbm's `schedule_decode` can provision the next decode block before the final input token is applied. A multi-token request can therefore hold `ceil((P + N) / block_size)` blocks at its peak; a one-token completion never schedules decode and only needs the prompt footprint. The scheduler's boundary tests compare this reservation against the real block-pool peak and cover cases where the older `P + N - 1` formula is short by one page.

This allocator-specific peak is not universal. Qwen3.5's current state machine and KV pool reserve from `P + N - 1`; other models may preallocate, append, or retire pages at different points. Encode the formula beside the owning scheduler and test it against the actual allocator lifecycle.

Round the resulting token count through the actual page geometry. Boundary tests should pin cases just below, exactly at, and just above a page transition; otherwise an off-by-one can hide behind page rounding.

## Three admission outcomes

1. **Admit:** the request's worst-case lifetime fits after active reservations.
2. **Defer:** it fits in an empty instance but not beside current work. Keep it waiting and retry after capacity is released.
3. **Reject:** its worst-case lifetime exceeds the instance's total usable capacity. Return an explicit request error so it cannot sit at the head of the queue forever.

Do not turn rejection into an empty successful response. The frontend must preserve the engine's error semantics and message.

## Cleanup is part of admission correctness

KV pages are usually returned through ownership/RAII only after all request state is dropped. Audit every terminal edge, not only the successful finish:

- normal length/EOS completion;
- client or receiver disconnect;
- prefill/decode/unified execution error;
- explicit rejection or cancellation;
- scheduler shutdown and worker failure.

A useful owner API is a single `drop_request(request_id)` path that removes executor state and releases the final page references. Error handling should report the terminal event and invoke the same owner cleanup.

## Verification pattern

Use layers of evidence:

- Unit-test admission with a fake executor and a small page pool. Cover impossible rejection, temporary deferral followed by admission, page boundaries, execution errors, and disconnect cleanup.
- Run a real serving workload that creates KV pressure. Assert every request completes or fails explicitly within a deadline; throughput is a separate claim.
- Immediately send a small post-pressure generation request. Health/model-list endpoints are insufficient because the original failure mode can leave the process alive while completions hang.
- Keep a deadline in concurrent tests. A deadlock without a deadline only wedges CI.

The decisive property is recovery: after pressure and failures, capacity becomes reusable and unrelated requests can still make progress.
Loading
Loading