Skip to content

[Bugfix][DiffusionGemma] Read the causal mask as int32 and compile the sample step - #59992

Merged
bigPYJ1151 merged 3 commits into
vllm-project:mainfrom
ojeda-e:fix-cpu-attn-int32-causal
Oct 6, 2026
Merged

bigPYJ1151 merged 3 commits into
vllm-project:mainfrom
ojeda-e:fix-cpu-attn-int32-causal

Conversation

@ojeda-e

@ojeda-e ojeda-e commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Fixes #59991.

This PR has two fixes for DiffusionGemma. The first unblocks the CPU backend. The second applies to every platform.

  1. DiffusionGemma fails on every CPU run. [Bugfix][Model] Fix DiffusionGemma silently freezing attention mask under CUDA graph replay #51994 made the per-request causal mask int32 and updated FlashAttention and Triton. CPU attention still reads it as bool. This PR makes the CPU kernel read int32.

  2. [Perf] DiffusionGemma: one-pass sampler statistics kernel #58226 left _compiled_sample_step uncompiled on every platform. This PR moves its torch.compile decorators back from the _denoise_temperature helper.

No open issue or PR covers either fix. For the causal mask I searched for the error message, dynamic_causal on CPU and DiffusionGemma on CPU. For the sample step I searched open PRs for _compiled_sample_step, _denoise_temperature and 58226. The only hits, #45954, #47278 and #56210, do not touch the decorators.

Claims

CPU causal mask (7b1cee8):

Sample step compile (c4a07b1):

  • _compiled_sample_step is compiled again, as it was before [Perf] DiffusionGemma: one-pass sampler statistics kernel #58226. _denoise_temperature stays compiled and drops the raised recompile limit, which it does not need.
  • On CPU, given the same random tokens, the compiled step matches the eager step. It is about 2x faster without self-conditioning and 1.0x to 1.5x faster with it. End-to-end CPU throughput is unchanged within noise.
  • test_sample_step_is_compiled fails if torch.compile moves off the step again.

Validation

Hardware: 22 vCPU Intel Xeon Platinum 8481C (AMX), Ubuntu 22.04, PyTorch 2.13.0+cpu. vLLM built from source for CPU at 7867d6c.

CPU causal mask

check main this PR
test_varlen_with_paged_kv_dynamic_causal 36 failed, expected scalar type Bool but found Int 36 passed
tests/kernels/attention/test_cpu_attn.py 1621 passed, 1153 skipped
DiffusionGemma structured read, google/diffusiongemma-26B-A4B-it in BF16 fails at startup with the same error 3 runs of 50 reads, identical outputs
pytest -v tests/kernels/attention/test_cpu_attn.py -k dynamic_causal
pytest -v tests/kernels/attention/test_cpu_attn.py

Any DiffusionGemma request on a CPU build reproduces it. I used LLM.generate with a structured read (32-token canvas, read-only, one step).

Sample step compile

  • test_sample_step_is_compiled fails before the change and passes after. Its module skips without CUDA and I have no GPU, so I called the test function directly on CPU.
  • Compiled against eager over 16 combinations of canvas width, self-conditioning on or off, fp32 or bf16 embeddings, and peaked or flat logits, 6 steps each, with inductor's fallback_random on. Sampled tokens and state match. No graph breaks.

Sample step alone, vocab 262144, hidden 2816, bf16 embeddings, median of 10 calls. Eager is the same build under torch.compiler.set_stance("force_eager").

requests x canvas self-conditioning eager compiled speedup
1 x 256 off 0.4 ms 0.2 ms 2.3x
8 x 64 off 0.5 ms 0.2 ms 2.1x
1 x 256 on 56.9 ms 56.7 ms 1.0x
8 x 64 on 189.7 ms 123.6 ms 1.5x

End to end with LLM.generate: google/diffusiongemma-26B-A4B-it in BF16, enforce_eager=True (it does not turn off the step's own torch.compile), canvas 64, 8 chat prompts per call, max_tokens=128. 3 timed calls after a warmup, arms run in the order shown.

arm sample step tokens (last call) tok/s (median call) per-call time (s)
before (7b1cee8) eager 769 16.45 49.8, 46.7, 40.2
after (c4a07b1) compiled 762 16.75 45.5, 48.2, 43.6
before (7b1cee8) eager 769 15.97 49.3, 48.2, 42.3

The difference is within run-to-run noise. On CPU the sample step is a small share of each call. The compiled arm words answers differently because the renoise tokens now come from inductor's RNG.

I couldn't test in GPU (running short today).

Details

CPU causal mask

#51994 fixed a FULL CUDA graph bug. FlashAttention cast the bool mask to int32 on every build(), and the captured graph kept reading the tensor from capture time. The fix allocates the mask as int32 in DiffusionGemma, and FlashAttention now rejects other dtypes. CPU attention passes the same tensor to its C++ ops, which read it with data_ptr<bool>(). The CPU tests built their own bool mask. CI stayed green.

The kernel reads int32 instead of CPUAttentionMetadataBuilder.build() casting the mask. A cast there would bring back the per-step allocation #51994 removed. It is harmless on CPU today, since CPU has no graph capture, but it would break the contract every other backend follows.

No measurable tradeoffs. The kernel reads the flag once per request and once per work group. The mask grows from 1 to 4 bytes per request.

Limitations:

  • The CPU ops now reject bool masks. The only caller passes the model's int32 mask.
  • test_varlen_with_paged_kv_dynamic_causal runs only on CPUs with AMX. Other ISAs share the changed lines, but no test covers their per-request causal path, before or after this PR.

Sample step compile

#58226 added _denoise_temperature directly below the @torch._dynamo.config.patch(recompile_limit=64) and @torch.compile decorators of _compiled_sample_step, and Python applied them to the helper. Since then the step has run eager on every platform. The step body has not changed since #58226. The fix restores the earlier layout.

Tradeoffs, each the same as before #58226:

  • The renoise torch.randint uses inductor's RNG. At a given global seed, outputs differ from the eager step. Diffusion models reject per-request seeds.
  • On CPU, warmup does not reach the sample step. Each new shape specialization compiles on first use while serving.

Limitations:

  • No GPU numbers and no accuracy eval.
  • test_sample_step_is_compiled checks _torchdynamo_orig_callable, which test_batched_count_greater_than.py also reads. Compiled and eager differ only in random draws, so no output assertion can tell them apart.

Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (Mandatory for agents, optional for humans).

  • AI assistance was used during the creation of this PR.

  • Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.

  • Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.

  • Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.

  • Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.

Signed-off-by: ojeda-e <eojeda@league.com>

@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.

@mergify mergify Bot added cpu Related to CPU backends bug Something isn't working labels Oct 5, 2026
token_num = sum(query_lens)
dynamic_causal_tensor = (
torch.tensor(dynamic_causal, dtype=torch.bool)
torch.tensor(dynamic_causal, dtype=torch.int32)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Leaving this note as a heads up.

Since tying the test to the model's buffer felt out of scope here, I only updated this dtype.

However, note that the kernel test still builds its own mask instead of reading DiffusionGemma's _causal_buf. As a consequence, a future dtype change in the model would not fail this test. It would fail at the kernel instead, because data_ptr<int32_t>() checks the dtype.

Signed-off-by: ojeda-e <eojeda@league.com>
@ojeda-e ojeda-e changed the title [Bugfix][CPU][DiffusionGemma]: Read the per-request causal mask as int32 [Bugfix][CPU][DiffusionGemma] Read the causal mask as int32 and compile the sample step Oct 5, 2026
@ojeda-e ojeda-e changed the title [Bugfix][CPU][DiffusionGemma] Read the causal mask as int32 and compile the sample step [Bugfix][DiffusionGemma] Read the causal mask as int32 and compile the sample step Oct 5, 2026

@Chessing234 Chessing234 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.

everything seems fine, ready to merge.

@bigPYJ1151 bigPYJ1151 added the verified Run pre-commit for new contributors without triggering other tests label Oct 6, 2026

@bigPYJ1151 bigPYJ1151 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! LGTM

@bigPYJ1151

Copy link
Copy Markdown
Member

/ci run

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

✅ Triggered Buildkite CI #93067 for commit a580d51b313e.

@vllm-agent

Copy link
Copy Markdown
Contributor

CI selector (shadow): 11 test steps (25 jobs) instead of 68 (112 jobs)

Shadow mode: this changes nothing about what CI runs. It shows what the evidence-based selector would pick for this PR, next to today's rules. How it works.

Feedback welcome: reply here if it would skip a step this change needs, or runs something unrelated.

steps (jobs) Today's rules Selector Would skip Would add
NVIDIA, CPU and others 68 (112) 11 (25) 63 (98) 6 (11)
AMD mirrors 62 (100) 2 (8) 60 (92) 0 (0)
Selector would run (11)
  • arm-cpu-test ×3
  • cpu-distributed-tests-dp-tp
  • cpu-distributed-tests-pp-tp
  • cpu-kernel-tests ×2
  • cpu-language-generation-and-pooling-model-tests ×3
  • cpu-multi-modal-model-tests-n ×4
  • cpu-quantization-model-tests
  • cpu-qwen2-5-vl-multimodal-tests
  • cpu-spec-decode-tests
  • kernels-attention-test ×7
  • v1-sample
Would skip (today's rules run them) (63)
  • amd-lm-eval-small-models-harness
  • amd-qwen3-next-mtp-async-eplb-accuracy
  • ascend-npu-test
  • async-engine-inputs-utils-worker
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-test-other-cpu
  • basic-models-tests-extra-initialization ×14
  • basic-models-tests-initialization
  • basic-models-tests-other
  • benchmarks-cli-test
  • distributed-model-tests-2-gpus ×3
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-multimodal
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • entrypoints-integration-speech_to_text
  • fusion-e2e-quick-h100
  • fusion-e2e-tp2-b200
  • fusion-e2e-tp2-quick-h100
  • kernels-core-operation-test ×3
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kernels-root-misc-test-b200
  • language-models-tests-extra-standard ×2
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • lm-eval-small-models
  • metrics-tracing-2-gpus
  • model-executor
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • multi-modal-models-standard-4-other-whisper
  • multi-modal-processor ×4
  • multi-modal-processor-cpu ×4
  • openai-api-correctness
  • pipeline-context-parallelism-4-gpus
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • pytorch-fullgraph-test
  • quantization ×4
  • regression
  • torch-stable-abi-audit
  • v1-core
  • v1-executor-worker
  • v1-kv-connectors ×4
  • v1-kv-offload
  • v1-logits-oracle
  • v1-metrics-lmeval
  • v1-others-cpu
  • v1-spec-decode
Would add (today's rules do not run them) (6)
  • arm-cpu-test ×3 (code map)
  • cpu-distributed-tests-dp-tp (code map)
  • cpu-distributed-tests-pp-tp (code map)
  • cpu-multi-modal-model-tests-n ×4 (code map)
  • cpu-qwen2-5-vl-multimodal-tests (code map)
  • cpu-spec-decode-tests (code map)
AMD mirrors: would skip (60)
  • async-engine-inputs-utils-worker
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-tests-extra-initialization ×14
  • basic-models-tests-initialization
  • basic-models-tests-other
  • benchmarks-cli-test
  • distributed-model-tests-2-gpus ×3
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-multimodal
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • entrypoints-integration-speech_to_text
  • fusion-e2e-quick-h100
  • fusion-e2e-tp2-ar-rms-config-sweep-h100
  • fusion-e2e-tp2-b200
  • fusion-e2e-tp2-quick-h100
  • kernels-core-operation-test ×3
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kernels-root-misc-test-b200
  • language-models-tests-extra-standard ×2
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • lm-eval-small-models
  • metrics-tracing-2-gpus
  • model-executor
  • mrcr-eval-small-models
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • multi-modal-models-standard-4-other-whisper
  • multi-modal-processor ×4
  • openai-api-correctness
  • pipeline-context-parallelism-4-gpus
  • platform-tests
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • pytorch-fullgraph-test
  • quantization ×4
  • regression
  • torch-stable-abi-audit
  • v1-core
  • v1-executor-worker
  • v1-kv-connectors ×4
  • v1-kv-offload
  • v1-logits-oracle
  • v1-metrics-lmeval
  • v1-spec-decode
AMD mirrors: would add (0)

none

5 changed files · base 31e2443c90 · head a580d51b31 · Python record: build 93039 at 1e5d0ea888 · kernel record: table 1e5d0ea (build 93039), map 1e5d0ea · not counted: 10 build steps, 5 A100 steps the generator no longer emits, 10 optional steps the selector would also run

@bigPYJ1151
bigPYJ1151 merged commit 3313825 into vllm-project:main Oct 6, 2026
181 of 182 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working cpu Related to CPU backends verified Run pre-commit for new contributors without triggering other tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: CPU attention rejects DiffusionGemma int32 causal mask since #51994

4 participants