Skip to content

[TRTLLM-14019][feat] MiniMax-M3 unified KV cache: share Eagle3 draft layers via a sub-page view - #17457

Draft
zheyuf wants to merge 13 commits into
NVIDIA:feat/m3_with_msafrom
zheyuf:feat/m3-eagle3-unified-kv-cache
Draft

[TRTLLM-14019][feat] MiniMax-M3 unified KV cache: share Eagle3 draft layers via a sub-page view#17457
zheyuf wants to merge 13 commits into
NVIDIA:feat/m3_with_msafrom
zheyuf:feat/m3-eagle3-unified-kv-cache

Conversation

@zheyuf

@zheyuf zheyuf commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Description

MiniMax-M3 + Eagle3 could not share a KV cache manager, so one-model speculative
decoding ran a separate draft manager — and every block-lifecycle feature had
to be duplicated for it or silently go missing: disaggregated transfer dropped the
drafter's prompt KV (−0.14 AL on chat-GSM8K, −0.5 and worse on long-prompt and
multi-turn regimes), prefix reuse never applied to draft blocks (−0.2 to −0.35 AL
on turns 2+ in both aggregated and disaggregated serving), and each gap was a
silent acceptance regression rather than an error.

This PR lets the drafter share MiniMaxM3KVCacheManagerV2 (unified KV cache):

  1. supports_shared_draft_layers=True — the drafter's buffers differ in
    per-block size from every M3 buffer, so V2 storage places them as their own
    sub-pages inside the shared slots. Block lifecycle then covers the drafter
    natively: a reused, evicted, or disagg-transferred logical block
    physically contains the drafter sub-pages. No mirror creation, no transfer
    merging, no reuse re-implementation.
  2. MiniMaxM3DraftSubpageView — presents the drafter's pool to the attention
    ops at 32-token kernel pages inside the 128-token logical blocks
    (tokens_per_block=32; block-table entry s → s·scale·subdiv + j for K,
    +subdiv for V). Same slot-geometry pattern as the dense-layer trtllm-gen
    adapter (trtllm_gen_dense_decode.py). This sidesteps both
    tokens_per_block=128 Eagle kernel issues on SM103 (missing context cubin →
    unfused-MHA workspace blow-up; ForGen IMA) with no kernel changes. The
    view owns no blocks and rides the existing draft-swap machinery in
    TrtllmAttentionMetadata. The draft kernel page size is overridable via
    TRTLLM_M3_DRAFT_KV_TOKENS_PER_BLOCK; once the kernels are fixed, =128
    degenerates the view to the identity expansion (unit-tested), making the
    workaround retirement a config flip.
  3. Dispatch fix (first commit) — get_draft_kv_cache_manager gated on a
    flag derived from the raw spec config, which disagrees with the manager-level
    attention-DP share decision; the draft loop then silently ran without a swap.
    Trust the registered resource, fall back to the target manager's
    draft_subpage_view (None for managers without one — today's behavior).
  4. Loud-failure resolution + engagement logging — the resolver probes the
    class descriptor and then accesses the property directly, so a failure inside
    view construction propagates instead of silently downgrading to "no view"
    (a defaulted getattr swallowed exactly that during development: a stale
    field in a log f-string disabled the view with zero symptoms). Three
    [unified-kv] INFO logs pin the mode end to end: shared-layout at manager
    creation, resolved mode at first dispatch (and on every transition), view
    geometry at construction. Sanity asserts pin the view's mega-slot
    preconditions (single pool; draft layer resident in pool 0).
  5. Retires the [None][fix] Allow MiniMax-M3's to use separate draft KV cache in disagg #17341 WAR exemption — with shared draft layers, the
    nvbugs/5807902 WAR's shared-manager fallback is exactly the path M3 wants in
    disaggregated mode.
  6. sparse_layer_ids no longer mislabels appended one-model draft layers as
    sparse layers; derive_shared_draft_layout anchors the draft tail on the
    per-layer heads list (the unambiguous total), correct under both creation
    conventions (num_layers = pretrained target count with spec extension, or
    pre-extended).

CI coverage (new in this PR)

TestMiniMaxM3::test_nvfp4_eagle3 gains a disagg parameter arm: context TP2
(block reuse on, chunked prefill, no overlap) → attention-DP generation TP2
(CUDA graphs, overlap, reuse off) on one 4-GPU node, NIXL PYTHON transceiver,
FP8 KV + indexer, Eagle3 one-model draft3 — the AgentX/InferenceMAX serving
shape, asserting GSM8K / gsm8k_inferencemax accuracy through the router. The
default-eval disagg point joins l0_dgx_b200_m3 pre-merge and the QA core
list; the aggregated arm is byte-identical to before (ids gain -disagg=False).

Validation (MiniMax-M3 NVFP4 + Eagle3 draft3, GB300)

rung unified (this PR) separate-manager reference
disagg GSM8K AL / rate 3.44–3.48 / 0.81–0.83 (re-run at PR tip: 3.476) 3.47 / 0.82 (broken baseline: 3.33 / 0.78)
agg GSM8K AL / rate 3.50–3.54 / 0.83–0.85 (re-run at PR tip: 3.501) 3.47 / 0.83
gsm8k_inferencemax accuracy (disagg) 95.45 reference 95.46, gate 94.0
multi-turn copy-task, turns 2-4 3.57–3.67 stock: 3.03–3.11 (reuse gap)
AgentX 256K real traces (c32 disagg) 3.005 transfer+reuse-fixed separate: 3.041
SPEED-Bench protocol on/off 3.25 / 3.23 golden (vLLM BF16): 2.83 / 2.93
step-resolved probe (early decode) matches fixed separate path confirms native drafter-KV transfer

Both PR-tip re-runs (aggregated and disaggregated) show all three
[unified-kv] engagement logs on every rank (draft layers [60] share the target KV cache manager (sparse layers 3..59)draft KV resolved: target manager's draft sub-page viewdraft subpage view active: draft_layers=[60] subdiv=4 tokens_per_block=32).

Perf receipt — the view's only steady-state work is one block-table
expansion per draft window (copy_batch_block_offsets): 84 / 341 / 1202 µs at
batch 8 / 64 / 256 (measured in-container, includes the H2D copy) ≈ 0.4–1 % of
a 30–80 ms generation iteration at concurrency 64. Acceptance is unaffected:
a direct-attention control run (view disabled) scores 3.53 vs 3.50 with the
view — the view exists for correctness at 32-token pages, not for AL.

Unit tests (16, pass against real torch/trtllm in-container): sub-page
addressing math (expansion, V offset, BAD_PAGE_INDEX padding, subdiv=1
degeneration, pool-0 sanity), draft-tail layout under both creation
conventions, the manager property itself (construction + logging + caching +
is_draft guard), and dispatch resolution (separate-wins, view fallback,
loud propagation of construction failures).

Notes for reviewers

  • Non-attention-DP aggregated M3 still selects the separate draft manager
    (default _allow_separate_draft_kv_cache=True); production shapes (attention
    DP, disaggregated) take the unified path.
  • The separate-manager path itself remains available for external-drafter and
    non-ADP one-model users; its transfer/reuse gaps are tracked separately
    ([TRTLLM-14019][feat] Transfer the separate draft KV cache in disaggregated serving #17371 stays a draft for that audience).
  • The per-row Python loop in the view's table expansion is the dominant term of
    the 341 µs @ bs64; happy to vectorize via a packed numpy staging buffer in
    this PR or a follow-up if desired.

PR Checklist

  • PR title follows the [JIRA/NVBUG/None][type] format
  • Commits are signed off (DCO)
  • yapf (legacy files) / ruff format (new-style files) clean
  • CI (/bot run)

zheyuf added 2 commits August 9, 2026 19:58
…r when draft layers are shared

get_draft_kv_cache_manager (both the worker method and the utils
helper) gated on use_separate_draft_kv_cache, a flag derived from the
raw speculative config. Under attention DP the manager-level decision
(_should_create_separate_draft_kv_cache) can choose SHARED draft layers
while that flag still reads True, so the worker looked up a
DRAFT_KV_CACHE_MANAGER resource that was never registered and silently
ran the draft loop without a swap. Harmless for same-geometry drafters,
but wrong whenever the shared manager exposes a draft-specific
attention view.

Trust the registered resource as ground truth instead: return the
separate manager when one exists, otherwise fall back to the target
manager's draft_subpage_view (None for managers that do not expose
one, preserving today's behavior).

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
…layers via a sub-page view

MiniMax-M3 was the only mainstream model forced onto the separate
draft KV cache manager: its slot-coalesced pools (K/V/index-K of every
layer packed per slot) break the uniform-stride assumptions of the
generic AttentionOp tables, and the Eagle3 kernels are only healthy at
32-token pages on SM103 while the MSA target requires 128. The
separate manager duplicated the drafter's block lifecycle, and every
lifecycle feature had to be re-implemented against it (disaggregated
transfer, prefix reuse, conversation retention) or silently go
missing.

Let the drafter share the target manager instead:

- supports_shared_draft_layers=True: the drafter's buffers differ in
  per-block size from every M3 buffer, so V2 storage gives them their
  own sub-pages inside the shared slots; block lifecycle (radix reuse,
  eviction, disaggregated transfer) covers them natively, since a
  reused or transferred logical block physically contains the drafter
  sub-pages.
- MiniMaxM3DraftSubpageView presents that pool to the attention ops at
  32-token kernel pages inside the 128-token logical blocks
  (tokens_per_block=32, block table entry s -> s*scale*subdiv + j for
  K and +subdiv for V), following the same slot-geometry pattern as
  the dense-layer trtllm-gen adapter. This sidesteps both
  tokens_per_block=128 Eagle kernel issues (missing SM103 context
  cubin -> unfused-MHA workspace blow-up; ForGen IMA) with no kernel
  changes. The view owns no blocks; it rides the existing draft-swap
  machinery in the attention metadata.
- sparse_layer_ids no longer mislabels appended one-model draft layers
  as sparse (derived from the per-layer kv-heads tail).
- The nvbugs/5807902 WAR exemption from NVIDIA#17341 is retired: with shared
  draft layers the WAR's shared-manager fallback is exactly the path
  M3 wants in disaggregated mode.

Validation (M3 NVFP4 + Eagle3 draft3, GB300 disagg unless noted):
GSM8K AL 3.44 disagg / 3.54 agg (separate-manager refs 3.47 / 3.47);
gsm8k_inferencemax accuracy 95.45 (reference 95.46, gate 94.0);
multi-turn copy-task turns 2-4 3.57-3.67 (stock separate 3.03-3.11);
AgentX 256K real traces AL 3.005; SPEED-Bench protocol 3.25/3.23 vs
golden 2.83/2.93. Step-resolved probes show early-decode acceptance
matching the fixed separate path, confirming drafter KV transfers
natively.

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
zheyuf added 11 commits August 9, 2026 20:43
… the sub-page table build

Self-review follow-ups on the unified KV cache change:

- The manager no longer infers appended one-model draft layers from the
  per-layer kv-heads list tail (fragile for models with heterogeneous
  target heads). The creation site knows exactly how many draft layers
  it appended and now passes num_one_model_draft_layers explicitly; the
  base KVCacheManagerV2 ignores it.
- The two draft-manager lookup sites shared duplicated fallback logic;
  both now delegate to a single resolve_draft_kv_cache_manager helper.
- MiniMaxM3DraftSubpageView.copy_batch_block_offsets builds the
  sub-page table with one fused expansion over a cached padded buffer
  instead of per-row tensor ops, keeping the per-step host cost flat in
  batch size.

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
…d target layer count

The per-layer kv-heads list can reach _create_kv_cache_manager either
built in place (scalar heads + _build_per_layer_num_kv_heads) or
pre-extended by the caller, in which case num_hidden_layers already
includes the draft tail and the length delta reads zero. Anchor the
count on config.num_hidden_layers (the target layer count), which is
unambiguous in both flows; masked/cross managers yield a non-positive
delta and correctly report zero.

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
…ds list

num_layers at the creation site carries the pretrained target count (60)
while the per-layer num_kv_heads list is already draft-extended (61);
deriving the draft tail as range(num_layers - num_draft, num_layers)
marked target layer 59 as draft, dropped its index-K allocation, and
crashed startup. Extract derive_shared_draft_layout() anchored on the
heads list's length (the unambiguous total) and cover both creation-site
flows with unit tests.

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
…vfp4_eagle3

CI coverage for Eagle3 + the unified draft KV cache across the
disaggregated transceiver. The new disagg parameter arm launches
context TP2 -> attention-DP generation TP2 on one 4-GPU node with the
AgentX/InferenceMAX serving shape (NIXL PYTHON transceiver, context
block reuse, FP8 KV and indexer, CUDA graphs + overlap on gen) and
asserts GSM8K / gsm8k_inferencemax accuracy through the router. The
aggregated arm is unchanged and keeps the acceptance-length checks.
The default-eval disagg point joins l0_dgx_b200_m3 pre-merge and the
QA core list.

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
…draft page size

Log the shared draft layout at manager creation (the counterpart of the
first-dispatch view log), assert the view's mega-slot preconditions
(single pool, draft layer resident in pool 0), and make the draft
kernel page size overridable via TRTLLM_M3_DRAFT_KV_TOKENS_PER_BLOCK so
retiring the 32-token workaround after the Eagle tokens_per_block=128
kernel fixes is a config flip; the subdiv=1 degeneration and the pool
sanity check are unit-tested.

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
Pure-logic tests for resolve_draft_kv_cache_manager: a registered
separate manager wins, a shared target manager falls back to its
draft_subpage_view, and a plain shared manager resolves to None. Guards
the regression where a stale worker-level flag disagreed with the
manager-level share decision and the drafting loop lost its manager.

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
…spatch

Completes the unified-KV triage trio: manager creation logs the shared
layout, first resolution logs which draft-side manager won (separate /
sub-page view / none), and view construction logs its geometry. A
silent fallback to direct shared-manager attention is now visible in
startup logs.

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
…ion test

The vectorized block-table expansion clamps unallocated tail slots to
slot 0, so pad entries tile that slot's sub-pages instead of staying
zero; kernels never read past the row's real block count, so the pads
are inert (the BAD_PAGE_INDEX test encodes the same property). Full
suite now passes against real torch: 13/13.

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
… breaks

resolve_draft_kv_cache_manager fetched the view with a defaulted
getattr, which also swallows AttributeError raised inside view
construction — a broken view silently downgraded to 'no view' and the
drafter attended the shared manager at the wrong page size. Probe the
class descriptor first, then access the property directly so
construction failures propagate. The resolved-mode log now records
every mode transition instead of only the first resolution.

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
…raft sub-page view

The view-active log statement inside the draft_subpage_view property
still referenced _draft_pool_ids, a field the sub-page-table refactor
removed from the view. The f-string raised AttributeError through the
view's __getattr__ delegation, the resolver's defaulted getattr
swallowed it, and every resolution since the refactor silently
downgraded to 'no view' — the drafter attended the shared manager at
tokens_per_block=128 instead of through 32-token sub-pages. Log the
view's real fields, and cover the property itself (construction, the
log statement, caching, the is_draft guard) with a unit test; direct
view construction in the existing tests had bypassed the property and
missed this. Verified against real V2 storage: view constructs with
draft_layers=[60], subdiv=4.

Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
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