Skip to content

[None][fix] Honour producer ignored layers for fused MoE and any quant format - #17551

Open
joerowell wants to merge 2 commits into
NVIDIA:mainfrom
joerowell:joerowell/fp8-ignored-layers-g-proj
Open

[None][fix] Honour producer ignored layers for fused MoE and any quant format#17551
joerowell wants to merge 2 commits into
NVIDIA:mainfrom
joerowell:joerowell/fp8-ignored-layers-g-proj

Conversation

@joerowell

@joerowell joerowell commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Dev Engineer Review

  • Merges producer-declared ignored_layers and ignore entries into quant_config.exclude_modules for all quantization formats.
  • Preserves exclusion order and removes duplicates.
  • Expands MoE exclusion matching to representative expert projection paths.
  • Adds Laguna g_proj handling that disables incompatible weight quantization while preserving KV-cache quantization.
  • Updates MoE weight-name conversion to avoid repeated weight_scale_inv renaming.
  • Updates the l0_cpu test configuration with valid test paths.
  • No public API changes require follow-up.

QA Engineer Review

  • Added test_excluded_g_proj_is_not_quantised.
  • Added test_unexcluded_g_proj_keeps_the_model_quant_config.
  • Added test_unquantised_model_is_passed_through.
  • Added test_missing_layer_idx_does_not_match_a_literal_none.
  • Added FP8 block-scale exclusion and producer-ignore regression coverage in tests/unittest/_torch/test_hf_quant_config.py.
  • Added the related tests to tests/integration/test_lists/test-db/l0_cpu.yml.
  • Verdict: sufficient.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2cbcb1fd-5999-41af-9578-13a8eb39a5ad

📥 Commits

Reviewing files that changed from the base of the PR and between 37e78a7 and 129c335.

📒 Files selected for processing (4)
  • tensorrt_llm/_torch/models/modeling_laguna.py
  • tests/integration/test_lists/test-db/l0_cpu.yml
  • tests/unittest/_torch/models/test_laguna_gproj_quant.py
  • tests/unittest/_torch/test_hf_quant_config.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unittest/_torch/test_hf_quant_config.py
  • tensorrt_llm/_torch/models/modeling_laguna.py
  • tests/unittest/_torch/models/test_laguna_gproj_quant.py

Walkthrough

Quantization configuration loading now merges producer exclusions with stable deduplication. Laguna applies layer-specific g_proj quantization while preserving KV-cache settings. MoE exclusion matching and weight-scale conversion were updated, with CPU-only regression coverage.

Changes

Quantization exclusion handling

Layer / File(s) Summary
Producer exclusion merging
tensorrt_llm/_torch/model_config.py, tests/unittest/_torch/test_hf_quant_config.py
FP8 defaults, ignored_layers, and ignore entries are merged into exclude_modules with order preservation and duplicate removal. Tests cover FP8 and compressed-tensors configurations.
Model-level exclusion application
tensorrt_llm/_torch/models/modeling_laguna.py, tensorrt_llm/_torch/models/modeling_utils.py
Laguna disables incompatible g_proj weight quantization while retaining KV-cache quantization. MoE exclusion matching expands representative projection paths. Already-converted weight_scale_inv names remain unchanged.
Regression coverage
tests/unittest/_torch/models/test_laguna_gproj_quant.py, tests/integration/test_lists/test-db/l0_cpu.yml
CPU-only tests cover exclusion patterns, nonmatching exclusions, unquantized models, and missing layer indices. The HF quantization test is added to the l0_cpu suite.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Mergeability Score: ⚪ Minimal · up to 129c3

The change is localized to quantization behavior and test-list updates; no concrete correctness, runtime, or deployment issue is identified at the current head, so no actionable merge-blocking risk remains beyond normal checks.

Possibly related PRs

Suggested reviewers: schetlur-nv, bowenfu

Sequence Diagram(s)

sequenceDiagram
  participant ModelConfig
  participant LagunaAttention
  participant g_proj_quant_config
  participant Linear
  ModelConfig->>LagunaAttention: provide quant_config and layer_idx
  LagunaAttention->>g_proj_quant_config: resolve g_proj quantization
  g_proj_quant_config->>g_proj_quant_config: match excluded g_proj module
  g_proj_quant_config-->>LagunaAttention: return resolved QuantConfig
  LagunaAttention->>Linear: construct g_proj with resolved QuantConfig
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided, so the required issue, solution, test coverage, and checklist information is missing. Add the required Description and Test Coverage sections, then complete the PR checklist.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the fix for producer-ignored layers across fused MoE and quantization formats.
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.
✨ Finishing Touches
🧪 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.

Actionable comments posted: 4

🤖 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.

Inline comments:
In `@tensorrt_llm/_torch/models/modeling_laguna.py`:
- Around line 217-237: Annotate all newly added functions: in
tensorrt_llm/_torch/models/modeling_laguna.py:217-237, add a precise type for
model_config in g_proj_quant_config and retain its Optional[QuantConfig] return
type; in tests/unittest/_torch/test_hf_quant_config.py:67-162, annotate
_fp8_block_scales_config and every new test function, using None for procedures;
and in tests/unittest/_torch/models/test_laguna_gproj_quant.py:36-89, annotate
_model_config and every test function, using None for procedures.

In `@tensorrt_llm/_torch/models/modeling_utils.py`:
- Around line 618-638: The MoE exclusion candidates in the module-handling logic
only cover expert index 0, so exact exclusions for other experts are missed.
Update the logic around the MoE/VanillaMoE branch and its base-name handling to
match exclusions for every expert index and gate/up/down projection, including
.backend-wrapped modules; add regression coverage for a nonzero exact expert
path and a wrapped module.

In `@tests/unittest/_torch/models/test_laguna_gproj_quant.py`:
- Around line 45-89: Add unittest/_torch/models/test_laguna_gproj_quant.py to
the l0_cpu.yml test list so its four tests are collected by CPU CI; do not add a
QA-list entry or modify the existing test implementations.

In `@tests/unittest/_torch/test_hf_quant_config.py`:
- Around line 77-162: Add the five new test functions in
tests/unittest/_torch/test_hf_quant_config.py to the CPU-only test list in
l0_cpu.yml, using the existing test-list format and preserving their unittest
path. Do not add a QA-list entry; verify the updated selection with pytest
tests/unittest/.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 866dd3ca-98cc-4f17-aa66-68b21511b2c3

📥 Commits

Reviewing files that changed from the base of the PR and between 07b3e82 and ab2b98d.

📒 Files selected for processing (5)
  • tensorrt_llm/_torch/model_config.py
  • tensorrt_llm/_torch/models/modeling_laguna.py
  • tensorrt_llm/_torch/models/modeling_utils.py
  • tests/unittest/_torch/models/test_laguna_gproj_quant.py
  • tests/unittest/_torch/test_hf_quant_config.py

Comment thread tensorrt_llm/_torch/models/modeling_laguna.py Outdated
Comment on lines +618 to +638
elif isinstance(module, (MoE, VanillaMoE)):
# Fused MoE: a checkpoint's ignore list names the
# per-expert weights (re:...experts.[0-9]+.gate_proj),
# but the fused module has no per-expert child, so such
# a rule never matches and those experts get quantized
# despite being bf16 in the checkpoint. Offer
# representative per-expert names, mirroring the Linear
# expansion above.
#
# Strip a trailing ".backend": ConfigurableMoE wraps the
# module that actually owns the weights, and excluding
# only the wrapper does nothing because create_weights()
# delegates to the backend. Without this the backend
# stays quantized and the output is still degenerate.
base = (name[:-len('.backend')]
if name.endswith('.backend') else name)
candidates += [
f'{base}.0.gate_proj',
f'{base}.0.up_proj',
f'{base}.0.down_proj',
]

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Match literal exclusions for every expert index.

Lines 634-637 only test expert 0. An exact producer exclusion such as ...experts.1.up_proj does not match. The fused MoE then remains quantized although that expert has no quantization scales.

Match all expert indices, or detect exclusions that target any {base}.<expert>.(gate_proj|up_proj|down_proj) path. Add regression coverage for an exact nonzero expert index and a .backend-wrapped module.

🤖 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/modeling_utils.py` around lines 618 - 638, The MoE
exclusion candidates in the module-handling logic only cover expert index 0, so
exact exclusions for other experts are missed. Update the logic around the
MoE/VanillaMoE branch and its base-name handling to match exclusions for every
expert index and gate/up/down projection, including .backend-wrapped modules;
add regression coverage for a nonzero exact expert path and a wrapped module.

Comment thread tests/unittest/_torch/models/test_laguna_gproj_quant.py
Comment thread tests/unittest/_torch/test_hf_quant_config.py Outdated
…t format

Signed-off-by: Joe Rowell <joerowell4@gmail.com>
@joerowell
joerowell force-pushed the joerowell/fp8-ignored-layers-g-proj branch from ab2b98d to 37e78a7 Compare August 12, 2026 16:30

@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.

The load_hf_quant_config merge is the right place for this and the switch from set() to dict.fromkeys fixes a real nondeterminism, but the fused-MoE half — the change the title leads with — has no test and rests on an assumption worth making explicit (see inline).

Two things at the whole-PR level:

Scope. Three independent fixes are bundled here: the general producer-ignore merge, the fused-MoE exclusion matching, and two Laguna-specific fixes (g_proj quant config, weight_scale_inv double-rename). The Laguna weight_scale_inv fix in particular is unrelated to exclusions and isn't mentioned in the PR title. Splitting the Laguna changes out would make each easier to bisect if one regresses.

Test coverage. apply_quant_config_exclude_modules is pure Python over a module tree and is directly testable — build a small nn.Module containing a ConfigurableMoE-shaped wrapper, set exclude_modules to a per-expert regex, and assert the backend's quant_config.quant_algo ends up None and _weights_created is False. Without it, the .backend strip and the per-expert candidate names are only exercised on real checkpoints. Please add coverage for that path.

Also worth confirming: tests/unittest/_torch/models/test_laguna_gproj_quant.py is a new directory-level file — check whether tests/unittest/_torch/models/ is already collected by an existing CI stage or whether the new file needs registering in tests/integration/test_lists/test-db/.

I couldn't run the suite in my checkout (missing deps), so the assertions above are from reading, not from a green run.

Notes on lines outside the diff:

  • tensorrt_llm/_torch/model_config.py:580: This MXFP8 ignored_layers handling is now fully subsumed by the general merge at line 613 — and it differs subtly (it drops default_exclude semantics and only merges when hf_exclude_modules is None vs. not). Leaving both means two places to keep in sync. Reduce this branch to just setting exclude_modules from modules_to_not_convert and let the general merge add ignored_layers.

if name.endswith('.backend') else name)
candidates += [
f'{base}.0.gate_proj',
f'{base}.0.up_proj',

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.

Using expert 0 as representative makes this an all-or-nothing decision for the whole fused module, and it fails silently in both directions:

  • Ignore list covers experts 0–3 of 128 → ...experts.0.gate_proj matches → the entire fused module is de-quantized, but experts 4–127 are FP8-with-scales in the checkpoint. Loading then either trips on shapes/scales or produces garbage.
  • Ignore list covers experts 1–127 but not 0 → nothing matches → the whole layer stays quantized even though most experts are BF16.

Uniform experts.[0-9]+.… rules are the common case, but partial lists exist and there's nothing here that says "we generalized from expert 0." Probe at least a second index (num_experts - 1) and logger.warning when the two disagree, so a partial list is visible rather than silently rounded to all-or-nothing.

# only the wrapper does nothing because create_weights()
# delegates to the backend. Without this the backend
# stays quantized and the output is still degenerate.
base = (name[:-len('.backend')]

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.

Worth noting in the comment that the .backend strip is only needed for the synthesized per-expert candidates. For plain rules, is_module_excluded_from_quantization already walks ancestors (...experts.backend...experts), so a rule naming the wrapper reaches the backend without this. As written the comment claims excluding the wrapper "does nothing," which reads as broader than what actually holds.

else:
quant_config.exclude_modules = default_exclude

# Honour the producer's "leave these layers unquantized" list, whatever

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 comment attributes ignored_layers to modelopt, but modelopt configs never reach here — line 520 returns early via _build_modelopt_quant_config, and read_modelopt_quant_config renames modelopt's ignore to exclude_modules itself. Drop modelopt from the list so a future reader doesn't assume this block covers that path.

alignment, so a quantised g_proj trips Linear.__init__'s assert under
tp_size > 1. __post_init__'s generic exclusion pass runs too late.
"""
quant_config = model_config.get_quant_config()

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.

get_quant_config() is called with no name, so this bypasses per_layer_quant_configs. For a MIXED_PRECISION checkpoint, self.quant_config is the global config and the per-layer entry for this layer is never consulted — the exclusion check then runs against the wrong config. The pre-existing call site had the same behaviour so this isn't a regression, but since the function's whole job is resolving one specific layer's config, passing name (and falling back on ValueError) would make it correct for that case too.

Signed-off-by: Joe Rowell <joerowell4@gmail.com>
@joerowell
joerowell requested review from a team as code owners August 13, 2026 10:15

@zhaoyangwang-nvidia zhaoyangwang-nvidia 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.

Approve with nits.

# g_proj whose out dim is not a multiple of the block alignment, and
# MoE experts the producer kept in bf16 for quality). Merged on top of
# whatever per-format defaults were set above.
producer_ignored = list(hf_quant_config.get("ignored_layers", []) or [])

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 new cross-format merge already folds in ignored_layers, so the mxfp8-specific read at L581-586 is now dead — it can no longer change the result after dedup. Please drop it so the producer-ignore list is read in exactly one place; otherwise a future edit to one site silently diverges from the other.

tp_size > 1. __post_init__'s generic exclusion pass runs too late.
"""
quant_config = model_config.get_quant_config()
if quant_config is None or layer_idx is None:

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.

layer_idx cannot be None here: LagunaAttention.__init__ already indexes per_layer_heads[layer_idx] (L257) and layer_types[layer_idx] (L263) before g_proj is built, and the only caller LagunaDecoderLayer types it as int. Falling back to the original quantized config on None also silently reinstates the exact TP>1 assert this PR fixes, and test_missing_layer_idx_does_not_match_a_literal_none pins that as intended behaviour. Suggest tightening the parameter to layer_idx: int and dropping both the guard and that test — or raising explicitly if you do want to defend against it.

if quant_config is None or layer_idx is None:
return quant_config

name = f"model.layers.{layer_idx}.self_attn.g_proj"

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.

This hardcodes the module path that named_modules() produces for g_proj, so the two must be kept in sync by hand. If the attribute name or the model.layers. prefix ever changes, this silently stops matching and the only symptom is the Linear.__init__ assert under TP>1 — far from the cause. Consider passing the module's own prefix in from the call site, or at minimum noting the coupling in the docstring.

@mihai-chiorean

Copy link
Copy Markdown
Contributor

One interaction to preserve when this conflicts with #17725: recursively adding producer ignore to the global exclude_modules is correct for homogeneous configs, but can shadow explicitly quantized descendants in a multi-config_groups checkpoint. #17725 applies ignore while materializing the authoritative per-module map and does not copy it into recursive global exclusions for that path; modules_to_not_convert remains recursive. unsloth/Qwen3.8-27B-NVFP4 exercises this distinction.

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.

5 participants