[OMNIML-3707] Model-specific PTQ recipes bootstrap (#1506)

### What does this PR do?

Type of change: new feature

Replaces the hardcoded model-type branches in `examples/llm_ptq/` with
opt-in declarative **model-specific recipes** under
`modelopt_recipes/huggingface/<model_type>/ptq/`. Any adjustment
specific to a model type or instance must live in that model's recipe —
there is no implicit model-specific path anymore. Users select a model's
recipe with `--recipe huggingface/<model_type>/ptq/<recipe>`; users on
the plain `--qformat` path get only the generic numerics.

What moved out of Python
(`examples/llm_ptq/example_utils.py::build_quant_cfg` and
`examples/llm_ptq/hf_ptq.py::mono_quantize`):

- **gemma / mpt** `w4a8_awq` → `awq_lite` with `alpha_step=1` (coarser
search to avoid TRT-LLM overflow).
- **gemma** `int8_sq` → SmoothQuant `alpha=0.5` (default `1.0` regresses
Gemma 7B).
- **phi4mm** → disable `*speech*`, `*audio*`, `*image*`, `*vision*`
(quantize only the language model).
- **Nemotron VL** → disable `*vision*`, `*image*`, `*radio*`,
`*visual*`, `*encoder*`, `*model_encoder*` (quantize only the decoder).

What stayed in Python:

- MTP dynamic layer exclusion in `hf_ptq.py` (depends on
runtime-detected layer indices).
- `is_nemotron_vl(full_model)` detection itself, which still drives the
VLM calibration loop and the post-quantize `full_model` update — only
the `quant_cfg` adjustment it triggered moved into the Nemotron VL
recipe.

`multinode_ptq.py` shares the same `build_quant_cfg` call site and was
updated to match the new 2/3-arg signature; multinode users on
`--qformat` get the generic numerics (no `--recipe` plumbing in
multinode yet, so model-specific recipes are only reachable via
`hf_ptq.py`).

Already-YAML recipes that were elsewhere in the tree are relocated into
the same `huggingface/<model_type>/ptq/` layout so all model-specific
recipes live under one convention:

- **Step3.5-Flash** — moved from
`modelopt_recipes/huggingface/step3p5/Step3.5-Flash/` to
`huggingface/step3p5/Step3.5-Flash/ptq/` to match the `<model>/ptq/`
convention.
- **Qwen3.5 / Qwen3.6** — moved from
`modelopt_recipes/models/Qwen3.5-Qwen3.6/w4a16.yaml` to per-model_type
folders, anchored on the HuggingFace `model_type` (verified against
transformers 5.8.1 + HF model hub `config.json` for `Qwen/Qwen3.6-27B`,
`Qwen/Qwen3.6-35B-A3B`, `nvidia/Qwen3.5-397B-A17B-NVFP4`):
- `huggingface/qwen3_5/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.yaml` —
dense `qwen3_5`
- `huggingface/qwen3_5_moe/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.yaml` —
`qwen3_5_moe`
- Both wrappers `$import` the shared `quant_cfg` snippet
`huggingface/qwen3_5/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.quant_cfg.yaml`
(one source of truth; the two model_types share the same hybrid
linear-attention + softmax-attention architecture so the rules apply
identically).

Full recipe layout (`modelopt_recipes/huggingface/`):

```
gemma/ptq/{w4a8_awq,int8_sq}-kv_fp8_cast.yaml
mpt/ptq/w4a8_awq-kv_fp8_cast.yaml
phi4mm/ptq/{disabled_quantizers,nvfp4-kv_fp8_cast}.yaml
nemotron_vl/ptq/{disabled_quantizers,nvfp4-kv_fp8_cast}.yaml
qwen3_5/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast{,.quant_cfg}.yaml
qwen3_5_moe/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.yaml
step3p5/Step3.5-Flash/ptq/nvfp4-mlp-only.yaml
```

All recipes ship with FP8 KV-cache cast (`kv_fp8_cast`). For phi4mm and
nemotron_vl, `disabled_quantizers.yaml` is a multi-document list unit
that `$import`s the standard `default_disabled_quantizers` exclusions
and appends the model-specific ones — so each recipe imports a single
disabled-quantizer slot instead of layering two, with no duplication in
YAML. Each `ptq/` folder has a `README.md` describing exactly what is
model-specific.

### Usage

```bash
# Gemma W4A8 AWQ with the Gemma-specific algorithm tuning + FP8 KV cache:
python examples/llm_ptq/hf_ptq.py \
  --pyt_ckpt_path google/gemma-7b \
  --recipe huggingface/gemma/ptq/w4a8_awq-kv_fp8_cast \
  --export_path ./out

# Nemotron VL with vision branches excluded automatically:
python examples/llm_ptq/hf_ptq.py \
  --pyt_ckpt_path nvidia/<nemotron-vl-model> \
  --recipe huggingface/nemotron_vl/ptq/nvfp4-kv_fp8_cast \
  --export_path ./out
```

### Testing

- Pre-commit recipe validator
(`tools/precommit/check_modelopt_recipes.py`) loads every new recipe via
`load_recipe()` — passes for all new YAMLs (gemma/mpt/phi4mm/nemotron_vl
recipes + phi4mm/nemotron_vl `disabled_quantizers` snippets + qwen3_5 /
qwen3_5_moe recipe wrappers + the shared
`w4a16_nvfp4-fp8_attn-kv_fp8_cast.quant_cfg` snippet + Step3.5-Flash
relocation).
- For qwen3_5 / qwen3_5_moe specifically, `load_recipe(...)` on both
wrappers produces an identical 33-entry resolved `quant_cfg`, confirming
the shared snippet is the single source of truth.
- `yamlfmt` + `markdownlint` + `bandit` + license-insertion hooks all
pass.
- No tests reference the removed `build_quant_cfg(qformat, ...,
model_type, ...)` signature; the only call sites (`hf_ptq.py`,
`multinode_ptq.py`) were updated to the new 2/3-arg form.

### Before your PR is "*Ready for review*"

- Is this change backward compatible?: ❌ — users who relied on
**automatic** model-specific quant_cfg behavior via `--qformat`
(gemma/mpt AWQ, gemma SmoothQuant, phi4mm exclusions, Nemotron VL
exclusions) now need to pass `--recipe
huggingface/<model_type>/ptq/<recipe>` to apply the model's recipe. The
flag itself is unchanged; only the implicit behavior was removed.
- 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?: ❌ — relies on the existing
pre-commit recipe validator that loads each new YAML.
- Did you update Changelog?: ✅
- Did you get Claude approval on this PR?: ❌


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

* **New Features**
* Added many model-specific PTQ recipes (Gemma, MPT, Nemotron VL,
Phi‑4‑Multimodal, Qwen3.5, Qwen3.5‑MoE) and support for AWQ block-size
and MoE calibration ratio in quantization options.

* **Documentation**
* Expanded READMEs and changelog to document recipe locations, layout,
and how to opt into model-specific PTQ recipes.

* **Refactor**
* Model-specific PTQ tweaks moved to opt‑in recipes; default behavior
uses generic numerics.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/NVIDIA/Model-Optimizer/pull/1506?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

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

---------

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
This commit is contained in:
Shengliang Xu
2026-05-22 13:39:32 -07:00
committed by GitHub
parent 9ced018ec4
commit 04f58166ab
23 changed files with 610 additions and 168 deletions
+4
View File
@@ -17,6 +17,10 @@ Changelog
- Deprecated GradNAS pruning algorithm as it is not actively maintained and supports very limited and old models. It is recommended to use Minitron or Puzzletron pruning for LLM models. Also deprecates related ``examples/chained_optimizations`` directory.
- Model-specific PTQ ``quant_cfg`` adjustments previously hardcoded in ``examples/llm_ptq/`` (``build_quant_cfg`` / ``mono_quantize``) for gemma, mpt, phi4mm, and Nemotron VL are now opt-in **model-specific recipes** under ``modelopt_recipes/huggingface/<model_type>/ptq/``. Any adjustment specific to a model type or instance must live in that model's recipe; the bare ``--qformat`` path produces only the generic numerics. Pass ``--recipe huggingface/<model_type>/ptq/<recipe>`` to apply the model's recipe. Covers gemma/mpt ``w4a8_awq`` (``awq_lite`` ``alpha_step=1``), gemma ``int8_sq`` (SmoothQuant ``alpha=0.5``), phi4mm speech/audio/image/vision exclusions, and Nemotron VL vision-branch exclusions. All shipped recipes also enable FP8 KV-cache cast. MTP dynamic layer exclusion and ``is_nemotron_vl`` detection remain in Python.
- The Step3.5-Flash recipe moved from ``modelopt_recipes/models/Step3.5-Flash/nvfp4-mlp-only.yaml`` (0.44) to ``modelopt_recipes/huggingface/step3p5/Step3.5-Flash/ptq/nvfp4-mlp-only.yaml`` to match the ``huggingface/<model_type>/ptq/`` layout convention. Update ``--recipe`` paths accordingly.
**New Features**
- Extend Claude Code agent skills for PTQ, deployment, evaluation, monitoring, and baseline-vs-quantized result comparison. Adds evaluation task references for additional benchmarks, stronger PTQ checkpoint validation gates, and session-scoped workspace/job tracking.
+10 -6
View File
@@ -511,8 +511,11 @@ General PTQ recipes are model-agnostic and apply to any supported architecture:
Model-specific recipes
----------------------
Model-specific recipes are tuned for a particular architecture and live under
``models/<model_name>/``:
Model-specific recipes are tuned for a particular Hugging Face ``model_type``
(or a specific released model) and live under
``huggingface/<model_type>/[<specific_model>/]<task>/``. See
`modelopt_recipes/huggingface/README.md <https://github.com/NVIDIA/Model-Optimizer/blob/main/modelopt_recipes/huggingface/README.md>`_
for the layout convention and recipe-lookup order.
.. list-table::
:header-rows: 1
@@ -520,7 +523,7 @@ Model-specific recipes are tuned for a particular architecture and live under
* - Recipe path
- Description
* - ``models/Step3.5-Flash/nvfp4-mlp-only``
* - ``huggingface/step3p5/Step3.5-Flash/ptq/nvfp4-mlp-only``
- NVFP4 MLP-only for Step 3.5 Flash MoE model
@@ -669,9 +672,10 @@ The ``modelopt_recipes/`` package is organized as follows:
| +-- nvfp4_experts_only-kv_fp8.yaml
| +-- nvfp4_experts_only-kv_fp8_layerwise.yaml
| +-- nvfp4_omlp_only-kv_fp8.yaml
+-- models/ # Model-specific recipes
| +-- Step3.5-Flash/
| +-- nvfp4-mlp-only.yaml
+-- huggingface/ # Model-specific recipes
| +-- <model_type>/ # see modelopt_recipes/huggingface/README.md
| +-- <task>/
| +-- <recipe>.yaml
+-- configs/ # Reusable config snippets (imported via $import)
+-- numerics/ # Numeric format definitions
| +-- fp8.yaml
+1 -1
View File
@@ -183,7 +183,7 @@ python hf_ptq.py \
--export_path <quantized_ckpt_path>
```
Built-in recipes are located in `modelopt_recipes/general/ptq/`. You can also provide a path to your own custom YAML recipe file or directory. See the [recipe documentation](https://nvidia.github.io/Model-Optimizer) for details on the YAML schema and available recipes.
Built-in recipes are located in `modelopt_recipes/general/ptq/` for model-agnostic recipes and in `modelopt_recipes/huggingface/<model_type>/ptq/` for recipes tuned to a specific Hugging Face `model_type` (see [`modelopt_recipes/huggingface/README.md`](../../modelopt_recipes/huggingface/README.md)). You can also provide a path to your own custom YAML recipe file or directory. See the [recipe documentation](https://nvidia.github.io/Model-Optimizer) for details on the YAML schema and available recipes.
> *When `--recipe` is specified, `--qformat` and `--kv_cache_qformat` are ignored. The recipe fully defines the quantization configuration.*
-17
View File
@@ -202,10 +202,8 @@ def create_vlm_calibration_loop(full_model, calib_dataloader):
def build_quant_cfg(
qformat,
quant_cfg,
awq_block_size,
model_type,
moe_calib_experts_ratio: float | None = None,
) -> dict[str, Any]:
quant_cfg = copy.deepcopy(quant_cfg)
@@ -222,10 +220,6 @@ def build_quant_cfg(
if awq_block_size:
weight_quantizer["block_sizes"][-1] = awq_block_size
# Coarser optimal scale search seems to resolve the overflow in TRT-LLM for some models
if qformat == "w4a8_awq" and model_type in ["gemma", "mpt"]:
quant_cfg["algorithm"] = {"method": "awq_lite", "alpha_step": 1}
if moe_calib_experts_ratio:
assert 0 < moe_calib_experts_ratio <= 1, "moe_calib_experts_ratio must be between 0 and 1"
if isinstance(quant_cfg["algorithm"], str):
@@ -240,17 +234,6 @@ def build_quant_cfg(
f"Quantization algorithm: {quant_cfg['algorithm']} does not support setting moe_calib_experts_ratio"
)
# Gemma 7B has accuracy regression using alpha 1. We set 0.5 instead.
if model_type == "gemma" and "int8_sq" in qformat:
quant_cfg["algorithm"] = {"method": "smoothquant", "alpha": 0.5}
if model_type == "phi4mm":
# Only quantize the language model
quant_cfg["quant_cfg"].append({"quantizer_name": "*speech*", "enable": False})
quant_cfg["quant_cfg"].append({"quantizer_name": "*audio*", "enable": False})
quant_cfg["quant_cfg"].append({"quantizer_name": "*image*", "enable": False})
quant_cfg["quant_cfg"].append({"quantizer_name": "*vision*", "enable": False})
return quant_cfg
+4 -20
View File
@@ -631,22 +631,6 @@ def mono_quantize(
"Consider reducing calib_size to reduce calibration time.\n####\n"
)
# For Nemotron VL models, disable quantization of vision components
if is_nemotron_vl_model:
print("Disabling quantization for vision components in Nemotron VL model")
quant_cfg["quant_cfg"].append({"quantizer_name": "*vision*", "enable": False})
quant_cfg["quant_cfg"].append({"quantizer_name": "*image*", "enable": False})
# Also disable radio model components specifically (for Nemotron-Parse)
quant_cfg["quant_cfg"].append({"quantizer_name": "*radio*", "enable": False})
quant_cfg["quant_cfg"].append({"quantizer_name": "*visual*", "enable": False})
quant_cfg["quant_cfg"].append(
{"quantizer_name": "*encoder*", "enable": False}
) # Disable encoder
quant_cfg["quant_cfg"].append(
{"quantizer_name": "*model_encoder*", "enable": False}
) # Nemotron-Parse specific
print("Quantization will only be applied to the decoder (text generation) component")
if not model_is_already_quantized or calibration_only:
# quantize the model
@@ -1115,10 +1099,8 @@ def quantize_main(
quant_cfg = QUANT_CFG_CHOICES[args.qformat]
quant_cfg = build_quant_cfg(
args.qformat,
quant_cfg,
args.awq_block_size,
model_type,
args.moe_calib_experts_ratio,
)
@@ -1132,8 +1114,10 @@ def quantize_main(
getattr(mtq, KV_QUANT_CFG_CHOICES[args.kv_cache_qformat])["quant_cfg"],
)
# Exclude MTP layers from quantization if detected (e.g., GLM-4.7's layer 92)
# These layers are typically speculative decoding layers that should be exported as-is
# Exclude MTP layers from quantization if detected (e.g., GLM-4.7's layer 92).
# These layers are typically speculative decoding layers that should be exported as-is.
# Complementary to recipe `*mtp*` wildcards (name-match); this catches MTP layers
# identified by index.
mtp_layer_prefixes = getattr(full_model, "_mtp_layer_prefixes", None)
if mtp_layer_prefixes:
quant_cfg = copy.deepcopy(quant_cfg)
-2
View File
@@ -330,10 +330,8 @@ def main(args):
quant_cfg = QUANT_CFG_CHOICES[args.qformat]
quant_cfg = build_quant_cfg(
args.qformat,
quant_cfg,
args.awq_block_size,
model_type,
)
enable_quant_kv_cache = args.kv_cache_qformat != "none"
+87
View File
@@ -0,0 +1,87 @@
# Model-specific recipes for Hugging Face models
This folder holds model-optimization recipes (e.g. PTQ recipes) whose
behavior is tied to a **specific Hugging Face model architecture or model instance**.
## Choosing a recipe
Built-in recipes live in two places: `modelopt_recipes/huggingface/<model_type>/`
for model-specific recipes and `modelopt_recipes/general/` for model-agnostic
ones. When deciding which to use:
1. **Look in `huggingface/<model_type>/` first** for the target model's
Hugging Face `model_type`, and inside it for a nested
`<specific_model>/` folder if the recipe is tuned for one released
checkpoint rather than every checkpoint of that `model_type`. The
presence of a folder here signals that there is a recommended recipe
for that `model_type` or model instance.
2. **Fall back to `general/`** if no `<model_type>/` folder applies. The
general recipes are a good starting point for any model — and the
recommended starting point for a model architecture that does not yet
have a model-specific entry.
## Folder structure
Recipes are categorized by the Hugging Face `model_type` string — the
value of the top-level `model_type` field in the model's `config.json`
(or, for multimodal configs, the `text_config.model_type` of the inner
language model). Use the exact `model_type` as the directory name:
```text
modelopt_recipes/huggingface/
<model_type>/
<task>/
<recipe>.yaml
[<recipe>.<aux>.yaml] # optional snippet helpers (see below)
[README.md] # optional; describes what's model-specific
```
`<task>` is the model-optimization workflow the recipe targets (e.g.
`ptq` for post-training quantization).
Selecting a recipe at runtime uses the path relative to
`modelopt_recipes/`, e.g.
`--recipe huggingface/<model_type>/<task>/<recipe>`.
### Verifying a model's `model_type`
The authoritative source for a model's `model_type` is the released
checkpoint's `config.json` on the Hugging Face Hub, e.g.
`https://huggingface.co/<org>/<model>/raw/main/config.json`. The
`transformers` library's per-model `configuration_<name>.py` files also
hardcode the `model_type` string. Do not guess — confirm against one of
these sources before placing a recipe.
### Sharing content across recipes
When the same body is reused by multiple recipes — for example, one
recipe that applies to several `model_type`s, or several recipes that
share a sub-block — extract the reused portion into a sibling
**snippet** file with a `# modelopt-schema:` header and have each
recipe `$import` it. The recipe wrappers stay thin; the shared body
lives in one place.
Name snippet files so they are obviously not runnable recipes, e.g.
include the field name the snippet represents as a secondary suffix
(`<recipe>.<field>.yaml`). The snippet lives next to whichever recipe
is its natural canonical home; other importers reference it by the
same relative path under `modelopt_recipes/`.
### Per-family nested layout for specific model variants
If a recipe is tuned for one specific released model rather than every
checkpoint under a `model_type`, nest the model name as an extra level:
```text
<model_type>/
<specific_model>/
<task>/
<recipe>.yaml
```
### Per-folder READMEs
Each `<task>/` folder may contain a short `README.md` describing exactly
what is model-specific about each recipe (the algorithm override, the
disabled-quantizer pattern, etc.) so reviewers and users do not have to
diff the YAML against the generic presets to see the intent.
@@ -0,0 +1,12 @@
# Gemma PTQ recipes
Recipes here override the algorithm defaults that ship in the general PTQ
presets because Gemma needs different settings to converge / stay accurate.
| Recipe | What's model-specific |
|--------|-----------------------|
| `w4a8_awq-kv_fp8_cast.yaml` | Uses `awq_lite` with `alpha_step: 1` instead of the default AWQ search. The default search overflows in TRT-LLM kernels on Gemma; the coarser sweep avoids it without measurably hurting accuracy. Numerics: INT4 block weights + FP8 inputs + FP8 KV-cache cast (constant amax, no KV calibration). |
| `int8_sq-kv_fp8_cast.yaml` | Sets SmoothQuant `alpha: 0.5` instead of the default `1.0`. Gemma 7B regresses with `alpha=1`; `0.5` recovers it. Numerics: INT8 per-channel weights + INT8 inputs + FP8 KV-cache cast. |
The base numerics units and the standard disabled-quantizer list are inherited
from the shared `configs/`; only the algorithm fields are model-specific.
@@ -0,0 +1,46 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Gemma-specific INT8 SmoothQuant PTQ recipe with FP8 KV-cache cast. Overrides
# the SmoothQuant alpha from the default 1.0 to 0.5 to recover accuracy on
# Gemma 7B (default alpha causes a regression).
imports:
base_disable_all: configs/ptq/units/base_disable_all
default_disabled_quantizers: configs/ptq/units/default_disabled_quantizers
int8: configs/numerics/int8
int8_per_channel: configs/numerics/int8_per_channel
kv_fp8_cast: configs/ptq/units/kv_fp8_cast
metadata:
recipe_type: ptq
description: >-
Gemma INT8 SmoothQuant recipe with FP8 KV-cache cast: alpha=0.5 (instead
of the default 1.0) to avoid accuracy regression on Gemma 7B, plus FP8
KV-cache using constant amax (no KV calibration).
quantize:
algorithm:
method: smoothquant
alpha: 0.5
quant_cfg:
- $import: base_disable_all
- quantizer_name: '*weight_quantizer'
cfg:
$import: int8_per_channel
- quantizer_name: '*input_quantizer'
cfg:
$import: int8
- $import: kv_fp8_cast
- $import: default_disabled_quantizers
@@ -0,0 +1,47 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Gemma-specific W4A8 AWQ PTQ recipe with FP8 KV-cache cast. Uses a coarser
# optimal-scale search (awq_lite with alpha_step=1) to avoid overflow observed
# in TRT-LLM kernels when using the default AWQ search on Gemma.
imports:
base_disable_all: configs/ptq/units/base_disable_all
default_disabled_quantizers: configs/ptq/units/default_disabled_quantizers
fp8: configs/numerics/fp8
int4_per_block: configs/numerics/int4_per_block
kv_fp8_cast: configs/ptq/units/kv_fp8_cast
metadata:
recipe_type: ptq
description: >-
Gemma W4A8 AWQ recipe with FP8 KV-cache cast: INT4 block weights + FP8
inputs, awq_lite with alpha_step=1 (coarser search) to avoid TRT-LLM
overflow, plus FP8 KV-cache using constant amax (no KV calibration).
quantize:
algorithm:
method: awq_lite
alpha_step: 1
quant_cfg:
- $import: base_disable_all
- quantizer_name: '*weight_quantizer'
cfg:
- $import: int4_per_block
- $import: fp8
- quantizer_name: '*input_quantizer'
cfg:
$import: fp8
- $import: kv_fp8_cast
- $import: default_disabled_quantizers
@@ -0,0 +1,8 @@
# MPT PTQ recipes
| Recipe | What's model-specific |
|--------|-----------------------|
| `w4a8_awq-kv_fp8_cast.yaml` | Uses `awq_lite` with `alpha_step: 1` instead of the default AWQ search. The default search overflows in TRT-LLM kernels on MPT; the coarser sweep avoids it. Numerics: INT4 block weights + FP8 inputs + FP8 KV-cache cast (constant amax, no KV calibration). Same algorithm override applied to Gemma — see `huggingface/gemma/ptq/`. |
The base numerics units and the standard disabled-quantizer list are inherited
from the shared `configs/`; only the AWQ algorithm fields are model-specific.
@@ -0,0 +1,47 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# MPT-specific W4A8 AWQ PTQ recipe with FP8 KV-cache cast. Uses a coarser
# optimal-scale search (awq_lite with alpha_step=1) to avoid overflow observed
# in TRT-LLM kernels when using the default AWQ search on MPT.
imports:
base_disable_all: configs/ptq/units/base_disable_all
default_disabled_quantizers: configs/ptq/units/default_disabled_quantizers
fp8: configs/numerics/fp8
int4_per_block: configs/numerics/int4_per_block
kv_fp8_cast: configs/ptq/units/kv_fp8_cast
metadata:
recipe_type: ptq
description: >-
MPT W4A8 AWQ recipe with FP8 KV-cache cast: INT4 block weights + FP8
inputs, awq_lite with alpha_step=1 (coarser search) to avoid TRT-LLM
overflow, plus FP8 KV-cache using constant amax (no KV calibration).
quantize:
algorithm:
method: awq_lite
alpha_step: 1
quant_cfg:
- $import: base_disable_all
- quantizer_name: '*weight_quantizer'
cfg:
- $import: int4_per_block
- $import: fp8
- quantizer_name: '*input_quantizer'
cfg:
$import: fp8
- $import: kv_fp8_cast
- $import: default_disabled_quantizers
@@ -0,0 +1,14 @@
# Nemotron VL PTQ recipes
Nemotron VL is a vision-language model family (including Nemotron-Parse).
Quantization should be applied only to the decoder (text-generation
component); the vision encoder, image encoder, and radio/model_encoder
branches stay in full precision.
| File | What's model-specific |
|------|-----------------------|
| `disabled_quantizers.yaml` | Reusable unit (`QuantizerCfgListConfig`). Merges the standard `default_disabled_quantizers` exclusions with Nemotron-VL ones (`*vision*`, `*image*`, `*radio*`, `*visual*`, `*encoder*`, `*model_encoder*`). The last two patterns are required for Nemotron-Parse. Imported by recipes below as the single `disabled_quantizers` slot so they don't pull in two disabled-quantizer sets. |
| `nvfp4-kv_fp8_cast.yaml` | NVFP4 W4A4 model quantization + FP8 KV-cache cast (constant amax, no KV calibration). Identical numerics to the general `nvfp4` preset / `kv_fp8_cast` unit; what makes it model-specific is that it imports `disabled_quantizers.yaml` from this folder to skip the vision/encoder branches. |
Additional `<qformat>-kv_fp8_cast.yaml` recipes can be generated for other formats
if needed; only `nvfp4-kv_fp8_cast.yaml` is shipped by default.
@@ -0,0 +1,39 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# QuantizerCfgList snippet of disabled quantizers for Nemotron VL. Splices in
# the standard `default_disabled_quantizers` exclusions and appends
# Nemotron-VL-specific ones so that only the decoder (text-generation
# component) is quantized; vision/encoder branches, including the
# Nemotron-Parse radio/model_encoder modules, are skipped. Recipes that
# import this should NOT also import `default_disabled_quantizers`.
# modelopt-schema: modelopt.torch.quantization.config.QuantizerCfgListConfig
imports:
default_disabled_quantizers: configs/ptq/units/default_disabled_quantizers
---
- $import: default_disabled_quantizers
- quantizer_name: '*vision*'
enable: false
- quantizer_name: '*image*'
enable: false
- quantizer_name: '*radio*'
enable: false
- quantizer_name: '*visual*'
enable: false
- quantizer_name: '*encoder*'
enable: false
- quantizer_name: '*model_encoder*'
enable: false
@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Nemotron VL-specific PTQ recipe for the `nvfp4` quantization format.
# Equivalent to the general `nvfp4` preset with quantization disabled
# on non-language branches.
imports:
base_disable_all: configs/ptq/units/base_disable_all
w4a4_nvfp4_nvfp4: configs/ptq/units/w4a4_nvfp4_nvfp4
disabled_quantizers: huggingface/nemotron_vl/ptq/disabled_quantizers
kv_fp8_cast: configs/ptq/units/kv_fp8_cast
metadata:
recipe_type: ptq
description: 'Nemotron VL PTQ recipe (nvfp4): same numerics as the general nvfp4 preset, applied to the decoder (text generation) component only (vision/encoder
branches are skipped).'
quantize:
algorithm: max
quant_cfg:
- $import: base_disable_all
- $import: w4a4_nvfp4_nvfp4
- $import: kv_fp8_cast
- $import: disabled_quantizers
@@ -0,0 +1,13 @@
# Phi-4-Multimodal PTQ recipes
Phi-4-Multimodal is a multimodal model. Quantization should be applied only to
the language model; the speech, audio, image, and vision branches are kept in
full precision to avoid accuracy regressions on those modalities.
| File | What's model-specific |
|------|-----------------------|
| `disabled_quantizers.yaml` | Reusable unit (`QuantizerCfgListConfig`). Merges the standard `default_disabled_quantizers` exclusions with Phi-4-MM ones (`*speech*`, `*audio*`, `*image*`, `*vision*`). Imported by recipes below as the single `disabled_quantizers` slot so they don't pull in two disabled-quantizer sets. |
| `nvfp4-kv_fp8_cast.yaml` | NVFP4 W4A4 model quantization + FP8 KV-cache cast (constant amax, no KV calibration). Identical numerics to the general `nvfp4` preset / `kv_fp8_cast` unit; what makes it model-specific is that it imports `disabled_quantizers.yaml` from this folder to skip the non-language branches. |
Additional `<qformat>-kv_fp8_cast.yaml` recipes can be generated for other formats
if needed; only `nvfp4-kv_fp8_cast.yaml` is shipped by default.
@@ -0,0 +1,34 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# QuantizerCfgList snippet of disabled quantizers for Phi-4-Multimodal.
# Splices in the standard `default_disabled_quantizers` exclusions and appends
# Phi-4-MM-specific ones so that only the language model is quantized;
# speech/audio/image/vision branches are skipped. Recipes that import this
# should NOT also import `default_disabled_quantizers`.
# modelopt-schema: modelopt.torch.quantization.config.QuantizerCfgListConfig
imports:
default_disabled_quantizers: configs/ptq/units/default_disabled_quantizers
---
- $import: default_disabled_quantizers
- quantizer_name: '*speech*'
enable: false
- quantizer_name: '*audio*'
enable: false
- quantizer_name: '*image*'
enable: false
- quantizer_name: '*vision*'
enable: false
@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Phi-4-Multimodal-specific PTQ recipe for the `nvfp4` quantization format.
# Equivalent to the general `nvfp4` preset with quantization disabled
# on non-language branches.
imports:
base_disable_all: configs/ptq/units/base_disable_all
w4a4_nvfp4_nvfp4: configs/ptq/units/w4a4_nvfp4_nvfp4
disabled_quantizers: huggingface/phi4mm/ptq/disabled_quantizers
kv_fp8_cast: configs/ptq/units/kv_fp8_cast
metadata:
recipe_type: ptq
description: 'Phi-4-Multimodal PTQ recipe (nvfp4): same numerics as the general nvfp4 preset, applied to the language model only (speech, audio, image,
and vision branches are skipped).'
quantize:
algorithm: max
quant_cfg:
- $import: base_disable_all
- $import: w4a4_nvfp4_nvfp4
- $import: kv_fp8_cast
- $import: disabled_quantizers
@@ -0,0 +1,93 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Shared `quant_cfg` snippet for the Qwen3.5 family's
# `w4a16_nvfp4-fp8_attn-kv_fp8_cast` recipe. Imported by both
# `huggingface/qwen3_5/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.yaml` (dense `qwen3_5`)
# and `huggingface/qwen3_5_moe/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.yaml` (MoE
# `qwen3_5_moe`); the two families share the hybrid linear-attention +
# softmax-attention architecture, so the wildcard rules apply identically.
# MoE-only patterns inside `default_disabled_quantizers`
# (`*block_sparse_moe.gate*`, `*mlp.shared_expert_gate.*`, `*router*`) are
# no-ops on dense.
# modelopt-schema: modelopt.torch.quantization.config.QuantizerCfgListConfig
imports:
base_disable_all: configs/ptq/units/base_disable_all
default_disabled_quantizers: configs/ptq/units/default_disabled_quantizers
fp8: configs/numerics/fp8
kv_fp8_cast: configs/ptq/units/kv_fp8_cast
nvfp4: configs/numerics/nvfp4
---
- $import: base_disable_all
# W4A16 NVFP4 on MLP projection targets. The gate/up/down projection patterns
# cover dense MLPs, shared experts, and fused MoE expert quantizers
# (e.g. gate_up_proj_weight_quantizers.N).
- quantizer_name: '*mlp*gate_proj*weight_quantizer*'
cfg: {$import: nvfp4}
- quantizer_name: '*mlp*up_proj*weight_quantizer*'
cfg: {$import: nvfp4}
- quantizer_name: '*mlp*down_proj*weight_quantizer*'
cfg: {$import: nvfp4}
# FP8 self-attention projections.
- quantizer_name: '*self_attn*weight_quantizer'
cfg: {$import: fp8}
- quantizer_name: '*self_attn*input_quantizer'
cfg: {$import: fp8}
# FP8 large linear-attention projections. in_proj_a and in_proj_b are
# re-disabled explicitly below; conv1d stays disabled via base_disable_all
# (no rule re-enables it).
- quantizer_name: '*linear_attn.in_proj_qkv*weight_quantizer'
cfg: {$import: fp8}
- quantizer_name: '*linear_attn.in_proj_qkv*input_quantizer'
cfg: {$import: fp8}
- quantizer_name: '*linear_attn.in_proj_z*weight_quantizer'
cfg: {$import: fp8}
- quantizer_name: '*linear_attn.in_proj_z*input_quantizer'
cfg: {$import: fp8}
- quantizer_name: '*linear_attn.out_proj*weight_quantizer'
cfg: {$import: fp8}
- quantizer_name: '*linear_attn.out_proj*input_quantizer'
cfg: {$import: fp8}
# FP8 KV cache with constant amax.
- $import: kv_fp8_cast
# Standard exclusions (BatchNorm, LeakyReLU, gates, routers, conv1d, output
# heads, etc.). Includes `*lm_head*` disable, which is re-enabled below.
- $import: default_disabled_quantizers
# Qwen-specific exclusions: linear-attention sub-modules that are not in the
# reference recipe, and any visual / MTP siblings on multimodal releases.
- quantizer_name: '*linear_attn.in_proj_a*'
enable: false
- quantizer_name: '*linear_attn.in_proj_b*'
enable: false
- quantizer_name: '*visual*'
enable: false
- quantizer_name: '*vision_tower*'
enable: false
# Name-match for "mtp"; complementary runtime path in hf_ptq.py catches
# MTP layers identified by index instead.
- quantizer_name: '*mtp*'
enable: false
# Re-enable NVFP4 on lm_head weights. Must come after
# default_disabled_quantizers, which disables `*lm_head*`.
- quantizer_name: '*lm_head*weight_quantizer'
cfg: {$import: nvfp4}
@@ -0,0 +1,39 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# W4A16 (NVFP4 weights) MLP / FP8 attention / FP8 KV-cast PTQ recipe for
# HuggingFace `qwen3_5` (dense) models. Covers Qwen3.5 and Qwen3.6 dense
# releases, which share the `qwen3_5` model_type and hybrid linear-attention +
# softmax-attention architecture. Shares its `quant_cfg` with the MoE
# counterpart at `huggingface/qwen3_5_moe/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.yaml`;
# the snippet lives under
# `huggingface/qwen3_5/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.quant_cfg.yaml`.
imports:
shared_quant_cfg: huggingface/qwen3_5/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.quant_cfg
metadata:
recipe_type: ptq
description: >-
W4A16 (NVFP4 weights) MLP / FP8 attention / FP8 KV-cast PTQ recipe for
HuggingFace `qwen3_5` (dense) models: NVFP4 for MLP projection weights and
lm_head; FP8 for self-attention and the large linear-attention
projections; FP8 KV cache with constant amax.
quantize:
algorithm:
method: max
layerwise: false
quant_cfg:
- $import: shared_quant_cfg
@@ -0,0 +1,40 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# W4A16 (NVFP4 weights) MLP / FP8 attention / FP8 KV-cast PTQ recipe for
# HuggingFace `qwen3_5_moe` models. Covers Qwen3.5-MoE and Qwen3.6-MoE
# releases, which share the `qwen3_5_moe` model_type and hybrid
# linear-attention + softmax-attention MoE architecture. Shares its
# `quant_cfg` with the dense counterpart at
# `huggingface/qwen3_5/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.yaml`; the snippet lives
# under `huggingface/qwen3_5/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.quant_cfg.yaml`.
imports:
shared_quant_cfg: huggingface/qwen3_5/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast.quant_cfg
metadata:
recipe_type: ptq
description: >-
W4A16 (NVFP4 weights) MLP / FP8 attention / FP8 KV-cast PTQ recipe for
HuggingFace `qwen3_5_moe` models (Qwen3.5-MoE and Qwen3.6-MoE releases):
NVFP4 for MoE / shared-expert MLP projection weights and lm_head; FP8 for
self-attention and the large linear-attention projections; FP8 KV cache
with constant amax.
quantize:
algorithm:
method: max
layerwise: false
quant_cfg:
- $import: shared_quant_cfg
@@ -1,122 +0,0 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
metadata:
recipe_type: ptq
description: >
W4A16 PTQ recipe for Qwen3.5/Qwen3.6 models: W4A16 NVFP4 for dense MLP,
routed MoE, shared-expert MLP projections, and lm_head; FP8 for
self-attention and the large linear-attention projections; FP8 KV cache
with constant amax (fp8_cast behavior).
quantize:
algorithm:
method: max
layerwise: false
quant_cfg:
- quantizer_name: '*'
enable: false
# W4A16 NVFP4 MLP projection targets. Matching the gate/up/down projection
# names covers dense MLPs, shared experts, and fused MoE expert quantizers
# such as gate_up_proj_weight_quantizers.N/down_proj_weight_quantizers.N.
- quantizer_name: '*mlp*gate_proj*weight_quantizer*'
enable: true
cfg: &nvfp4_cfg
block_sizes:
-1: 16
type: dynamic
scale_bits: e4m3
num_bits: e2m1
- quantizer_name: '*mlp*up_proj*weight_quantizer*'
enable: true
cfg: *nvfp4_cfg
- quantizer_name: '*mlp*down_proj*weight_quantizer*'
enable: true
cfg: *nvfp4_cfg
- quantizer_name: '*lm_head*weight_quantizer'
enable: true
cfg: *nvfp4_cfg
# FP8 self-attention projections.
- quantizer_name: '*self_attn*weight_quantizer'
enable: true
cfg: &fp8_cfg
num_bits: e4m3
axis:
- quantizer_name: '*self_attn*input_quantizer'
enable: true
cfg: *fp8_cfg
# FP8 large linear-attention projections. Keep in_proj_a, in_proj_b, and
# conv1d disabled to match the reference checkpoint policy.
- quantizer_name: '*linear_attn.in_proj_qkv*weight_quantizer'
enable: true
cfg: *fp8_cfg
- quantizer_name: '*linear_attn.in_proj_qkv*input_quantizer'
enable: true
cfg: *fp8_cfg
- quantizer_name: '*linear_attn.in_proj_z*weight_quantizer'
enable: true
cfg: *fp8_cfg
- quantizer_name: '*linear_attn.in_proj_z*input_quantizer'
enable: true
cfg: *fp8_cfg
- quantizer_name: '*linear_attn.out_proj*weight_quantizer'
enable: true
cfg: *fp8_cfg
- quantizer_name: '*linear_attn.out_proj*input_quantizer'
enable: true
cfg: *fp8_cfg
# FP8 KV cache with constant amax. This matches fp8_cast behavior and
# avoids exporting per-layer KV scale tensors.
- quantizer_name: '*[kv]_bmm_quantizer'
enable: true
cfg:
num_bits: e4m3
axis:
use_constant_amax: true
# Explicitly keep non-reference targets unquantized.
- quantizer_name: '*linear_attn.conv1d*'
enable: false
- quantizer_name: '*linear_attn.in_proj_a*'
enable: false
- quantizer_name: '*linear_attn.in_proj_b*'
enable: false
- quantizer_name: '*mlp.gate.*'
enable: false
- quantizer_name: '*mlp.shared_expert_gate.*'
enable: false
- quantizer_name: '*router*'
enable: false
- quantizer_name: '*block_sparse_moe.gate*'
enable: false
- quantizer_name: '*mixer.conv1d*'
enable: false
- quantizer_name: '*output_layer*'
enable: false
- quantizer_name: '*proj_out.*'
enable: false
- quantizer_name: 'output.*'
enable: false
- quantizer_name: '*visual*'
enable: false
- quantizer_name: '*vision_tower*'
enable: false
- quantizer_name: '*mtp*'
enable: false
- parent_class: 'nn.BatchNorm1d'
quantizer_name: '*'
enable: false
- parent_class: 'nn.BatchNorm2d'
quantizer_name: '*'
enable: false
- parent_class: 'nn.BatchNorm3d'
quantizer_name: '*'
enable: false
- parent_class: 'nn.LeakyReLU'
quantizer_name: '*'
enable: false