Skip to content

[Bugfix][V1] Warm attention through dummy profiles#42215

Draft
lesj0610 wants to merge 19 commits into
vllm-project:mainfrom
lesj0610:lesj/tq-decode-jit-warmup-20260510
Draft

[Bugfix][V1] Warm attention through dummy profiles#42215
lesj0610 wants to merge 19 commits into
vllm-project:mainfrom
lesj0610:lesj/tq-decode-jit-warmup-20260510

Conversation

@lesj0610

@lesj0610 lesj0610 commented May 10, 2026

Copy link
Copy Markdown
Contributor

Problem

Old V1 startup profiling/dummy runs can skip attention metadata for request-shaped decode and cached-prefill variants. For TurboQuant, that means decode workspace and Triton decode kernels can first be touched after CUDA graph capture has locked workspace and after the JIT monitor is active.

Maintainer feedback asked to improve dummy/profile coverage instead of adding a TurboQuant-specific synthetic decode launcher. This PR now follows that direction.

Approach

  • Remove the backend-level synthetic TurboQuant decode warmup path.
  • Add a bounded old V1 attention warmup before CUDA graph capture that uses the existing _dummy_run() profile path.
  • Run one forced-attention uniform decode dummy profile and one request-shaped cached-prefill dummy profile.
  • Add num_reqs_override to _dummy_run() so the cached-prefill shape can stay single-request without creating a large scheduler workload.
  • Keep the TurboQuant Triton do_not_specialize annotations for runtime stride/meta parameters. That is separate from warmup routing and reduces unnecessary specialization churn.

This keeps warmup in the same dummy/profile mechanism used by the rest of V1 startup instead of introducing a backend-specific attention launcher.

Test Plan

.venv/bin/python -m py_compile \
  vllm/v1/worker/gpu/warmup.py \
  vllm/v1/worker/gpu_model_runner.py \
  vllm/v1/worker/gpu_worker.py \
  tests/v1/worker/test_gpu_warmup.py

.venv/bin/python -m pytest tests/v1/worker/test_gpu_warmup.py -q
.venv/bin/python -m pytest tests/v1/worker/test_gpu_model_runner.py -k "dummy or uniform_decode" -q
git diff --check

Test Result

  • py_compile: passed
  • tests/v1/worker/test_gpu_warmup.py: 2 passed
  • tests/v1/worker/test_gpu_model_runner.py -k "dummy or uniform_decode": 1 passed, 34 deselected
  • git diff --check: passed
  • commit hooks passed, including ruff, mypy, SPDX/header checks, forbidden import checks, and signoff check

GPU runtime smoke was not rerun after this consolidation, so absence of runtime JIT warnings remains to be validated on a CUDA serving run.

Checklist
  • Purpose
  • Test plan and results
  • AI assistance disclosed

AI assistance: Codex.

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

@lesj0610

Copy link
Copy Markdown
Contributor Author

@ZJY0516 @qiching @tdoublep @vadiklyutiy Sorry to bother again, third one from me for the #40137 area.

This time it is TurboQuant decode. _tq_decode_stage1 and _tq_decode_stage2 compile during first real decode because startup warmup does not go through TQ decode path. There is also workspace buffer issue — if buffers are not pre-allocated before CUDA graph capture locks workspace, first request crashes.

I call _decode_attention() directly with synthetic inputs during warmup. This handles both kernel compile and workspace allocation. Verified on Qwen3-8B with turboquant_4bit_nc.

If you have concerns about the approach or scope please let me know.

@mergify mergify Bot added the bug Something isn't working label May 10, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a warmup mechanism for TurboQuant decode kernels to ensure they are compiled before serving requests, thereby reducing latency on the first inference. It adds the turboquant_warmup.py module, integrates it into the kernel_warmup flow, and provides comprehensive unit tests. Feedback was provided regarding the calculation of block_table_stride, noting that the current approach might default to an incorrect value during the initial warmup phase and suggesting a more direct way to access the required constant from the model runner to avoid unnecessary re-compilation.

Comment thread vllm/model_executor/warmup/kernel_warmup.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb6fd58b07

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vllm/model_executor/warmup/kernel_warmup.py Outdated
@lesj0610 lesj0610 force-pushed the lesj/tq-decode-jit-warmup-20260510 branch from fb6fd58 to 42aafd5 Compare May 10, 2026 07:49
@MidasMining

Copy link
Copy Markdown

This PR's problem statement matches Issue #41565 exactly:

"Decode scratch buffers from WorkspaceManager must be allocated before CUDA graph capture calls lock_workspace(). If warmup skips this allocation, first decode request tries to grow locked workspace and crashes."

That's the workspace lock-violation we filed in #41565 (AssertionError: Workspace is locked but allocation from 'turboquant_attn.py:757:_continuation_prefill' requires 2.00 MB, current size is 0.26 MB). We've been pinned to the pre-#40941 fork waiting for either this PR or PR #40798 to land.

The two PRs attack the same root cause from different angles:

These are complementary, not competing. Both landing would be belt-and-suspenders against the same regression. Either one resolves #41565.

For the maintainer queue: I can validate this on 8× RTX A4000 (SM86) / Nemotron-3-Super-120B-AWQ-4bit / TurboQuant — different model class than your Qwen3-8B test (hybrid Mamba+MoE+Attention vs dense attention) and different arch generation. Worth a cross-platform second data point. Happy to run a sweep at 4K / 16K / 64K / 131K cached tokens once review opens.

@MidasMining

Copy link
Copy Markdown

Following up on this — we're still pinned to a pre-PR#40941 fork on production specifically because of the workspace lock-violation this PR addresses. The bug fires reliably on:

  • 8× RTX A4000 (SM_86 Ampere)
  • Nemotron-3-Super-120B-AWQ-4bit (Nemotron-H: 88 layers, 40 Mamba + 40 MoE + 8 Attention; 512 experts top-22)
  • --kv-cache-dtype tq-t3nc, TP=8 EP=8, --max-model-len 131072
  • vLLM v0.20.x onwards (last working version is the snapshot we forked from)

Trace matches #41565 exactly: first-decode AssertionError: Workspace is locked but allocation from 'turboquant_decode_stage1' grew. Reproducible on cold start.

Today's expanded scope (full-dequant JIT warmup + V2 single-request execute path) covers more of our profile than the original — we hit JIT compile spikes on first decode under TQ regardless of the specific kernel, so the broader warmup matches the bug shape better.

Happy to validate on our 8× A4000 SM_86 stack against Super-120B once you've stabilized rebases and the pre-run-check clears. Production daily-driver workload, real bench corpus available. Just ping when you'd like a runtime validation report.

@lesj0610

Copy link
Copy Markdown
Contributor Author

@MidasMining Thanks for following up and the detailed repro.

Current branch covers full-dequant JIT warmup, continuation path, prefix-cache hit variants, and V2 single-request execute path.

Production validation from different platform would be really helpful, let me know if you need anything.

@MidasMining

Copy link
Copy Markdown

@lesj0610 Validation report from our 8× RTX A4000 SM_86 production stack.

TL;DR: the patch resolves the workspace-lock crash on cold start. ✅

Setup

  • vLLM: clean install of PR head d8fa820 (current branch HEAD), VLLM_USE_PRECOMPILED=1 editable install in a fresh uv venv. Got vllm 0.21.1rc1.dev134+gd8fa8207d, torch 2.11.0+cu130, transformers 5.9.0, triton 3.6.0.
  • Hardware: 8× RTX A4000 (16 GB each, 128 GB total), EPYC 7532, PCIe Gen4. Driver 580.76.05 (CUDA 13.0 capable). Local CUDA toolkit 12.8 used for flashinfer JIT (12.8 nvcc compiles cleanly for SM_86 against the cu130 wheel ABI; first attempt with system /usr/bin/nvcc from CUDA 12.0 hit 100 errors in flashinfer's sampling kernel JIT, fixed by CUDA_HOME=/usr/local/cuda-12.8).
  • Model: Nemotron-3-Super-120B-AWQ-4bit (cyankiwi compressed-tensors INT4 g32). Nemotron-H hybrid arch: 88 layers (40 Mamba + 40 MoE + 8 Attention), 512 experts top-22.
  • Launch:
vllm serve <path>/Nemotron-3-Super-120B-AWQ-4bit \
  --tensor-parallel-size 8 --gpu-memory-utilization 0.90 \
  --max-num-seqs 4 --max-model-len 131072 \
  --trust-remote-code --enable-expert-parallel \
  --mamba-ssm-cache-dtype float16 \
  --kv-cache-dtype turboquant_3bit_nc

Results

  • Cold-start boot succeeded — no AssertionError: Workspace is locked but allocation from 'turboquant_decode_stage1' grew. This is the trace [Bug]: TurboQuant _continuation_prefill workspace allocation fails at long context — v0.20.0 regression #41565 documents and that pinned us to a pre-PR#40941 fork.
  • First decode succeeded — the request that previously triggered the crash returned a clean response without exception (test prompt: trivial single-token answer).
  • Warm decode succeeded — second request served at expected latency.
  • Steady-state decode — ~39 t/s at concurrency 1 (engine log: Avg generation throughput: 39.4 tokens/s).
  • ℹ️ KV cache size: 3,593,755 tokens at 131K context, max concurrency 27.42×. Notably larger than what our pinned fork reports (574,464 tokens, 4.38×) at the same --gpu-memory-utilization 0.90, presumably due to the new CUDA graph memory profiling default + TQ shared dequant buffers ([Attention][TurboQuant] Share dequant buffers, eliminate float16_copy #40941) + this PR's pre-allocated warmup. Did not stress-test the 27× concurrency claim — boot-reported only.

Warmup behavior observed

All workers logged the CUDA graph memory profiling notice cleanly. No exceptions during kernel_warmup()turboquant_decode_warmup(). The full-dequant + V2 single-request execute path + prefix-hit warmup additions you pushed on 2026-05-19/20 all completed without warning on this config.

What this validates / doesn't

✅ The expanded warmup scope covers our Nemotron-H + TQ-3bit-NC + EP=8 + cyankiwi compressed-tensors INT4 combination — the previously-crashing path.

❌ Did not test: heavy concurrency stress, long-context (>32K) decode, prefix-cache eviction churn, or comparison to a pre-PR baseline reproducing the exact crash signature on this build (the prior repro was on our fork, not on upstream main at PR base SHA). Happy to add any of these if useful for review.

Ask

If this is the validation signal you needed, no further from us. If review surfaces config edge cases (different num_kv_splits, different --max-num-seqs, EP off, etc.) that you'd like a second-platform datapoint on, just ping — production-similar bench is cheap to re-run.

@MidasMining

Copy link
Copy Markdown

Additional datapoint: ran our 5-test practical-debugging quality bench (zmq + pplns subset, thinking-on, single-shot, temperature=0) against the patched build with the same config as my previous comment. Result: 100% (5/5) — matches our production fork's score on the same bench. No quality regression introduced by the warmup path on Nemotron-H + tq-3bit-nc + EP=8 + compressed-tensors INT4.

That closes out validation from our end. Ping if you'd like a runtime check after any future rebases.

@lesj0610

lesj0610 commented May 23, 2026

Copy link
Copy Markdown
Contributor Author

@MidasMining Thanks for testing this. Your setup is quite different from mine (SM86, Nemotron-H hybrid, TP=8/EP=8) so this is really helpful.

Good to see no workspace-lock crash on cold start and decode works fine. That was the main problem I was trying to fix.

Concurrency / long-context / prefix-cache stress — yeah those are separate topics. Maybe worth testing later but not what this PR is about.

Appreciate it.

@mgoin

mgoin commented May 26, 2026

Copy link
Copy Markdown
Member

@lesj0610 is there any chance you can simplify this PR? It is changing lots of unrelated code in the general warmup path. I would think we can resolve this issue in a more modular way with the current structures in the codebase

lesj0610 and others added 2 commits May 29, 2026 01:32
Warm TurboQuant decode through the runtime decode helper so the decode Triton kernels and workspace buffers are initialized before serving requests.

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Gemini <noreply@google.com>
Signed-off-by: lesj0610 <lesj0610@users.noreply.github.com>
(cherry picked from commit 42aafd5)
Warm the TurboQuant continuation-prefill full-dequant kernel with runtime block-table constants, and stop Triton from specializing runtime stride arguments for decode/dequant kernels.

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: lesj0610 <lesj0610@users.noreply.github.com>
(cherry picked from commit c0d1a33)
Add a pre-capture old V1 attention warmup that uses the existing dummy-run path instead of a backend-specific synthetic launcher. The forced-attention decode and cached-prefill-shaped profiles let attention backends allocate workspace and compile before CUDA graph capture locks workspace and before the JIT monitor starts.

Signed-off-by: lesj0610 <lesj0610@users.noreply.github.com>
@lesj0610 lesj0610 changed the title [Bugfix][V1][TurboQuant] Warm up decode kernels [Bugfix][V1] Warm attention through dummy profiles Jun 22, 2026
@lesj0610

lesj0610 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@mgoin that makes sense. I reworked this to follow that direction.

The PR no longer uses a TurboQuant-specific synthetic warmup. It now extends the existing old V1 dummy/profile path so attention metadata is built before CUDA graph capture and before the JIT monitor starts.

This should cover TurboQuant through the normal dummy-run path instead of a backend-specific launcher. I also updated the PR description and tests accordingly.

@mergify

mergify Bot commented Jun 22, 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, @lesj0610.

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 Jun 22, 2026
…rmup-20260510

# Conflicts:
#	vllm/model_executor/warmup/kernel_warmup.py
#	vllm/v1/worker/gpu/warmup.py

Signed-off-by: lesj0610 <lesj0610@users.noreply.github.com>
@mergify mergify Bot removed the needs-rebase label Jun 23, 2026
Sandermage added a commit to Sandermage/sndr_core_engine that referenced this pull request Jun 26, 2026
…y upstream PR #42637)

After full upstream audit (vllm PRs #42637/#40798/#42215/#40914 and issues
#42808/#41403/#40069/#41559), determined that 4 G4_* Genesis patches are
superseded by upstream PR #42637 (Mixed-attention TurboQuant for Gemma 4,
lesj0610, OPEN):

- G4_30 (supports_mm_prefix gate flip) -> PR adds real mm_prefix Triton
  kernel mask (USE_MM_PREFIX constexpr, mm_prefix_range_tensor metadata).
- G4_43 (revert forced TRITON_ATTN) -> PR makes per-layer routing the
  default; review after cherry-pick.
- G4_44 (head_dim>256 torch SDPA fallback) -> PR's _can_use_flash_attn
  gate + _sdpa_causal_prefill cover this surface.
- G4_45 (page-size unification diag/fix) -> PR's unify_kv_cache_spec_
  page_size TQ-aware branch covers fix mode.

Plus G4_50 (Genesis-native AttentionBackend) + genesis_tq/ companion
(10 files) moved to sndr_private/genesis_tq_abandoned/. That direction
was abandoned because upstream TurboQuantAttentionImpl + wrapper strategy
(via g4_19_*) is strictly better than custom backend.

G4_31 (preserve_tq_dtype - AWQ boundary) and G4_32 (validation_bypass)
KEEP in main - PR #42637 does NOT touch AWQ/quant_config or validator
relax. They remain Genesis-original boundary patches.

Updated sndr_core/__init__.py with sndr_private fallback shim - existing
env flags GENESIS_ENABLE_G4_30/43/44/45/50 still resolve via
sndr_private/g4_upstream_tq_wip/ for back-compat. Production users with
slim distributions get silent no-op (ImportError gracefully handled).

Plan document moved to sibling Genesis_internal_docs/ per iron rule #6
(no internal docs in public repo).

Production state (verified): vllm-g4-256k-packed Up 42min, smoke test
correct factual answers, 5.33x compression preserved, ~102 TPS.

Refs:
  - upstream PR vllm-project/vllm#42637
  - upstream PR vllm-project/vllm#40798
  - upstream PR vllm-project/vllm#42215
  - my PR vllm-project/vllm#40914
  - issue vllm-project/vllm#42808
  - issue vllm-project/vllm#41403
Sandermage added a commit to Sandermage/sndr_core_engine that referenced this pull request Jun 26, 2026
…e 3)

Cherry-picks 2 upstream vllm PRs that resolve the v0.21.0 "workspace
locked at 0.00 MB" assertion family (#41565, #41726, #42544, #42808):

  G4_61 (g4_61_tq_shared_workspace.py) — PR #40798 (Bot1822)
    Per-layer _tq_mid_o_buf / _tq_output_buf / _tq_lse_buf allocations
    replaced with shared WorkspaceManager acquisition. Layers execute
    sequentially per request so one buffer set is enough.

    capture_model() now pre-reserves max-shape workspace BEFORE
    lock_workspace fires. _reserve_turboquant_decode_workspace iterates
    ALL attn_groups (not just [0] — addresses gemini-code-assist review).
    Also reserves continuation prefill cache buffer when chunked prefill
    enabled and max_num_batched_tokens > _TURBOQUANT_CONTINUATION_DECODE_
    THRESHOLD (128).

    PR validation: Llama-3.1-70B TP=2 turboquant_3bit_nc 65536 ctx
      Loading mem: 105 GiB -> 66 GiB  (40 GiB recovered)
      Available KV: 14.61 GiB -> 53.97 GiB  (3.7x boost)
      KV tokens @ 64K: 400,128 -> 1,478,384  (3.7x)
      Max concurrency: 6.11x -> 22.56x

    env: GENESIS_ENABLE_G4_61_TQ_SHARED_WORKSPACE

  G4_62 (g4_62_tq_kernel_warmup.py) — PR #42215 (lesj0610)
    Adds turboquant_decode_warmup function that walks Attention layers
    with kv_cache_dtype.startswith("turboquant_"), deduplicates by
    13-field _TurboQuantDecodeWarmupKey (Triton specialization criteria),
    and calls impl._decode_attention with synthetic batch=max_num_decode_
    tokens, seq_lens=1, block_table[:,0]=1 inputs.

    Wraps kernel_warmup(worker) to call our function after deep_gemm
    warmup. Reads block_size + block_table_stride from worker.model_
    runner.input_batch.block_table.block_tables[0] (V1 may split KV
    manager blocks into smaller attention-kernel blocks).

    Complementary to G4_61: G4_61 pre-reserves max-shape, G4_62 compiles
    kernels + actually-allocates. Either resolves #41565 family; both
    together = belt-and-suspenders. Per MidasMining's comment.

    Removes 5-25s first-request TTFT spike that comes from JIT-compiling
    _tq_decode_stage1 + _tq_decode_stage2 on first real decode.

    env: GENESIS_ENABLE_G4_62_TQ_KERNEL_WARMUP

Registry entries added with full credit blocks. Import-time hook in
sndr_core/__init__.py adds G4_61 and G4_62 with documented dependency
ordering (G4_61 patches launcher first, G4_62 wraps kernel_warmup after).

Smoke test (fresh container with both env flags):
  G4_61 applied: True
  G4_62 applied: True

Refs:
  - upstream PR vllm-project/vllm#40798
  - upstream PR vllm-project/vllm#42215
  - issue vllm-project/vllm#41565
  - issue vllm-project/vllm#42544
  - issue vllm-project/vllm#42808
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@lesj0610 lesj0610 force-pushed the lesj/tq-decode-jit-warmup-20260510 branch from f2a738f to 8b094b3 Compare June 26, 2026 17:13
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@mergify

mergify Bot commented Jun 29, 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, @lesj0610.

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 Jun 29, 2026
lesj0610 added 2 commits June 30, 2026 09:00
Signed-off-by: lesj0610 <lesj0610@users.noreply.github.com>
Signed-off-by: lesj0610 <lesj0610@users.noreply.github.com>
@mergify mergify Bot removed the needs-rebase label Jun 30, 2026
@mergify

mergify Bot commented Jun 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, @lesj0610.

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 Jun 30, 2026
@lesj0610 lesj0610 marked this pull request as draft July 3, 2026 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working needs-rebase v1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants