fix(QUANT-FP8-GENERIC): the per-tensor fp8 QKV projection emits f32 where upstream emits the model dtype
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 423
- Forks
- 53
- Avg merge
- 20h 26m
- Merged PRs (30d)
- 310
Description
Row: QUANT-FP8-GENERIC
Found by wave PORTQ-2 re-deriving PORT-NOW entry 61 of 5559679229..e126687a9a
(#2646). Upstream:
0b37d8389f vllm#48861, "NVFP4 quantization out_dtype should match model dtype,
not torch default".
The invariant, and where this tree breaks it
Upstream's commit replaces self.out_dtype = torch.get_default_dtype() with
get_current_vllm_config().model_config.dtype on three ModelOpt FP8 linear
methods. The VALUE is usually the same; what changes is that the model dtype
becomes the stated authority for an FP8 linear's output.
This tree's per-tensor FP8 QKV projection emits f32, which is wider than
either. src/vllm/model_executor/models/qwen3_5.cpp:2835-2839, inside
ProjectFullAttnQkv's project:
if (fp8) {
return h_fp8 != nullptr
? MatmulFp8CutlassPreQuantD(d, *h_fp8, fp8_weight, DType::kF32)
: MatmulFp8CutlassD(d, h, fp8_weight, DType::kF32);
}
The bf16 arm below it and the fp4 arm (:2815-2816) both emit kBF16, so bf16
q/k/v is already a supported downstream shape at this site.
This is known debt, not a discovery
.agents/specs/model-fp8-block-linear.md:133 already records it: "The per-tensor
fp8 arm's kF32 beside it is that arm's own pre-existing choice, not upstream's,
and this row does not adopt it", and :136 says the same for the split GDN
in_proj_qkv. What this entry adds is upstream authority: at the pin target the
model dtype is the explicit rule, so the deviation now contradicts a stated
upstream contract rather than an inferred one.
AGENTS.md is direct about why this matters: "A token gate cannot detect a dtype
that is too wide." The tokens still match while the path moves twice the bytes.
Scope
Change the per-tensor fp8 out_dtype at ProjectFullAttnQkv's project and at
the merged-FP8 QKV helper (qwen3_5.cpp:2757 deliberately matches the split
path's f32) from kF32 to the model dtype.
One coupling to respect: the split GDN in_proj_qkv hardcodes
fp8_mixed_dt = DType::kF32 (qwen3_5.cpp:4356) under a VT_CHECK asserting it
equals GdnProjectedMixedQkvDType(...). The packed-decode predictor moves with it
or vt::GdnPackedDecode throws.
Roughly 15-40 lines over 1-2 files, plus a memory-format assertion, since a
token gate cannot see this.
Not settled
The reader did not trace every consumer of out.qgate/key/value to prove bf16 is
accepted on the fp8 arm specifically. The fp4 arm already emits bf16 under
Bf16GemmOutEnabled(), which is strong but not conclusive.
model-fp8-block-linear.md:304-306 names exactly this as a NEEDS_DECISION.
QUANT-FP8-MO-STATIC (.agents/quantization-matrix.md:126) is the exact-scope
row but is DONE; this issue is filed against QUANT-FP8-GENERIC (:127,
PARTIAL) so it lands on an open row rather than reopening a closed one. Move it
if the owner prefers.
Nothing was executed for this finding.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/vllm/model_executor/models/qwen3_5.cpp at ProjectFullAttnQkv, the merged-FP8 QKV helper, and split GDN in_proj_qkv; read the linked dtype checks and the NEEDS_DECISION note in .agents/specs/model-fp8-block-linear.md. Trace qgate/key/value consumers before deciding the accepted output dtype, then verify the packed-decode coupling and add the requested memory-format assertion. Done means the generic FP8 paths follow the model dtype without breaking the GDN predictor.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100