Skip to content

Qwen3.6 NVFP4 baselines (27B and 35B-A3B) must reach vLLM speed parity #213

Description

@localai-bot

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions