Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Changelog

**Deprecations**

- Rename the architecture-specific recipe tier from ``modelopt_recipes/huggingface/`` to ``modelopt_recipes/model_type/`` to clarify that it holds recipes shared across every checkpoint of a Hugging Face ``model_type``. Saved ``--recipe huggingface/<model_type>/...`` paths still resolve via a backward-compatibility alias, but update them to ``model_type/<model_type>/...`` as the ``huggingface/`` prefix is deprecated.

**Bug Fixes**

- Fix ``megatron_generate`` dropping the VLM vision inputs (``pixel_values`` / ``image_grid_thw`` / ``image_sizes``) after the first generated token when KV-cache decoding is off, including the automatic fallback under sequence parallelism, which made generation silently ignore the image. No other ModelOpt feature is affected.
Expand Down
11 changes: 9 additions & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
exclude modelopt_recipes/huggingface/models
prune modelopt_recipes/huggingface/models
# Backward-compat symlinks in the recipe library (huggingface -> model_type and the
# nested model_type/models -> ../models). Walking them would ship the recipe library
# multiple times; the sdist ships each recipe once from its real path. Old
# huggingface/... --recipe paths keep working via the loader alias in
# modelopt/recipe/loader.py.
exclude modelopt_recipes/huggingface
prune modelopt_recipes/huggingface
exclude modelopt_recipes/model_type/models
prune modelopt_recipes/model_type/models
17 changes: 12 additions & 5 deletions docs/source/guides/10_recipes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -520,14 +520,21 @@ Model-specific recipes
----------------------

Model-specific recipes come in two tiers: architecture recipes keyed by a
Hugging Face ``model_type`` under ``huggingface/<model_type>/<task>/``, and
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/huggingface/README.md <https://github.com/NVIDIA/Model-Optimizer/blob/main/modelopt_recipes/huggingface/README.md>`_
`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
Expand All @@ -536,7 +543,7 @@ for the layout conventions and recipe-lookup order.
- Description
* - ``models/stepfun-ai/Step-3.5-Flash/ptq/nvfp4-mlp-only``
- NVFP4 MLP-only for Step 3.5 Flash MoE model
* - ``huggingface/minimax_m3_vl/ptq/mxfp8_nvfp4_experts``
* - ``model_type/minimax_m3_vl/ptq/mxfp8_nvfp4_experts``
- MXFP8 language-model base with MSE-calibrated NVFP4 routed experts for MiniMax-M3


Expand Down Expand Up @@ -689,8 +696,8 @@ The ``modelopt_recipes/`` package is organized as follows:
| +-- nvfp4_omlp_only-kv_fp8_cast.yaml
| +-- nvfp4_omlp_only-kv_fp8.yaml
| +-- nvfp4_weight_only-kv_fp8_cast.yaml
+-- huggingface/ # Architecture-specific recipes (by model_type)
| +-- <model_type>/ # see modelopt_recipes/huggingface/README.md
+-- 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)
Expand Down
10 changes: 5 additions & 5 deletions examples/hf_ptq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ python hf_ptq.py \
--export_path <quantized_ckpt_path>
```

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.
Built-in recipes are located in `modelopt_recipes/general/ptq/` for model-agnostic recipes and in `modelopt_recipes/model_type/<model_type>/ptq/` for recipes tuned to a specific Hugging Face `model_type` (see [`modelopt_recipes/model_type/README.md`](../../modelopt_recipes/model_type/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` is ignored. KV cache handling depends on the recipe type: a **PTQ** recipe bakes KV cache into its config and ignores `--kv_cache_qformat`; an **AutoQuantize** recipe falls back to `--kv_cache_qformat` unless it sets an explicit `kv_cache` field.*

Expand Down Expand Up @@ -287,7 +287,7 @@ Use the recipe directory matching the checkpoint's `model_type`: `qwen3_vl` or `
# Vision encoder only: FP8 vision Linears and merger, BF16 LLM and KV cache.
python hf_ptq.py \
--pyt_ckpt_path <Qwen3-VL-or-Qwen3.5-checkpoint> \
--recipe huggingface/qwen3_vl/ptq/fp8_vision-kv_none \
--recipe model_type/qwen3_vl/ptq/fp8_vision-kv_none \
--calib_with_images \
--calib_size 512 \
--skip_generate \
Expand All @@ -296,7 +296,7 @@ python hf_ptq.py \
# Joint vision encoder + language model FP8 with FP8 KV-cache cast.
python hf_ptq.py \
--pyt_ckpt_path <Qwen3-VL-or-Qwen3.5-checkpoint> \
--recipe huggingface/qwen3_vl/ptq/fp8_vision_lm-kv_fp8_cast \
--recipe model_type/qwen3_vl/ptq/fp8_vision_lm-kv_fp8_cast \
--calib_with_images \
--calib_size 512 \
--skip_generate \
Expand Down Expand Up @@ -410,7 +410,7 @@ search-disabled layers, and cost-excluded layers — see
[`AutoQuantizeConfig`](../../modelopt/recipe/config.py). Shipped recipes live in
[`modelopt_recipes/general/auto_quantize/`](../../modelopt_recipes/general/auto_quantize); model-specific
recipes (carrying architecture-specific disabled layers — e.g. VL vision towers) live under
`modelopt_recipes/huggingface/<model>/auto_quantize/`.
`modelopt_recipes/model_type/<model>/auto_quantize/`.

[Script](./scripts/huggingface_example.sh)

Expand Down Expand Up @@ -469,7 +469,7 @@ not actually searched.

The fixed baseline may also reuse a model-specific PTQ configuration. For example, the Qwen3.6 MoE
AutoQuantize recipe imports the same model-specific `quant_cfg` used by
`huggingface/qwen3_5_moe/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast`, reproduces that recipe's `quantize`
`model_type/qwen3_5_moe/ptq/w4a16_nvfp4-fp8_attn-kv_fp8_cast`, reproduces that recipe's `quantize`
section, and lists only shared experts, attention, and `lm_head` under `module_search_spaces`. A
loader test asserts that the inherited fixed baseline remains equal to the original PTQ recipe while
leaving the original recipe unchanged.
Expand Down
2 changes: 1 addition & 1 deletion examples/minimax_m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ so it never loads either complete model.
python examples/minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py \
--mxfp8_ckpt /models/minimax-m3-mxfp8 \
--bf16_ckpt /models/minimax-m3-bf16 \
--recipe huggingface/minimax_m3_vl/ptq/nvfp4_experts_only \
--recipe model_type/minimax_m3_vl/ptq/nvfp4_experts_only \
--output_ckpt /models/minimax-m3-mxfp8-nvfp4 \
--device cuda
```
Expand Down
2 changes: 1 addition & 1 deletion examples/minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
python hf_ptq_mixed_mxfp8_nvfp4.py \\
--mxfp8_ckpt /models/minimax-m3-mxfp8 \\
--bf16_ckpt /models/minimax-m3-bf16 \\
--recipe huggingface/minimax_m3_vl/ptq/nvfp4_experts_only \\
--recipe model_type/minimax_m3_vl/ptq/nvfp4_experts_only \\
--output_ckpt /workspace/quant/minimax-m3-mxfp8-nvfp4-mixed \\
--device cuda
"""
Expand Down
4 changes: 2 additions & 2 deletions examples/torch_onnx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ mean pooling and L2 normalization on top of the encoder; reranking
graphs take `input_ids` and `attention_mask` with dynamic batch/sequence axes.

The default recipe
(`modelopt_recipes/huggingface/nemotron_llama/ptq/nvfp4_output_quant_proj.yaml`)
(`modelopt_recipes/model_type/nemotron_llama/ptq/nvfp4_output_quant_proj.yaml`)
quantizes weights and activations to NVFP4 and additionally quantizes the
projection-Linear outputs. Without output-side quantization, quantized GEMMs
emit FP16 activations, so FP8/FP4 engines can use as much or more activation
Expand All @@ -134,7 +134,7 @@ engines, 5 dynamic-shape profiles up to 32x512), engine activation memory:
python hf_embedding_quant_to_onnx.py \
--model_path=nvidia/llama-nemotron-embed-1b-v2 \
--trust_remote_code \
--recipe=huggingface/nemotron_llama/ptq/nvfp4_output_quant_proj \
--recipe=model_type/nemotron_llama/ptq/nvfp4_output_quant_proj \
--onnx_save_path=llama_nemotron_embed_nvfp4.onnx

# Reranking variant (auto-detected from the model architecture)
Expand Down
2 changes: 1 addition & 1 deletion examples/torch_onnx/hf_embedding_quant_to_onnx.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"register_bidirectional_sdpa",
]

DEFAULT_RECIPE = "huggingface/nemotron_llama/ptq/nvfp4_output_quant_proj"
DEFAULT_RECIPE = "model_type/nemotron_llama/ptq/nvfp4_output_quant_proj"

# TODO: Add an accuracy evaluation pipeline for the embedding and reranking models.
CALIBRATION_TEXTS = [
Expand Down
12 changes: 6 additions & 6 deletions examples/torch_trt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ from modelopt.recipe import load_recipe
from modelopt.torch.quantization.utils import export_torch_mode

# 1. Quantize the eager PyTorch model with a Model Optimizer PTQ recipe.
recipe = load_recipe("huggingface/vit/ptq/fp8")
recipe = load_recipe("model_type/vit/ptq/fp8")
mtq.quantize(model, recipe.quantize.model_dump(), forward_loop=calibrate)

# 2. Compile the quantized (Q/DQ) graph with Torch-TensorRT.
Expand Down Expand Up @@ -138,7 +138,7 @@ This is the recipe the CLI selects by default when `--model_id` points at a HF V

| `--recipe` value | Calibration | What it quantizes |
| :---: | :---: | :--- |
| `huggingface/vit/ptq/fp8` (default) | `max` | Per-tensor FP8 (E4M3) on every weight + input quantizer matched by the `*weight_quantizer` / `*input_quantizer` globs — encoder Linears, the patch-embed `nn.Conv2d` projection, and the `classifier` head — plus FP8 on the attention Q/K/V BMMs and softmax. All output quantizers disabled. |
| `model_type/vit/ptq/fp8` (default) | `max` | Per-tensor FP8 (E4M3) on every weight + input quantizer matched by the `*weight_quantizer` / `*input_quantizer` globs — encoder Linears, the patch-embed `nn.Conv2d` projection, and the `classifier` head — plus FP8 on the attention Q/K/V BMMs and softmax. All output quantizers disabled. |

</div>

Expand All @@ -153,7 +153,7 @@ This is the recipe the CLI selects by default when `--model_id` points at a HF V
| Flag | Default | Description |
| :---: | :---: | :--- |
| `--model_id` | `google/vit-large-patch16-224` | HuggingFace model id of the ViT classifier to quantize. |
| `--recipe` | `huggingface/vit/ptq/fp8` | Recipe path (relative to `modelopt_recipes/` or an absolute YAML). |
| `--recipe` | `model_type/vit/ptq/fp8` | Recipe path (relative to `modelopt_recipes/` or an absolute YAML). |
| `--calib_samples` | `1024` | Number of tiny-imagenet samples to use for calibration. |
| `--batch_size` | `128` | Batch size for calibration / TRT compile. |
| `--save_dir` | `./modelopt_quantized` | Directory the quantized Model Optimizer state-dict (FP16 weights + Q/DQ metadata) is always saved to, as `vit_modelopt_state.pt` — re-usable across runs without recalibration. |
Expand Down Expand Up @@ -182,7 +182,7 @@ python torch_tensorrt_ptq.py --layer_info_path ./vit_fp8_layers.txt
| Flag | Default | Description |
| :---: | :---: | :--- |
| `--model_id` | `google/vit-large-patch16-224` | HuggingFace model id of the ViT classifier to quantize and score. |
| `--recipe` | `huggingface/vit/ptq/fp8` | Recipe path (relative to `modelopt_recipes/` or an absolute YAML). |
| `--recipe` | `model_type/vit/ptq/fp8` | Recipe path (relative to `modelopt_recipes/` or an absolute YAML). |
| `--calib_samples` | `1024` | Number of tiny-imagenet samples to use for calibration. |
| `--batch_size` | `128` | Calibration / compile / eval batch size. The Torch-TRT engine is dynamic (`min=1`, `opt=max(--batch_size, 2)`, `max=1024`) and handles any batch including the trailing partial batch. |
| `--eval_data_size` | full 50k | Number of ImageNet validation images to score. |
Expand All @@ -199,7 +199,7 @@ python torch_tensorrt_ptq.py --layer_info_path ./vit_fp8_layers.txt

```bash
python torch_tensorrt_accuracy.py \
--recipe huggingface/vit/ptq/fp8 \
--recipe model_type/vit/ptq/fp8 \
--batch_size 128 \
--baseline \
--eval_data_size 5000 \
Expand All @@ -215,7 +215,7 @@ python torch_tensorrt_accuracy.py \

## Custom Recipes

Use `--recipe <path>` to plug in a different recipe — either a path relative to `modelopt_recipes/` (resolved against the built-in recipe library) or an absolute filesystem path to a YAML file. The recipe is loaded via `modelopt.recipe.load_recipe`, must declare `metadata.recipe_type: ptq` and a `quantize:` section, and its `quantize` config is passed straight to `mtq.quantize`. See the existing [`modelopt_recipes/huggingface/vit/ptq/*.yaml`](../../modelopt_recipes/huggingface/vit/ptq/) for the patterns used here.
Use `--recipe <path>` to plug in a different recipe — either a path relative to `modelopt_recipes/` (resolved against the built-in recipe library) or an absolute filesystem path to a YAML file. The recipe is loaded via `modelopt.recipe.load_recipe`, must declare `metadata.recipe_type: ptq` and a `quantize:` section, and its `quantize` config is passed straight to `mtq.quantize`. See the existing [`modelopt_recipes/model_type/vit/ptq/*.yaml`](../../modelopt_recipes/model_type/vit/ptq/) for the patterns used here.

### Resuming From a Saved Checkpoint

Expand Down
6 changes: 3 additions & 3 deletions examples/torch_trt/torch_tensorrt_ptq.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
2. Build a calibration loader from `zh-plus/tiny-imagenet` so the recipe runs
end-to-end without ImageNet access.
3. Run ``mtq.quantize`` with the ViT-specific FP8 recipe under
`modelopt_recipes/huggingface/vit/ptq/`.
`modelopt_recipes/model_type/vit/ptq/`.
4. Compile the quantized model with ``torch_tensorrt.compile(ir="dynamo",
min_block_size=1)`` and verify the compiled-model argmax matches the
fake-quant argmax on a sample input.
Expand All @@ -44,10 +44,10 @@
from modelopt.recipe import ModelOptPTQRecipe, load_recipe
from modelopt.torch.quantization.utils import export_torch_mode

# Default ViT PTQ recipe under `modelopt_recipes/huggingface/vit/ptq/`. The
# Default ViT PTQ recipe under `modelopt_recipes/model_type/vit/ptq/`. The
# recipe loader resolves this relative path against the built-in recipe library;
# pass `--recipe` for a different one.
DEFAULT_RECIPE = "huggingface/vit/ptq/fp8"
DEFAULT_RECIPE = "model_type/vit/ptq/fp8"


def load_model_and_processor(model_id: str, device: torch.device, dtype: torch.dtype):
Expand Down
38 changes: 21 additions & 17 deletions modelopt/recipe/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from omegaconf import OmegaConf

from modelopt.torch.opt.config_loader import BUILTIN_CONFIG_ROOT as BUILTIN_RECIPES_LIB
from modelopt.torch.opt.config_loader import load_config
from modelopt.torch.opt.config_loader import _alias_builtin_recipe_prefix, load_config
from modelopt.torch.quantization.config import QuantizeConfig

from .config import (
Expand Down Expand Up @@ -58,24 +58,28 @@ def _resolve_recipe_path(recipe_path: str | Path | Traversable) -> Path | Traver
isinstance(recipe_path, Path) and recipe_path.is_absolute()
):
rp_str = str(recipe_path)
# Backward-compat alias: checkpoint-mirror recipes moved from the old
# ``huggingface/models/<org>/<model_id>/`` layout to the top-level ``models/``
# tier. A source checkout also keeps a ``huggingface/models`` -> ``../models``
# symlink, but symlinks don't survive into built wheels, so rewrite the old
# prefix here too — that keeps saved ``--recipe huggingface/models/...`` paths
# working for pip-installed users, not just source checkouts.
_bc_prefix = "huggingface/models/"
if rp_str.replace("\\", "/").startswith(_bc_prefix):
rp_str = "models/" + rp_str.replace("\\", "/")[len(_bc_prefix) :]
suffixes = [""] if rp_str.endswith((".yml", ".yaml")) else ["", ".yml", ".yaml"]
for suffix in suffixes:
candidate = BUILTIN_RECIPES_LIB.joinpath(rp_str + suffix)
# Backward-compat aliases for the recipe-library restructure. A source checkout keeps
# ``huggingface`` -> ``model_type`` (and the nested ``model_type/models`` -> ``../models``)
# symlinks, but symlinks don't survive into built wheels, so the deprecated tier
# prefixes are rewritten (see ``_alias_builtin_recipe_prefix``) before the built-in
# lookup — that keeps saved ``--recipe huggingface/...`` paths working for pip-installed
# users, not just source checkouts. The rewrite is applied to built-in candidates only;
# the filesystem fallback below probes the path exactly as given first, so a user's own
# local ``huggingface/`` recipe tree still loads by its natural relative name.
aliased = _alias_builtin_recipe_prefix(rp_str)

def _suffixes(s: str) -> list[str]:
return [""] if s.endswith((".yml", ".yaml")) else ["", ".yml", ".yaml"]

for suffix in _suffixes(aliased):
candidate = BUILTIN_RECIPES_LIB.joinpath(aliased + suffix)
if candidate.is_file() or candidate.is_dir():
return candidate
for suffix in suffixes:
fs_candidate = Path(rp_str + suffix)
if fs_candidate.is_file() or fs_candidate.is_dir():
return fs_candidate
for probe in dict.fromkeys((rp_str, aliased)):
for suffix in _suffixes(probe):
fs_candidate = Path(probe + suffix)
if fs_candidate.is_file() or fs_candidate.is_dir():
return fs_candidate
return Path(rp_str)
return recipe_path

Expand Down
13 changes: 8 additions & 5 deletions modelopt/torch/export/quant_aware_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,14 @@ def _scope_prefixes(rev) -> tuple[str, ...]:
"""Candidate key prefixes a scoped sub-model transform may apply under.

transformers tags a conversion collected from a sub-model with ``scope_prefix`` (the
sub-module path) and ``base_model_prefix``, then matches keys against
``base_model_prefix.scope_prefix.`` first and ``scope_prefix.`` second (see
``WeightTransform._scoped_match``). Returned in that same priority order, each with a
trailing dot. Empty tuple when the transform is unscoped (owned by the root model),
in which case its patterns already address the full key space.
sub-module path). Older versions also tagged a ``base_model_prefix`` and matched keys
against ``base_model_prefix.scope_prefix.`` first and ``scope_prefix.`` second;
transformers>=5.9 dropped ``base_model_prefix`` and ``WeightTransform._scoped_match``
now keys off ``scope_prefix`` alone. The ``getattr`` fallback below covers both: an
absent ``base_model_prefix`` collapses to just the ``scope_prefix.`` candidate.
Returned in priority order, each with a trailing dot. Empty tuple when the transform is
unscoped (owned by the root model), in which case its patterns already address the full
key space.
"""
scope = getattr(rev, "scope_prefix", None)
if scope is None:
Expand Down
Loading
Loading