Skip to content

[Kernel] Batch invariance: pin FlashInfer FP8 bmm and W4A16 NVFP4 kernels - #62192

Merged
aoshen02 merged 7 commits into
vllm-project:mainfrom
0z5a:nemotron-bi/bi-gemm-dispatch
Oct 31, 2026
Merged

aoshen02 merged 7 commits into
vllm-project:mainfrom
0z5a:nemotron-bi/bi-gemm-dispatch

Conversation

@0z5a

@0z5a 0z5a commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Under VLLM_BATCH_INVARIANT=1, pins two GEMM paths so that a row's result does not depend on the batch:

  • FlashInfer's FP8 bmm (flashinfer_scaled_fp8_mm) is pinned to the cutlass backend.
  • W4A16 NVFP4 linears use the Humming kernel.

This was split from #62017 by @aoshen02. Without BI, behavior is unchanged.

Changes

  • vllm/utils/flashinfer.py: flashinfer_scaled_fp8_mm passes backend="cutlass" to bmm_fp8 under BI and "auto" otherwise. With FlashInfer autotune (on by default at -O1 and above), "auto" tunes over cutlass, cuBLAS and cuDNN per M bucket, so different batch sizes can run different backends.
  • vllm/model_executor/kernels/linear/__init__.py: under BI, a W4A16 NVFP4 linear selects HummingNvFp4LinearKernel, which already reads use_batch_invariant from VLLM_BATCH_INVARIANT. Before this change, the BI branch forced the W4A4-only CutlassNvFp4LinearKernel, and kernel init raised ValueError: CutlassNvFp4LinearKernel does not support W4A16. With --linear-backend set to something other than Humming, a warning is logged and the setting is overridden.
  • Tests:
    • test_flashinfer_fp8_mm_batch_invariant_after_autotune in tests/v1/determinism/test_cutlass_batch_invariance.py (SM100+);
    • test_batch_invariant_w4a16_selects_humming in tests/kernels/quantization/test_nvfp4_kernel_selection.py (CPU).

The router FP32-logits branch (with the out_dtype argument of mm_batch_invariant) and the two sm100 tuned M buckets for Nemotron-H shapes were moved out of this PR. They stay in #62017 for the Nemotron-H wiring.

Validation

Hardware: GB200 (SM100), vLLM nightly image 0.32.0rc1.dev281+g49cb2bbf7, FlashInfer 0.7.2. The touched files are identical between that image and this PR's base fa75d3456d, so "base" is the image as shipped and "PR" is the image with this PR's two files overlaid.

FP8 bmm. Static per-tensor FP8 inputs. Each M slice x[:M] was compared bitwise against the first M rows of the M=2048 output, for M in {1, 2, 7, 16, 64, 128, 512, 2048}, on four (N, K) shapes. The table counts rows that differ.

Backend Autotune 4096x4096 2688x4096 4608x2688 14336x4096
auto (base) off 0 0 0 0
auto (base) on M=128: 14, M=512: 50 M=512: 21 0 M=7: 2, M=64: 15, M=128: 30
cutlass (PR) off 0 0 0 0
cutlass (PR) on 0 0 0 0

Max abs diff of the differing rows is 3e-5 to 6e-5. Without autotune, "auto" falls back to its first runner (cutlass), so it is invariant; the variance only appears after tuning. As a diagnostic, cuBLAS alone and cuDNN alone were each invariant after autotune on these shapes: the variance comes from switching backends between buckets. The new test failed in 1 of 2 runs at base (the tuner's pick is timing dependent) and passed in 2 of 2 runs with the PR.

W4A16 NVFP4. A synthetic CompressedTensorsW4A4Fp4(use_a16=True) layer, with the same M sweep, on shapes 4096x4096, 2688x4096 and 4608x2688.

Code BI Kernel Result
base 1 — ValueError: CutlassNvFp4LinearKernel does not support W4A16 at init
PR 1 HummingNvFp4LinearKernel 0 rows differ at every M, outputs finite
base/PR 0 FlashInferCuteDslNvFp4W4A16LinearKernel (unchanged) 0 rows differ in this sweep (without autotune)

So the W4A16 change fixes a crash at init under BI rather than a numerical difference.

Tests.

pytest tests/kernels/quantization/test_nvfp4_kernel_selection.py
# 4 passed; the new test fails at base with the ValueError above
pytest tests/v1/determinism/test_cutlass_batch_invariance.py -k test_flashinfer_fp8_mm_batch_invariant_after_autotune
# GB200: PR 2/2 passed; base 1/2 failed

pre-commit (ruff, mypy) passes on the changed files.

Not tested: end-to-end serving of an FP8 or W4A16 NVFP4 model under BI, and throughput. Humming and pinned cutlass are chosen for invariance, not speed.


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.

Under VLLM_BATCH_INVARIANT: the router serves BF16 x BF16 -> FP32 logits on
one persistent matmul (mm_batch_invariant gains out_dtype; on top of vllm-project#59985),
FP8 bmm is pinned to the cutlass backend, W4A16 NVFP4 linears use Humming,
and the Blackwell BI-matmul table gets the tuned M buckets.

Split out of vllm-project#62017; the code is unchanged from that PR.

Signed-off-by: aoshen02 <[email protected]>
Signed-off-by: 0z5a <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>

@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 commented Oct 25, 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, @0z5a.

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

Move the router FP32-logits branch, the mm_batch_invariant out_dtype
parameter and the sm100 Nemotron-H tuned M buckets back to vllm-project#62017.
Add regression tests for the two remaining pins.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: aoshen02 <[email protected]>
@aoshen02
aoshen02 requested a review from a team October 30, 2026 03:50
@aoshen02 aoshen02 changed the title [Kernel] Batch invariance: pin row-invariant GEMM paths [Kernel] Batch invariance: pin FlashInfer FP8 bmm and W4A16 NVFP4 kernels Oct 30, 2026
@aoshen02
aoshen02 enabled auto-merge (squash) October 30, 2026 04:26
@github-actions github-actions Bot added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 30, 2026
@aoshen02

Copy link
Copy Markdown
Contributor

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #98922 for commit 01893c302b2f.

ℹ️ This PR is 21 commits behind upstream main. Merged upstream main into this branch as 01893c302b2f before starting CI. Pull the branch before pushing more changes.

aoshen02 and others added 2 commits October 31, 2026 14:08
Drop the helper and the CI dependency line it needed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: aoshen02 <[email protected]>
@aoshen02

Copy link
Copy Markdown
Contributor

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #98938 for commit 2846dc857c78.

@yewentao256 yewentao256 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 for the work!

Comment on lines +1211 to +1219
if envs.VLLM_BATCH_INVARIANT and use_a16:
if linear_backend not in ("auto", "humming"):
logger.warning_once(
"VLLM_BATCH_INVARIANT overrides --linear-backend=%s; using the "
"Humming backend for deterministic W4A16 execution.",
linear_backend,
)
force_kernel = HummingNvFp4LinearKernel
elif envs.VLLM_BATCH_INVARIANT:

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.

Try combining the code

Comment on lines +36 to +48


def test_batch_invariant_w4a16_selects_humming(monkeypatch):
# The batch-invariant CUTLASS kernel is W4A4-only.
monkeypatch.setattr(envs, "VLLM_BATCH_INVARIANT", True)
for kernel in (CutlassNvFp4LinearKernel, HummingNvFp4LinearKernel):
monkeypatch.setattr(
kernel,
"is_supported",
classmethod(lambda cls, compute_capability=None: (True, None)),
)
kernel = init_nvfp4_linear_kernel(use_a16=True)
assert isinstance(kernel, HummingNvFp4LinearKernel)

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.

Suggested change
def test_batch_invariant_w4a16_selects_humming(monkeypatch):
# The batch-invariant CUTLASS kernel is W4A4-only.
monkeypatch.setattr(envs, "VLLM_BATCH_INVARIANT", True)
for kernel in (CutlassNvFp4LinearKernel, HummingNvFp4LinearKernel):
monkeypatch.setattr(
kernel,
"is_supported",
classmethod(lambda cls, compute_capability=None: (True, None)),
)
kernel = init_nvfp4_linear_kernel(use_a16=True)
assert isinstance(kernel, HummingNvFp4LinearKernel)

This test not needed for this small update

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.

Sorry for that and will not do enable auto merge next time. Will add a follow up pr right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/build nvidia ready ONLY add when PR is ready to merge/full CI is needed

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants