[Model] Honor fp32 head_dtype in Inkling muP logits (target + MTP draft) - #49120
Open
KKothuri wants to merge 5 commits into
Open
[Model] Honor fp32 head_dtype in Inkling muP logits (target + MTP draft)#49120KKothuri wants to merge 5 commits into
KKothuri wants to merge 5 commits into
Conversation
Inkling folds the muP width divisor into a bespoke lm_head ``addmm`` that
emits logits in the input (bf16) dtype, silently dropping an fp32
``head_dtype`` (``--hf-overrides '{"head_dtype": "float32"}'``). fp32 logits
are required for RL training-inference consistency, and in speculative
decoding the draft's logits drive the rejection-sampling acceptance
distribution, so the MTP draft must match the target.
Route both ``InklingLogitsProcessor`` and ``InklingMTP.compute_logits``
through the dtype-aware head projection when a non-model head dtype is
requested, applying the muP divisor as an elementwise multiply in that
dtype. The fused-alpha addmm fast path is preserved for the default
(model-dtype) serving case, so there is no perf regression there.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Karthik Kothuri <karthikkothuri2009@gmail.com>
KKothuri
requested review from
AndreasKaratzas,
DarkLight1337 and
ywang96
as code owners
July 19, 2026 18:26
aoshen02
reviewed
Jul 20, 2026
KKothuri
force-pushed
the
fix/inkling-fp32-head-dtype
branch
from
July 20, 2026 17:09
f7d03f9 to
716db75
Compare
The fp32 head path applied muP as a separate elementwise multiply after the projection. Fold it into the GEMM instead via addmm(out_dtype=float32, alpha=1/mup) when the fast lm_head path applies (unquantized head, CUDA/ROCm, no bias) -- bit-for-bit identical to the dtype-aware projection, no extra kernel. The elementwise-multiply path is kept as the fallback for the non-CUDA cast path, quantized heads, and embedding bias. Applied to both the target LP and the MTP draft via shared can_fold_fp32_head / mup_addmm helpers. Signed-off-by: Karthik Kothuri <karthikkothuri2009@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
KKothuri
force-pushed
the
fix/inkling-fp32-head-dtype
branch
from
July 20, 2026 17:17
716db75 to
9b76ac6
Compare
aoshen02
reviewed
Jul 29, 2026
aoshen02
reviewed
Jul 29, 2026
Use the dtype-aware projection for ROCm and host tensors, while keeping the fused muP addmm path for CUDA tensors. Remove the redundant fp32-fold capability helper. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Karthik Kothuri <karthikkothuri2009@gmail.com>
Route the AMD target and MTP muP paths through the dtype-aware logits projection when the requested head dtype differs from the model dtype. Extend the focused coverage to both platform implementations. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Karthik Kothuri <karthikkothuri2009@gmail.com>
KKothuri
requested review from
dllehr-amd,
hongxiayang and
tjtanaa
as code owners
August 2, 2026 18:10
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Inkling folds the muP width divisor into a bespoke lm_head
torch.addmmthat emits logits in the input (bf16) dtype, silently dropping an fp32head_dtype(--hf-overrides '{"head_dtype": "float32"}') whenever muP is active (the normal served-checkpoint path).fp32 logits are required for RL training-inference consistency. In speculative decoding the draft's logits drive the rejection-sampling acceptance distribution, so the MTP draft must match the target — otherwise the target runs fp32 while the draft runs bf16.
This fixes both bespoke muP paths:
InklingLogitsProcessor._base_forward(target)InklingMTP.compute_logits(MTP draft)When an fp32
head_dtypeis requested, both now fold the muP divisor directly into the fp32-accumulate lm_head GEMM viatorch.addmm(..., alpha=1/mup, out_dtype=float32)— theaddmm.dtypeoverload accumulates bf16 inputs into fp32 without materializing an fp32 weight copy, and scales in the epilogue. This is bit-for-bit identical to the dtype-aware projection (_get_logits→_apply_head) followed by an elementwise multiply, with no extra kernel. That elementwise-multiply projection is kept as the fallback for cases the fused op does not cover (non-CUDA cast path, quantized head, embedding bias). The default (model-dtype) serving path is unchanged, so there is no perf regression there.Note: the general spec-decode draft path is not affected — a draft using the default
LogitsProcessoralready inherits the target'shead_dtypevia the current-config context (verified at runtime;_create_draft_vllm_configkeeps the targetmodel_config). Inkling was the sole breakage because it bypasses the default projection.muP is folded into the fp32 GEMM (not a bypass)
The fp32 branch is not a muP bypass. muP is applied as the
alphascalar of the same fusedaddmmthat computes the logits —out = alpha * (hidden @ w.t())withalpha = 1/mup— so it costs nothing beyond the projection and mutates no shared state.An earlier revision applied muP as a separate
logits * (1/mup)after the projection, on the assumption that the fp32 accumulate path (torch.mm(out_dtype=fp32)) had no scalar to fold into. That is not the case: the CUDAaddmm.dtypeoverload takes bothalphaandout_dtype, and folding through it is bit-identical to the unfused projection + multiply (verified on an RTX 5080; max abs diff0.0). The separate multiply now only runs in the fallback cases above. Sharedcan_fold_fp32_head/mup_addmmhelpers keep the target LP and the MTP draft in sync.Not a duplicate
Searched open issues/PRs for
Inkling fp32 / head_dtype / mup logitsandhead_dtype draft speculative— none address this.#48768(Inkling multi-depth MTP) is unrelated follow-up work.Testing
tests/models/inkling/test_logits_fp32_head.py(5 tests) — asserts fp32 output for both the targetInklingLogitsProcessorandInklingMTP.compute_logitsunderhead_dtype=float32, that the default path stays bf16 (fast addmm preserved), and (CUDA-guarded) that the fp32 fold is bit-for-bit identical to the unfused dtype-aware projection + multiply.Confirmed the fp32 tests fail on unpatched source (bf16 output) and pass after the fix; the default-path tests confirm bf16 is unchanged.
pre-commit run(ruff, ruff-format, mypy) is clean.Model evaluation
The change is a no-op for the default (model-dtype) serving path, so standard serving output is unaffected. The fp32 path is opt-in for RL training-inference consistency. A full Inkling-checkpoint eval was not run (large NVFP4 checkpoint not available locally); the unit tests assert the exact fp32 dtype/value contract at the production method boundary.
AI assistance
This change was prepared with AI assistance. I have reviewed every changed line.