Skip to content

[None][fix] Qwen3.5 weight mapper for FP8 per-channel checkpoints - #17433

Open
amukkara wants to merge 1 commit into
NVIDIA:mainfrom
amukkara:qwen35-weights
Open

[None][fix] Qwen3.5 weight mapper for FP8 per-channel checkpoints#17433
amukkara wants to merge 1 commit into
NVIDIA:mainfrom
amukkara:qwen35-weights

Conversation

@amukkara

@amukkara amukkara commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Dev Engineer Review

  • Renamed _normalize_scale_names to _normalize_fp8_block_scale_names.
  • Limited scale-name normalization to FP8 block-scale checkpoints.
  • Removed per-channel scale squeezing because Fp8RowwiseLinearMethod already converts [out, 1] tensors to [out].
  • Updated per-tensor FP8 dequantization to skip non-scalar scales.
  • Updated preprocess_weights to call the renamed normalizer.
  • The changes match the Qwen3.5 FP8 per-channel checkpoint requirements.
  • No public API, configuration, or test-list changes were found.

QA Engineer Review

No test changes.

Description

  1. Remove special case for llm-compressor FP8_PER_CHANNEL_PER_TOKEN since Fp8RowwiseLinearMethod already handles the [out, 1] -> [out] tensor squeeze.
  2. Make sure per-tensor fp8 dequantize path is skipped for FP8_PER_CHANNEL_PER_TOKEN

Test Coverage

PR Checklist

Please review the following before submitting your PR:

  • PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.

  • PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.

  • Test cases are provided for new code paths (see test instructions)

  • If PR introduces API changes, an appropriate PR label is added - either api-compatible or api-breaking. For api-breaking, include BREAKING in the PR title.

  • Any new dependencies have been scanned for license and vulnerabilities

  • CODEOWNERS updated if ownership changes

  • Documentation updated as needed

  • Update tava architecture diagram if there is a significant design change in PR.

  • The reviewers assigned automatically/manually are appropriate for the PR.

  • Please check this after reviewing the above items as appropriate for this PR.

GitHub Bot Help

To see a list of available CI bot commands, please comment /bot help.

Signed-off-by: Anurag Mukkara <134339030+amukkara@users.noreply.github.com>
@amukkara
amukkara marked this pull request as ready for review August 7, 2026 23:21
@amukkara
amukkara requested a review from a team as a code owner August 7, 2026 23:21
@amukkara
amukkara requested a review from kaiyux August 7, 2026 23:21
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The Qwen3.5 weight mapper now normalizes only FP8 block-scale names. It preserves per-channel scale shapes and skips per-tensor FP8 dequantization when weight_scale is non-scalar. Preprocessing calls the renamed helper.

Changes

Qwen3.5 FP8 scale handling

Layer / File(s) Summary
FP8 scale normalization and dequantization guards
tensorrt_llm/_torch/models/checkpoints/hf/qwen3_5_weight_mapper.py
The mapper limits normalization to FP8 block-scale tensors, removes per-channel scale squeezing, preserves non-scalar weight_scale tensors during per-tensor FP8 dequantization, and updates preprocessing to use the renamed helper.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: bowenfu, pamelap-nvidia

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the fix to the Qwen3.5 weight mapper for FP8 per-channel checkpoints.
Description check ✅ Passed The description clearly states the issue and solution, but the Test Coverage section is empty and the checklist is not fully completed.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (2)
tensorrt_llm/_torch/models/checkpoints/hf/qwen3_5_weight_mapper.py (2)

181-181: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add precise annotations to the renamed helper.

Line 181 leaves quant_algo untyped and uses bare dict annotations. Use QuantAlgo | None and the repository’s precise weight-map type for the input and returned mappings. Confirm that the selected map type matches ConsumableWeightsDict.

As per coding guidelines, annotate every function and prefer precise types.

Example signature
-    def _normalize_fp8_block_scale_names(self, weights: dict, quant_algo) -> tuple[dict, bool]:
+    def _normalize_fp8_block_scale_names(
+        self,
+        weights: dict[str, torch.Tensor],
+        quant_algo: QuantAlgo | None,
+    ) -> tuple[dict[str, torch.Tensor], bool]:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/models/checkpoints/hf/qwen3_5_weight_mapper.py` at line
181, Update _normalize_fp8_block_scale_names to annotate quant_algo as QuantAlgo
| None and replace bare dict annotations with the repository’s precise
weight-map type, using ConsumableWeightsDict for the weights input and returned
mapping as appropriate. Preserve the existing tuple return contract while
ensuring every parameter and return value is explicitly typed.

Source: Coding guidelines


181-205: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the scale-shape contract.

Test that FP8_PER_CHANNEL_PER_TOKEN preserves [out, 1] scales, FP8_BLOCK_SCALES remaps 4D ModelOpt scales, and scalar scales still dequantize and remove their scale keys.

Based on the PR objective, these tests protect the per-channel checkpoint path while preserving the per-tensor fallback.

Also applies to: 462-463

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/models/checkpoints/hf/qwen3_5_weight_mapper.py` around
lines 181 - 205, The scale normalization path in
_normalize_fp8_block_scale_names lacks regression coverage for its
shape-specific behavior. Add tests covering FP8_PER_CHANNEL_PER_TOKEN
preservation of [out, 1] scales, FP8_BLOCK_SCALES conversion of 4D ModelOpt
scales to the expected remapped form, and scalar-scale dequantization with
scale-key removal; keep the existing per-channel path and per-tensor fallback
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tensorrt_llm/_torch/models/checkpoints/hf/qwen3_5_weight_mapper.py`:
- Line 181: Update _normalize_fp8_block_scale_names to annotate quant_algo as
QuantAlgo | None and replace bare dict annotations with the repository’s precise
weight-map type, using ConsumableWeightsDict for the weights input and returned
mapping as appropriate. Preserve the existing tuple return contract while
ensuring every parameter and return value is explicitly typed.
- Around line 181-205: The scale normalization path in
_normalize_fp8_block_scale_names lacks regression coverage for its
shape-specific behavior. Add tests covering FP8_PER_CHANNEL_PER_TOKEN
preservation of [out, 1] scales, FP8_BLOCK_SCALES conversion of 4D ModelOpt
scales to the expected remapped form, and scalar-scale dequantization with
scale-key removal; keep the existing per-channel path and per-tensor fallback
behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5af43ddf-0789-44ae-848a-15eb5c325387

📥 Commits

Reviewing files that changed from the base of the PR and between a88f889 and 7765c9c.

📒 Files selected for processing (1)
  • tensorrt_llm/_torch/models/checkpoints/hf/qwen3_5_weight_mapper.py

@brnguyen2 brnguyen2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — the comments below are optional touch-ups, not blockers.

The simplification checks out: _pack_projection_tensor preserves trailing dims, so an [out, 1] per-channel scale gets the same row permutation as the weight, and FP8RowwiseLinearMethod.load_weights_vanilla / load_weights_fused_qkv_linear reshape it to 1-D on load.

Two things:

  • No test. The existing mapper tests only exercise weight_scale_inv, so nothing covers the per-channel path this PR fixes, and nothing would catch a regression if the squeeze needs to come back. A preprocess_weights unit test with synthetic [out, 1] scales on split linear-attn projections — asserting the scales survive to the packed in_proj_qkvz.weight_scale with the right rows and that the weight stays FP8 — would be cheap next to tests/unittest/_torch/modeling/test_qwen3_5_partial_loading.py.
  • Title is [None] for a checkpoint-loading fix. If there's an NVBug or JIRA for the per-channel checkpoint failure, tag it so the fix is traceable from the bug.

if weight.dtype != torch.float8_e4m3fn:
continue
scale = scale[...] if not isinstance(scale, torch.Tensor) else scale
if scale.numel() != 1:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bare numel() != 1 skip is silent for every non-scalar shape, not just the per-channel [out, 1] case you're targeting. A scale that arrives in some other unexpected layout now leaves an FP8 weight in place that fails much later (packing, or the loader) with an error that points nowhere near here.

Worth narrowing to the shape you actually intend to pass through and rejecting the rest:

if scale.numel() != 1:
    # Per-channel [out, 1] scales stay quantized: the fused module loads
    # them through FP8RowwiseLinearMethod, which flattens to [out].
    assert scale.ndim == 2 and scale.shape[1] == 1, (
        f"unexpected weight_scale shape for {name}: {tuple(scale.shape)}"
    )
    continue

The docstring above also still says this path exists partly to keep scalar scales out of _pack_split_projections; it's worth a sentence saying per-channel scales are now deliberately routed there instead.


is_modelopt_pb_wo = False
if quant_algo not in (QuantAlgo.FP8_BLOCK_SCALES, QuantAlgo.FP8_PER_CHANNEL_PER_TOKEN):
if quant_algo != QuantAlgo.FP8_BLOCK_SCALES:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removed squeeze applied to every .weight_scale key in the checkpoint, but the justification (FP8RowwiseLinearMethod flattens [out, 1]) only covers Linear. Did you confirm the other consumers on this model see the same shapes? Specifically the MoE path — handle_special_instance_module forwards expert weight_scale tensors straight into module.load_weights, and there's no FP8-rowwise MoE method, so I'd expect the experts in these checkpoints to be excluded or on a different recipe. If that's the case it'd be good to confirm it in the PR description; if any expert scales do come through as [out, 1], they no longer get squeezed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants