Skip to content

Fix prequant layernorm export without scales#1838

Merged
realAsma merged 2 commits into
mainfrom
beebot/nvbug-6311597-export-fix
Jun 27, 2026
Merged

Fix prequant layernorm export without scales#1838
realAsma merged 2 commits into
mainfrom
beebot/nvbug-6311597-export-fix

Conversation

@realAsma

@realAsma realAsma commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Skip pre-quant LayerNorm fusion when the representative input quantizer has no _pre_quant_scale buffer.
  • Add CPU unit coverage for the no-scale weight-only path and the existing fusion/removal behavior when pre-quant scales are present.
  • Compose the public general/ptq/int4_blockwise_weight_only recipe from the shared recipe units so it stays aligned with INT4_BLOCKWISE_WEIGHT_ONLY_CFG.

Context

NVBug: https://nvbugspro.nvidia.com/bug/6311597

NVBug 6311597 reports a deterministic HF export crash for general/ptq/int4_blockwise_weight_only on Llama-3.1-8B. That weight-only recipe disables input quantization and skips calibration, so _pre_quant_scale is not registered, but export still reaches fuse_prequant_layernorm through the AWQ-like format path.

This PR also carries the recipe-sync diff that was previously in draft PR #1836; PR #1836 has been closed after moving that diff here.

Tests

  • pre-commit run --files modelopt/torch/export/quant_utils.py tests/unit/torch/export/test_unified_export_hf.py modelopt_recipes/general/ptq/int4_blockwise_weight_only.yaml tests/unit/recipe/test_loader.py
  • pytest_pwd tests/unit/torch/export/test_unified_export_hf.py::test_fuse_prequant_layernorm_skips_modules_without_pre_quant_scale tests/unit/recipe/test_loader.py::test_load_recipe_all_builtins tests/unit/recipe/test_loader.py::test_general_ptq_yaml_matches_config_dicts tests/unit/recipe/test_presets.py -q -> 29 passed

Full GB10/Llama repro was not run locally.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of layer norm fusion so it safely skips cases where pre-quantization scale data is unavailable, avoiding unexpected errors.
    • Updated the layer norm fusion flow to correctly apply scaling when pre-quantization data is present.
  • Tests

    • Expanded coverage for export and recipe loading scenarios, including cases with and without pre-quantization scale data.
    • Added validation for the new int4 blockwise weight-only recipe.

Signed-off-by: realAsma <akuriparambi@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an early return in fuse_prequant_layernorm when _pre_quant_scale is absent, adds export tests for both fusion paths, refactors the int4 blockwise weight-only PTQ recipe to use imports, and updates loader tests to cover the new recipe and recipes without KV configs.

Changes

Pre-quant LayerNorm fusion

Layer / File(s) Summary
Guard and fusion tests
modelopt/torch/export/quant_utils.py, tests/unit/torch/export/test_unified_export_hf.py
fuse_prequant_layernorm now returns when input_quantizer has no _pre_quant_scale, and the HF export tests cover both the skipped and fused behaviors.

PTQ int4 blockwise recipe

Layer / File(s) Summary
Import-based quantization config
modelopt_recipes/general/ptq/int4_blockwise_weight_only.yaml
The recipe replaces the inline quant_cfg list with imported disable-all and default-disabled quantizer configs plus int4_per_block for *weight_quantizer.
Loader coverage for recipe without KV config
tests/unit/recipe/test_loader.py
The built-in PTQ recipe list includes general/ptq/int4_blockwise_weight_only, and the YAML-vs-config test handles entries where kv_cfg_name is None.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

  • NVIDIA/Model-Optimizer#1826: Updates PTQ quantizer disable patterns that affect the same recipe family as the new import-based recipe.

Suggested reviewers

  • shengliangxu
  • cjluo-nv
  • kevalmorabia97
🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main fix: handling prequant LayerNorm export when _pre_quant_scale is absent.
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.
Security Anti-Patterns ✅ Passed Touched modelopt/examples Python files and recipe metadata add no weights_only=False/allow_pickle=True/trust_remote_code=True/eval/exec/# nosec patterns or dependency changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch beebot/nvbug-6311597-export-fix

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

Signed-off-by: realAsma <akuriparambi@nvidia.com>
@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-27 00:35 UTC

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.77%. Comparing base (33bfa8b) to head (8d4711c).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1838      +/-   ##
==========================================
- Coverage   77.29%   76.77%   -0.53%     
==========================================
  Files         513      513              
  Lines       56920    56922       +2     
==========================================
- Hits        43997    43700     -297     
- Misses      12923    13222     +299     
Flag Coverage Δ
examples 42.08% <66.66%> (-0.11%) ⬇️
gpu 57.96% <66.66%> (-0.65%) ⬇️
regression 14.82% <0.00%> (+0.05%) ⬆️
unit 54.64% <100.00%> (+0.02%) ⬆️

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.

@realAsma realAsma left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

LGTM! Approved!

@realAsma
realAsma marked this pull request as ready for review June 26, 2026 23:01
@realAsma
realAsma requested review from a team as code owners June 26, 2026 23:01
@realAsma
realAsma requested a review from meenchen June 26, 2026 23:01

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

LGTM

@@ -16,47 +16,19 @@
metadata:

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.

@realAsma could you share why we need this int4 recipe in the modelopt_recipes/general/ptq/? I think we don't support export for int4 blockwise. cc @Edwardf0t1

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

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 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 `@modelopt/torch/export/quant_utils.py`:
- Around line 1260-1263: The LayerNorm export logic is checking the private
TensorQuantizer buffer `_pre_quant_scale` directly, which can miss the case
where `TensorQuantizer.pre_quant_scale` is `None` and incorrectly fuse a stale
scale. Update the guard in `quant_utils` where the `modules[0].input_quantizer`
is handled to use the public `pre_quant_scale` property instead of `hasattr(...,
"_pre_quant_scale")`, and only proceed with the fusion path when that property
is present and non-None before moving it to `layernorm_module.weight.device`.
🪄 Autofix (Beta)

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: d4845bc5-56e1-4e34-bf8a-b5a0dad9aba3

📥 Commits

Reviewing files that changed from the base of the PR and between 33bfa8b and 8d4711c.

📒 Files selected for processing (4)
  • modelopt/torch/export/quant_utils.py
  • modelopt_recipes/general/ptq/int4_blockwise_weight_only.yaml
  • tests/unit/recipe/test_loader.py
  • tests/unit/torch/export/test_unified_export_hf.py

Comment on lines +1260 to +1263
if not hasattr(modules[0].input_quantizer, "_pre_quant_scale"):
return

pre_quant_scale = modules[0].input_quantizer._pre_quant_scale.to(layernorm_module.weight.device)

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

Use pre_quant_scale instead of the private _pre_quant_scale buffer.

TensorQuantizer.pre_quant_scale is also None when pre-quant scaling is disabled, not just when the buffer is missing. This guard bypasses that contract, so export can still fuse a stale scale into the LayerNorm even though the forward path no longer applies it.

Suggested fix
-    if not hasattr(modules[0].input_quantizer, "_pre_quant_scale"):
+    pre_quant_scale = modules[0].input_quantizer.pre_quant_scale
+    if pre_quant_scale is None:
         return
-
-    pre_quant_scale = modules[0].input_quantizer._pre_quant_scale.to(layernorm_module.weight.device)
+    pre_quant_scale = pre_quant_scale.to(layernorm_module.weight.device)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if not hasattr(modules[0].input_quantizer, "_pre_quant_scale"):
return
pre_quant_scale = modules[0].input_quantizer._pre_quant_scale.to(layernorm_module.weight.device)
pre_quant_scale = modules[0].input_quantizer.pre_quant_scale
if pre_quant_scale is None:
return
pre_quant_scale = pre_quant_scale.to(layernorm_module.weight.device)
🤖 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 `@modelopt/torch/export/quant_utils.py` around lines 1260 - 1263, The LayerNorm
export logic is checking the private TensorQuantizer buffer `_pre_quant_scale`
directly, which can miss the case where `TensorQuantizer.pre_quant_scale` is
`None` and incorrectly fuse a stale scale. Update the guard in `quant_utils`
where the `modules[0].input_quantizer` is handled to use the public
`pre_quant_scale` property instead of `hasattr(..., "_pre_quant_scale")`, and
only proceed with the fusion path when that property is present and non-None
before moving it to `layernorm_module.weight.device`.

@realAsma
realAsma merged commit 4b04e73 into main Jun 27, 2026
58 checks passed
@realAsma
realAsma deleted the beebot/nvbug-6311597-export-fix branch June 27, 2026 00:34
kevalmorabia97 pushed a commit that referenced this pull request Jul 3, 2026
- Skip pre-quant LayerNorm fusion when the representative input
quantizer has no `_pre_quant_scale` buffer.
- Add CPU unit coverage for the no-scale weight-only path and the
existing fusion/removal behavior when pre-quant scales are present.
- Compose the public `general/ptq/int4_blockwise_weight_only` recipe
from the shared recipe units so it stays aligned with
`INT4_BLOCKWISE_WEIGHT_ONLY_CFG`.

NVBug: https://nvbugspro.nvidia.com/bug/6311597

NVBug 6311597 reports a deterministic HF export crash for
`general/ptq/int4_blockwise_weight_only` on Llama-3.1-8B. That
weight-only recipe disables input quantization and skips calibration, so
`_pre_quant_scale` is not registered, but export still reaches
`fuse_prequant_layernorm` through the AWQ-like format path.

This PR also carries the recipe-sync diff that was previously in draft
PR #1836; PR #1836 has been closed after moving that diff here.

- `pre-commit run --files modelopt/torch/export/quant_utils.py
tests/unit/torch/export/test_unified_export_hf.py
modelopt_recipes/general/ptq/int4_blockwise_weight_only.yaml
tests/unit/recipe/test_loader.py`
- `pytest_pwd
tests/unit/torch/export/test_unified_export_hf.py::test_fuse_prequant_layernorm_skips_modules_without_pre_quant_scale
tests/unit/recipe/test_loader.py::test_load_recipe_all_builtins
tests/unit/recipe/test_loader.py::test_general_ptq_yaml_matches_config_dicts
tests/unit/recipe/test_presets.py -q` -> 29 passed

Full GB10/Llama repro was not run locally.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

* **Bug Fixes**
* Improved handling of layer norm fusion so it safely skips cases where
pre-quantization scale data is unavailable, avoiding unexpected errors.
* Updated the layer norm fusion flow to correctly apply scaling when
pre-quantization data is present.

* **Tests**
* Expanded coverage for export and recipe loading scenarios, including
cases with and without pre-quantization scale data.
  * Added validation for the new int4 blockwise weight-only recipe.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: realAsma <akuriparambi@nvidia.com>
kevalmorabia97 pushed a commit that referenced this pull request Jul 3, 2026
- Skip pre-quant LayerNorm fusion when the representative input
quantizer has no `_pre_quant_scale` buffer.
- Add CPU unit coverage for the no-scale weight-only path and the
existing fusion/removal behavior when pre-quant scales are present.
- Compose the public `general/ptq/int4_blockwise_weight_only` recipe
from the shared recipe units so it stays aligned with
`INT4_BLOCKWISE_WEIGHT_ONLY_CFG`.

NVBug: https://nvbugspro.nvidia.com/bug/6311597

NVBug 6311597 reports a deterministic HF export crash for
`general/ptq/int4_blockwise_weight_only` on Llama-3.1-8B. That
weight-only recipe disables input quantization and skips calibration, so
`_pre_quant_scale` is not registered, but export still reaches
`fuse_prequant_layernorm` through the AWQ-like format path.

This PR also carries the recipe-sync diff that was previously in draft
PR #1836; PR #1836 has been closed after moving that diff here.

- `pre-commit run --files modelopt/torch/export/quant_utils.py
tests/unit/torch/export/test_unified_export_hf.py
modelopt_recipes/general/ptq/int4_blockwise_weight_only.yaml
tests/unit/recipe/test_loader.py`
- `pytest_pwd
tests/unit/torch/export/test_unified_export_hf.py::test_fuse_prequant_layernorm_skips_modules_without_pre_quant_scale
tests/unit/recipe/test_loader.py::test_load_recipe_all_builtins
tests/unit/recipe/test_loader.py::test_general_ptq_yaml_matches_config_dicts
tests/unit/recipe/test_presets.py -q` -> 29 passed

Full GB10/Llama repro was not run locally.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

* **Bug Fixes**
* Improved handling of layer norm fusion so it safely skips cases where
pre-quantization scale data is unavailable, avoiding unexpected errors.
* Updated the layer norm fusion flow to correctly apply scaling when
pre-quantization data is present.

* **Tests**
* Expanded coverage for export and recipe loading scenarios, including
cases with and without pre-quantization scale data.
  * Added validation for the new int4 blockwise weight-only recipe.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: realAsma <akuriparambi@nvidia.com>
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
@kevalmorabia97 kevalmorabia97 added the cherry-pick-done Added by bot once PR is cherry-picked to the release branch label Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cherry-pick-done Added by bot once PR is cherry-picked to the release branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants