Skip to content

[None][fix] Keep the standalone drafter's pool unpaired on disagg workers that ship its window - #24

Draft
zheyuf wants to merge 2 commits into
brb-nv:user/brb/m3.1-flash-gb300-initialfrom
zheyuf:zheyuf/brb-dspark-disagg-unpaired
Draft

zheyuf wants to merge 2 commits into
brb-nv:user/brb/m3.1-flash-gb300-initialfrom
zheyuf:zheyuf/brb-dspark-disagg-unpaired

Conversation

@zheyuf

@zheyuf zheyuf commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Alternative to #23.

Bug. #22 pairs the standalone DSpark drafter's pool with the target's block reuse whenever the two page sizes match. On SM107 the drafter now takes the target's 128-token pages, so with block reuse on, the two pools are paired.

The SM107 MiniMax-M3.1 targets opt into the disaggregated draft-window transfer (RuntimeHooks.disagg_draft_kv=True), which ships an unpaired pool only. select_disagg_draft_kv_cache_manager therefore raises ValueError at executor creation, and every disaggregated worker with block reuse fails to start.

The conflict comes from serving mode, not from the GPU. The pairing decision did not know that the worker is disaggregated.

Fix (2 commits):

  1. Decline the pairing. KvCacheCreator._joint_reuse_supported declines the pairing for a disaggregated worker that ships the drafter window. It uses the selector's own opt-in predicate, model_ships_disagg_draft_window, factored out of select_disagg_draft_kv_cache_manager, so the creator never carves a pool the selector refuses.
  2. Keep the static page. Such a worker also keeps the static draft page size (64 tokens), as it did before [None][feat] Reuse the standalone DSpark drafter's KV with the target's prefix cache #22.
    • The 128-token pages serve only the pairing.
    • In an unpaired pool, a prefix hit's first stored position is rounded up to a draft page (DFlashWorker._ctx_base_for_store). A 128-token page therefore hides up to 127 tail tokens from the drafter, while a 64-token page hides up to 63.
    • Measured: the 128-token unpaired pool cost 0.16 AL on warm disagg passes (table below).
agg disagg worker shipping the window disagg, kill switch or no opt-in
branch tip 128, paired 128, paired → ValueError 128, paired
this PR 128, paired (unchanged) 64, unpaired (as before #22) 128, paired (unchanged)
#23 on SM107 64, unpaired 64, unpaired 64, unpaired

Why not #23 (gate the 128-token pages on SM100/SM103):

  • It unpairs aggregated serving on SM107 and loses AL. On Rubin agg, K7 block falls from 4.033 to 3.790 ± 0.027, against the golden 4.04 (−6.2%, z = −2.75); see the table below.
  • It does not fix the underlying conflict. An SM100/SM103 target that opted into disagg_draft_kv would still hit the ValueError.
  • Its kernel premise does not hold. On SM107, aggregated serving already runs the 128-token pages: every server binds a paired page_size=128 pool, and AL matches the SPEED-Bench golden at K=1..7 within ±0.2% over 166 passes.

Test Coverage

Unit tests. TestDraftPoolPageSize::test_disagg_draft_window_keeps_the_pool_unpaired covers four cases: aggregated, disaggregated with the window transfer, disaggregated with the kill switch, and disaggregated without the opt-in. Each case checks the draft page size and the pairing decision, and checks that the selector accepts the pool the creator carves.

On a Rubin GPU:

  • The new cases pass, together with test_draft_tokens_per_block, test_joint_reuse_needs_one_page_size, test_paired_draft_reuse (8) and test_kv_cache_v2_scheduler (325).
  • 6 failures fail identically on branch tip 20a1a00, so they are not from this PR:
    • 5 × test_estimation_temporarily_uses_inferred_pool_sizing;
    • test_gen_init_sizing_is_inert_on_a_context_only_worker: DFlashWorker has no _ctx_stored_host, which a66e3b5 added without updating this test.

Vera Rubin (SM107) end to end

Setup:

  • Image trtllm-m31-vr-pr-edc2be364a. Its Python is byte-identical to 1aa2dfca65; the branch commits and each candidate are applied as a sha256-checked overlay.
  • MiniMax-M3.1-Flash + DSpark, identical to the HF release.
  • Golden semantics: use_rejection_sampling with rejection_sample_method: block, K=7.
  • 80 SPEED-Bench coding prompts, T=1, top_p 0.95, 4096 tokens, concurrency 64, /v1/completions.
  • Pass 1 is cold (36.5% prefix-cache hit, the shared system prefix). Passes 2+ repeat the prompts with new seeds (95.9% hit: everything but the last prompt token).
  • Agg: TP2, peihengh's VR agg config.
  • Disagg: 1P1D on one node, CTX TP2 to GEN TP2, peihengh's VR disagg configs (block reuse on both roles, NIXL). The three disagg variants ran on the same node with the same seeds.
serving code draft pool startup AL cold AL warm AL all (± se)
agg branch tip 128, paired ok 4.033 ± 0.009 (22 passes)
agg this PR 128, paired ok 4.040 4.038 4.038 ± 0.012 (10 passes)
agg #23 64, unpaired ok 3.943 3.751 3.790 ± 0.027 (10 passes)
disagg branch tip 128, paired CTX exits after 130 s: ValueError
disagg this PR 64, unpaired ok 4.052 3.772 (4 passes)
disagg first commit only 128, unpaired ok 3.989 3.608 (4 passes)
disagg #23 64, unpaired ok 4.015 3.765 (4 passes)

Golden: 4.04.

  • Every disagg run transfers cleanly: every CTX and GEN rank ships the window (draft_kv_fallback=0), 0 tracebacks, 320/320 requests succeeded.
  • Agg: no non-golden or fallback markers after startup.

Known limit (not introduced here): disagg AL after CTX-side prefix hits.

PR Checklist

  • Pre-commit hooks (not run on Hecate, which has no formatter; the changed lines keep the files' yapf/ruff layouts and line limits)

🤖 Generated with Claude Code

…kers that ship its window

NVIDIA#22 pairs the standalone DSpark drafter's pool with the target's block reuse
whenever the two page sizes match. A disaggregated worker of a model that
opts into the draft-window transfer (RuntimeHooks.disagg_draft_kv: the
sm_107 MiniMax-M3.1 targets) ships an unpaired pool only, so with block
reuse on, select_disagg_draft_kv_cache_manager refused the paired pool at
executor creation and the worker failed to start.

KvCacheCreator._joint_reuse_supported now declines the pairing for a
disaggregated worker that ships the window, using the selector's own
opt-in predicate (model_ships_disagg_draft_window). Aggregated serving
keeps the pairing on every GPU; the kill switch
TRTLLM_M31_DISABLE_DISAGG_DRAFT_KV=1 keeps it on disagg workers too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
…e drafter window

The target's 128-token draft pages (NVIDIA#22) serve only the pairing with the
target's block reuse. A disaggregated worker that ships the drafter window
keeps its pool unpaired, and there a prefix hit's first stored position is
rounded up to a draft page (DFlashWorker._ctx_base_for_store): a 128-token
page hides up to 127 tokens of the tail from the drafter, a 64-token page
up to 63. On Rubin 1P1D disagg (K7 block, 96% prefix hits) the 128-token
unpaired pool lost ~0.16 AL against the 64-token one, so such a worker now
keeps the static page size, as before NVIDIA#22.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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