Skip to content

[Bugfix] Share FlashInfer B12x MoE workspace across layers - #48698

Draft
dumko2001 wants to merge 1 commit into
vllm-project:mainfrom
dumko2001:flashinfer-b12x-dp-sizing
Draft

[Bugfix] Share FlashInfer B12x MoE workspace across layers#48698
dumko2001 wants to merge 1 commit into
vllm-project:mainfrom
dumko2001:flashinfer-b12x-dp-sizing

Conversation

@dumko2001

@dumko2001 dumko2001 commented Jul 15, 2026

Copy link
Copy Markdown

Purpose

#43328 replaced FlashInfer SM12x B12x MoE's functional API with one
B12xMoEWrapper per expert layer. Every wrapper owns large graph-stable scratch
buffers. That creates both failures reported in #47982: DP all-gather can exceed
the per-rank capacity, while persistent workspace is multiplied by the number
of MoE layers. Scaling every per-layer wrapper by dp_size avoids the exception
but worsens the memory amplification.

This PR keeps FlashInfer's caller-owned wrapper contract but weakly shares one
fixed-capacity wrapper across all live expert layers with the same geometry.
Capacity is max_num_tokens * dp_size, established before graph capture. The
layers keep the wrapper alive; a weak registry does not extend model lifetime,
so the wrapper and its GPU scratch are released after the last owning model
layer is destroyed.

This deliberately does not use FlashInfer 0.6.14's functional API: that path
stores mutable scratch in a process-global cache with no public clear/lock
contract and can grow outside vLLM's graph-lifetime control.

Addresses #47982. Use Fixes #47982 only after the DP2 crash and DP1/DP2 memory
regressions pass on CUDA-13 SM120. Mandatory duplicate searches found no
competing PR that repairs both capacity and per-layer amplification.

AI assistance was used. The human submitter must review every changed line and
the recorded hardware/model results before readiness.

Implementation

  • Key wrapper ownership by expert count, top-k, hidden/intermediate geometry,
    DP-scaled capacity, local experts, dtype, device, and activation.
  • Construct a graph-enabled fixed-capacity wrapper during model construction,
    which occurs inside vLLM's normal model-load memory-pool context.
  • Share it across sequential MoE layers while passing each layer's own weights,
    scales, alphas, routing tensors, and FC2 input scale on every call.
  • Keep stable wrapper workspace/output pointers for graph capture.
  • Use weak ownership so same-process model destruction releases the workspace.
  • Preserve the existing EP rejection and SiLU/ReLU2 behavior.

vLLM DBO does not create a scratch race here: every ubatch context receives the
same compute stream, CPU execution is explicitly serialized, and the B12x call
has no yield inside it. However, today's naive-DP prepare path is synchronous
and the modular kernel rejects DBO before reaching the wrapper. This PR does
not add DBO support.

Validation

Current head 78388ad4a is based on upstream 1a659a0c3.

pytest -q tests/model_executor/test_flashinfer_b12x_moe.py
4 passed

pre-commit run --files \
  vllm/model_executor/layers/fused_moe/experts/flashinfer_b12x_moe.py \
  tests/model_executor/test_flashinfer_b12x_moe.py
all applicable hooks passed, including mypy

git diff --check origin/main...HEAD
passed

The contract tests prove that two layers under one model owner construct one
wrapper, different live model owners do not alias mutable scratch, DP2 capacity
is doubled without the unrelated 8192-token autotuning floor, each layer's
distinct tensors reach run, output is copied into the modular-kernel buffer,
and dropping the final owners releases the wrapper.

No B12x kernel, DP2, memory, graph, sleep/reload, or exact-model result is
claimed on this head. The available two-RTX-5090 host exposed SM120 but CUDA
12.8; pinned FlashInfer requires CUDA 13.

Adjacent exact-model work

The #47982 reporter checkpoint is ModelOpt MIXED_PRECISION with W4A16 NVFP4
experts. Current main's B12x selection accepts that scheme, but semantic
quant_mode="w4a16" and source_format="modelopt" are not yet propagated to
the wrapper. #43929 tracks that separate correctness gap and must be
rebased/reduced or explicitly handed off rather than duplicated here.

#48536/FlashInfer #3932 concern the W4A4 scale path. #49031 concerns prepared
W4A16 weight duplication and source-weight lifetime. Workspace sharing does not
solve either issue, so exact-model correctness and peak residency must be
measured on the appropriate stacked revisions.

Required before readiness

On a CUDA-13-capable two-SM120 host:

Release note

Share graph-stable FlashInfer B12x MoE scratch across layers and size it for
data-parallel gathered tokens, addressing the DP capacity crash without
multiplying multi-GiB workspace by every MoE layer.

After complete SM120 validation and mainline merge, request a v0.26 backport.

@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. Once the PR is approved and ready to go, your PR reviewer(s) can run CI to test the changes comprehensively before merging.

To run CI, PR reviewers can either: Add ready label to the PR or enable auto-merge.

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 Jul 15, 2026
@devin-ai-integration
devin-ai-integration Bot force-pushed the flashinfer-b12x-dp-sizing branch from 8f8494c to 05c8d53 Compare July 15, 2026 06:26

@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: 8f8494c3ff

ℹ️ 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/layers/fused_moe/experts/flashinfer_b12x_moe.py Outdated
@dumko2001
dumko2001 force-pushed the flashinfer-b12x-dp-sizing branch from 3088550 to 9a7c8ff Compare July 22, 2026 08:13
@dumko2001 dumko2001 changed the title [Bugfix] Scale FlashInfer b12x capacity for data parallelism [Bugfix] Restore shared FlashInfer B12x MoE workspaces Jul 22, 2026
@dumko2001
dumko2001 marked this pull request as draft July 22, 2026 08:14
@dumko2001
dumko2001 force-pushed the flashinfer-b12x-dp-sizing branch from 9a7c8ff to c154fc8 Compare July 22, 2026 08:38
@dumko2001

Copy link
Copy Markdown
Author

Current-head audit (3e57e8176) used an isolated two-RTX-5090 (SM120) pod and checkout.

Confirmed in the installed FlashInfer 0.6.14 source that the functional path owns a process-global workspace cache keyed by expert/hidden/top-k/device/backend/quantization/activation geometry. It reuses an entry with sufficient routed-row capacity and replaces it only when a larger capacity is required. That validates the ownership model in this PR and avoids one wrapper workspace per MoE layer.

The strengthened vLLM contract test now checks the full functional call surface—input/output identity, output dtype, routing tensors, both weights and scale-factor layouts, both alphas, fc2_input_scale, activation, local/global expert counts, and top-k—and passes:

pytest -q tests/model_executor/test_flashinfer_b12x_moe.py
2 passed, 14 warnings

I did not claim an actual B12x kernel/DP2/model pass. The available host had driver 570 and CUDA 12.8; the installed FlashInfer implementation explicitly rejects SM12x below CUDA 13 (b12x fused MoE requires CUDA 13 or later). A source-built vLLM native extension verified both GPUs are SM120, but bypassing FlashInfer's compatibility gate would not be valid evidence. A CUDA-13-capable two-5090 host is still required for the DP2 crash, memory/KV-cache comparison, graph replay, eager numerical reference, and model eval. Keeping this PR draft.

@dumko2001
dumko2001 force-pushed the flashinfer-b12x-dp-sizing branch 2 times, most recently from 3389083 to 2195454 Compare July 22, 2026 13:29
@dumko2001 dumko2001 changed the title [Bugfix] Restore shared FlashInfer B12x MoE workspaces [Bugfix] Share FlashInfer B12x MoE workspace across layers Jul 22, 2026
@dumko2001

Copy link
Copy Markdown
Author

Architecture correction on current head 21954544a: this supersedes my earlier
comment describing the FlashInfer functional process-global cache as the
ownership model. Further source review found that cache has no public lifecycle
or graph-lock contract, so it is not an acceptable long-term integration.

The PR now keeps FlashInfer's caller-owned B12xMoEWrapper, sizes it to
max_num_tokens * dp_size before capture, and weakly shares one wrapper across
same-geometry expert layers. This removes per-layer workspace amplification
without moving mutable scratch into a process-lifetime cache. The final model
owner releases the wrapper.

pytest -q tests/model_executor/test_flashinfer_b12x_moe.py
3 passed

focused pre-commit hooks
all passed, including mypy

The tests prove one wrapper for two layers, DP-scaled capacity, distinct
per-layer call tensors, output behavior, and release on owner destruction. No
CUDA-13 SM120 kernel/DP2/graph/memory result is claimed; the PR remains draft
pending that hardware validation and the separately tracked W4A16 propagation
needed by the reporter checkpoint.

@dumko2001
dumko2001 force-pushed the flashinfer-b12x-dp-sizing branch from 2195454 to 68d8131 Compare July 22, 2026 13:35
Own one fixed-capacity B12x wrapper per model geometry and share it across sequential expert layers. Scope sharing to one live vLLM configuration, scale capacity for DP before graph capture, keep workspace pointers stable, and release the wrapper when its model owners die.

Addresses vllm-project#47982

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

Assisted-by: OpenAI Codex

Signed-off-by: Sidharth Rajmohan <dumko.raj@gmail.com>
@dumko2001
dumko2001 force-pushed the flashinfer-b12x-dp-sizing branch from 68d8131 to 78388ad Compare July 22, 2026 13:41
@ducphuc

ducphuc commented Jul 23, 2026

Copy link
Copy Markdown

I independently validated exact head
78388ad4ace221ad5275ee5225f7cc68cc968e00 against exact base
1a659a0c370942bae3c8567902e38bddbbae95f3 on two RTX 5060 Ti
16 GiB GPUs (SM120), driver 610.43.03, CUDA toolkit 13.3.1,
PyTorch cu130, and FlashInfer 0.6.14.

Public evidence:

Scope

This is real-hardware validation of shared-wrapper ownership and lifecycle at
dp_size=1, with TP=2. It does not claim real DP2 runtime validation.

The tested model requires approximately 30 GiB per complete replica. This host
can run TP=2/DP=1 by sharding the model across both 16 GiB GPUs, but cannot run
TP=1/DP=2 because each DP rank would require a complete replica on one 16 GiB
GPU.

The PR's CPU contract tests cover DP-scaled capacity construction for
dp_size=2 and both SiLU and ReLU2. Distributed DP2 token gathering, the
original DP2 capacity failure, and DP2 graph/memory behavior remain untested on
this host.

Primary result

Primary checkpoint: Unsloth
Qwen3.6-35B-A3B-NVFP4-Fast snapshot
1c3f884bc99aac2524f6d49bcbac8c88401afd66, TP=2,
gpu_memory_utilization=0.92, 262,144 max model length, 3 sequences,
8,192 batched tokens, FP8 KV, async scheduling, prefix caching, and full plus
piecewise CUDA graphs.

The exact base reproduced the per-layer B12x workspace OOM during its first
profiling inference.

With only the PR diff changed, the exact head completed profiling and served
successfully with the same parameters:

  • FLASHINFER_B12X remained selected.
  • NCCL used P2P/CUMEM.
  • Full and piecewise CUDA graphs captured and replayed.
  • KV capacity was 484,569 tokens / 2.44 GiB.
  • Matched current-head FlashInfer CUTLASS capacity was
    464,709 tokens / 2.34 GiB.

A focused two-layer A/B CUDA-graph diagnostic used distinct layer weights and
alternated execution. Outputs matched independent BF16 references, with no
cross-layer weight or workspace contamination. The complete 48-case SM120
numerical matrix also passed, covering SiLU and ReLU2 across multiple
geometries.

Serving, stability, and performance

  • Forced tool calls: 4/4 passed.
  • Automatic tool selection: 12/12 passed with zero misses.
  • Streaming events and arguments were valid, with no parser errors.
  • The earlier 192-token weather request was inconclusive because it exhausted
    its output budget; no tool-parser or serving defect remained with valid
    budgets.
  • A 145-request warmup/sequential/concurrent sequence had zero failures,
    preemptions, CUDA/NCCL errors, or memory growth.
  • Ten matched single-stream, batch-one trials with exactly 1,024 generated
    tokens measured 182.456 tok/s for shared B12x versus 163.570 tok/s for
    FlashInfer CUTLASS, a +11.55% decode result, with essentially identical TTFT.

Eager-allocation lifecycle

Because the PR moves wrapper construction into model initialization and the
model-load memory-pool context, I specifically tested clean shutdown,
fresh-process restart, and two real CUMEM level-1 sleep/wake cycles.

Both cycles restored successfully. Deterministic temperature-zero output
matched before and after wake, with no stale-pointer, graph-replay, or
workspace-lifetime failure.

Additional checkpoint and remaining scope

The NVIDIA ModelOpt W4A16 checkpoint also started, avoided the per-layer
workspace OOM, captured graphs, and exposed 504,428 KV tokens. This is startup
and memory validation only; W4A16/ModelOpt numerical correctness against Marlin
is not established.

Final scope assessment:

  • DP1 shared-wrapper ownership and eager-allocation lifecycle: PASS on real
    CUDA-13 SM120 hardware.
  • DP2 capacity construction: source-grounded and CPU-contract-tested.
  • Real DP2 runtime: NOT TESTED due per-replica GPU-memory requirements.
  • ModelOpt W4A16 numerical correctness: NOT ESTABLISHED.
  • Overall PR readiness: not determined by this validation.

randomvariable added a commit to randomvariable/vllm that referenced this pull request Jul 25, 2026
…orkspace across layers

Cherry-picks unmerged upstream PR vllm-project#48698 (dumko2001) to fix the per-layer
B12xMoEWrapper workspace blowup that hard-wedged GB10 DGX Spark nodes.

Root cause (diagnosed via ora-1 + lib-1, 4 physical wedges):
FlashInferB12xExperts allocated a B12xMoEWrapper static workspace lazily
on first apply(), PER MoE LAYER. On GB10 UMA (shared 121GiB pool) this
summed to a multi-GiB blowup during profile_run() — before any KV/util
knob applies — exhausting the pool and starving the kubelet (vLLM vllm-project#47982,
vllm-project#49476). --gpu-memory-utilization, --enforce-eager, max-num-batched-tokens
all failed to bound it.

The patch:
- Weakref-shared wrapper registry keyed by (owner, geometry): ONE wrapper
  per geometry+vllm-config instead of one per layer.
- Eager acquisition in __init__ so the workspace lands in the profiling
  baseline (not lazily in apply()).
- DP fix: max_num_tokens = moe_config.max_num_tokens * dp_size.

Preflight verified:
- git apply --check clean on gb10-main (no local mods to this file).
- flashinfer 0.6.15.post1 B12xMoEWrapper.__init__ accepts the new
  output_dtype + device kwargs (kwonly).

Still necessary-but-not-sufficient for GB10: the UMA shared-pool overshoot
(vllm-project#46307) is independent — keep gpu-memory-utilization <=0.6 + --enforce-eager
on first boot. Upstream vllm-project#48698 is DRAFT/unmerged; drop this carry once it lands.
randomvariable added a commit to randomvariable/vllm that referenced this pull request Jul 26, 2026
…orkspace across layers

Cherry-picks unmerged upstream PR vllm-project#48698 (dumko2001) to fix the per-layer
B12xMoEWrapper workspace blowup that hard-wedged GB10 DGX Spark nodes.

Root cause (diagnosed via ora-1 + lib-1, 4 physical wedges):
FlashInferB12xExperts allocated a B12xMoEWrapper static workspace lazily
on first apply(), PER MoE LAYER. On GB10 UMA (shared 121GiB pool) this
summed to a multi-GiB blowup during profile_run() — before any KV/util
knob applies — exhausting the pool and starving the kubelet (vLLM vllm-project#47982,
vllm-project#49476). --gpu-memory-utilization, --enforce-eager, max-num-batched-tokens
all failed to bound it.

The patch:
- Weakref-shared wrapper registry keyed by (owner, geometry): ONE wrapper
  per geometry+vllm-config instead of one per layer.
- Eager acquisition in __init__ so the workspace lands in the profiling
  baseline (not lazily in apply()).
- DP fix: max_num_tokens = moe_config.max_num_tokens * dp_size.

Preflight verified:
- git apply --check clean on gb10-main (no local mods to this file).
- flashinfer 0.6.15.post1 B12xMoEWrapper.__init__ accepts the new
  output_dtype + device kwargs (kwonly).

Still necessary-but-not-sufficient for GB10: the UMA shared-pool overshoot
(vllm-project#46307) is independent — keep gpu-memory-utilization <=0.6 + --enforce-eager
on first boot. Upstream vllm-project#48698 is DRAFT/unmerged; drop this carry once it lands.
randomvariable added a commit to randomvariable/vllm that referenced this pull request Jul 29, 2026
…orkspace across layers

Cherry-picks unmerged upstream PR vllm-project#48698 (dumko2001) to fix the per-layer
B12xMoEWrapper workspace blowup that hard-wedged GB10 DGX Spark nodes.

Root cause (diagnosed via ora-1 + lib-1, 4 physical wedges):
FlashInferB12xExperts allocated a B12xMoEWrapper static workspace lazily
on first apply(), PER MoE LAYER. On GB10 UMA (shared 121GiB pool) this
summed to a multi-GiB blowup during profile_run() — before any KV/util
knob applies — exhausting the pool and starving the kubelet (vLLM vllm-project#47982,
vllm-project#49476). --gpu-memory-utilization, --enforce-eager, max-num-batched-tokens
all failed to bound it.

The patch:
- Weakref-shared wrapper registry keyed by (owner, geometry): ONE wrapper
  per geometry+vllm-config instead of one per layer.
- Eager acquisition in __init__ so the workspace lands in the profiling
  baseline (not lazily in apply()).
- DP fix: max_num_tokens = moe_config.max_num_tokens * dp_size.

Preflight verified:
- git apply --check clean on gb10-main (no local mods to this file).
- flashinfer 0.6.15.post1 B12xMoEWrapper.__init__ accepts the new
  output_dtype + device kwargs (kwonly).

Still necessary-but-not-sufficient for GB10: the UMA shared-pool overshoot
(vllm-project#46307) is independent — keep gpu-memory-utilization <=0.6 + --enforce-eager
on first boot. Upstream vllm-project#48698 is DRAFT/unmerged; drop this carry once it lands.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working nvidia

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants