Skip to content

Keep nn.LayerNorm inputs unquantized in the default PTQ presets - #2814

Merged
kevalmorabia97 merged 1 commit into
NVIDIA:mainfrom
gkswns0531:fix/default-presets-skip-layernorm-inputs
Oct 15, 2026
Merged

kevalmorabia97 merged 1 commit into
NVIDIA:mainfrom
gkswns0531:fix/default-presets-skip-layernorm-inputs

Conversation

@gkswns0531

@gkswns0531 gkswns0531 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: Bug fix

#1289 (in 0.44.0 through #1350) registered nn.LayerNorm in QuantModuleRegistry so that FP8 MHA recipes can quantize its output. As a side effect, presets and recipes that import configs/ptq/units/default_disabled_quantizers and enable a bare *input_quantizer (INT8_DEFAULT_CFG, FP8_DEFAULT_CFG, NVFP4_DEFAULT_CFG, ...) also quantize every LayerNorm input, i.e. the residual stream (49 extra NVFP4 quantizers in XLM-R large). In ONNX export this becomes a dynamic quantize in front of LayerNormalization that no GEMM consumes. Weight-only and layer-scoped presets are not affected.

This adds a parent_class: 'nn.LayerNorm' exclusion to default_disabled_quantizers, in the same form as the existing nn.BatchNorm*, nn.LeakyReLU and nn.Embedding entries. LayerNorm quantization stays opt-in: the FP8 MHA override in examples/torch_onnx/torch_quant_to_onnx.py, appended after the preset, enables the LayerNorm output quantizer and already disables the input quantizer, so its result is unchanged. As with the BatchNorm entries, an nn.LayerNorm subclass registered as its own quant class is not covered.

#2402 fixed the same regression for the diffusers NVFP4 presets with the same entry; those presets do not import this unit. Out of scope: NVFP4_FP8_MHA_CONFIG, model_type/vit/ptq/fp8.yaml and the diffusers fp8, int8 and nvfp4_fp8_conv presets also skip this unit and still quantize LayerNorm inputs.

Effect on BAAI/bge-reranker-v2-m3 (XLM-R large), NVFP4 W4A4 fake quant (ModelOpt 0.47.0, same as main here), 60 queries × 100 candidates each:

NVFP4_DEFAULT_CFG (classifier excluded) Ko-StrategyQA Recall@10 same top-1 as FP32 mean |logit − FP32| MIRACL-ko Recall@10 same top-1 mean |logit − FP32|
as is (LayerNorm inputs quantized) 87.6% 80% 0.94 91.1% 80% 1.05
LayerNorm quantizers disabled (this PR) 90.6% 90% 0.61 94.5% 87% 0.66
FP32 88.9% 93.4%

Usage

No API change. A config that wants LayerNorm input quantization adds the entry after the default exclusions:

cfg = copy.deepcopy(mtq.FP8_DEFAULT_CFG)
cfg["quant_cfg"].append(
    {"parent_class": "nn.LayerNorm", "quantizer_name": "*input_quantizer", "cfg": {"num_bits": (4, 3), "axis": None}}
)

Testing

  • New test_default_presets_keep_layernorm_inputs_unquantized (INT8_DEFAULT_CFG, FP8_DEFAULT_CFG, NVFP4_DEFAULT_CFG): fails on main, passes here.
  • pytest tests/unit/torch/quantization tests/unit/recipe: 1912 passed, 1 skipped.
  • pytest tests/examples/torch_onnx/test_torch_quant_to_onnx.py -k "vit_tiny or swin" (RTX PRO 6000, TensorRT 11.3): the 12 FP8, INT8, MXFP8 and auto cases pass. The 3 NVFP4 cases fail the same way on main, in the FP32 NVFP4 export that [6721556] Fix mixed-dtype NVFP4 ONNX precision conversion #2415 fixes, and pass with a local fix for it.
  • pre-commit run --files on the changed files: pass.
  • End to end: bge-reranker-v2-m3 with NVFP4_DEFAULT_CFG has 0 LayerNorm input quantizers enabled, and its TensorRT engine matches fake quant (Recall@10 89.8% for both) with the [6721556] Fix mixed-dtype NVFP4 ONNX precision conversion #2415 export fix applied locally.

Before your PR is "Ready for review"

Make sure you read and follow Contributor guidelines and your commits are signed (git commit -s -S).

Make sure you read and follow the Security Best Practices (e.g. avoiding hardcoded trust_remote_code=True, torch.load(..., weights_only=False), pickle, etc.).

  • Is this change backward compatible?: ❌ New mtq.quantize calls with these presets no longer add LayerNorm input quantizers, as before 0.44. Saved checkpoints restore unchanged, because restore replays their stored config. Usage shows how to re-enable them.
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: N/A
  • Did you write any new necessary tests?: ✅
  • Did you update Changelog?: ✅ Under 0.49.0; I can move the entry if this should go into 0.48.0.
  • Did you get Claude approval on this PR?: N/A

Additional Information

Found while quantizing BAAI/bge-reranker-v2-m3 to NVFP4; its TensorRT engine also needs the FP32 NVFP4 export fix in #2415.

Summary by CodeRabbit

  • Bug Fixes
    • Default quantization presets no longer quantize inputs to LayerNorm modules, avoiding added error in residual streams.
    • Re-quantize models affected by this change. Recipes that need LayerNorm input quantization can enable it after importing the relevant preset.

nn.LayerNorm became a quant module in 0.44 so that recipes can opt into its
output quantizer for FP8 MHA. As a side effect, the bare "*input_quantizer"
wildcard also enables its input quantizer, so every preset or recipe that
imports default_disabled_quantizers and enables "*input_quantizer" quantizes
the residual stream entering each LayerNorm. Disable the LayerNorm
quantizers in default_disabled_quantizers, as for BatchNorm and Embedding; a
recipe that wants them enables them after importing the unit.

Signed-off-by: Hanjun Cho <gkswns0531@gmail.com>
@gkswns0531
gkswns0531 requested review from a team as code owners October 15, 2026 07:36
@copy-pr-bot

copy-pr-bot Bot commented Oct 15, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: NVIDIA/Model-Optimizer/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: fd8c2536-ce62-4246-9b22-b58181f38562
📥 Commits

Reviewing files that changed from the base of the PR and between f425b6a and 294e8e4.

📒 Files selected for processing: 3
  • CHANGELOG.rst
  • modelopt_recipes/configs/ptq/units/default_disabled_quantizers.yaml
  • tests/unit/torch/quantization/test_quant_layernorm.py

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

📝 Walkthrough

The default-disabled quantizer configuration now disables quantizers under nn.LayerNorm. A parameterized test checks this behavior for INT8, FP8, and NVFP4 default configurations. The changelog describes the prior behavior and notes how recipes can enable LayerNorm quantizers.

Changes: LayerNorm quantizer defaults

Layer / File(s) Summary
Default rule and preset validation
modelopt_recipes/configs/ptq/units/default_disabled_quantizers.yaml, tests/unit/torch/quantization/test_quant_layernorm.py, CHANGELOG.rst
The default-disabled quantizer unit disables all quantizers under nn.LayerNorm. Tests check that default INT8, FP8, and NVFP4 configurations enable the Linear input quantizer and disable the LayerNorm input quantizer. The changelog documents the prior behavior and recipe guidance.

Priority: ➖ Normal

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

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 294e8

The default presets leave LayerNorm quantizers disabled while keeping input quantization enabled for other layers. No concrete issue blocks merging this change.

Pre-merge checks: Passed 5 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 1 files. (2 skipped: 2 … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 5 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: excluding nn.LayerNorm inputs from quantization in the default PTQ presets.
Linked Issues check Passed No active directly linked issue remains. Issue #1289 is closed and supplies historical context only. Therefore, no linked-issue coding requirement applies to this pull request.
Out of Scope Changes check Passed The reported changes stay within the stated objective. The default_disabled_quantizers.yaml rule prevents default nn.LayerNorm input quantization, the unit test covers INT8_DEFAULT_CFG, `FP8_DEF…
Security Anti-Patterns Passed No stated security anti-pattern is introduced. The authoritative diff changes only a changelog, a YAML quantizer rule, and one test file; it adds no modelopt or examples Python code. The test addition…
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 1 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@kevalmorabia97

Copy link
Copy Markdown
Collaborator

/ok to test 294e8e4

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

Bot review (gpt-6.1-sol) — DM the bot to share feedback.

LGTM: the exclusion correctly follows preset enables and preserves the FP8 MHA override; human sign-off is needed for the justified test-file extension.

Needs action:

  • Confirm the additive regression coverage in tests/unit/torch/quantization/test_quant_layernorm.py; the existing identity test and assertions remain unchanged.

No action needed:

  • The three preset regressions check that Linear inputs remain quantized while LayerNorm inputs are disabled.
  • Tests were inspected, not executed.

@kevalmorabia97
kevalmorabia97 enabled auto-merge (squash) October 15, 2026 08:07
@codecov

codecov Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.32%. Comparing base (e191eac) to head (294e8e4).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2814      +/-   ##
==========================================
+ Coverage   69.52%   78.32%   +8.79%     
==========================================
  Files         649      649              
  Lines       72081    72286     +205     
==========================================
+ Hits        50114    56616    +6502     
+ Misses      21967    15670    -6297     
Flag Coverage Δ
examples-diffusers 20.21% <ø> (ø)
examples-gpt-oss 13.50% <ø> (ø)
examples-hf_ptq 23.29% <ø> (-0.04%) ⬇️
examples-llm_distill 13.58% <ø> (-0.01%) ⬇️
examples-llm_eval 17.27% <ø> (ø)
examples-llm_qat 17.57% <ø> (-0.01%) ⬇️
examples-llm_sparsity 15.82% <ø> (ø)
examples-megatron_bridge 26.63% <ø> (-0.15%) ⬇️
examples-specdec_bench 13.28% <ø> (ø)
examples-speculative_decoding 17.56% <ø> (-0.07%) ⬇️
examples-torch_onnx 21.58% <ø> (ø)
examples-torch_trt 15.26% <ø> (ø)
examples-vllm_serve 14.91% <ø> (ø)
gpu 59.39% <ø> (+37.51%) ⬆️
unit 59.86% <ø> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kevalmorabia97
kevalmorabia97 merged commit 7dde5ae into NVIDIA:main Oct 15, 2026
58 checks passed
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.

3 participants