Skip to content

[Bugfix][XPU] Apply attn_sink in the DeepSeek-V4 sparse MLA Triton kernel - #76

Draft
afierka-intel wants to merge 1 commit into
mainfrom
afierka/xpu-v4-attn-sink
Draft

afierka-intel wants to merge 1 commit into
mainfrom
afierka/xpu-v4-attn-sink

Conversation

@afierka-intel

Copy link
Copy Markdown
Owner

What

Apply DeepSeek-V4 attn_sink in the XPU sparse-MLA Triton kernel (_bf16_mla_sparse_kernel) and pass it from both V4 call sites on XPU (prefill in xpu_sparse.py, decode in xpu_sparse_decode_fp8.py). Add kernel tests.

Why

On XPU the V4 path never applied the per-head sink logit: xpu_sparse_decode_fp8() accepted attn_sink but did not use it, and the prefill call did not pass it at all. NVIDIA (FlashMLA / FlashInfer), ROCm and CPU backends all apply it, so XPU silently computed a different softmax.

The sink is not small in the released checkpoint: the 44 *.attn.attn_sink tensors of deepseek-ai/DeepSeek-V4-Flash span [-2.20, +2.49]. Error of the XPU kernel ignoring the sink vs. an fp32 reference with the sink (Intel Arc Pro B70, bf16, sinks drawn from that range, worst over seeds/scales):

valid keys in the row sink ignored (main) this PR
1 105–118 % ≤ 1.7e-3
4 24–56 % ≤ 2.0e-3
16 0.6–22 % ≤ 2.1e-3
128 0.2–3.5 % ≤ 2.2e-3
512–2048 0.2–0.8 % ≤ 2.2e-3

Rows with few valid keys are exactly the start of every sequence / the SWA window.

Implementation: optional attn_sink_ptr + HAS_ATTN_SINK constexpr; after the key loop, m = max(e_max, sink·log2e), rescale acc and e_sum, e_sum += 2^(sink·log2e − m). The returned lse stays over the selected keys only (same convention as reference_mla_sparse_prefill); -inf is a bitwise no-op. Callers that do not pass attn_sink (V3.2 / GLM-5 backend) compile the same kernel as before.

Not a duplicate: vllm-project#57798 changes how V4 sinks are loaded (all platforms), not how the XPU kernel applies them; no open PR touches vllm/v1/attention/ops/xpu_mla_sparse.py.

Test plan

pytest tests/kernels/attention/test_xpu_mla_sparse.py -v

On Intel Arc Pro B60: 10 passed, base 187a0eb98a (6 new: DSv4 shape with 1/4/16/128/512 valid keys vs. reference_mla_sparse_prefill(attn_sink=...), and the -inf no-op test; the new tests also require the no-sink result to differ for short rows).

End to end:

  • V4 tiny model (yujiepan/deepseek-v4-fp4-fp8-tiny-random with q_lora_rank 256 and sinks from the V4-Flash range), XPU vs. NVIDIA H200 on identical weights/prompts: rel. L2 of layer-0 attention output 0.22 (main) → 0.09 (this PR). Re-checked on base 187a0eb98a, prompt-logprob distance to H200 (1500 / 6000 / 16-token prompts): max |Δ| 1.57 / 1.76 / 2.14 nats on main → 0.38 / 0.52 / 0.41 with this PR; mean |Δ| 0.111 / 0.109 / 0.79 → 0.079 / 0.096 / 0.16.
  • deepseek-ai/DeepSeek-V4-Flash real weights, TP=8 on 8× Intel Arc Pro B60 (with this PR and fix: avoid overlapping stores in XPU KV cache kernel vllm-project/vllm#52582): coherent answers; first 16 GSM8K test questions, greedy, 700 new tokens: 13/16, every question whose reasoning finished in the budget was correct. At n=16 the A/B against main shows no accuracy gap (both 8/16 at a 256-token budget) — the case for this PR is fidelity to the reference, not a headline accuracy change.

XPU-only change: triton_bf16_mla_sparse_interface is imported only by vllm/v1/attention/backends/mla/xpu_mla_sparse.py and vllm/models/deepseek_v4/xpu/.

AI-assisted (Claude Code / Opus).

…rnel

The XPU DeepSeek-V4 path passed attn_sink to xpu_sparse_decode_fp8 but never
applied it, and the prefill call did not pass it at all, so the per-head sink
logit was silently ignored (NVIDIA/ROCm/CPU backends apply it). The error is
largest for rows with few valid keys (start of a sequence, SWA window).

Add an optional attn_sink pointer to _bf16_mla_sparse_kernel
(HAS_ATTN_SINK): the sink joins the softmax denominator, lse is still
reported over the selected keys only, and -inf is a no-op. Pass
attn_sink[: num_heads] from the V4 prefill and decode call sites.

Adds kernel tests against reference_mla_sparse_prefill (DSv4 shape,
sinks in the range of the released checkpoints, 1..512 valid keys) and a
-inf no-op test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Artur Fierka <artur.fierka@intel.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