Repository navigation
[Perf][Core] Reduce free KV cache queue overhead - #60269
Conversation
Cache neighboring blocks and queue sentinels in locals, and avoid the batch loop when allocating one block. Cover draining and refilling the queue. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: NickLucche <nicolo.lucchesi@mistral.ai>
| if n == 1: | ||
| assert curr_block is not None | ||
| next_block = curr_block.next_free_block | ||
| curr_block.prev_free_block = curr_block.next_free_block = None |
There was a problem hiding this comment.
nit:
| curr_block.prev_free_block = curr_block.next_free_block = None | |
| curr_block.prev_free_block = None | |
| curr_block.next_free_block = None |
Apply review feedback by resetting each link on a separate line, matching the bulk pop path. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: NickLucche <nicolo.lucchesi@mistral.ai>
|
/ci run --allow-stale |
|
✅ Triggered Buildkite CI #93448 for commit
|
CI selector (shadow): 134 test steps (173 jobs) instead of 60 (77 jobs)Shadow mode: this changes nothing about what CI runs. It shows what the evidence-based selector would pick for this PR, next to today's rules. How it works. Feedback welcome: reply here if it would skip a step this change needs, or runs something unrelated.
Selector would run (134)
Would skip (today's rules run them) (11)
Would add (today's rules do not run them) (85)
AMD mirrors: would skip (5)
AMD mirrors: would add (77)
2 changed files · base |
|
/ci retry |
Signed-off-by: NickLucche <nicolo.lucchesi@mistral.ai>

Overview
Reduce Python overhead in
FreeKVCacheBlockQueueby reading block links and queue sentinels once into local variables, reusing them for validation and link updates, and handlingpopleft_n(1)inline. The intrusive doubly linked list, eviction order, detached-block link clearing, and existing error checks are preserved.Claims
Validation
The benchmark compares the original and modified class definitions in separate namespaces using the same actual slotted
KVCacheBlockclass. It measures repeated allocation/free cycles, excluding setup. Both hosts use CPython 3.12.13, one pinned CPU, warmup, randomized baseline/candidate order, pools of 2,048 and 100,000 blocks, and 11 fresh processes with 9 samples per case. A separate 9-process run confirms singleton/two-block batches and removal across two alternating shuffled block orders.For 100,000 blocks, times are nanoseconds per complete cycle. Percentages are time reductions, computed from the median of per-process candidate/baseline ratios; intervals bootstrap those process ratios. Baseline and modified times are independently aggregated medians.
popleft_n(1)+append_npopleft()+append()remove()+append()popleft_n(2)+append_npopleft_n(8)+append_npopleft_n(32)+append_npopleft_n(128)+append_nThe confirmation run found 20.1% less time for singleton batches on Grace and 17.6% on Xeon. Two-block batches improved by 1.1% on Grace but regressed by 3.6% on Xeon. The synthetic mix contains 90 one-block, 5 two-block, 4 eight-block, and 1 thirty-two-block allocations in a shuffled order; it is not a serving workload.
Hardware: ARM Neoverse V2 / Grace on a GB300; Intel Xeon Platinum 8562Y+
Correctness and static checks:
On the clean PR branch based on upstream main: 389 passed; all applicable pre-commit hooks and mypy-3.12 passed.
The benchmark also validates each implementation against a Python list model for 60,000 randomized operations before timing. It checks ordering, counts, forward/backward links, detached links, prepend/append, and cursor iteration after every operation. Both implementations passed on Grace, Xeon, and local ARM macOS. Nine invalid-operation comparisons also preserved exception types/messages and queue state. The added pytest covers complete draining and refilling for batches of 1, 2, and 16. The existing
tests/v1/coresuites are covered by Buildkite.Full benchmark harness, raw samples, and reproduction commands: benchmark code, raw samples, and reproduction commands. No third-party dependencies are needed for the isolated benchmark. The queue and block class definitions are unchanged between the measured baseline and the PR base commit.
Details
Normal allocations use
popleft_n(), whileremove()is used when touching a cached block whose reference count is zero. The singleton path avoidsrange()iteration and growing the return list withappend(). For example,remove()readsblock.prev_free_blockandblock.next_free_blockinto local variables, then uses those same references for the validity check and to reconnect the neighboring blocks. Previously, those steps repeatedly accessed the attributes onblock. Similarly,popleft()and the append methods reuse local references to the head or tail sentinel. This reduces Python attribute accesses; it does not add a cache or change KV-cache behavior. This small path is inlined because callingpopleft()adds function-call and validation overhead; it preserves the original bulk method's checks and mutations.The singleton branch is a workload-dependent tradeoff: two-block batches regress by approximately 3–5% on Xeon, although they improve slightly on Grace. Larger batches show little change. This PR deliberately claims only the measured queue effects; no end-to-end throughput, accuracy, or model-output claim is made.
Duplicate-work checks searched open PRs for
FreeKVCacheBlockQueue,popleft_n, and free-block queue performance. Related #59283 and #55841 change eviction/contiguity policy; #40459 explores a Rust control-plane replacement. None implements this Python-only optimization with the existing queue contract. Issue #40244 concerns restoring allocation order; this PR does not address that behavior and is not linked as its fix.AI assistance was used (OpenAI Codex) for implementation, testing, benchmarking, and this description. The human submitter reviewed the changes before publication.
Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Small change to existing queue methods; preserves API and list invariants. The singleton branch's tradeoff is documented.
Testing and Validation: Existing cache suites, targeted drain/refill test, randomized checks, and repeated CPU microbenchmarks. End-to-end serving/model evaluations are deferred.
Code Quality and Style: Minimal diff, no dependencies or new abstraction; Ruff and mypy checks.
Pull Request Contents: Claims, configuration, run counts, variability, duplicate-work explanation, AI attribution, reproduction, and limitations included.