Skip to content

[None][fix] KVv2 C++ backend: nanobind None/uint64 argument handling, constraint-floor parity, backend-aware OutOfPagesError - #2

Draft
lancelly wants to merge 9 commits into
lowsfer:kvCacheManagerV2-cppfrom
lancelly:fix/kvv2-cpp-bindings
Draft

[None][fix] KVv2 C++ backend: nanobind None/uint64 argument handling, constraint-floor parity, backend-aware OutOfPagesError#2
lancelly wants to merge 9 commits into
lowsfer:kvCacheManagerV2-cppfrom
lancelly:fix/kvv2-cpp-bindings

Conversation

@lancelly

@lancelly lancelly commented Jul 15, 2026

Copy link
Copy Markdown

What

Three fixes for issues hit while running this branch's C++ backend end-to-end on a DeepSeek-V4 4p1d disaggregated deployment (4×TP8 ctx + 1×TP16 gen, GB300, conc=1000, agentic multi-turn workload), plus an A/B validation of the backend itself. All three are interface-layer issues — the core design ran flawlessly once they were fixed (≈106K requests, zero crashes).

1. set_base_page_index_buf rejects None → every ctx worker dies (kvCacheManagerV2.cpp)

release_index_slot passes buf=None to clear the DSA index buffer. The lambda body already handles is_none(), but the nb::arg("buf") annotation lacks .none(), so nanobind rejects the call at the argument-casting layer:

TypeError: set_base_page_index_buf(): incompatible function arguments ...
Invoked with types: ..._KVCache, int, int, NoneType

On the C++ backend every ctx worker crashes at the first index-slot release. One-line fix: nb::arg("buf").none().

2. int64 bindings reject valid ids/salts → gen workers die in CUDA-graph padding (kvCacheManagerV2.cpp)

Two producers exceed int64 range:

  • CUDA_GRAPH_DUMMY_REQUEST_ID = 2**64 - 1 (cuda_graph_runner.py) — create_kv_cache(id=...) raises the same incompatible function arguments TypeError inside CUDA-graph padding, killing all gen ranks (AssertionError: Sampling failed). Only gen uses graphs, and the Python backend's unbounded ints are unaffected — so this is cpp-backend-gen-specific and 100% reproducible.
  • ReuseScope.salt is derived via int.from_bytes(sha256(...)[:8]) — a full 64-bit value, i.e. >2**63 with ~50% probability per salted request. Same crash, latent until a cache_salt-carrying request arrives.

Fix: accept uint64 at every id/salt boundary (create_kv_cache, ReuseScope ctor/accessors, the 5 stats APIs, get_dirty_stats_kv_cache_ids) and reinterpret via two's complement in both directions. This is bijective (small ids unchanged), and ReuseScope::toBytes serializes the same 8 bytes either way, so reuse keys stay identical to the Python backend's.

3. except OutOfPagesError never matches on the C++ backend (py_executor.py)

The nanobind module registers its own OutOfPagesError exception class, while py_executor imports the pure-Python class from kv_cache_manager_v2._exceptions — so the guarded allocation paths crash instead of backing off. The package __init__ already re-exports the backend-appropriate class; import that instead (one line).

Also included in the first commit: the C++ StorageManager::computeMinSlotsFromConstraints port of the Python-side fix from NVIDIA#16269 — explicit initialPoolRatio disabled constraint floors entirely and floors weren't scaled by 1/maxUtilForResume (DSv4 warmup starvation when pool_ratio is set). The Python backend has this fix; the C++ translation predates it. More generally, a sweep for Python-side KVv2 fixes that postdate the translation snapshot might be worthwhile before merge.

Validation

With these fixes, TLLM_KV_CACHE_MANAGER_V2_BACKEND=cpp vs python A/B on the setup above (~100K requests per arm, arms differ only in the env var):

metric python cpp Δ
req/s 36.74 38.85 +5.7%
ITL p50 14.2 ms 11.5 ms −19%
TTFT p90 38.8 s 41.9 s +8% (closed-loop backpressure onto the ctx-bound stage)

Gen-side nsys (steady state, iters 25000–25100, 16 ranks/arm) attributes the win to the expected places: _schedule p90 11.0→1.0 ms, update_resources p90 1.9→0.3 ms, _handle_responses p90 4.6→1.7 ms, per-event GIL-wait p90 −57%.

lowsfer and others added 9 commits July 10, 2026 10:07
… backend

Backend-neutral Python groundwork for the upcoming C++ implementation
of KVCacheManagerV2 (introduced in a follow-up PR):

- Expand the public API of tensorrt_llm.runtime.kv_cache_manager_v2:
  export layout descriptors (pool_group_descs: PoolDesc, PoolGroupDesc,
  SlotDesc, CoalescedBuffer, ExpandedBuffer), stats/eventing types, and
  a backend-neutral _introspection helper module.
- Use the public layout/introspection APIs in the disaggregation
  kv_extractor and the DSv4/MiniMax sparse cache managers instead of
  reaching into implementation internals.
- Keep vocab_size in the _build_cache_config() virtual-method contract
  but drop it from the generic KVCacheManagerConfig.
- Add v2_blake3 / v2_blake3_64 KV cache event hash options to llm_args
  and regenerate the LLM args golden manifest.
- Fix StagingBuffer wrap-around when the ring tail cannot satisfy
  min_size; misc small fixes surfaced while translating the code.
- Extend unit test coverage (stats API, hash options); route tests
  through the public API where possible.
- Fix create_perf_comparison_report.py crash when the only perf test is
  waived and no CSV is produced (pre-existing main issue).
- Rename debug assertion env var to TLLM_DEBUG_MODE.

Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
Add the C++ translation of KVCacheManagerV2 under
cpp/tensorrt_llm/batch_manager/kv_cache_manager_v2/ with nanobind
bindings, vendored BLAKE3, build integration, and unit tests.

Turn tensorrt_llm/runtime/kv_cache_manager_v2/__init__.py into a
backend dispatcher (TLLM_KV_CACHE_MANAGER_V2_BACKEND, default: cpp)
and add the C++-backend introspection paths in pyexecutor and tests.

Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
Signed-off-by: Jin Li <59594262+liji-nv@users.noreply.github.com>
Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
Signed-off-by: Jin Li <59594262+liji-nv@users.noreply.github.com>
Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
Signed-off-by: Jin Li <59594262+liji-nv@users.noreply.github.com>
Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
Port of main commit 7d600ec (DSv4 PR-6): a null beam_block page may
now also mean a skipped stale block, not only an SWA scratch block, so
drop the two TLLM_CHECK_DEBUG(mEnableSwaScratchReuse) checks in the
page unlock/release paths and update the comments to match the Python
reference implementation.

Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
…o and accept None in set_base_page_index_buf

Two fixes found running the C++ backend on DeepSeek-V4 4p1d disagg:

1) StorageManager::computeMinSlotsFromConstraints — an explicit
   initialPoolRatio disabled constraint floors entirely and the floors were
   not scaled by 1/maxUtilForResume. This is the C++ counterpart of the
   Python-side fix in PR NVIDIA#16269 (DSv4 warmup starvation when pool_ratio is
   set and the estimation quota is tight); the Python backend has the fix,
   the C++ translation predates it.

2) set_base_page_index_buf's buf argument rejected None at the nanobind
   layer (missing .none() annotation), although the lambda body already
   handles is_none(). release_index_slot passes None to clear the DSA
   index buffer, so on the C++ backend every ctx worker died with
   'incompatible function arguments ... NoneType' at the first index-slot
   release.

Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
…nd reuse-scope salts

CUDA_GRAPH_DUMMY_REQUEST_ID is 2**64-1 and sha256-derived reuse salts use
the full 64-bit range; the previous std::optional<int64_t> casters raised
TypeError (gen workers died in cuda-graph padding). Reinterpret as two's
complement in both directions: bijective, small ids unchanged, and
ReuseScope::toBytes serializes the same 8 bytes so reuse keys stay
identical to the Python backend.

Signed-off-by: Lance Liao <laliao@nvidia.com>
Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
The nanobind module registers its own OutOfPagesError exception class, so
'except OutOfPagesError' clauses importing the pure-Python class from
kv_cache_manager_v2._exceptions never match on the C++ backend and the
guarded allocation paths crash instead of backing off. The package
__init__ already re-exports the backend-appropriate class; import that.

Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com>
@lowsfer
lowsfer force-pushed the kvCacheManagerV2-cpp branch 20 times, most recently from 6f646e5 to 2ca5340 Compare July 22, 2026 16:34
@lowsfer
lowsfer force-pushed the kvCacheManagerV2-cpp branch 17 times, most recently from cef027a to f0852d3 Compare July 29, 2026 05:29
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.

3 participants