Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ Changelog
**Backward Breaking Changes**

- Move the Mistral Medium 3.5 checkpoint-mirror recipe from ``huggingface/models/nvidia/Mistral-Medium-3.5-128B-NVFP4/ptq/nvfp4-max-calib`` to ``huggingface/models/mistralai/Mistral-Medium-3.5-128B/ptq/nvfp4-max-calib``, keying it by the canonical Hugging Face base model. Update any saved ``--recipe`` paths to the new location.
- Remove the ``--auto_quantize_bits``, ``--auto_quantize_method``, ``--auto_quantize_score_size``, ``--auto_quantize_cost_model`` and ``--auto_quantize_active_moe_expert_ratio`` flags from ``examples/hf_ptq`` (deprecated in 0.46). Use an AutoQuantize ``--recipe`` from ``modelopt_recipes/general/auto_quantize/`` instead. Those recipes now also splice in the shared base ``cost_excluded_layers`` unit, which the removed CLI applied unconditionally, so a VL model keeps its vision tower and MTP layers out of the effective-bits denominator.
- Remove the ``examples/llm_ptq`` symlink and the ``examples/vlm_ptq`` forwarder (both deprecated in 0.46). Use ``examples/hf_ptq``, passing ``--vlm`` for vision-language models.
- Remove the backward-compat ``--qformat`` / ``--quant_cfg`` short names ``int8_sq``, ``int8_wo``, ``w4a8_awq``, ``nvfp4_awq``, ``nvfp4_mse``, ``nvfp4_local_hessian``, ``fp8_pb_wo`` and ``fp8_pc_pt`` (deprecated in 0.45). Use the preset basename under ``modelopt_recipes/configs/ptq/presets/model/`` instead: ``int8_smoothquant``, ``int8_weight_only``, ``w4a8_awq_beta``, ``nvfp4_awq_lite``, ``nvfp4_w4a4_weight_mse_fp8_sweep``, ``nvfp4_w4a4_weight_local_hessian``, ``fp8_2d_blockwise_weight_only`` and ``fp8_per_channel_per_token``. The ``modelopt.recipe.presets.QFORMAT_ALIASES`` table and the ``aliases`` argument of ``load_quant_cfg_choices()`` are removed along with them.
- Remove the legacy ``layerwise`` bool form, its ``use_sequential`` alias, and the top-level ``layerwise_checkpoint_dir`` key from calibration algorithm configs (deprecated in 0.45). Use the nested form, e.g. ``layerwise: {enable: true, checkpoint_dir: /path}``. A pre-0.45 ``modelopt_state`` carrying either legacy key now fails validation on restore instead of being migrated; re-save it with a 0.45/0.46 release first.
- Remove in-trainer quantization via ``QuantizationArguments.quant_cfg`` / ``--quant_cfg`` (deprecated in 0.45); use ``--recipe``. New recipes ``general/ptq/mxfp4_mlp_weight_only`` and ``general/ptq/nvfp4_mlp_weight_only`` replace ``MXFP4_MLP_WEIGHT_ONLY_CFG`` / ``NVFP4_MLP_WEIGHT_ONLY_CFG`` in the ``examples/gpt-oss`` QAT flow.
- Remove the ``QuantizationArgumentsWithConfig`` alias in ``modelopt.torch.quantization.plugins.transformers_trainer`` (deprecated in 0.45). Use ``QuantizationArguments``.
- Transformer Engine ``TEGroupedMLP`` (fused MoE experts) now uses **per-expert** weight quantization (one ``amax`` per expert) instead of a single shared ``amax``, so ModelOpt checkpoints containing quantized ``TEGroupedMLP`` modules saved before 0.47 are **not compatible** with 0.47. Re-run PTQ to regenerate compatible checkpoints.

**Deprecations**
Expand Down
9 changes: 5 additions & 4 deletions examples/gpt-oss/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ If you are training Huggingface models with trainer classes from Huggingface suc
A real end-to-end example for this is in `sft.py` in this folder. To perform QAT with full parameter SFT on GPT-OSS 20B model, run:

```sh
# Other supported quantization configs include NVFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_ONLY_CFG etc.
# Other supported quantization recipes include general/ptq/nvfp4_mlp_weight_only, or
# general/ptq/nvfp4_mlp_only-kv_fp8 (also quantizes activations and the KV cache to FP8, which needs calibration).
# [Optional] For faster FlashAttention3, add '--attn_implementation kernels-community/vllm-flash-attn3'
accelerate launch --config_file configs/zero3.yaml sft.py \
--config configs/sft_full.yaml --model_name_or_path openai/gpt-oss-20b \
--quant_cfg MXFP4_MLP_WEIGHT_ONLY_CFG \
--recipe general/ptq/mxfp4_mlp_weight_only \
--output_dir gpt-oss-20b-qat
```

Expand Down Expand Up @@ -89,7 +90,7 @@ accelerate launch --config_file configs/zero3.yaml sft.py \
# Step 2: Perform QAT on the high precision SFT checkpoint
accelerate launch --config_file configs/zero3.yaml sft.py \
--config configs/sft_full.yaml --model_name_or_path gpt-oss-20b-sft \
--quant_cfg MXFP4_MLP_WEIGHT_ONLY_CFG \
--recipe general/ptq/mxfp4_mlp_weight_only \
--output_dir gpt-oss-20b-qat \
```

Expand Down Expand Up @@ -160,7 +161,7 @@ Here is how to run LoRA QAT for GPT OSS 120B model:
```bash
python sft.py --config configs/sft_lora.yaml \
--model_name_or_path openai/gpt-oss-120b \
--quant_cfg MXFP4_MLP_WEIGHT_ONLY_CFG \
--recipe general/ptq/mxfp4_mlp_weight_only \
--output_dir gpt-oss-120b-lora-qat
```

Expand Down
4 changes: 2 additions & 2 deletions examples/gpt-oss/configs/sft_full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ eval_steps: 8
dataset_test_split: test

# ModelOpt Quantization Parameters
quant_cfg: # Examples: MXFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_ONLY_CFG
# For the full list of supported configs, do: mtq.config.choices
recipe: # Examples: general/ptq/mxfp4_mlp_weight_only, general/ptq/nvfp4_mlp_weight_only
# For the full list of built-in recipes, see modelopt_recipes/general/ptq/
calib_size: 128
4 changes: 2 additions & 2 deletions examples/gpt-oss/configs/sft_lora.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ eval_steps: 8
dataset_test_split: test

# ModelOpt Quantization Parameters
quant_cfg: # Examples: MXFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_ONLY_CFG
# For the full list of supported configs, do: mtq.config.choices
recipe: # Examples: general/ptq/mxfp4_mlp_weight_only, general/ptq/nvfp4_mlp_weight_only
# For the full list of built-in recipes, see modelopt_recipes/general/ptq/
calib_size: 128
2 changes: 1 addition & 1 deletion examples/gpt-oss/sft.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
--packing true packing_strategy wrapped \
--run_name 20b-full-qat \
--attn_implementation kernels-community/vllm-flash-attn3
--quant_cfg MXFP4_MLP_WEIGHT_ONLY_CFG
--recipe general/ptq/mxfp4_mlp_weight_only
"""

from transformers import AutoModelForCausalLM, AutoTokenizer, Mxfp4Config
Expand Down
25 changes: 6 additions & 19 deletions examples/hf_ptq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Please reference our [framework scripts](#framework-scripts) and our [docs](http

### Hugging Face Supported Models

| Model | fp8 | int8_sq | int4_awq | w4a8_awq<sup>1</sup> | nvfp4<sup>5</sup> |
| Model | fp8 | int8_smoothquant | int4_awq | w4a8_awq_beta<sup>1</sup> | nvfp4<sup>5</sup> |
| :---: | :---: | :---: | :---: | :---: | :---: |
| LLAMA 3.x | ✅ | ❌ | ✅ | ✅<sup>3</sup> | ✅ |
| LLAMA 4 <sup>6</sup> | ✅ | ❌ | ❌ | ❌ | ✅ |
Expand Down Expand Up @@ -125,7 +125,7 @@ Please reference our [framework scripts](#framework-scripts) and our [docs](http

> *This is a subset of the models supported. For the full list please check the [TensorRT-LLM support matrix](https://nvidia.github.io/TensorRT-LLM/reference/precision.html#support-matrix)*

> *<sup>1.</sup>The w4a8_awq is an experimental quantization scheme that may result in a higher accuracy penalty.* \
> *<sup>1.</sup>The w4a8_awq_beta is an experimental quantization scheme that may result in a higher accuracy penalty.* \
> *<sup>2.</sup>For some models, there is only support for exporting quantized checkpoints.* \
> *<sup>3.</sup>W4A8_AWQ is only available on some models but not all* \
> *<sup>4.</sup>For some models, KV cache quantization may result in a higher accuracy penalty.* \
Expand All @@ -136,7 +136,7 @@ Please reference our [framework scripts](#framework-scripts) and our [docs](http
> *<sup>9.</sup>Running Whisper model with transformers>=5.0 requires [torchcodec](https://github.com/meta-pytorch/torchcodec?tab=readme-ov-file#installing-cuda-enabled-torchcodec) and other system packages (e.g. ffmpeg).* \
> *<sup>10.</sup>GPT-OSS ships with native MXFP4 weights; NVFP4 export is produced via the closed-form `--cast_mxfp4_to_nvfp4` cast (see [MXFP4 → NVFP4 cast](#mxfp4--nvfp4-cast-for-gpt-oss)).* \
> *<sup>11.</sup>Vision-language model (VLM): only the language model is quantized while the vision encoder is kept in high precision. Pass `--vlm` to the shell script (see [VLM quantization](#vlm-quantization)).* \
> *<sup>12.</sup>For VLMs, `int8_sq` only supports TensorRT-LLM checkpoint export and is not compatible with the TensorRT-LLM torch backend.* \
> *<sup>12.</sup>For VLMs, `int8_smoothquant` only supports TensorRT-LLM checkpoint export and is not compatible with the TensorRT-LLM torch backend.* \
> *<sup>13.</sup>Nemotron VL automatically calibrates with image-text pairs; see [VLM calibration with image-text pairs](#vlm-calibration-with-image-text-pairs-eg-nemotron-vl).*

> *The accuracy loss after PTQ may vary depending on the actual model and the quantization method. Different models may have different accuracy loss and usually the accuracy loss is more significant when the base model is small. If the accuracy after PTQ is not meeting the requirement, please try either modifying [hf_ptq.py](./hf_ptq.py) and disabling the KV cache quantization or using the [QAT](./../llm_qat/README.md) instead. For NVFP4 quantization specifically, we recommend `nvfp4_mlp_only`, `nvfp4_experts_only`, or `nvfp4_omlp_only` to achieve higher accuracy by restricting quantization to the MLP/expert layers (and optionally the `o_proj` layer) while keeping the attention QKV projections unquantized.*
Expand All @@ -162,7 +162,7 @@ export HF_PATH=<the downloaded LLaMA checkpoint from the Hugging Face hub, or si
scripts/huggingface_example.sh --model $HF_PATH --quant <QFORMAT> --tp [1|2|4|8]
```

Supported `QFORMAT` values: `fp8`, `fp8_pc_pt`, `fp8_pb_wo`, `int8`, `int8_sq`, `int8_wo`, `int4_awq`, `w4a8_awq`, `nvfp4`, `nvfp4_awq`, `nvfp4_mse`, `nvfp4_mlp_only`, `nvfp4_experts_only`, `nvfp4_omlp_only`, `nvfp4_svdquant`, `nvfp4_local_hessian`, `w4a8_nvfp4_fp8`, `w4a8_mxfp4_fp8`, `mxfp8`.
`QFORMAT` accepts any preset basename under [`modelopt_recipes/configs/ptq/presets/model/`](../../modelopt_recipes/configs/ptq/presets/model) — e.g. `fp8`, `fp8_per_channel_per_token`, `fp8_2d_blockwise_weight_only`, `int8`, `int8_smoothquant`, `int8_weight_only`, `int4_awq`, `w4a8_awq_beta`, `nvfp4`, `nvfp4_awq_lite`, `nvfp4_w4a4_weight_mse_fp8_sweep`, `nvfp4_mlp_only`, `nvfp4_experts_only`, `nvfp4_omlp_only`, `nvfp4_svdquant`, `nvfp4_w4a4_weight_local_hessian`, `w4a8_nvfp4_fp8`, `w4a8_mxfp4_fp8`, `mxfp8`.

> *By default `trust_remote_code` is set to false. Please turn it on if model calibration and eval requires it using `--trust_remote_code`.*

Expand Down Expand Up @@ -265,10 +265,8 @@ TensorRT-LLM multimodal quickstart as the deploy smoke test instead of the text-
scripts/huggingface_example.sh --model <Hugging Face model card or checkpoint> --quant fp8 --vlm
```

Supported `--quant` values for VLMs are `fp8`, `nvfp4`, `int8_sq`, `int4_awq`, and `w4a8_awq` (see
the `(VLM)` rows in the [Support Matrix](#hugging-face-supported-models)).

> *This consolidates the former `examples/vlm_ptq` example, which now forwards here.*
Supported `--quant` values for VLMs are `fp8`, `nvfp4`, `int8_smoothquant`, `int4_awq`, and
`w4a8_awq_beta` (see the `(VLM)` rows in the [Support Matrix](#hugging-face-supported-models)).

#### VLM calibration with image-text pairs (e.g., Nemotron VL)

Expand Down Expand Up @@ -359,17 +357,6 @@ search-disabled layers, and cost-excluded layers — see
recipes (carrying architecture-specific disabled layers — e.g. VL vision towers) live under
`modelopt_recipes/huggingface/<model>/auto_quantize/`.

> *Migration: prefer an AutoQuantize `--recipe`. The `--auto_quantize_bits`, `--auto_quantize_method`,
> `--auto_quantize_score_size`, `--auto_quantize_cost_model`, and `--auto_quantize_active_moe_expert_ratio`
> CLI flags are **deprecated but still work** — they are converted into an `AutoQuantizeConfig` on the fly
> (with a `DeprecationWarning`) and will be removed in a future release. They map to recipe fields:
> `--auto_quantize_bits` → `constraints.effective_bits`, `--auto_quantize_method` → `auto_quantize_method`,
> `--auto_quantize_score_size` → `score_size`, `--auto_quantize_cost_model` → `constraints.cost_model`,
> `--auto_quantize_active_moe_expert_ratio` → `constraints.cost.active_moe_expert_ratio`, and the
> `--qformat fp8,nvfp4` candidate list → `candidate_formats`. When converted, the shared base
> `disabled_layers` and `cost_excluded_layers` patterns are appended automatically. `--auto_quantize_checkpoint`
> is unchanged. Start from a shipped recipe under `modelopt_recipes/general/auto_quantize/`.*

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

```bash
Expand Down
25 changes: 7 additions & 18 deletions examples/hf_ptq/example_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,37 +1110,30 @@ def copy_custom_model_files(source_path: str, export_path: str, trust_remote_cod
print("No custom model files found to copy")


def _layerwise_checkpoint_dir_location(algorithm) -> tuple[str, str] | None:
"""Return ``("flat"/"nested", checkpoint_dir)`` for the layerwise checkpoint dir, or None."""
def _layerwise_checkpoint_dir(algorithm) -> str | None:
"""Return the nested ``layerwise.checkpoint_dir``, or None."""
if not isinstance(algorithm, dict):
return None
flat = algorithm.get("layerwise_checkpoint_dir")
if flat is not None:
return "flat", flat
nested = algorithm.get("layerwise") or {}
ckpt = nested.get("checkpoint_dir") if isinstance(nested, dict) else None
return ("nested", ckpt) if ckpt is not None else None
return nested.get("checkpoint_dir") if isinstance(nested, dict) else None


def needs_checkpoint_path_update(quant_cfg: dict) -> bool:
"""Check if quant_cfg has a layerwise checkpoint_dir that should be auto-resolved to a unique subpath."""
return _layerwise_checkpoint_dir_location(quant_cfg.get("algorithm")) is not None
return _layerwise_checkpoint_dir(quant_cfg.get("algorithm")) is not None


def resolve_checkpoint_dir(quant_cfg: dict, model_path: str) -> tuple[dict, str]:
"""Append a unique ``<model_name>_<config_hash>`` subdirectory to the layerwise checkpoint_dir.

Allows a single recipe to be reused across models without checkpoint collisions.
Supports both the legacy flat ``layerwise_checkpoint_dir`` and the nested
``layerwise.checkpoint_dir`` shape, writing back to whichever the user provided.
Must only be called when :func:`needs_checkpoint_path_update` returns True.

Returns ``(updated_quant_cfg, resolved_path)`` so the caller can log or
reference the resolved path without re-deriving the dict shape.
"""
location = _layerwise_checkpoint_dir_location(quant_cfg["algorithm"])
assert location is not None # guaranteed by needs_checkpoint_path_update
shape, base_dir = location
base_dir = _layerwise_checkpoint_dir(quant_cfg["algorithm"])
assert base_dir is not None # guaranteed by needs_checkpoint_path_update

name = model_path.rstrip("/")
if "/" in name and not os.path.isabs(name):
Expand All @@ -1152,11 +1145,7 @@ def resolve_checkpoint_dir(quant_cfg: dict, model_path: str) -> tuple[dict, str]
resolved = os.path.join(base_dir, f"{name}_{config_hash}")

quant_cfg = copy.deepcopy(quant_cfg)
algo = quant_cfg["algorithm"]
if "layerwise_checkpoint_dir" in algo:
algo["layerwise_checkpoint_dir"] = resolved
if isinstance(algo.get("layerwise"), dict) and "checkpoint_dir" in algo["layerwise"]:
algo["layerwise"]["checkpoint_dir"] = resolved
quant_cfg["algorithm"]["layerwise"]["checkpoint_dir"] = resolved
return quant_cfg, resolved


Expand Down
Loading
Loading