Both NVFP4 baselines from nvidia/ trail vLLM on decode throughput on GB10 (sm_121a), and neither is at the gate's floor of >= 1.00x on every axis. This issue tracks closing both to parity.
Where we are
| Gate model |
Revision |
Ratio vs vLLM 0.25.0 (production graphed) |
nvidia/Qwen3.6-27B-NVFP4 |
0893e160 |
0.847x / 0.861x / 0.853x / 0.843x at c1/c2/c4/c8 |
nvidia/Qwen3.6-35B-A3B-NVFP4 |
491c2f1e |
c1 0.977x, c2 0.964x, c4 1.025x, c8 0.964x, c16 0.932x, c32 0.971x; mean TTFT 0.93-0.98x everywhere |
Correctness is not in question on the 27B: the greedy continuation is byte-identical to vLLM, captured from the same warm process as the numbers.
The 27B deficit is flat across the concurrency sweep, so it is not batching or the
scheduler. A decode-window nsys diff (--cuda-graph-trace=node) shows we are
~100% GPU-busy at c1, so there is no host or launch-overhead lever either: the
whole deficit is kernel efficiency. ~96 ms/token for us against vLLM's ~79 ms,
which is 78% of this box's ~273 GB/s bandwidth roof against vLLM's ~95%.
Root-cause theme
A ten-lens scan of the decode hot path against pinned vLLM 555967922 found that
the deficit is dominated by one recurring defect class: we materialize a wider
dtype than vLLM does, at load or at a GEMM output, and then re-read it every
decode step. This is the same class as the already-fixed LoadAttnDense bug,
where the modelopt_mixed FP8 attention tower fell through to a BF16 branch that
dequantized it — worth 18% when fixed.
Confirmed instances, largest first:
- The NVFP4
lm_head is dequantized to BF16 at load. LoadLmHeadAnyDtype's
U8 branch (qwen3_5_dense_weights.cpp:266-306) runs
DequantCtNvfp4WeightToF32 -> BF16 -> TransposeBf16, yielding a dense BF16
[in,out] operand. We stream ~2.54 GB/step for logits where vLLM streams
~0.72 GB. The code comment at :208-210 names this explicitly as deferred
work. vLLM keeps the head quantized: ModelOptMixedPrecisionConfig.get_quant_method
handles ParallelLMHead, and _quantized_layer_prefix_candidates appends the
bare lm_head key. This is the 11.38 ms/step single-call
cutlass_80_tensorop_s16816gemm_bf16_128x64_32x6_nn_align2 in the profile —
and storing the operand transposed to [K,N] is also why an SM80 tile is
selected on an sm_121a part.
- FP8 tower GEMMs hardcode an F32 output where vLLM's
fp8_linear emits the
model dtype (BF16). Caller-side invocation-parity, the axis our existing GEMV
guard does not cover.
- Dense MLP
gate_proj/up_proj are never merged on the ModelOpt W4A16
Marlin route — 192 Marlin launches/step where vLLM issues 128. The merged
arm exists and is default-ON but is gated on IsTrueW4A4(), which a W4A16
checkpoint never satisfies. This is also a shared-seam violation: AGENTS.md
requires mergeable MLP projections to route through
layers::MlpGateUpMethodBase / vt::MergedGemmGroup, and the Qwen3.6 dense
path does not.
- GDN
in_proj_qkv + in_proj_z stay two separate FP8 GEMMs. The merged
path exists and is default-ON, but only for a BF16 owner, so an FP8 tower can
never reach it.
- 35B GDN activations are F32 where vLLM's are BF16, and
GdnOutDType() is
gated on num_experts == 0, so the MoE model never got the BF16 flip. This is
the documented "41 CastF32 launches per step".
Definition of done
- Both models at
>= 1.00x vLLM decode throughput at every measured concurrency,
plus TTFT and memory axes at or above floor.
- Token-exactness preserved against the pinned oracle. Every change here either
keeps arithmetic identical or moves it toward vLLM's own dtype flow, so
correctness should improve rather than be traded away.
- Same-tool
nsys traces on both arms before any throughput claim, medians of 3
on an idle box, same-binary A/B for each lever.
Each lever lands as its own row/<ID> PR with a committed spec under
.agents/specs/, per AGENTS.md.
Both NVFP4 baselines from
nvidia/trail vLLM on decode throughput on GB10 (sm_121a), and neither is at the gate's floor of>= 1.00xon every axis. This issue tracks closing both to parity.Where we are
nvidia/Qwen3.6-27B-NVFP40893e160nvidia/Qwen3.6-35B-A3B-NVFP4491c2f1eCorrectness is not in question on the 27B: the greedy continuation is byte-identical to vLLM, captured from the same warm process as the numbers.
The 27B deficit is flat across the concurrency sweep, so it is not batching or the
scheduler. A decode-window
nsysdiff (--cuda-graph-trace=node) shows we are~100% GPU-busy at c1, so there is no host or launch-overhead lever either: the
whole deficit is kernel efficiency. ~96 ms/token for us against vLLM's ~79 ms,
which is 78% of this box's ~273 GB/s bandwidth roof against vLLM's ~95%.
Root-cause theme
A ten-lens scan of the decode hot path against pinned vLLM
555967922found thatthe deficit is dominated by one recurring defect class: we materialize a wider
dtype than vLLM does, at load or at a GEMM output, and then re-read it every
decode step. This is the same class as the already-fixed
LoadAttnDensebug,where the
modelopt_mixedFP8 attention tower fell through to a BF16 branch thatdequantized it — worth 18% when fixed.
Confirmed instances, largest first:
lm_headis dequantized to BF16 at load.LoadLmHeadAnyDtype'sU8 branch (
qwen3_5_dense_weights.cpp:266-306) runsDequantCtNvfp4WeightToF32-> BF16 ->TransposeBf16, yielding a dense BF16[in,out]operand. We stream ~2.54 GB/step for logits where vLLM streams~0.72 GB. The code comment at
:208-210names this explicitly as deferredwork. vLLM keeps the head quantized:
ModelOptMixedPrecisionConfig.get_quant_methodhandles
ParallelLMHead, and_quantized_layer_prefix_candidatesappends thebare
lm_headkey. This is the 11.38 ms/step single-callcutlass_80_tensorop_s16816gemm_bf16_128x64_32x6_nn_align2in the profile —and storing the operand transposed to
[K,N]is also why an SM80 tile isselected on an
sm_121apart.fp8_linearemits themodel dtype (BF16). Caller-side invocation-parity, the axis our existing GEMV
guard does not cover.
gate_proj/up_projare never merged on the ModelOpt W4A16Marlin route — 192 Marlin launches/step where vLLM issues 128. The merged
arm exists and is default-ON but is gated on
IsTrueW4A4(), which a W4A16checkpoint never satisfies. This is also a shared-seam violation: AGENTS.md
requires mergeable MLP projections to route through
layers::MlpGateUpMethodBase/vt::MergedGemmGroup, and the Qwen3.6 densepath does not.
in_proj_qkv+in_proj_zstay two separate FP8 GEMMs. The mergedpath exists and is default-ON, but only for a BF16 owner, so an FP8 tower can
never reach it.
GdnOutDType()isgated on
num_experts == 0, so the MoE model never got the BF16 flip. This isthe documented "41
CastF32launches per step".Definition of done
>= 1.00xvLLM decode throughput at every measured concurrency,plus TTFT and memory axes at or above floor.
keeps arithmetic identical or moves it toward vLLM's own dtype flow, so
correctness should improve rather than be traded away.
nsystraces on both arms before any throughput claim, medians of 3on an idle box, same-binary A/B for each lever.
Each lever lands as its own
row/<ID>PR with a committed spec under.agents/specs/, per AGENTS.md.