Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d4c8fb6
Keep hf_ptq's previews of a sharded model in lockstep, on the quantiz…
shengliangxu Oct 30, 2026
7b6586f
Pin a whole-model load to the rank's own GPU by its distribution plan
shengliangxu Oct 30, 2026
6761d72
Hand load_pretrained only a distribution plan get_model's caller gave
shengliangxu Oct 30, 2026
9bd6591
Refuse a plan that shards the model in get_model, which loads it whole
shengliangxu Oct 30, 2026
d5556a2
Stop the run when a sharded model's preview after quantization fails
shengliangxu Oct 30, 2026
1af92ad
Abort a failed rank of a multi-rank hf_ptq run instead of tearing the…
shengliangxu Oct 30, 2026
223a3ee
Let distributed_forward_loop run each batch through a forward it is g…
shengliangxu Oct 30, 2026
2650415
Initialize NVML before probing it in launch_memory_monitor
shengliangxu Oct 30, 2026
a7691fe
Stop a GPU memory monitor once, however often stop() is called
shengliangxu Oct 30, 2026
a3b9b22
Check the first stop's report without waiting for a GPU sample
shengliangxu Oct 30, 2026
b2c562d
Wait for the memory monitor's sampling pass before reporting its peaks
shengliangxu Oct 30, 2026
653a6bc
Type the forward distributed_forward_loop calls as taking a mapping
shengliangxu Nov 2, 2026
26f0771
Run expert-parallel PTQ from hf_ptq.py
shengliangxu Oct 30, 2026
0c12453
Say in the changelog what a multi-rank torchrun launch of hf_ptq now …
shengliangxu Oct 30, 2026
cb704c2
Drop the calibration test's claim that FSDP2 keeps the out-of-memory …
shengliangxu Oct 30, 2026
580028d
Drop the failed-preview test's --use_fsdp2 row, which [2/3] now tests
shengliangxu Oct 30, 2026
4c306cb
Move hf_ptq's distribution decision into example_utils
shengliangxu Oct 30, 2026
d12305a
Trim the comments hf_ptq's expert-parallel run adds
shengliangxu Oct 30, 2026
37a7563
Add a Slurm example of expert-parallel PTQ across nodes
shengliangxu Oct 30, 2026
00b2951
Install the transformers expert parallelism needs in the EP Slurm exa…
shengliangxu Oct 30, 2026
2a9e94d
Keep hf_ptq's expert-parallel run off private helpers
shengliangxu Oct 30, 2026
45304fb
Warn about LSQ on a multi-rank hf_ptq run, and say which calibrations…
shengliangxu Oct 30, 2026
1a35fe1
Say the EP Slurm example needs shared storage, take turns on its inst…
shengliangxu Nov 3, 2026
eed54f9
Make reinstall_a2a_forwards the one re-plant function, not an alias
shengliangxu Nov 3, 2026
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 @@ -25,6 +25,7 @@ Changelog
- ``hf_ptq.py`` calibrates Qwen3.8 (``qwen4_exp``) without running its QSA indexer's per-query Python loop when every visible key fits the indexer's token budget, with bit-identical results. On Qwen3.8-Flash-Next this saves about two hours of layerwise calibration.
- Speed up Hugging Face QAT and QAD with ``QATTrainer``, ``QADTrainer`` or ``--manual_gc``: the ``gc.collect()`` run before each training step now skips the model and optimizer state, saving about 1 s per step (30%) on Qwen3-30B-A3B.
- Add an opt-in no-gather HuggingFace export for FSDP2-sharded models: with ``MODELOPT_FSDP2_EXPORT=nogather``, ``export_hf_checkpoint`` moves each tensor once, to the one rank that writes it, instead of all-gathering each FSDP2 unit on every rank as the default export does. Fused MoE experts stay sharded, each rank writing its own, and a conversion mapping that splits or merges tensors, which the default export refuses, is reversed as a single-process export reverses it.
- Add expert-parallel PTQ for HuggingFace MoE models to ``examples/hf_ptq/hf_ptq.py``: under ``torchrun``, each rank holds only its share of the routed experts, tokens reach them by all-to-all, and every rank calibrates on its own slice of the data. ``MODELOPT_EP_DEGREE`` and ``MODELOPT_FSDP_SIZE`` override the automatic choice of expert-parallel degree and of FSDP2-sharding the dense layers, and the quantized checkpoint is written by every rank in parallel without gathering the model.

*Speculative Decoding*

Expand All @@ -51,6 +52,7 @@ Changelog
**Backward Breaking Changes**

- ``QDQAutotunerBase.export_onnx(output_path=...)`` now returns ``None`` instead of serialized bytes. Omit ``output_path`` to keep the bytes-returning behavior, which remains subject to protobuf size limits.
- ``examples/hf_ptq/hf_ptq.py`` launched by ``torchrun`` on more than one rank without ``--use_fsdp2`` now runs one distributed job instead of one independent run per process: an MoE that can shard its experts runs expert-parallel, any other model is replicated with each rank calibrating its own share of the data, and only rank 0 exports a replicated model. Such a launch refuses ``--low_memory_mode``, ``--use_seq_device_map``, ``--offload_folder``, AutoQuantize recipes and, under expert parallelism, ``--vllm_fakequant_export`` and ``--experts_implementation``, and any multi-rank launch now refuses layerwise-export recipes, the TensorRT-LLM checkpoint export and a ``--device`` other than ``cuda``: launch without ``torchrun`` for them.
- ``examples/hf_ptq/hf_ptq.py --use_fsdp2`` on more than one GPU now refuses calibration that keeps its statistics on each rank, which calibrated the ranks' shards of a weight differently: AWQ-Lite, AWQ-Full, SVDQuant, local Hessian and imatrix, also as an AutoQuantize candidate (run them without ``--use_fsdp2``). It also raises when a calibration batch runs out of GPU memory, instead of retrying it in smaller pieces: set ``--batch_size`` to one that fits.
- ``export_mcore_gpt_to_hf`` given a Hugging Face Hub model ID no longer writes the tokenizer, processor, generation config or remote-code files. Pass a local directory instead, e.g. from ``modelopt.torch.export.ensure_local_checkpoint``.
- Drop support for Python 3.10, which has reached end-of-life. ModelOpt now requires Python 3.11 or later.
Expand Down
36 changes: 35 additions & 1 deletion examples/hf_ptq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ ModelOpt enables quantization of LLMs across multiple GPU nodes using FSDP2 for

> *`nvfp4_awq_clip` still works with `--use_fsdp2` on more than one GPU: it calibrates with AWQ-Clip alone, which folds no smoothing scale into the weights, and its clip search averages each candidate clip's loss over the ranks, so every rank takes the same clip.*

> *With `--use_fsdp2` on more than one GPU, a calibration batch that runs out of memory is not retried in smaller pieces, since a rank that runs out of memory partway through a forward would leave the others waiting in its collectives. With `--batch_size 0`, each rank estimates the batch size from one forward and the ranks use the smallest, which is not probed for out-of-memory either: set `--batch_size` if calibration runs out of memory.*
> *On more than one GPU, with `--use_fsdp2` or in any other `torchrun` launch of several ranks, a calibration batch that runs out of memory is not retried in smaller pieces, since a rank that runs out of memory partway through a forward would leave the others waiting in its collectives. With `--batch_size 0`, each rank estimates the batch size from one forward and the ranks use the smallest, which is not probed for out-of-memory either: set `--batch_size` if calibration runs out of memory.*

### Usage

Expand Down Expand Up @@ -660,6 +660,40 @@ Qwen checkpoints with `model_type` `qwen3_5` or `qwen3_5_moe` support this path

> *Set `MODELOPT_FSDP2_EXPORT=nogather` to move each tensor once, to the one rank that writes it, instead of all-gathering each FSDP2 unit on every rank as the default export does. Fused MoE experts then stay sharded, and a conversion mapping that splits or merges tensors, which the default export refuses, is reversed as a single-process export reverses it. It does not overwrite a checkpoint already in the export directory.*

## Expert-Parallel Post-Training Quantization for MoE Models

Under `torchrun`, `hf_ptq.py` shards an MoE model's routed experts across the ranks (expert parallelism) instead of loading the whole model on every GPU. Each rank holds `num_experts / ep_degree` experts and calibrates on its own shard of the calibration data. An all-to-all sends every token to the rank that owns its expert, so each expert's quantizers see the tokens routed to it from all ranks. The dense (non-expert) part is replicated on every rank, or FSDP2-sharded when it does not fit one GPU. Each rank then writes its share of the unified HF checkpoint, without gathering the model onto one GPU.

A model can be expert-parallel when its config declares a `base_model_ep_plan`. transformers ships one for several MoEs, and ModelOpt supplies the plan or an adapter for others (see the `ep_spec` entries in `modelopt/torch/models/*/specs.py`). Expert parallelism needs transformers 5.7 or newer, and some models a newer release; a run that cannot be expert-parallel is refused before loading.

```bash
torchrun --nproc_per_node=<num_gpus_per_node> \
hf_ptq.py \
--pyt_ckpt_path <path_to_moe_model> \
--recipe general/ptq/nvfp4_default-kv_fp8 \
--calib_size <num_calib_samples> \
--export_path <export_path>
```

To run across nodes, add the `--nnodes`, `--node_rank`, `--master_addr` and `--master_port` flags shown in [Manual (run on each node)](#manual-run-on-each-node). `--calib_size` is the total over all ranks. The export directory and the Hugging Face cache (`HF_HOME`) must be on storage every node can see: every rank writes its share of the checkpoint, and rank 0 downloads a Hub model for the others to read.

Under Slurm, [`slurm/multinode_ep_ptq.slurm`](./slurm/multinode_ep_ptq.slurm) is a ready-to-run example that quantizes Qwen3-235B-A22B to NVFP4 across nodes. Edit its `CONFIG` block and submit:

```bash
sbatch --nodes=2 slurm/multinode_ep_ptq.slurm
```

Two environment variables override the automatic distribution plan:

| Variable | Default | Effect |
| :--- | :--- | :--- |
| `MODELOPT_EP_DEGREE` | `auto` | Expert-parallel degree. `auto` picks the largest degree that divides both the routed-expert count and the world size, or 1 for a model whose spec refuses expert parallelism or needs a newer transformers. Below the world size, each group of `MODELOPT_EP_DEGREE` ranks holds a full set of experts, and the groups calibrate on different data. |
| `MODELOPT_FSDP_SIZE` | `auto` | Whether to FSDP2-shard the dense part. `auto` shards it when it would take more than 60% of one GPU's memory; `world` always shards it; `1` replicates it. With `MODELOPT_EP_DEGREE` below the world size the export cannot write a sharded dense part yet, so such a run fails before loading. |

On more than one rank, `max` and `mse` calibration have run on GPUs; `gptq`, `smoothquant`, `awq_clip` and `nvfp4_act_headroom` are supported but not yet run on GPUs; `awq_lite`, `awq_full`, `svdquant`, `local_hessian` and `imatrix`, and `max` or `mse` with `distributed_sync` off, are refused before quantizing; and an LSQ recipe runs with a warning, since nothing has checked its learnable scales across ranks yet.

A run that cannot shard the model the way the plan asks fails before loading, rather than loading the whole model on every rank. `--use_fsdp2` skips expert parallelism and uses the [FSDP2 path](#multi-node-post-training-quantization-with-fsdp2) instead. Expert parallelism refuses GPT-OSS and Llama-4: their experts are stored transposed, which the expert-parallel export cannot write yet. `--use_fsdp2` refuses their released checkpoints too (GPT-OSS's carry an MXFP4 `quantization_config`, and Llama-4's are multimodal), so quantize those in a single process (without `torchrun`), which spreads the model over the node's GPUs.

## Evaluate Accuracy

### TensorRT-LLM Validation
Expand Down
Loading
Loading