Skip to content

feat: prefix KV reuse - #181

Open
drunkcoding wants to merge 12 commits into
mainfrom
plan/prefix-kv-reuse
Open

feat: prefix KV reuse#181
drunkcoding wants to merge 12 commits into
mainfrom
plan/prefix-kv-reuse

Conversation

@drunkcoding

@drunkcoding drunkcoding commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Description

Opt-in, correctness-preserving longest-prefix KV reuse wired into the active OpenAI continuous-batching path, with exact cold/warm equivalence and unchanged disabled or unsupported-runtime behavior.

Motivation

A PrefixCache scaffold existed, but the active request path performed no KV lookup, attachment, publication, or reuse. This PR implements the full path per the Momus-approved plan (docs/superpowers/plans/2026-08-21-prefix-kv-reuse.md).

What changed

  • Prefix contract + index (serving/prefix_contract.py, serving/prefix_cache.py): single definition site for PrefixLease/PrefixMatch/PrefixLeaseProvider; namespace-scoped exact parent-path prefix tree with SHA-256 only as a bucket accelerator, pinned leases with retain/release, lease-aware subtree LRU eviction.
  • Layer-complete KV storage (runtime/attention_backend.py, models/qwen3_paged_attention.py, serving/model_runner.py): one validated LayeredPagedKVStore across all layers with export/import/checkpoint/restore; per-module layer_idx plumbing; ModelRunner.get_prefix_reuse_capability with complete-registry validation.
  • Query vs KV metadata (runtime/attention_types.py, serving/batch.py, serving/model_runner.py, runtime/attention_backend.py): canonical PagedBatchLengths(query_lengths, query_offsets, context_lengths, kv_seq_lengths); FlashInfer qo_indptr uses query lengths while KV pages use total; last_flashinfer_plan snapshot.
  • Ownership + COW (serving/kv_cache.py): block refcounts, validated store binding that disables independent tensors, layer-complete copy-on-write for shared partial tails, checkpoint/restore-based swap.
  • Transactional group admission (serving/scheduler.py, serving/sequence.py, serving/kv_cache.py): pin-before-evict, all-or-nothing SequenceGroup admission via prepare_group/commit_group/abort_group.
  • Committed publication + DFlash reconciliation (serving/engine.py): advance committed_kv_tokens only after a successful forward, publish only block-complete committed prompt ranges, exclude reused-prefix and non-cold requests from DFlash delegation.
  • Active-path gating + observability (serving/engine.py, entrypoints/openai/api_server_v2.py): capability + flag gate, validated store bound before scheduler construction, --prefix-cache-max-entries, /v1/reload invalidation, prefix_cache_* stats.
  • Tests, benchmark, docs: exact-collision/lease, layer completeness, metadata parity, ownership/COW/preemption, atomic-group, committed-range/DFlash, engine gating, opt-in Qwen3/FlashInfer CUDA parity (test_prefix_cache_cuda.py), disabled/cold/warm benchmark with digest-mismatch abort (benchmarks/serving/prefix_cache_benchmark.py), and serving/benchmarking/README/ARCHITECTURE/CHANGELOG updates.

Scope / limitations

  • Reuse activates only on the Qwen3 paged-attention path with a complete layer registry, matching geometry, and real FlashInfer prefill/decode; every other runtime keeps the unchanged cold path with a stable disabled reason.
  • Shares canonical metadata, storage, and PrefixLeaseProvider contracts with chunked prefill; co-enablement requires future reconciliation tests.
  • Real Qwen3-30B + FlashInfer CUDA parity and the real-model benchmark/rollback steps are environment-gated (GPU + FlashInfer + weights) and were not executed in this CPU CI environment; the CUDA test SKIPS without MOE_PREFIX_CACHE_CUDA=1, and the benchmark dry-run schema check passes.

Type of Change

  • Production implementation
  • Documentation update

Verification

  • CPU suites green: tests/python/serving (174 passed, 3 CUDA/FlashInfer skips), tests/python/contextpilot (76 passed), touched tests/python/unit, tests/python/dflash, tests/python/integration.
  • ruff check clean on all touched files; LSP reports no errors in serving/ or attention_backend.py.
  • Benchmark dry-run: equal disabled/cold/warm token+logit digests, warm suffix geometry, mismatch aborts with exit 2 and no JSON.

Checklist

  • Active-reference safety (pin-before-evict, lease-aware eviction)
  • Reload/cancellation rollback
  • Cold/warm equivalence path implemented and covered
  • Disabled/unsupported runtime falls back to cold path unchanged

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@drunkcoding

Copy link
Copy Markdown
Contributor Author

Momus plan review: [OKAY] on the first review round.

drunkcoding and others added 6 commits August 28, 2026 22:27
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Migrate BatchMetadata and runtime AttentionMetadata to the canonical PagedBatchLengths (query/query_offsets/context/kv) contract; qo_indptr uses query lengths while KV pages use total; record last_flashinfer_plan; update all consumers.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add block refcounts (retain/release/ref_count), validated layered store binding, layer-complete copy-on-write for shared partial tails, and checkpoint/restore-based swap.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add SequenceAllocationPlan/GroupAllocationReceipt with prepare/commit/abort group protocol, pin-before-evict scheduler admission, lease-aware prefix eviction, and committed-KV/has_prefix_lease sequence state.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Advance committed_kv_tokens only after successful forward, publish newly complete prompt blocks by committed range, and exclude reused-prefix/non-cold requests from DFlash delegation.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
drunkcoding and others added 3 commits August 28, 2026 23:32
Gate prefix reuse behind capability + enable flag; bind validated layered store before scheduler construction; expose prefix_cache lifecycle stats; add --prefix-cache-max-entries and reload invalidation.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add opt-in MOE_PREFIX_CACHE_CUDA parity + active-reference eviction tests that verify real cold/warm FlashInfer geometry and exact refcount transitions on a supported Qwen3 runner.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add disabled/cold/warm prefix-cache benchmark with digest-mismatch abort and dry-run schema test; re-export FlashInferPlanMetadata; document scope, invariants, metrics, and rollback across serving/benchmarking/README/ARCHITECTURE/CHANGELOG.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@drunkcoding

Copy link
Copy Markdown
Contributor Author

Independent implementation verification:

  • Focused prefix ownership/admission/publication suites: 56 passed.
  • Python LSP scan: 0 errors across 50 files.
  • Branch clean and synchronized.

PR remains draft for real Qwen3/FlashInfer qualification.

@drunkcoding

Copy link
Copy Markdown
Contributor Author

Productization findings (full report: docs/superpowers/reports/2026-08-30-productization-verdicts.md). Draft fix: #190.

The reported native segfault was the multi-model kTopologyHandle global (same root cause as #177; guard added in #187), not a prefix-COW bug. After isolating one model per process:

  • device-index mismatch (cuda vs cuda:0) was rejecting the store → prefix cache now binds/activates (fixed);
  • hit counters were tracked but never surfaced (fixed);
  • FlashInfer plan dtype float16 vs bf16 (fixed).

Remaining correctness concern: with reuse active (query=8 / context=64 / kv=72), the warm run produces different generated tokens than cold on sm120 (e37a9d14 vs 3739c3b3). A read-only Oracle pass verified geometry, block tables, slot mapping, RoPE (K stored post-RoPE, reused at identical positions), COW, and FlashInfer causal/append masking are all correct on paper — most likely numerical greedy-argmax flips from the append kernel on Blackwell. Your parity test test_real_flashinfer_warm_suffix_matches_cold_logits is the decisive check but can't run here (loads 2 engines/process → the multi-model limit above). Please run it on reference hardware. #190 is a draft pending that.

…ging

Other tests purge moe_infinity.runtime from sys.modules at import time,
which re-registers a duplicate flashinfer_utils. importlib.reload on the
module-level binding then failed with 'module ... not in sys.modules'.
Re-fetch the current module via importlib.import_module before reloading.
@drunkcoding
drunkcoding marked this pull request as ready for review September 2, 2026 21:53
@drunkcoding drunkcoding changed the title WIP: plan prefix KV reuse feat: prefix KV reuse Sep 2, 2026
* fix(serving): prefix-cache binding, hit stats, FlashInfer dtype, sm120 eager

- kv_cache.set_block_store: normalize cuda device index so 'cuda' vs 'cuda:0'
  geometry no longer rejects the store (prefix cache now binds/activates)
- prefix_cache/engine: surface prefix_cache_hits_total + matched_tokens_total
- attention_backend: pass q_data_type/kv_data_type to FlashInfer plans
- big_modeling: use eager attention for qwen3 when flash-attn unusable (sm120)
- prefix_cache_benchmark: one model per subprocess (avoids multi-model crash)
- engine: include bind exception detail in disabled reason

* test(prefix): kernel-parity evidence + runnable subprocess parity harness

- benchmarks/prefix_cache_benchmark: add --parity-report (per-mode tensor
  dumps + numerical analysis instead of digest-only failure), and
  --device-memory-ratio/--kv-cache-ratio so the suite fits shared GPUs.
- tests/integration/test_flashinfer_kernel_parity: prove full-prefill and
  append kernel schedules are each deterministic, within ULP-scale error
  of an fp32 reference, and mask partial-page tails (3/3 pass on sm120).
  Cross-kernel bitwise equality is not an invariant.
- tests/serving/test_prefix_cache_cuda: run warm-vs-cold parity through
  one-engine-per-subprocess (kTopologyHandle-safe) with correct
  invariants: disabled==cold bitwise control, geometry-proven reuse,
  tolerance + near-tie-aware token comparison.

* test(prefix): tolerate near-tie swaps, require shared top-2 at any flip

Warm-vs-cold token equality is unattainable: the paths use different
FlashInfer kernel schedules (ULP-level per-op deltas) and expert-output
accumulation order is nondeterministic, so near-tie argmax swaps occur
even between same-mode reruns. Keep disabled==cold bitwise as the strict
store-path regression check; at a flip require the two runs to share the
same top-2 candidate pair, which still catches real corruption.

---------

Co-authored-by: drunkcoding <leyang.xue@ed.ac.uk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant