[GDN] Spec-decoding-aware attention kernel - #336
jasonboukheir wants to merge 2 commits into
Conversation
…oding-aware GDN attention kernel Adds an MTP/EAGLE-aware code path to the fused SYCL gdn_attention kernel via two optional tensors (spec_state_indices_tensor, num_accepted_tokens). Required by the vLLM-side dispatcher in vllm-project/vllm#42382 to escape the assert spec_sequence_masks is None hit on the first verify pass for hybrid-GDN models (Qwen3-Next / Qwen3.5 / Qwen3.6). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Hello, we are planning to support this feature. |
e443258 to
e0cc26e
Compare
Adds tests/gdn_attn/test_gdn_attn_spec.py covering the
spec_state_indices_tensor / num_accepted_tokens path:
- K=N aligned-ring equivalence with non-spec native decode (bit-exact
core_attn_out + ssm_state at the touched slot).
- NULL_BLOCK_ID suppression (no pre-state load, no pool writeback when
ring[:, 0] is the null sentinel).
- Argument validation: spec args must be set together; both must be
int32; spec batch dim must match query_start_loc.
Skips the K=1 spec ≡ non-spec bit-exact comparison: state_len for K=1
is Width-1, and the spec_state_roll_kernel only writes positions
{0} ∪ [state_len-K_call, state_len) of the rolled tmp buffer per
work-item — when K_call < state_len-K_call (true for K=1 with Width≥4)
the intermediate tmp rows are uninitialized and the post-state conv
slot differs from non-spec. Production never hits this — vLLM dispatches
K=1 requests to the non-spec path — but the test docstring flags it so
a future reader doesn't burn time re-deriving it.
Full FLA-Triton-oracle correctness for the non-degenerate spec path
with K>1 and arbitrary num_accepted_tokens is covered by the paired
vLLM replay harness in tests/kernels/xpu/test_spec_gdn_replay.py
(vllm-project/vllm#42382).
Closes review request from vllm-project#336 (mayuyuace).
|
@mayuyuace — thanks! UT: added MTP results: Arc Pro B70 is 32 GiB VRAM and the public Qwen3-Next-80B-A3B quants don't fit (Intel AutoRound-int4-mixed ≈ 47 GiB, AWQ-4bit / MLX-4bit ≈ 40 GiB, NVFP4 ≈ 38 GiB; no public weights-only INT3). Ran on
20/20 requests successful, no FLA fallback. Acceptance lines up with the 64.5% the PR describes on the 35B-A3B run. (The 35B MoE flavor still needs vllm-project/vllm#41426 to start up; once that lands I'll rerun on the same model from the PR description.) If Intel has a 32-GiB-fitting Qwen3-Next variant I should be testing against, happy to grab it. |
Adds a spec-decoding code path to the SYCL gdn_attention kernel that mirrors the FLA Triton IS_SPEC_DECODING semantics: - causal_conv1d.hpp: two-pass spec-aware conv1d. Pass 1 stages each candidate token's per-token state into conv_states_tmp; pass 2 consolidates into the rolled history at slot cache_indices[batch_id]. Slots 1..K-1 are left untouched, matching FLA's "only writes slot 0" contract. - gated_delta_rule.hpp: adds IS_SPEC template parameter. When IS_SPEC, the per-sequence load slot comes from spec_state_indices[i_n, num_accepted_tokens[i_n]-1], the per-token store slot comes from spec_state_indices[i_n, t-seq_start], and a raw slot value <= 0 (NULL_BLOCK_ID) means "no valid prior state". - gdn_attn_interface.cpp: dispatch — both spec_state_indices_tensor and num_accepted_tokens must be provided together; mismatched is rejected. When set, routes through native kernels with IS_SPEC=true. - ops.h, torch_bindings.cpp: extend the gdn_attention signature with the two new optional tensors (default None preserves the existing non-spec behavior). Signed-off-by: Jason Elie Bou Kheir <5115126+jasonboukheir@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds tests/gdn_attn/test_gdn_attn_spec.py covering the
spec_state_indices_tensor / num_accepted_tokens path:
- K=N aligned-ring equivalence with non-spec native decode (bit-exact
core_attn_out + ssm_state at the touched slot).
- NULL_BLOCK_ID suppression (no pre-state load, no pool writeback when
ring[:, 0] is the null sentinel).
- Argument validation: spec args must be set together; both must be
int32; spec batch dim must match query_start_loc.
Skips the K=1 spec ≡ non-spec bit-exact comparison: state_len for K=1
is Width-1, and the spec_state_roll_kernel only writes positions
{0} ∪ [state_len-K_call, state_len) of the rolled tmp buffer per
work-item — when K_call < state_len-K_call (true for K=1 with Width≥4)
the intermediate tmp rows are uninitialized and the post-state conv
slot differs from non-spec. Production never hits this — vLLM dispatches
K=1 requests to the non-spec path — but the test docstring flags it so
a future reader doesn't burn time re-deriving it.
Full FLA-Triton-oracle correctness for the non-degenerate spec path
with K>1 and arbitrary num_accepted_tokens is covered by the paired
vLLM replay harness in tests/kernels/xpu/test_spec_gdn_replay.py
(vllm-project/vllm#42382).
Closes review request from vllm-project#336 (mayuyuace).
e0cc26e to
1fd286b
Compare
|
Can you provide accuracy data of the models also? |
|
@mayuyuace I see you have a PR here #368 . Looking through it, I think it's best to close this out and take that! |
Purpose
Adds a spec-decoding code path to the SYCL
gdn_attentionkernel that mirrorsthe FLA Triton
IS_SPEC_DECODINGsemantics, so vLLM's hybrid-linear-attentionmodels (Qwen3-Next / Qwen3.5 / Qwen3.6) can run MTP / EAGLE-style draft +
verify on XPU through the native kernel instead of falling back to the FLA
Triton path on every spec step.
Problem
The current SYCL
gdn_attentionschema doesn't carry the two tensors thespec-decode verify step needs (
spec_state_indices_tensor,num_accepted_tokens), so vLLM's dispatcher routes the spec path to the FLATriton fallback in
_gdn_xpu_spec_python_path. That fallback works but is~10× slower than the native SYCL kernel on the same captures (0.29 ms vs
3.0 ms median on K=1 and K=4 layer captures — measured via the replay
harness in the next section).
The deeper issue is the conv-state layout contract. Under spec decoding, FLA
treats the per-sequence conv ring as a rolled history at slot 0 of
cache_indices— only that slot is updated, slots 1..K-1 are untouched. Aprior native spec attempt that scattered per-token snapshots across K slots
diverged from FLA (max conv-state diff ≈ 47 on K=4) and propagated through
the SSM, making the kernel unusable for the verify step.
Changes
csrc/xpu/gdn_attn/causal_conv1d.hpp— two-pass spec-aware conv1d.state into the existing
conv_states_tmpbuffer (resized to(batch, state_len, conv_elems)under spec). Per-work-item, thisis one write of the current input at
tmp[batch, state_len - K_call + t_in_seq, dim]plus, fort_in_seq < state_len - K_call, a shift copy from the slot'spre-state.
spec_state_roll_kernel): consolidatestmpinto therolled history at
slot[cache_indices[batch], :, :]. Pass 2 isthe sole writer of the slot — race-free with pass 1, which only
wrote
tmp.cache_indices[batch_id]with aload_offset_shift = n_acc - 1, matching FLA's column-0 contract.state_lenis plumbed throughkernel_launcher/KERNEL_LAUNCHER_IMPL, computed at the launcher asconv_states_stride_0 / conv_elems.csrc/xpu/gdn_attn/gated_delta_rule.hpp—IS_SPECtemplateparameter. When
IS_SPEC, the per-sequence load slot comes fromspec_state_indices[i_n, num_accepted_tokens[i_n]-1], the per-tokenstore slot comes from
spec_state_indices[i_n, t-seq_start], and araw slot value ≤ 0 (
NULL_BLOCK_ID) is treated as "no valid priorstate". The non-spec path is template-specialized and behaviorally
unchanged.
csrc/xpu/gdn_attn/gdn_attn_interface.cpp— dispatch. Bothspec_state_indices_tensorandnum_accepted_tokensmust be providedtogether; mismatched pairs are rejected at the interface boundary.
When both are set, routes through native kernels with
IS_SPEC=true.csrc/xpu/ops.h,csrc/xpu/torch_bindings.cpp. Extends thegdn_attentionschema with the two new tensors asOptional[Tensor] = None. Existing non-spec callers see no schemachange; passing
Nonefor both preserves bit-exact pre-PR behavior.The SSM kernel is unchanged — FLA's
fused_sigmoid_gatingalready usesper-token spec slots for the SSM ring, and the existing SYCL
gated_delta_rule.hppSSM path was already correct under that contract.Test Plan
The kernel is exercised by a replay harness (in the paired PR) at
tests/kernels/xpu/test_spec_gdn_replay.py. It captures(metadata, inputs, outputs)tuples from a live Qwen3.6 MTP run viaVLLM_XPU_DUMP_SPEC_GDN=<dir>(default cap 200 tuples), then replayseach capture through the native kernel and diffs against an inline FLA
oracle within
bf16tolerance (atol=rtol=2e-2).The harness covers:
non_specand
specflavors.test_sycl_mixed_batch_matches_per_subset) —pairs each
non_speccapture with the same-layerspec_K4_min1_max1capture, builds a unified pool with disjoint slotranges, replicates the production split / two-kernel-call /
index_copy_scatter, and diffs each subset against its ownreference.
reorder_input=Falseequivalence(
test_sycl_reorder_input_false_equivalence) — repacks capturedprojections into the per-
k_headinterleaved layout used inproduction Qwen3-Next traffic and verifies the kernel produces
equivalent outputs under both
reorder_input=Trueandreorder_input=Falseon fresh pools.core_attn_out+conv_state+ssm_stateon K=1 and K=4 captures.E2E benchmark via
vllm bench serveon Intel Arc B70:Test Result
Replay harness (kernel-level correctness vs. FLA):
atol=2e-2)num_accepted=1, K=3reorder_input=Falseequivalencecore_attn_out+ conv_state + ssm_statePerf (replay harness, median over captures on Intel Arc B70):
E2E (single-stream
vllm bench serve, baseline = no spec decoding):20 requests, no per-step fallback to FLA, no failures.
Raw JSON available on request.
Related issues
gdn::causal_conv1dandgdn::gated_delta_ruleas standalone torch ops). This PR keeps theexisting single-op surface and extends its signature; it does not
split the kernel.
from
torch.compile/ cudagraph capture). Same file(
gdn_attn_interface.cpp) but a separate bug — that one is about thesize(0) == num_actual_tokensprecondition under PIECEWISE captureand is unaffected by this PR.