# Quantization Aware Training (QAT) and Distillation (QAD)
Quantization Aware Training (QAT) improves model accuracy beyond post-training quantization (PTQ) at low precisions (e.g., INT4, FP4 on [NVIDIA Blackwell](https://www.nvidia.com/en-us/data-center/technologies/blackwell-architecture/)). Quantization Aware Distillation (QAD) further improves accuracy by using the original full-precision model as a teacher.
For background on how QAT enables low-precision accuracy recovery, see the [QAT/QAD blog post](https://developer.nvidia.com/blog/how-quantization-aware-training-enables-low-precision-accuracy-recovery/).
| **Section** | **Description** | **Link** | **Docs** |
| :---: | :---: | :---: | :---: |
| Quick Start | Prerequisites and setup | \[[Link](#quick-start)\] | |
| End-to-End Example | Run QAT/QAD in 3 steps: quantize, train, export | \[[Link](#run-end-to-end-qatqad-example)\] | |
| Arguments | Full CLI/YAML argument reference | \[[Link](ARGUMENTS.md)\] | |
| Background | How QAT/QAD work and when to use each | \[[Link](#background)\] | \[[docs](https://nvidia.github.io/Model-Optimizer/guides/1_quantization.html)\] |
| Support Matrix | Supported models, quantization formats, and backends | \[[Link](#support-matrix)\] | |
| QLoRA | Model training with reduced GPU memory | \[[Link](#qlora-real-quantization)\] | |
| Advanced Topics | FSDP2 config, YAML options | \[[Link](#advanced-topics)\] | |
| Results | Accuracy benchmarks | \[[Link](#results)\] | |
| Resources | Extra links and references | \[[Link](#resources)\] | |
## Quick Start
### Prerequisites
Please refer to [hf_ptq/README.md](../hf_ptq/README.md#pre-requisites) for container
recommendations and base ModelOpt installation guidance. For this QAT/QAD example,
install the Hugging Face dependencies and the example-specific requirements:
```bash
pip install -U nvidia-modelopt[hf]
pip install -r examples/llm_qat/requirements.txt
```
The Qwen3-8B example below requires a minimum of **2 x 80GB GPUs**.
## Run End-to-End QAT/QAD Example
All arguments can be set via YAML, CLI, or both (CLI overrides YAML). See
[ARGUMENTS.md](ARGUMENTS.md), `--help`, and [Configuration](#advanced-configuration).
### QAT
Quantize, fine-tune on labeled data, and export:
```sh
# 1. Quantize
python quantize.py \
--model_name_or_path Qwen/Qwen3-8B \
--dataset_config configs/dataset/blend.yaml \
--recipe general/ptq/nvfp4_default-kv_fp8 \
--output_dir qwen3-8b-quantized
# 2. Train
accelerate launch --config-file configs/accelerate/fsdp2.yaml train.py \
--config configs/train/qat_nvfp4.yaml \
--model_name_or_path qwen3-8b-quantized \
--output_dir qwen3-8b-qat-nvfp4
# 3. Export
python export.py --pyt_ckpt_path qwen3-8b-qat-nvfp4 --export_path qwen3-8b-qat-deploy
```
### QAD
Quantize, recover accuracy using the original model as teacher, and export:
```sh
# 1. Quantize
python quantize.py \
--model_name_or_path Qwen/Qwen3-8B \
--dataset_config configs/dataset/blend.yaml \
--recipe general/ptq/nvfp4_default-kv_fp8 \
--output_dir qwen3-8b-quantized
# 2. Train with distillation
accelerate launch --config-file configs/accelerate/fsdp2.yaml train.py \
--config configs/train/qad_nvfp4.yaml \
--model_name_or_path qwen3-8b-quantized \
--teacher_model Qwen/Qwen3-8B \
--output_dir qwen3-8b-qad-nvfp4
# 3. Export
python export.py --pyt_ckpt_path qwen3-8b-qad-nvfp4 --export_path qwen3-8b-qad-deploy
```
Exported checkpoints can be deployed on [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM), [vLLM](https://github.com/vllm-project/vllm), or [SGLang](https://github.com/sgl-project/sglang). See [hf_ptq/README.md](../hf_ptq/README.md#deployment) for deployment instructions. For quick accuracy evaluation without exporting, see [Native Fake-Quantized Evaluation](#native-fake-quantized-evaluation).
> [!NOTE]
> For a minimal end-to-end demo (quantize + train + save in one script), see [simple_qat_train.py](simple_qat_train.py). It runs on a **single GPU** only and is intended as a quick introduction to the QAT flow (without transformer trainer)—not for distributed training.
>
> ```sh
> python simple_qat_train.py --model-path meta-llama/Llama-3.2-3B --recipe general/ptq/nvfp4_default-kv_fp8
> ```
>
> For multi-GPU training (FSDP2, DDP, DeepSpeed), use [train.py](train.py) with `accelerate launch` as shown in the [commands](#qat) above.
> [!TIP]
> For more performant QAD, please refer to [examples/megatron_bridge/README.md](../megatron_bridge/README.md) for example scripts for PTQ / QAD with Megatron-Bridge which is generally more performant than the Hugging Face scripts.
## Background
### What is QAT?
**Quantization Aware Training (QAT)** inserts simulated quantization operations into the model graph and then fine-tunes the model so its weights learn to compensate for quantization error. During training, quantization scales are frozen while weights are updated. QAT is a general technique — it learns from labeled data on a quantized model.
```python
import modelopt.torch.quantization as mtq
from modelopt.recipe import load_recipe
# 1. Load a quantization recipe
recipe = load_recipe("general/ptq/nvfp4_default-kv_fp8")
# 2. Quantize the model in-place
model = mtq.quantize(model, recipe.quantize, forward_loop)
# 3. Fine-tune the quantized model
trainer.train()
trainer.save_model()
```
> ModelOpt provides accelerated quantization kernels using Triton for NVFP4 QAT. See the [installation guide](https://nvidia.github.io/Model-Optimizer/getting_started/_installation_for_Linux.html#accelerated-quantization-with-triton-kernels).
### What is QAD?
**Quantization Aware Distillation (QAD)** is a special case of QAT that uses a teacher model (typically the original unquantized model) to guide the quantized student via a distillation loss. QAD is a **pure accuracy recovery technique** — its goal is to recover accuracy lost from quantization, not to teach the model a new task.
To learn more, read the [QAT/QAD blog post](https://developer.nvidia.com/blog/how-quantization-aware-training-enables-low-precision-accuracy-recovery/).
### When to Use QAT vs QAD
| | **QAT** (without distillation) | **QAD** (with distillation) |
|-|---------|----------------------|
| **What it does** | Fine-tunes a quantized model on labeled data | Recovers quantization accuracy using the original model as teacher |
| **When to use** | The model is already quantized and you want to fine-tune it for a **new task** (e.g., fine-tuning a [GPT-OSS](../gpt-oss/) quantized checkpoint) | You want the **best possible accuracy recovery** after quantization |
| **Recommended workflow** | Start from a quantized checkpoint, fine-tune with task-specific data | Full-precision fine-tuning first, then QAD to recover quantization loss |
**QAD is Model Optimizer's recommended strategy for accuracy recovery after quantization.** In our experiments, full-precision fine-tuning followed by QAD delivers the best accuracy, especially at aggressive quantization levels (e.g., NVFP4). The optimal balance between QAT and QAD for a given model and task is an active area of research.
### Using `QATTrainer` and `QADTrainer`
`QATTrainer` is a drop-in replacement for HuggingFace's `Trainer` that handles quantization-aware training seamlessly with various distributed backends (FSDP2, DeepSpeed, DDP):
```python
from modelopt.torch.quantization.plugins.transformers_trainer import QATTrainer
trainer = QATTrainer(
model=model, # pre-quantized model
processing_class=tokenizer,
args=training_args,
**data_module,
)
trainer.train()
trainer.save_model()
```
`QADTrainer` extends `QATTrainer` with distillation. Pass the teacher model and a `DistillArguments` instance:
```python
from modelopt.torch.distill.plugins.huggingface import DistillArguments
from modelopt.torch.quantization.plugins.transformers_trainer import QADTrainer
distill_args = DistillArguments(
distill=True,
teacher_model="Qwen/Qwen3-8B",
criterion="logits_loss",
)
trainer = QADTrainer(
model=model, # pre-quantized model
processing_class=tokenizer,
args=training_args,
distill_args=distill_args,
**data_module,
)
trainer.train()
trainer.save_model()
```
### Quantization Recipes
Recipes are declarative YAML files that specify the quantization configuration. Built-in recipes are available in [`modelopt_recipes/`](../../modelopt_recipes/):
```sh
# List available built-in recipes
ls modelopt_recipes/general/ptq/
```
See [custom calibration](https://nvidia.github.io/Model-Optimizer/guides/_pytorch_quantization.html#advanced-configuration-creation) for creating your own recipe.
## Support Matrix
### Supported Models
| Model | Chat Template | Support |
|-------|---------------|---------|
| Qwen2, 2.5, 3, 3.5 dense models; Nemotron ChatML models | ChatML | Yes (chat + assistant-only labels + pretrain) |
| Models with `{% generation %}` chat templates | Model-specific | Yes (chat + assistant-only labels + pretrain) |
| Other models with HuggingFace chat templates, including Llama 2, 3, 3.1 | Model-specific | Yes (chat full-label + pretrain) |
> **Note:** `apply_chat_template` controls chat formatting. `train_only_assistant_tokens` controls label masking: `auto` uses assistant-only labels when native `{% generation %}` masks or the tested Qwen/Nemotron ChatML heuristic is available, then falls back to all non-padding chat-template tokens; set `train_only_assistant_tokens: true` to require native or ChatML assistant-only labels, or `false` to always train on all chat-template tokens.
### Supported Quantization Formats
Built-in recipes support full-model, partial-layer, and mixed-precision quantization. Common entry points:
| Format | Precision | Example Recipe | Use Case |
|--------|-----------|----------------|----------|
| **NVFP4** | W4A4 + FP8 KV | `general/ptq/nvfp4_default-kv_fp8` | FP4 compute and compression on Blackwell GPUs |
| **FP8** | W8A8 + FP8 KV | `general/ptq/fp8_default-kv_fp8` | Near-BF16 accuracy on Hopper or later GPUs |
| **INT4** weight-only | W4A16 | `general/ptq/int4_blockwise_weight_only` | Deployable on all Ampere or later GPUs |
| **Partial / mixed** | Pattern-specific | `general/ptq/nvfp4_mlp_only-kv_fp8` | Quantize selected layers or combine precisions |
> Recipes can target different layers or GEMMs with different precisions, such as NVFP4
> for MLP/MoE GEMMs and FP8 for attention GEMMs or KV cache. See
> [`modelopt_recipes/general/ptq/`](../../modelopt_recipes/general/ptq/) and
> [`modelopt_recipes/configs/ptq/`](../../modelopt_recipes/configs/ptq/) for built-in
> options and reusable recipe units.
### Supported Backends
| Backend | Config File | Notes |
|---------|------------|-------|
| FSDP2 | `configs/accelerate/fsdp2.yaml` | **Recommended** |
| DDP | `configs/accelerate/ddp.yaml` | Add `--gradient_checkpointing True` |
| DeepSpeed | `configs/accelerate/deepspeed.yaml` | Add `--gradient_checkpointing True` |
Replace `--config-file configs/accelerate/fsdp2.yaml` with the desired backend config in any of the commands above.
## QLoRA (Real Quantization)
[QLoRA](https://arxiv.org/pdf/2305.14314) reduces training memory by quantizing LoRA backbone weights with real quantization via `mtq.compress()`.
```sh
# 1. Quantize with compression
python quantize.py \
--model_name_or_path Qwen/Qwen3-8B \
--dataset_config configs/dataset/blend.yaml \
--recipe general/ptq/nvfp4_default-kv_fp8 \
--compress True \
--output_dir qwen3-8b-quantized
# 2. Train with QLoRA
accelerate launch --config-file configs/accelerate/ddp.yaml train.py \
--config configs/train/qlora_nvfp4.yaml \
--model_name_or_path qwen3-8b-quantized \
--output_dir qwen3-8b-fp4-qlora
# 3. Export
python export.py \
--pyt_ckpt_path qwen3-8b-fp4-qlora \
--export_path qwen3-8b-fp4-qlora-hf
# 4. Serve with vLLM
vllm serve qwen3-8b-fp4-qlora-hf/base_model --enable-lora \
--lora-modules adapter=qwen3-8b-fp4-qlora-hf --port 8000 \
--tokenizer qwen3-8b-fp4-qlora-hf
```
> QLoRA export is not currently supported with FSDP2.
## Advanced Topics
FSDP2 and Model-Specific Layer Wrapping
The default `fsdp2.yaml` uses `TRANSFORMER_BASED_WRAP` with `fsdp_transformer_layer_cls_to_wrap: Qwen3DecoderLayer`. This setting is **model-specific** — if you are training a different model architecture, you must update it to match your model's decoder layer class.
You can either:
1. **Override via CLI** (recommended for one-off runs):
```sh
accelerate launch --config-file configs/accelerate/fsdp2.yaml \
--fsdp_transformer_layer_cls_to_wrap LlamaDecoderLayer \
train.py --config configs/train/qat_nvfp4.yaml ...
```
2. **Create a custom config** (recommended for repeated use):
```sh
cp configs/accelerate/fsdp2.yaml configs/accelerate/fsdp2_llama.yaml
# Edit fsdp2_llama.yaml: change Qwen3DecoderLayer -> LlamaDecoderLayer
```
Common layer class names:
| Model Family | `fsdp_transformer_layer_cls_to_wrap` |
|---|---|
| Qwen2, Qwen2.5, Qwen3 | `Qwen3DecoderLayer` (or `Qwen2DecoderLayer`) |
| Llama 2, 3, 3.1 | `LlamaDecoderLayer` |
Configuration
There are two types of configs:
- **Dataset configs** (`configs/dataset/`): Define the dataset blend — sources, `blend_size` (total samples), and `splits` (train/eval/test ratios). These are self-contained and determine what gets cached.
- **Training configs** (`configs/train/`): Define training hyperparameters plus runtime caps (`train_samples`, `eval_samples`) that subset the pre-built dataset without retriggering caching.
`quantize.py` only needs `--dataset_config` and `--recipe`. `train.py` uses a full training config via `--config`. All arguments can be specified via YAML, CLI flags, or both (CLI overrides YAML). See [ARGUMENTS.md](ARGUMENTS.md) for the full reference, regenerated with `python_pwd examples/llm_qat/arguments.py --generate_docs examples/llm_qat/ARGUMENTS.md`.
```sh
# YAML + CLI override
accelerate launch --config-file configs/accelerate/fsdp2.yaml train.py \
--config configs/train/qat_nvfp4.yaml --learning_rate 5e-5
```
See [Dataset Configuration](configs/dataset/README.md) for custom dataset blends and adding new datasets.
Pre-Building the Dataset
You can pre-tokenize and cache the dataset before training using `dataset_utils.py`. This is useful for large blends or multi-node setups where you want to build the cache once and reuse it across experiments.
```sh
python dataset_utils.py \
--dataset_config configs/dataset/blend.yaml \
--model_name_or_path Qwen/Qwen3-8B
```
The cached dataset is stored under `.dataset_cache/tokenized/` by default (configurable via `--dataset_cache_dir`). The cache key depends on the dataset config (`blend_size`, `splits`, sources) and tokenizer — changing `train_samples` or `eval_samples` in the training config does **not** invalidate the cache.
## Results
\[Coming Soon\]
## Native Fake-Quantized Evaluation
ModelOpt quantized models can be saved and restored without exporting to a deployment platform. This is useful for fast evaluation with fake quantization using standard LLM benchmarks (MMLU, WikiText, etc.). See [HuggingFace checkpointing](https://nvidia.github.io/Model-Optimizer/guides/2_save_load.html#modelopt-save-restore-using-huggingface-checkpointing-apis) for details.
```sh
cd ../llm_eval
python lm_eval_hf.py --model hf \
--tasks mmlu,wikitext \
--model_args pretrained=../llm_qat/qwen3-8b-qat-nvfp4 \
--batch_size 4
```
See [llm_eval/README.md](../llm_eval/README.md) for supported tasks.
## Pre-Quantized Checkpoints
- Ready-to-deploy checkpoints: [Hugging Face - NVIDIA Model Optimizer Collection](https://huggingface.co/collections/nvidia/inference-optimized-checkpoints-with-model-optimizer)
- Deployable on [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM), [vLLM](https://github.com/vllm-project/vllm) and [SGLang](https://github.com/sgl-project/sglang)
## Resources
- [Roadmap](https://github.com/NVIDIA/Model-Optimizer/issues/1699)
- [Documentation](https://nvidia.github.io/Model-Optimizer)
- [Benchmarks](../benchmark.md)
- [Release Notes](https://nvidia.github.io/Model-Optimizer/reference/0_changelog.html)
- [File a bug](https://github.com/NVIDIA/Model-Optimizer/issues/new?template=1_bug_report.md)
- [Feature Request](https://github.com/NVIDIA/Model-Optimizer/issues/new?template=2_feature_request.md)