Repository navigation
feat: add native physical KV reservation APIs - #487
Conversation
a885b66 to
fe09783
Compare
fe09783 to
83fbfc7
Compare
53925d6 to
0e1ec05
Compare
|
/ci run |
|
GPU CI (basic) on 0e1ec05, requested by @shipiyouniao: run |
Basic correctness · 2026-10-14 · PASSRun · kvcached 0e1ec05 (merged into main at a7dfbfe) All 22 cases passed. Setup of every caseOn request in a pull request (gpu-pr.yml) on 2x NVIDIA L4 (23034 MiB, driver 580.178.04); the SGLang and vLLM cases run at the same time, one GPU each. kvcached is installed from the pull request merged into main into each engine's official image; each case starts one server in it.
CasesEach kvcached case is compared with the native case of the same engine and model.
Each case:
Elastic tests
Limit tests
Logs: artifact, kept 30 days: serve.log and cmd.txt per case, summary.json with every request's tokens, setup.json, run.log, GPU state at the end. |
Co-authored-by: 石皮幼鸟 <2960474346@qq.com>
Co-authored-by: 石皮幼鸟 <2960474346@qq.com>
|
@RixinLiu @zou-zhiheng could you review the two candidates and the A/B results above? The implementation choice is still open. I’d especially appreciate your thoughts on the recovery/maintenance trade-off and whether #585 is likely to make budget accounting a frequent operation. |
6462d60 to
87f6fab
Compare
|
/ci run |
|
GPU CI (basic) on 87f6fab, requested by @shipiyouniao: run |
|
Synchronization at the current CAS head:
The normal path still checks file identity and the calibration lease, and may enter calibration when its sample is due. This is not a lock-free allocator. Source inspection alone does not show that a remaining lock is a serving bottleneck; the PR description separates historical microbenchmarks, current validation and the completed matched A/B. |
Basic correctness · 2026-10-16 · FAIL (1 of 22 cases)Run · kvcached 87f6fab (merged into main at 04ccd1d)
Setup of every caseOn request in a pull request (gpu-pr.yml) on 2x NVIDIA L4 (23034 MiB, driver 580.178.04); the SGLang and vLLM cases run at the same time, one GPU each. kvcached is installed from the pull request merged into main into each engine's official image; each case starts one server in it.
CasesEach kvcached case is compared with the native case of the same engine and model.
Each case:
Elastic tests
Limit tests
Logs: artifact, kept 30 days: serve.log and cmd.txt per case, summary.json with every request's tokens, setup.json, run.log, GPU state at the end. |
87f6fab to
6d63913
Compare
|
/ci run |
Basic correctness · 2026-10-17 · PASSRun · kvcached dbacefb (merged into main at 04ccd1d) All 22 cases passed. Setup of every caseOn request in a pull request (gpu-pr.yml) on 2x NVIDIA L4 (23034 MiB, driver 580.178.04); the SGLang and vLLM cases run at the same time, one GPU each. kvcached is installed from the pull request merged into main into each engine's official image; each case starts one server in it.
CasesEach kvcached case is compared with the native case of the same engine and model.
Each case:
Elastic tests
Limit tests
Logs: artifact, kept 30 days: serve.log and cmd.txt per case, summary.json with every request's tokens, setup.json, run.log, GPU state at the end. |
|
With this simplification, I prefer flock for the smaller accounting and recovery surface. It keeps per-pool records and short critical sections, allows calibration during activity, and returns confirmed releases in bounded byte batches. The T4 comparisons do not establish a consistent throughput advantage; the trade-off includes higher refund-lock activity and sampling error, documented in the PR. CAS is unchanged. |
|
/ci run |
|
GPU CI (basic) on 519b6a2, requested by @shipiyouniao: run |
Basic correctness · 2026-10-18 · PASSRun · kvcached 519b6a2 (merged into main at 04ccd1d) All 22 cases passed. Setup of every caseOn request in a pull request (gpu-pr.yml) on 2x NVIDIA L4 (23034 MiB, driver 580.178.04); the SGLang and vLLM cases run at the same time, one GPU each. kvcached is installed from the pull request merged into main into each engine's official image; each case starts one server in it.
CasesEach kvcached case is compared with the native case of the same engine and model.
Each case:
Elastic tests
Limit tests
Logs: artifact, kept 30 days: serve.log and cmd.txt per case, summary.json with every request's tokens, setup.json, run.log, GPU state at the end. |
519b6a2 to
1cc0c19
Compare
One document for sharing a GPU between kvcached instances: the per-GPU ledger this PR adds, and the planned per-instance controls, watermarks, reclaim, CPU tier and coordination API.
…dles misses ovg-project#554 returns a prefill batch that the pools cannot back to the waiting queue, so the separate admission guard is no longer needed; both wrapped Scheduler._get_new_batch_prefill_raw. Also drop the A/B benchmark script.
|
/ci run |
Basic correctness · 2026-10-18 · PASSRun · kvcached cae31f2 (merged into main at a2af415) All 22 cases passed. Setup of every caseOn request in a pull request (gpu-pr.yml) on 2x NVIDIA L4 (23034 MiB, driver 580.178.04); the SGLang and vLLM cases run at the same time, one GPU each. kvcached is installed from the pull request merged into main into each engine's official image; each case starts one server in it.
CasesEach kvcached case is compared with the native case of the same engine and model.
Each case:
Elastic tests
Limit tests
Logs: artifact, kept 30 days: serve.log and cmd.txt per case, summary.json with every request's tokens, setup.json, run.log, GPU state at the end. |
|
/ci run weekly |
Weekly correctness · 2026-10-18 · PASSRun · kvcached cae31f2 (merged into main at a2af415) All 26 cases passed. Setup of every caseOn request in a pull request (gpu-pr.yml) on 2x NVIDIA L4 (23034 MiB, driver 580.178.04). kvcached is installed from the pull request merged into main into each engine's official image; each case starts one server in it.
CasesEach kvcached case is compared with the native case of the same engine and model.
Each case:
Elastic tests
Logs: artifact, kept 30 days: serve.log and cmd.txt per case, summary.json with every request's tokens, setup.json, run.log, GPU state at the end. |
Under TP each SGLang rank has a kvcached pool of its own, and its available_size() is the physical headroom of that rank's GPU. The scheduler reads that count to admit (PrefillAdder.rem_total_tokens), to evict the token shortfall before every allocation and inside the decode gate (evict_from_tree_cache), and to evict the Mamba shortfall in alloc_req_slots. With a second process on one GPU only, the ranks read different counts, admit different batches or evict different amounts, and the forward desyncs, which the agreed miss alone does not prevent since both allocations can succeed locally. Once per scheduling round (get_next_batch_to_run, or the prefill and decode entries the PD and multiplexing loops call directly) every rank reads its managers' counts and one MIN all-reduce over the attention TP and CP CPU groups makes the smallest the budget of all. The count each manager reports is the forecast ovg-project#487 clamps to the per-GPU ledger, so the agreed budget is the shortest rank's ledger forecast. The elastic allocators serve the agreed count plus a tally of the round's own allocations and frees, never the live count, which re-reads the GPU's physical free pages after every allocation. The reduce runs whatever the local counts, once per outermost round, before the MAX reduces of the round's attempts, so every rank issues the same collectives in the same order. Without a queue group no round opens and every read stays local, and a pipeline does not open a round, as the miss patches keep SGLang's own behaviour there. Refs ovg-project#547 Signed-off-by: Rishabh Sinha <rsinha17@terpmail.umd.edu>
This PR reserves native physical KV growth before allocation and retains each charge until physical release and unmapping are confirmed. The implementation uses flock with totals derived from per-pool records.
Accounting and calibration
Grants and instance limits use a short flock around the per-pool records. CUDA allocation, mapping and release run outside that lock. There are no stored aggregate counters.
Calibration no longer requires global idle or an active/version check. A nonblocking kernel lease coordinates only calibrators across the driver sample and publication, preventing older samples from overwriting newer ones. Grants, refunds and owner retirement continue while sampling. Publication sums the current pool charges and sets
budget = max(0, current charges + sampled free memory - safety margin), retaining negative headroom until the final clamp.Successful releases refund in byte batches below 64 MiB per releasing thread and budget connection. A physical handle of at least 64 MiB refunds directly; outer eviction, trim and destroy scopes no longer defer all refunds until completion. Failed release does not refund, and a failed accounting flush retains its backlog for retry.
The budget remains an estimate. All concurrent unmaterialized grants, queued refunds, the largest indivisible physical handles, sample/publication delay and unrelated allocations can contribute error. Five logical pages are not a global bound. Accounting failures can add further backlog; the safety margin is not a guarantee against CUDA OOM.
A pool's kernel owner lease determines whether registration can retire it. Retirement reduces both its charge and the GPU budget, creating no spendable credit and making no claim about completed driver cleanup. A later sample can recover memory actually observed free.
Workers need a trusted local Linux filesystem supporting flock and OFD byte-range locks. Equal pathnames in isolated container mounts do not coordinate. The default
/dev/shmsetup assumes one Unix UID. Multiple UIDs require provisioned access through a common group, setgid directory and group-write-preserving umask (for example0007), or equivalent default ACLs. All participants must be able to read and write the ledger, use the same protocol, and restart together after a layout change. The existing ledger pathname is retained so incompatible live layouts fail instead of creating independent budgets. Do not remove or replace live coordination files. The implementation does not chmod files or bypass permission errors. GPU partitions sharing a PCI identity are not separate accounting domains in this version.Use a consistent
KVCACHED_IPC_NAMEacross an instance's workers. Limits apply to the sum of its pools on the GPU. A limit reduction blocks new growth without reclaiming existing pages. Failed physical cleanup retains its charge and ownership for retry; uncertain mappings remain quarantined and may require worker restart.Engine boundary
The existing SGLang prefill guard and Jeff's admission-retry fix remain intact: cleanup preserves each request's original
is_retracted,retracted_stainandretraction_count, including requeue. Prefix-lock/Mamba cleanup and the retained-chunk path remain intact. The guard is scoped to SGLang 0.5.20 single-rank, page-size-one prefill; it is not general distributed admission. Themanager.world_size == 1check does not establish TP=1. The earlier collective concern remains a code inference, not a normal-run reproduction. No #554/#578 integration is included.Calibration and refund timing
Before the flock simplification, global activity could postpone calibration and an outer release scope deferred all refunds:
sequenceDiagram participant W as Worker participant L as Ledger participant C as Calibrator participant D as Driver W->>L: Begin outer mutation C->>L: Check global active count L-->>C: Skip sample while active loop Whole eviction or trim W->>D: Unmap and release handle W->>W: Accumulate refund end W->>L: Refund entire outer scope C->>L: Exclude grants and check idle C->>D: Sample free memory C->>L: Publish if version is unchangedThe current flock implementation coordinates only calibrators across sampling and publication. Ordinary accounting continues and confirmed refunds flush in bounded batches:
sequenceDiagram participant W as Worker participant L as Ledger participant C as Calibrator participant D as Driver C->>C: Acquire sampler-only lease C->>D: Sample free memory outside ledger lock par Normal accounting continues W->>L: Grant under short lock W->>D: Create or release physical handles W->>L: Flush confirmed refunds in byte batches and Publish sample C->>L: Sum current pools and update budget end C->>C: Release sampler-only leaseValidation
Native compilation, the GPU-free C++ suite, four budget-interface tests, the compiled Python snapshot export check and clang-format passed. A private source review found no actionable findings.
Historical validation and measurements at 519b6a2
The native build and GPU-free C++ suite passed for
519b6a2. Real-VMM and admission regressions passed: 85 tests, with three two-GPU skips. The isolated Python 3.11 CPU suite passed 1,247 tests with 12 SGLang-runtime skips; the classification manifest passed. Changed-file formatting, lint, spelling and Markdown checks passed. A dedicated private Codex source review found no actionable defects; runtime checks were performed separately.Two normal SGLang 0.5.15 servers using the cached Qwen2.5-0.5B model completed 152 HTTP requests on both old and new flock. Each instance grew from 96 to 768 MiB, returned to 96 MiB after a public limit change plus cache flush, and grew again to 288 MiB. Repeated 2,048-token prompts reported 2,047 cached tokens before and after the flush/regrowth sequence. Both runs recorded zero admission misses, retractions, OOM or state-inconsistency errors.
This is normal serving validation, not a reproduction of Jeff's admission-refusal case: no refusal occurred. It covers the available 0.5.15 runtime, not 0.5.20; the latter still has the previously disclosed kernel-version mismatch.
Full private pre-commit hooks and strict MyPy checks for Python 3.10–3.13 passed. Public CPU CI passed on Python 3.10–3.13, with 1,449 passed and 14 skipped in each version; public pre-commit CI also passed. Public GPU CI also passed all 22 normal-serving cases for
519b6a2merged intomainat04ccd1d, using SGLang 0.5.20 and vLLM 0.30.0 on two L4 GPUs (formal report). The elastic SGLang Qwen3.5-4B run logged one admission refusal returning one request to scheduling, and the case passed. This exercises rejection/requeue through normal serving, but does not correlate that retry with prefix-cache accounting or reproduce the oldcached_tokensdefect. Each server used TP=1; this is not TP=2 validation.Three paired native runs compared the new flock code with
0315b5don the same T4, alternating order, with two processes and 60 cycles per worker.KVCACHED_GPU_UTILIZATION=0.9set the safety margin to approximately 10% of the CUDA-reported total memory. Each sampling loop attempted explicit calibration, read the ledger and then queried driver memory, with a 5 ms sleep between loops. Calibration, waiting and read time add to that interval; this was not a fixed sampling frequency. This is a constructed allocator benchmark, not normal serving or the default one-second refresh cadence.Both throughput ranges cross 1.0; these runs establish no consistent speedup. The median accounting-lock acquisitions per cycle increased from 10.0 to 16.3 and 8.8 to 11.2, respectively, including sampler and observation queries. Calibration-call p50 medians were 24.67 → 26.17 ms and 1.85 → 5.32 ms; these include allocator-lock waits and skipped attempts. Release timings include unmap, physical release and accounting, not isolated refund cost. All measured cycles completed with zero admission misses and zero OOM.
Across the three paired native runs, the largest positive differences between a ledger snapshot's available credit and a subsequent driver free-memory-minus-margin reading were 470 MiB for 2 MiB handles and 864 MiB for 96 MiB handles (old flock: 2 MiB and 480 MiB, respectively). These sequential observations combine possible estimation error with concurrent changes between reads. They do not isolate true simultaneous error, establish an upper bound, or demonstrate that the margin prevents OOM. No near-OOM or multi-GPU validation was performed in this benchmark.
Earlier historical validation
The previous flock
dbacefbmerged withmainpassed 22 normal-serving GPU CI cases, all at TP=1. The earlier CAS 22-case L4 run also predates this change.Historical five-round A/B: flock 3310135 / CAS 6d63913
On one T4, identical optimized builds and benchmark code alternated order across five paired rounds. Median paired CAS/flock throughput ratios (minimum–maximum):
All GPU-growth ranges cross 1.0. CAS improved CPU-only accounting under contention, but also replaced owner-table scans with aggregate counters; this was a whole-implementation comparison. Runs used a two-core container quota, unfixed GPU clocks, 200 growth iterations after 20 warmups, 2,000 reserve-reuse iterations and 5,000 CPU-ledger attempts per worker. Ranges are descriptive, not confidence intervals. These are allocator measurements, not model-serving throughput or multi-GPU results, and neither head includes the current simplification.
Refs #467