mirror of
https://github.com/NVIDIA/Model-Optimizer.git
synced 2026-10-02 03:14:52 +08:00
### What does this PR do?
Type of change: Refactor + deprecation (recipe-library restructure,
backward compatible), plus an unrelated transformers-compat test fix.
Rename the architecture-specific recipe tier
`modelopt_recipes/huggingface/` to
`modelopt_recipes/model_type/`, making explicit that it holds recipes
**shared across
every checkpoint of a Hugging Face `model_type`** — as opposed to the
checkpoint-mirror
`models/<org>/<model_id>/` tier. The old `huggingface/` path keeps
working as a
deprecated backward-compat alias (a source-tree symlink plus a loader
alias), so no
saved `--recipe` path breaks.
- **Loader alias** (`modelopt/recipe/loader.py`): generalized so saved
`--recipe huggingface/<model_type>/...` paths rewrite to
`model_type/...`, alongside
the existing `huggingface/models/... -> models/...` rewrite (checked
first as the more
specific prefix). This keeps old paths resolving for pip-installed
wheels, where the
source-tree symlinks don't survive.
- **Internal `$import`s**: rewritten from `huggingface/... ->
model_type/...` inside the
shipped recipes so they resolve without the symlink — mandatory for
wheels, since
`$import` resolution goes through `config_loader` (no alias there).
- **Packaging** (`pyproject.toml`, `MANIFEST.in`): extended the
symlink-exclusion globs
so the recursive `**/*.yaml` package-data glob doesn't double-ship
recipes through the
`huggingface -> model_type` and `model_type/models -> ../models`
symlinks.
- **Docs / examples / skills / tests**: migrated all internal references
to the canonical
`model_type/`; `huggingface/` remains only in the deprecated-alias tests
and explanatory
notes.
- **Unrelated fix (2nd commit):**
`tests/unit/torch/export/test_quant_aware_conversion.py`
failed on transformers>=5.9, which dropped `base_model_prefix` from
`WeightTransform.__slots__` (the scoped-rule tests assigned it on the
now-slotted
object). Production `_scope_prefixes` already reads it via `getattr(...,
None)` and
degrades correctly, so there is no runtime change — the tests now set it
through a
helper that suppresses `AttributeError` across the supported
transformers range.
### Usage
```bash
# New canonical path
python examples/hf_ptq/hf_ptq.py --model <ckpt> \
--recipe model_type/qwen3_vl/ptq/fp8_vision-kv_none
# Old path still works (deprecated backward-compat alias)
python examples/hf_ptq/hf_ptq.py --model <ckpt> \
--recipe huggingface/qwen3_vl/ptq/fp8_vision-kv_none
```
```python
from modelopt.recipe import load_recipe
load_recipe("model_type/vit/ptq/fp8") # canonical
load_recipe("huggingface/vit/ptq/fp8") # deprecated alias, resolves to the same recipe
```
### Testing
- `tests/unit/recipe/` — **336 passed**, including the new
`test_load_recipe_huggingface_arch_backward_compat_alias` and the
updated
structural/doc tests (`test_recipe_docs.py`).
- `tests/unit/torch/export/test_quant_aware_conversion.py` — **16
passed** (was 4 failed
on transformers 5.9.0).
- Built an sdist **and** a wheel and inspected both manifests: each
recipe ships exactly
once (29 `model_type/`, 13 `models/`, 2 `timm/`, 162 total) with
**zero** `huggingface/` or
`model_type/models/` duplicates and no build error on the symlinks.
- Simulated a wheel install (symlink-free extracted tree) and confirmed
`huggingface/<arch>/...`, `model_type/...`, and `huggingface/models/...`
all resolve via
the loader alias — including a recipe that pulls internal `$import`s.
### Before your PR is "*Ready for review*"
- Is this change backward compatible?: ✅ — old `huggingface/...` recipe
paths keep resolving via the symlink + loader alias.
- 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?: ✅ — backward-compat alias test
added; structural/doc tests updated to the new layout.
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
✅ — Deprecations entry under 0.48.0. (The transformers-compat test fix
is not changelog-worthy.)
- Did you get Claude approval on this PR?: ❌ — not yet.
### Additional Information
The `model_type/models -> ../models` symlink is kept purely as a
backward-compat alias for
old `huggingface/models/<org>/<model_id>/...` paths; `model_type/` is
otherwise
architecture-only. If we ever want it strictly architecture-only, that
symlink can be
dropped later without breaking anything, since the loader rewrites
`huggingface/models/...`
straight to the top-level `models/` tier.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added post-training quantization recipes for Gemma, Gemma 4,
MiniMax-M3, Nemotron, Qwen, Step-3.7, ViT, and other architectures.
- Added vision, multimodal, mixed-precision, and experts-only
quantization options.
- **Documentation**
- Standardized architecture-specific recipes under `model_type/` and
updated examples and guidance.
- **Compatibility**
- Legacy `huggingface/` recipe paths remain supported with deprecation
warnings.
- Local recipe files now take precedence over built-in recipes.
- Deprecated quantization-format flags warn when explicitly provided.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
765 lines
27 KiB
ReStructuredText
765 lines
27 KiB
ReStructuredText
.. _recipes:
|
|
|
|
Recipes
|
|
#######
|
|
|
|
A **recipe** is a declarative specification that fully describes how to optimize a model.
|
|
A recipe can be a single YAML file or a directory containing YAML configs and other files
|
|
that together define a model optimization workflow.
|
|
Recipes decouple optimization settings from Python code, enabling reuse, sharing, version
|
|
control, and reproducibility. Instead of editing Python scripts to change optimization
|
|
parameters, you author (or select) a recipe and pass it to the ModelOpt tooling.
|
|
While the examples below focus on PTQ (the first supported recipe type), the recipe
|
|
system is designed to support any optimization technique.
|
|
|
|
.. contents:: On this page
|
|
:local:
|
|
:depth: 2
|
|
|
|
|
|
Motivation
|
|
==========
|
|
|
|
Without recipes, optimization settings are scattered across command-line arguments, Python
|
|
constants, and ad-hoc code edits. This makes it difficult to:
|
|
|
|
* **Reproduce** a published result -- the exact configuration is buried in script arguments.
|
|
* **Share** a configuration -- there is no single artifact to hand off.
|
|
* **Version-control** changes -- diffs are mixed in with unrelated code changes.
|
|
* **Onboard new models** -- engineers must read source code to discover which
|
|
settings to tweak.
|
|
|
|
Recipes solve these problems by capturing **all** the configuration needed to optimize a
|
|
model in a single, portable artifact -- either a YAML file or a directory of files.
|
|
|
|
|
|
Design overview
|
|
===============
|
|
|
|
The recipe system is part of the :mod:`modelopt.recipe` package and consists of three
|
|
layers:
|
|
|
|
1. **Recipe sources** -- YAML files or directories stored in the ``modelopt_recipes/``
|
|
directory (shipped with the package) or on the user's filesystem.
|
|
2. **Config loader** -- :func:`~modelopt.recipe.load_config` reads YAML files, resolves
|
|
paths, and performs automatic ``ExMy`` floating-point notation conversion.
|
|
3. **Recipe loader** -- :func:`~modelopt.recipe.load_recipe` validates the loaded
|
|
configuration against Pydantic models and returns a typed recipe object ready for use.
|
|
|
|
|
|
Recipe format
|
|
=============
|
|
|
|
A recipe contains two top-level sections: ``metadata`` and a type-specific
|
|
configuration section (for example, ``quantize`` for PTQ recipes). These can live
|
|
in a single YAML file or be split across files in a directory.
|
|
|
|
Recipes support two authoring styles: **inline** (all values written directly)
|
|
and **import-based** (reusable snippets referenced via ``$import``). Both
|
|
styles can be used in a single-file or directory layout.
|
|
|
|
Single-file format
|
|
------------------
|
|
|
|
The simplest form is a single ``.yaml`` file.
|
|
|
|
**Inline style** — all config values are written directly:
|
|
|
|
.. code-block:: yaml
|
|
|
|
metadata:
|
|
recipe_type: ptq
|
|
description: FP8 per-tensor weight and activation (W8A8), FP8 KV cache, max calibration.
|
|
|
|
quantize:
|
|
algorithm: max
|
|
quant_cfg:
|
|
- quantizer_name: '*'
|
|
enable: false
|
|
- quantizer_name: '*input_quantizer'
|
|
cfg:
|
|
num_bits: e4m3
|
|
axis:
|
|
- quantizer_name: '*weight_quantizer'
|
|
cfg:
|
|
num_bits: e4m3
|
|
axis:
|
|
- quantizer_name: '*[kv]_bmm_quantizer'
|
|
cfg:
|
|
num_bits: e4m3
|
|
# ... standard exclusions omitted for brevity
|
|
|
|
**Import style** — the same recipe using reusable config snippets:
|
|
|
|
.. code-block:: yaml
|
|
|
|
imports:
|
|
base_disable_all: configs/ptq/units/base_disable_all
|
|
default_disabled: configs/ptq/units/default_disabled_quantizers
|
|
fp8: configs/numerics/fp8
|
|
|
|
metadata:
|
|
recipe_type: ptq
|
|
description: FP8 per-tensor weight and activation (W8A8), FP8 KV cache, max calibration.
|
|
|
|
quantize:
|
|
algorithm: max
|
|
quant_cfg:
|
|
- $import: base_disable_all
|
|
- quantizer_name: '*input_quantizer'
|
|
cfg:
|
|
$import: fp8
|
|
- quantizer_name: '*weight_quantizer'
|
|
cfg:
|
|
$import: fp8
|
|
- quantizer_name: '*[kv]_bmm_quantizer'
|
|
cfg:
|
|
$import: fp8
|
|
- $import: default_disabled
|
|
|
|
Both styles produce identical results at load time. The import style reduces
|
|
duplication when multiple recipes share the same numeric formats or exclusion
|
|
lists. See :ref:`composable-imports` below for the full ``$import`` specification.
|
|
|
|
Directory format
|
|
----------------
|
|
|
|
For larger recipes or when you want to keep metadata separate from the
|
|
optimization configuration, use a directory with multiple files. Here is a PTQ
|
|
example:
|
|
|
|
.. code-block:: text
|
|
|
|
my_recipe/
|
|
metadata.yaml # metadata section body
|
|
quantize.yaml # quantize section (+ optional imports)
|
|
|
|
``metadata.yaml``:
|
|
|
|
.. code-block:: yaml
|
|
|
|
recipe_type: ptq
|
|
description: My custom NVFP4 recipe.
|
|
|
|
``quantize.yaml``:
|
|
|
|
.. code-block:: yaml
|
|
|
|
algorithm: max
|
|
quant_cfg:
|
|
- quantizer_name: '*'
|
|
enable: false
|
|
- quantizer_name: '*weight_quantizer'
|
|
cfg:
|
|
num_bits: e2m1
|
|
block_sizes: {-1: 16, type: dynamic, scale_bits: e4m3}
|
|
- quantizer_name: '*input_quantizer'
|
|
cfg:
|
|
num_bits: e4m3
|
|
axis:
|
|
|
|
Both inline and import styles work with the directory format. Imports are
|
|
scoped to the file that declares them; for PTQ quantization snippets, declare
|
|
the relevant ``imports`` section in ``quantize.yaml``.
|
|
|
|
.. _composable-imports:
|
|
|
|
Composable imports
|
|
------------------
|
|
|
|
Recipes can import **reusable config snippets** via the ``imports`` section.
|
|
This eliminates duplication — numeric format definitions and standard exclusion
|
|
lists are authored once and referenced by name across recipes.
|
|
|
|
The ``imports`` section is a dict mapping short names to config file paths.
|
|
References use the explicit ``{$import: name}`` marker so they are never
|
|
confused with literal values.
|
|
|
|
.. note::
|
|
|
|
``imports`` (no ``$``) is a **top-level structural section** — like
|
|
``metadata`` or ``quantize``, it declares the recipe's dependencies.
|
|
``$import`` (with ``$``) is an **inline directive** that appears inside
|
|
data values and gets resolved at load time.
|
|
|
|
The ``$import`` marker can appear anywhere in the recipe:
|
|
|
|
- As a **dict value** — the marker is replaced with the snippet content, or
|
|
merged with inline overrides when sibling keys are present.
|
|
- As a **list element** — the surrounding list's schema and the imported
|
|
snippet's ``modelopt-schema`` determine whether the imported snippet is
|
|
appended as one element or spliced as multiple elements.
|
|
|
|
As a **dict value**, ``$import`` supports composition with clear override
|
|
precedence (lowest to highest):
|
|
|
|
1. **Imports in list order** — ``$import: [base, override]``: later snippets
|
|
override earlier ones on key conflicts.
|
|
2. **Inline keys** — extra keys alongside ``$import`` override all imported
|
|
values.
|
|
|
|
This is equivalent to calling ``dict.update()`` in order: imports first (in
|
|
list order), then inline keys last.
|
|
|
|
.. code-block:: yaml
|
|
|
|
# Single import
|
|
cfg:
|
|
$import: nvfp4
|
|
|
|
# Import + override — import nvfp4, then override type inline
|
|
cfg:
|
|
$import: nvfp4 # imports {num_bits: e2m1, block_sizes: {-1: 16, type: dynamic, ...}}
|
|
block_sizes:
|
|
-1: 16
|
|
type: static # overrides type: dynamic → static calibration
|
|
|
|
# Multiple imports — later snippet overrides earlier on conflict
|
|
cfg:
|
|
$import: [base_format, kv_tweaks] # kv_tweaks wins on shared keys
|
|
|
|
# All three: multi-import + inline override
|
|
cfg:
|
|
$import: [bits, scale]
|
|
axis: 0 # highest precedence
|
|
|
|
As a **list element**, ``$import`` must be the only key — extra keys alongside
|
|
a list import are not supported. List imports require a typed containing list
|
|
and a schema-declared snippet:
|
|
|
|
* If the snippet schema is the same list type as the containing list, its
|
|
entries are spliced into the surrounding list.
|
|
* If the snippet schema is the list element type, it is appended as one list
|
|
item.
|
|
|
|
.. code-block:: yaml
|
|
|
|
imports:
|
|
base_disable_all: configs/ptq/units/base_disable_all
|
|
default_disabled: configs/ptq/units/default_disabled_quantizers
|
|
fp8: configs/numerics/fp8
|
|
|
|
metadata:
|
|
recipe_type: ptq
|
|
description: FP8 W8A8, FP8 KV cache.
|
|
|
|
quantize:
|
|
algorithm: max
|
|
quant_cfg:
|
|
- $import: base_disable_all # appended from a single-entry snippet
|
|
- quantizer_name: '*weight_quantizer'
|
|
cfg:
|
|
$import: fp8 # cfg value replaced with imported dict
|
|
- $import: default_disabled # spliced from a multi-element list snippet
|
|
|
|
In this example:
|
|
|
|
- ``$import: base_disable_all`` and ``$import: default_disabled`` are **list elements**
|
|
— ``base_disable_all`` is appended as one entry, while ``default_disabled`` is
|
|
a YAML list spliced into ``quant_cfg``.
|
|
- ``$import: fp8`` under ``cfg`` is a **dict value** — the snippet (a YAML dict of
|
|
quantizer attributes) replaces the ``cfg`` field.
|
|
|
|
Import paths are resolved via :func:`~modelopt.recipe.load_config` — the
|
|
built-in ``modelopt_recipes/`` library is checked first, then the filesystem.
|
|
|
|
**Recursive imports:** An imported snippet may itself contain an ``imports``
|
|
section. Each file's imports are scoped to that file — the same name can be
|
|
used in different files without conflict. Circular imports are detected and
|
|
raise ``ValueError``.
|
|
|
|
Multi-document snippets
|
|
^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Dict-valued snippets (e.g., numeric format definitions) can use ``imports``
|
|
directly because the ``imports`` key and the snippet content are both part of
|
|
the same YAML mapping. List-valued snippets have a problem: YAML only allows
|
|
one root node per document, so a file cannot be both a mapping (for
|
|
``imports``) and a list (for entries) at the same time.
|
|
|
|
The solution is **multi-document YAML**: the first document holds the
|
|
``imports``, and the second document (after ``---``) holds the list content.
|
|
The loader parses both documents, resolves ``$import`` markers in the content,
|
|
and returns the resolved list:
|
|
|
|
.. code-block:: yaml
|
|
|
|
# configs/ptq/units/kv_fp8.yaml — list snippet that imports a dict snippet
|
|
# modelopt-schema: modelopt.torch.quantization.config.QuantizerCfgListConfig
|
|
imports:
|
|
fp8: configs/numerics/fp8
|
|
---
|
|
- quantizer_name: '*[kv]_bmm_quantizer'
|
|
cfg:
|
|
$import: fp8
|
|
|
|
This enables full composability — list snippets can reference dict snippets,
|
|
dict snippets can reference other dict snippets, and recipes can reference
|
|
any of them. All import resolution happens at load time with the same
|
|
precedence rules.
|
|
|
|
Schema modelines
|
|
^^^^^^^^^^^^^^^^^
|
|
|
|
Reusable snippets referenced from an ``imports`` section must declare the
|
|
Pydantic-compatible schema they are expected to satisfy using a
|
|
``modelopt-schema`` comment preamble. The comment is ignored by YAML itself,
|
|
but ModelOpt's loader reads it before parsing and validates the resolved
|
|
snippet payload after any imports have been expanded:
|
|
|
|
.. code-block:: yaml
|
|
|
|
# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig
|
|
num_bits: e2m1
|
|
block_sizes:
|
|
-1: 16
|
|
type: dynamic
|
|
scale_bits: e4m3
|
|
|
|
The schema comment itself is not returned as part of the loaded config. The
|
|
declared schema is the validation contract: after imports are resolved, the
|
|
loader validates the payload against that schema and returns the result --
|
|
a Pydantic model instance for ``BaseModel`` schemas (with defaults populated)
|
|
or a validated ``dict``/``list`` for ``TypedDict`` schemas. The schema can
|
|
also be supplied at the call site via ``load_config(path, schema_type=...)``,
|
|
which takes precedence over an in-file comment when both are present.
|
|
|
|
Top-level recipe files are validated by :func:`~modelopt.recipe.load_recipe`;
|
|
they do not need ``modelopt-schema`` comments. The comments are the contract
|
|
for reusable snippets, especially snippets under ``modelopt_recipes/configs/``:
|
|
every file referenced from an ``imports`` section must declare
|
|
``modelopt-schema``, whether it is imported into a dict value or a list.
|
|
Schemas should be concrete ModelOpt config types, Pydantic models,
|
|
``TypedDict`` classes, or explicitly typed container aliases such as
|
|
``list[QuantizerCfgEntry]``. Untyped list schemas are not supported for list
|
|
imports because the loader must know the element type. For safety,
|
|
``modelopt-schema`` paths must resolve under the ``modelopt.`` package.
|
|
|
|
List imports are schema-driven. When a typed list field such as
|
|
``quant_cfg: list[QuantizerCfgEntry]`` contains a bare import entry, the
|
|
imported snippet must declare its own ``modelopt-schema``:
|
|
|
|
* If the snippet schema matches the containing list type
|
|
(``QuantizerCfgListConfig``, i.e. ``list[QuantizerCfgEntry]``), the imported
|
|
entries are spliced into the containing list.
|
|
* If the snippet schema matches the element type (``QuantizerCfgEntry``), the
|
|
imported entry is appended as a single list item.
|
|
* If the containing list or imported snippet has no schema, or the snippet
|
|
schema is neither the list type nor the element type, loading raises
|
|
``ValueError``.
|
|
|
|
Built-in config snippets
|
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Reusable snippets are stored under ``modelopt_recipes/configs/``:
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 45 55
|
|
|
|
* - Snippet path
|
|
- Description
|
|
* - ``configs/numerics/fp8``
|
|
- FP8 E4M3 quantizer attributes
|
|
* - ``configs/numerics/nvfp4``
|
|
- NVFP4 E2M1 blockwise, dynamic calibration, FP8 scales (default)
|
|
* - ``configs/numerics/nvfp4_static``
|
|
- NVFP4 E2M1 blockwise, static calibration, FP8 scales
|
|
* - ``configs/ptq/units/base_disable_all``
|
|
- Disable all quantizers (deny-all-then-configure pattern)
|
|
* - ``configs/ptq/units/default_disabled_quantizers``
|
|
- Standard exclusions (LM head, routers, BatchNorm, etc.)
|
|
* - ``configs/ptq/units/kv_fp8``
|
|
- FP8 E4M3 KV cache quantization (multi-document, imports ``fp8``)
|
|
* - ``configs/ptq/units/kv_fp8_cast``
|
|
- FP8 E4M3 KV cache with constant amax (skips KV calibration)
|
|
* - ``configs/ptq/units/kv_nvfp4_cast``
|
|
- NVFP4 KV cache with constant amax (skips KV calibration)
|
|
|
|
|
|
Metadata section
|
|
================
|
|
|
|
Every recipe must contain a ``metadata`` mapping with at least a ``recipe_type`` field:
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 20 15 65
|
|
|
|
* - Field
|
|
- Required
|
|
- Description
|
|
* - ``recipe_type``
|
|
- Yes
|
|
- The optimization category. Determines which configuration sections are
|
|
expected (e.g., ``"ptq"`` expects a ``quantize`` section). See
|
|
:class:`~modelopt.recipe.config.RecipeType` for supported values.
|
|
* - ``description``
|
|
- No
|
|
- A human-readable summary of what the recipe does.
|
|
|
|
|
|
Type-specific configuration sections
|
|
=====================================
|
|
|
|
Each recipe type defines its own configuration section. The section name and
|
|
schema depend on the ``recipe_type`` value in the metadata.
|
|
|
|
PTQ (``recipe_type: ptq``)
|
|
--------------------------
|
|
|
|
PTQ recipes contain a ``quantize`` mapping with:
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 20 15 65
|
|
|
|
* - Field
|
|
- Required
|
|
- Description
|
|
* - ``quant_cfg``
|
|
- Yes
|
|
- An ordered list of
|
|
:class:`~modelopt.torch.quantization.config.QuantizerCfgEntry` entries.
|
|
In YAML each entry is authored as a mapping; after loading they are
|
|
validated Pydantic instances. See :ref:`quant-cfg` for the full
|
|
specification of entries, ordering semantics, and atomicity rules.
|
|
* - ``algorithm``
|
|
- No
|
|
- The calibration algorithm: ``"max"`` (default), ``"mse"``, ``"smoothquant"``,
|
|
``"awq_lite"``, ``"awq_full"``, ``"awq_clip"``, ``"gptq"``, or ``null`` for
|
|
formats that need no calibration (e.g. MX formats).
|
|
|
|
|
|
ExMy floating-point notation
|
|
=============================
|
|
|
|
The config loader supports a convenient shorthand for floating-point bit formats.
|
|
This is primarily used in PTQ recipes for ``num_bits`` and ``scale_bits`` fields,
|
|
but applies to any YAML value loaded through :func:`~modelopt.recipe.load_config`.
|
|
Instead of writing a Python tuple, you write the format name directly:
|
|
|
|
.. code-block:: yaml
|
|
|
|
num_bits: e4m3 # automatically converted to (4, 3)
|
|
scale_bits: e8m0 # automatically converted to (8, 0)
|
|
|
|
The notation is case-insensitive (``E4M3``, ``e4m3``, ``E4m3`` all work). The
|
|
conversion is performed by :func:`~modelopt.recipe.load_config` when loading any
|
|
YAML file, so it works in both recipe files and standalone config files.
|
|
|
|
Common formats:
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 15 15 70
|
|
|
|
* - Notation
|
|
- Tuple
|
|
- Description
|
|
* - ``e4m3``
|
|
- ``(4, 3)``
|
|
- FP8 E4M3 -- standard FP8 weight/activation format
|
|
* - ``e5m2``
|
|
- ``(5, 2)``
|
|
- FP8 E5M2 -- wider dynamic range, used for gradients
|
|
* - ``e2m1``
|
|
- ``(2, 1)``
|
|
- FP4 E2M1 -- NVFP4 weight format
|
|
* - ``e8m0``
|
|
- ``(8, 0)``
|
|
- E8M0 -- MX block scaling format
|
|
|
|
|
|
Built-in recipes
|
|
================
|
|
|
|
ModelOpt ships a library of built-in recipes under the ``modelopt_recipes/`` package.
|
|
These are bundled with the Python distribution and can be referenced by their relative
|
|
path (without the ``modelopt_recipes/`` prefix).
|
|
|
|
PTQ recipes
|
|
-----------
|
|
|
|
General PTQ recipes are model-agnostic and apply to any supported architecture:
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 40 60
|
|
|
|
* - Recipe path
|
|
- Description
|
|
* - ``general/ptq/fp8_default-kv_fp8_cast``
|
|
- FP8 per-tensor W8A8, FP8 KV cache with constant amax, max calibration
|
|
* - ``general/ptq/fp8_default-kv_fp8``
|
|
- FP8 per-tensor W8A8, FP8 KV cache with data-driven calibration
|
|
* - ``general/ptq/nvfp4_default-kv_fp8_cast``
|
|
- NVFP4 W4A4, FP8 KV cache with constant amax, max calibration
|
|
* - ``general/ptq/nvfp4_default-kv_fp8``
|
|
- NVFP4 W4A4, FP8 KV cache with data-driven calibration
|
|
* - ``general/ptq/nvfp4_default-kv_nvfp4_cast``
|
|
- NVFP4 W4A4, NVFP4 KV cache with constant amax, max calibration
|
|
* - ``general/ptq/nvfp4_mlp_only-kv_fp8_cast``
|
|
- NVFP4 for MLP layers only, FP8 KV cache with constant amax
|
|
* - ``general/ptq/nvfp4_mlp_only-kv_fp8``
|
|
- NVFP4 for MLP layers only, FP8 KV cache
|
|
* - ``general/ptq/nvfp4_experts_only-kv_fp8_cast``
|
|
- NVFP4 for MoE expert layers only, FP8 KV cache with constant amax
|
|
* - ``general/ptq/nvfp4_experts_only-kv_fp8``
|
|
- NVFP4 for MoE expert layers only, FP8 KV cache
|
|
* - ``general/ptq/nvfp4_experts_only-kv_fp8_layerwise``
|
|
- NVFP4 for MoE expert layers only, FP8 KV cache, layerwise calibration
|
|
* - ``general/ptq/nvfp4_omlp_only-kv_fp8_cast``
|
|
- NVFP4 for output projection + MLP layers, FP8 KV cache with constant amax
|
|
* - ``general/ptq/nvfp4_omlp_only-kv_fp8``
|
|
- NVFP4 for output projection + MLP layers, FP8 KV cache
|
|
* - ``general/ptq/nvfp4_weight_only-kv_fp8_cast``
|
|
- NVFP4 W4A16 weight-only, FP8 KV cache with constant amax
|
|
|
|
Model-specific recipes
|
|
----------------------
|
|
|
|
Model-specific recipes come in two tiers: architecture recipes keyed by a
|
|
Hugging Face ``model_type`` under ``model_type/<model_type>/<task>/``, and
|
|
checkpoint mirrors keyed by a model-hub path under
|
|
``models/<org>/<model_id>/<task>/``. See
|
|
`modelopt_recipes/model_type/README.md <https://github.com/NVIDIA/Model-Optimizer/blob/main/modelopt_recipes/model_type/README.md>`_
|
|
and
|
|
`modelopt_recipes/models/README.md <https://github.com/NVIDIA/Model-Optimizer/blob/main/modelopt_recipes/models/README.md>`_
|
|
for the layout conventions and recipe-lookup order.
|
|
|
|
.. note::
|
|
|
|
``model_type/`` was previously named ``huggingface/``. Old
|
|
``huggingface/<model_type>/...`` recipe paths still resolve for backward
|
|
compatibility, but ``model_type/`` is the canonical location — prefer it in
|
|
new ``--recipe`` flags and ``load_recipe`` calls.
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 40 60
|
|
|
|
* - Recipe path
|
|
- Description
|
|
* - ``models/stepfun-ai/Step-3.5-Flash/ptq/nvfp4-mlp-only``
|
|
- NVFP4 MLP-only for Step 3.5 Flash MoE model
|
|
* - ``model_type/minimax_m3_vl/ptq/mxfp8_nvfp4_experts``
|
|
- MXFP8 language-model base with MSE-calibrated NVFP4 routed experts for MiniMax-M3
|
|
|
|
|
|
Loading recipes
|
|
===============
|
|
|
|
Python API
|
|
----------
|
|
|
|
Use :func:`~modelopt.recipe.load_recipe` to load a recipe. The path is resolved
|
|
against the built-in library first, then the filesystem. The returned object's
|
|
type depends on the ``recipe_type`` in the metadata:
|
|
|
|
.. code-block:: python
|
|
|
|
from modelopt.recipe import load_recipe
|
|
|
|
# Load a built-in recipe by relative path (suffix optional)
|
|
recipe = load_recipe("general/ptq/fp8_default-kv_fp8_cast")
|
|
|
|
# For PTQ recipes, the quantize dict can be passed directly to mtq.quantize()
|
|
import modelopt.torch.quantization as mtq
|
|
|
|
model = mtq.quantize(model, recipe.quantize, forward_loop)
|
|
|
|
.. code-block:: python
|
|
|
|
# Load a custom recipe from the filesystem (file or directory)
|
|
recipe = load_recipe("/path/to/my_custom_ptq.yaml")
|
|
# or: recipe = load_recipe("/path/to/my_recipe_dir/")
|
|
|
|
Command-line usage
|
|
------------------
|
|
|
|
Some example scripts accept a ``--recipe`` flag. For instance, the PTQ example:
|
|
|
|
.. code-block:: bash
|
|
|
|
python examples/hf_ptq/hf_ptq.py \
|
|
--model Qwen/Qwen3-8B \
|
|
--recipe general/ptq/fp8_default-kv_fp8_cast \
|
|
--export_path build/fp8 \
|
|
--calib_size 512 \
|
|
--export_fmt hf
|
|
|
|
When ``--recipe`` is provided, the script loads the recipe and uses its configuration
|
|
directly, bypassing format-specific flags (e.g., ``--qformat`` / ``--kv_cache_qformat``
|
|
for PTQ).
|
|
|
|
|
|
Loading standalone configs
|
|
--------------------------
|
|
|
|
:func:`~modelopt.recipe.load_config` loads arbitrary YAML config files with
|
|
automatic ``ExMy`` conversion and built-in path resolution. This is useful
|
|
for loading shared configuration fragments:
|
|
|
|
.. code-block:: python
|
|
|
|
from modelopt.recipe import load_config
|
|
|
|
cfg = load_config("configs/some_shared_config")
|
|
|
|
|
|
Path resolution
|
|
===============
|
|
|
|
Both :func:`~modelopt.recipe.load_recipe` and :func:`~modelopt.recipe.load_config`
|
|
resolve paths using the same strategy:
|
|
|
|
1. If the path is absolute, use it directly.
|
|
2. If relative, check the **built-in recipes library** first
|
|
(``modelopt_recipes/``), probing ``.yml`` and ``.yaml`` suffixes as well as
|
|
directories.
|
|
3. Then check the **filesystem**, probing the same suffixes and directories.
|
|
|
|
This means built-in recipes can be referenced without any prefix:
|
|
|
|
.. code-block:: python
|
|
|
|
# These are all equivalent:
|
|
load_recipe("general/ptq/fp8_default-kv_fp8_cast")
|
|
load_recipe("general/ptq/fp8_default-kv_fp8_cast.yaml")
|
|
|
|
|
|
Writing a custom recipe
|
|
=======================
|
|
|
|
To create a custom recipe:
|
|
|
|
1. Start from an existing recipe that is close to your target configuration.
|
|
2. Copy it and modify the type-specific configuration as needed (for PTQ recipes,
|
|
see :ref:`quant-cfg` for ``quant_cfg`` entry format details).
|
|
3. Update the ``metadata.description`` to describe your changes.
|
|
4. Save the file (or directory) and pass its path to ``load_recipe()`` or ``--recipe``.
|
|
|
|
Example -- creating a custom PTQ recipe using imports:
|
|
|
|
.. code-block:: yaml
|
|
|
|
# my_int8_ptq.yaml
|
|
imports:
|
|
base_disable_all: configs/ptq/units/base_disable_all
|
|
default_disabled: configs/ptq/units/default_disabled_quantizers
|
|
|
|
metadata:
|
|
recipe_type: ptq
|
|
description: INT8 per-channel weight, per-tensor activation.
|
|
|
|
quantize:
|
|
algorithm: max
|
|
quant_cfg:
|
|
- $import: base_disable_all
|
|
- quantizer_name: '*weight_quantizer'
|
|
cfg:
|
|
num_bits: 8
|
|
axis: 0
|
|
- quantizer_name: '*input_quantizer'
|
|
cfg:
|
|
num_bits: 8
|
|
axis:
|
|
- $import: default_disabled
|
|
|
|
The built-in snippets (``base_disable_all``, ``default_disabled``) handle the
|
|
deny-all prefix and standard exclusions. Only the format-specific entries need
|
|
to be written inline.
|
|
|
|
|
|
Recipe repository layout
|
|
========================
|
|
|
|
The ``modelopt_recipes/`` package is organized as follows:
|
|
|
|
.. code-block:: text
|
|
|
|
modelopt_recipes/
|
|
+-- __init__.py
|
|
+-- general/ # Model-agnostic recipes
|
|
| +-- ptq/
|
|
| +-- fp8_default-kv_fp8_cast.yaml
|
|
| +-- fp8_default-kv_fp8.yaml
|
|
| +-- nvfp4_default-kv_fp8_cast.yaml
|
|
| +-- nvfp4_default-kv_fp8.yaml
|
|
| +-- nvfp4_default-kv_nvfp4_cast.yaml
|
|
| +-- nvfp4_mlp_only-kv_fp8_cast.yaml
|
|
| +-- nvfp4_mlp_only-kv_fp8.yaml
|
|
| +-- nvfp4_experts_only-kv_fp8_cast.yaml
|
|
| +-- nvfp4_experts_only-kv_fp8.yaml
|
|
| +-- nvfp4_experts_only-kv_fp8_layerwise.yaml
|
|
| +-- nvfp4_omlp_only-kv_fp8_cast.yaml
|
|
| +-- nvfp4_omlp_only-kv_fp8.yaml
|
|
| +-- nvfp4_weight_only-kv_fp8_cast.yaml
|
|
+-- model_type/ # Architecture-specific recipes (by model_type)
|
|
| +-- <model_type>/ # see modelopt_recipes/model_type/README.md
|
|
| +-- <task>/
|
|
| +-- <recipe>.yaml
|
|
+-- models/ # Checkpoint-specific recipes (by model-hub path)
|
|
| +-- <org>/<model_id>/ # see modelopt_recipes/models/README.md
|
|
| +-- <task>/
|
|
| +-- <recipe>.yaml
|
|
+-- configs/ # Reusable config snippets (imported via $import)
|
|
+-- numerics/ # Numeric format definitions
|
|
| +-- fp8.yaml
|
|
| +-- nvfp4_static.yaml
|
|
| +-- nvfp4.yaml
|
|
+-- ptq/
|
|
+-- units/ # Reusable quant_cfg building blocks
|
|
| +-- base_disable_all.yaml
|
|
| +-- default_disabled_quantizers.yaml
|
|
| +-- kv_fp8.yaml
|
|
| +-- kv_fp8_cast.yaml
|
|
| +-- kv_nvfp4_cast.yaml
|
|
| +-- w8a8_fp8_fp8.yaml
|
|
| +-- w4a4_nvfp4_nvfp4.yaml
|
|
+-- presets/ # Complete configs (backward compat with *_CFG dicts)
|
|
+-- model/
|
|
| +-- fp8.yaml
|
|
+-- kv/
|
|
+-- fp8.yaml
|
|
|
|
|
|
Recipe data model
|
|
=================
|
|
|
|
Recipes are validated at load time using Pydantic models:
|
|
|
|
:class:`~modelopt.recipe.config.ModelOptRecipeBase`
|
|
Base class for all recipe types. Contains a required ``metadata`` field
|
|
typed as :class:`~modelopt.recipe.config.RecipeMetadataConfig` -- a
|
|
:class:`~modelopt.torch.opt.config.ModeloptBaseConfig` subclass exposing
|
|
``recipe_type`` and ``description`` as Pydantic fields.
|
|
|
|
:class:`~modelopt.recipe.config.ModelOptPTQRecipe`
|
|
PTQ-specific recipe. Adds a required ``quantize`` field typed as
|
|
:class:`~modelopt.torch.quantization.config.QuantizeConfig` (also a
|
|
``ModeloptBaseConfig`` subclass, containing ``quant_cfg`` and ``algorithm``).
|
|
|
|
:class:`~modelopt.recipe.config.RecipeType`
|
|
Enum of supported recipe types.
|
|
|
|
|
|
Future directions
|
|
=================
|
|
|
|
The recipe system is designed to grow:
|
|
|
|
* **QAT recipes** -- ``recipe_type: qat`` with training hyperparameters, distillation
|
|
settings, and dataset configuration.
|
|
* **Sparsity recipes** -- structured and unstructured pruning configurations.
|
|
* **Speculative decoding recipes** -- draft model and vocabulary calibration settings.
|
|
* **Composite recipes** -- chaining multiple optimization stages
|
|
(e.g., quantize then prune) in a single recipe.
|
|
* **Dataset configuration** -- standardized ``dataset`` section for calibration data
|
|
specification.
|
|
* **Recipe merging and override utilities** -- programmatic tools to compose and
|
|
customize recipes.
|
|
* **Unified entry point** -- a ``nv-modelopt`` CLI that accepts ``--recipe`` as the
|
|
primary configuration mechanism, replacing per-example scripts.
|