File size: 24,621 Bytes
0ed6b0e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# Model Quantization

Quantization reduces VRAM usage by lowering the numerical precision of model weights, allowing large models to run on smaller GPUs. `DiffSynth-Studio` provides a unified quantization entry point `QuantizeConfig`, supporting multiple quantization backends such as bitsandbytes, torchao, and comfy-kitchen, as well as online quantization, loading pre-quantized weights, mixed quantization, and quantization + LoRA training.

This document uses `Z-Image` as an example. If you want to use `diffsynth.core.quant` in your own codebase, refer to the [`diffsynth.core.quant` API documentation](../API_Reference/core/quant.md).

> **Difference between quantization and FP8 in VRAM management**
>
> The FP8 in [VRAM management](./VRAM_management.md) controls the storage precision of weights in VRAM through parameters such as `offload_dtype` / `onload_dtype`. It applies to all parameters and requires no third-party libraries, but only supports simple FP8 conversion.
>
> The quantization in this document (`QuantizeConfig`) is a dedicated scheme for `nn.Linear` layers, supporting finer formats such as NF4, INT8, INT4, MXFP4, and NVFP4. It can save/load quantized weights and supports activation quantization and quantization + LoRA training. The two can be combined.

## Installation

Different quantization backends require the corresponding third-party libraries:

| Backend | Install Command | Project Page |
| --- | --- | --- |
| bitsandbytes | `pip install bitsandbytes` | [bitsandbytes](https://github.com/bitsandbytes-foundation/bitsandbytes) |
| torchao | `pip install torchao>=0.16` | [torchao](https://github.com/pytorch/ao) |
| comfy-kitchen | `pip install comfy-kitchen` | [comfy-kitchen](https://github.com/Comfy-Org/comfy-kitchen) |

Install all at once: `pip install "diffsynth[quant]"`

## Quick Start

Pass `quantize` to any `ModelConfig` to enable online quantization for that model. The following code loads Z-Image's DiT with NF4 quantization:

```python
from diffsynth.pipelines.z_image import ZImagePipeline, ModelConfig
from diffsynth.core.quant import QuantizeConfig
import torch

pipe = ZImagePipeline.from_pretrained(
    torch_dtype=torch.bfloat16,
    device="cuda",
    model_configs=[
        ModelConfig(
            model_id="Tongyi-MAI/Z-Image", origin_file_pattern="transformer/*.safetensors",
            quantize=QuantizeConfig(method="bitsandbytes_nf4"),
        ),
        ModelConfig(model_id="Tongyi-MAI/Z-Image-Turbo", origin_file_pattern="text_encoder/*.safetensors"),
        ModelConfig(model_id="Tongyi-MAI/Z-Image-Turbo", origin_file_pattern="vae/diffusion_pytorch_model.safetensors"),
    ],
    tokenizer_config=ModelConfig(model_id="Tongyi-MAI/Z-Image-Turbo", origin_file_pattern="tokenizer/"),
)
prompt = "A delicate portrait of an underwater girl, blue dress flowing, hair gently drifting, light and shadow clear, surrounded by bubbles, serene expression, exquisite details, dreamlike and beautiful."
image = pipe(prompt=prompt, seed=42, num_inference_steps=50, cfg_scale=4)
image.save("image_z_image_nf4.jpg")
```

## Supported Quantization Methods

The following are all built-in quantization methods. `method` is the name passed to `QuantizeConfig`. The naming follows the `W<weight bits>A<activation bits>` convention: `w8a16` means only the weights are quantized (weight-only), while `w8a8` means both weights and activations are quantized.

| method | Backend | Weight / Activation | Serializable | LoRA Training |
| --- | --- | --- | --- | --- |
| `bitsandbytes_nf4` | bitsandbytes | NF4 / none | βœ… | βœ… |
| `bitsandbytes_fp4` | bitsandbytes | FP4 / none | βœ… | βœ… |
| `torchao_int8_w8a16` | torchao | INT8 / none | βœ… | βœ… |
| `torchao_fp8_w8a16` | torchao | FP8 / none | βœ… | βœ… |
| `torchao_int4_w4a16` | torchao | INT4 / none | βœ… | ❌ |
| `torchao_nvfp4_w4a16` | torchao | NVFP4 / none | βœ… | βœ… |
| `torchao_int8_w8a8` | torchao | INT8 / INT8 dynamic | βœ… | ❌ |
| `torchao_fp8_w8a8` | torchao | FP8 / FP8 dynamic | βœ… | ❌ |
| `torchao_int4_w4a8` | torchao | INT4 / FP8 dynamic | βœ… | ❌ |
| `torchao_mxfp8_w8a8` | torchao | MXFP8 / MXFP8 | βœ… | ❌ |
| `torchao_mxfp4_w4a4` | torchao | MXFP4 / MXFP4 | βœ… | ❌ |
| `torchao_nvfp4_w4a4` | torchao | NVFP4 / NVFP4 | βœ… | ❌ |
| `comfy_kitchen_int8_w8a8` | comfy_kitchen | INT8 / INT8 dynamic | βœ… | βœ… |
| `comfy_kitchen_fp8_w8a8` | comfy_kitchen | FP8 E4M3 / FP8 | βœ… | βœ… |

Some notes:

- **Activation quantization** (`w8a8` / `w4a4`) quantizes activations in addition to compressing weights. On hardware that supports the corresponding low-precision matrix multiplication, this can deliver real speedups, while weight-only schemes typically only save VRAM.
- **LoRA training**: only methods marked βœ… in the "LoRA Training" column can be used for quantization + LoRA training.
- `comfy_kitchen_*` methods read and write ComfyUI's quantized weight format, interoperable with the ComfyUI ecosystem. comfy-kitchen requires CUDA 13.0 or later.
- Formats such as MXFP8 / MXFP4 / NVFP4 have compute hardware requirements; see the [torchao](https://github.com/pytorch/ao) documentation for compatibility details.

You can query all available methods and their parameters in code:

```python
from diffsynth.core.quant import describe_quant_method, QUANT_METHODS, backends

backends.load_all_backends()
print(sorted(QUANT_METHODS))          # all registered method names

describe_quant_method("bitsandbytes_nf4")
```

The output is as follows. `backend_config_kwargs (user-tunable)` lists the adjustable parameters of the method and their default values; these parameters determine the quantization behavior, and you can modify them as needed. For the torchao backend, some parameters are passed directly to torchao's own config (e.g. `Int8WeightOnlyConfig`):

> Unless you know what these parameters mean, we recommend keeping the default values.

```
method: bitsandbytes_nf4
backend: bitsandbytes
detail: 4bit, nf4, weight-only
backend config: diffsynth.core.quant.backends.bitsandbytes.BitsAndBytesNF4Config
backend_config_kwargs (user-tunable):
  compress_statistics = True
  blocksize           = None
  quant_storage       = torch.uint8
pinned by method (not overridable):
  quant_type = 'nf4'
```

## QuantizeConfig in Detail

`QuantizeConfig` describes "which method to use, which layers to quantize, and how to run after quantization":

- **`method`**: the quantization method name, see the table above. Required.
- **`mode`**: how quantized layers run.
    - `"dynamic"` (default): keeps the quantized Linears, dequantizing on demand at forward time, with low VRAM usage.
    - `"dequant_once"`: after quantization, restores all quantized layers to plain fp `nn.Linear` once (keeping the quantization error). Suitable for scenarios that need standard `nn.Linear`; no longer saves VRAM.
- **`target_modules` / `exclude_modules`**: filter the `nn.Linear` layers to quantize by layer name, given as lists. A layer matches if its full dotted name equals an entry, or ends with `"." + entry` (e.g. `"img_mod.1"` matches `transformer_blocks.0.img_mod.1`).
- **`backend_config_kwargs`**: a dict of parameters passed to the backend config, determining the quantization behavior (for the torchao backend, some parameters are passed directly to torchao's own config). Use `describe_quant_method(method)` to query the available parameters.
- **`load_prequantized`**: set to `True` when the checkpoint already holds quantized weights, so they are loaded directly (see below).

Example: exclude quantization-sensitive layers and adjust NF4 backend parameters:

```python
from diffsynth.core.quant import QuantizeConfig

quantize = QuantizeConfig(
    method="bitsandbytes_nf4",
    mode="dynamic",
    exclude_modules=["time_embedder.proj_in", "time_embedder.proj_out", "proj_out"],
    backend_config_kwargs={"compress_statistics": False},
)
```

Activation quantization methods are used in exactly the same way, just with a different `method`:

```python
quantize = QuantizeConfig(method="comfy_kitchen_int8_w8a8", backend_config_kwargs={"convrot_groupsize": 128})
```

## Loading Pre-quantized Weights

Besides online quantization, you can also load checkpoints that are already quantized, avoiding the quantization overhead on every load.

For officially released quantized models (e.g. `ideogram-ai/ideogram-4-nf4`), the quantization info is already written in the config, so you can load them like ordinary models:

```python
ModelConfig(model_id="ideogram-ai/ideogram-4-nf4", origin_file_pattern="transformer/diffusion_pytorch_model.safetensors")
```

For checkpoints you saved yourself (see the next section), pass `quantize` explicitly with `load_prequantized=True` when loading. The `method` and `exclude_modules` must match those used when saving:

```python
from diffsynth.core.quant import QuantizeConfig

ModelConfig(
    path="models/z-image-nf4/transformer.safetensors",
    quantize=QuantizeConfig(method="bitsandbytes_nf4", load_prequantized=True),
)
```

## Saving a Quantized Model

To save the result of an online quantization for reuse, use `save_quantized_model`:

```python
from diffsynth.core.loader import ModelConfig
from diffsynth.core.quant import QuantizeConfig
from diffsynth.utils.quant.serialization import save_quantized_model

model_config = ModelConfig(
    model_id="Tongyi-MAI/Z-Image",
    origin_file_pattern="transformer/*.safetensors",
    quantize=QuantizeConfig(method="bitsandbytes_nf4"),
)
save_quantized_model(model_config, "models/z-image-nf4/transformer.safetensors")
```

It downloads and loads the original fp weights, performs the quantization, and saves the quantized state dict as `.safetensors`. After saving, you can load it as described in the previous section.

## Mixed Quantization

Different layers have different sensitivity to quantization. `MixedQuantizeConfig` allows applying different methods to different layer sets. For example, use INT8 for precision-sensitive modulation layers and NF4 for the rest:

```python
from diffsynth.core.quant import QuantizeConfig, MixedQuantizeConfig

mod_layers = ["img_mod.1", "txt_mod.1", "norm_out.linear", "img_in", "txt_in", "proj_out"]
quantize = MixedQuantizeConfig(configs=[
    QuantizeConfig(method="bitsandbytes_nf4", exclude_modules=mod_layers),
    QuantizeConfig(method="torchao_int8_w8a16", target_modules=mod_layers),
])
```

The layer sets matched by the sub-configs must not overlap. All sub-configs must share the same `mode`. `MixedQuantizeConfig` exposes the same interface as `QuantizeConfig`, and can be passed directly to `ModelConfig(quantize=...)` or `save_quantized_model`.

> When loading a pre-quantized mixed checkpoint, set `load_prequantized=True` on the `MixedQuantizeConfig` itself, not on the sub-configs.

## Quantization + LoRA Training

In most cases, a quantized model does not support training, but it does support LoRA training with the base model frozen, enabling training of large models with very little VRAM.

Methods usable for quantization + LoRA training are listed in the last column of the [methods table](#supported-quantization-methods). There are two ways to do it.

### Approach 1: Train with a pre-quantized base model

The training script points `--model_id_with_origin_paths` at the pre-quantized model:

```bash
accelerate launch examples/.../train.py \
  --model_id_with_origin_paths "DiffSynth-Studio/MiniMax-H3-NF4:minimax-h3-fl2va-nf4.safetensors,..." \
  --lora_base_model "dit" \
  --lora_target_modules "attn.qkv_proj,attn.out_proj,mlp.fc1,mlp.fc2" \
  --lora_rank 32 \
  --output_path "./models/train/xxx-nf4"
```

### Approach 2: Online quantization with `--quant_options`

If no pre-quantized weights are available, use `--quant_options` to quantize the loaded models online at training startup. The value is a semicolon-separated list of entries, each formatted as `<model_string>:<method>[/<exclude_modules>]`:

- `<model_string>`: the model to quantize; must match the entry in `--model_paths` / `--model_id_with_origin_paths` exactly.
- `<method>`: the quantization method name, see the [methods table](#supported-quantization-methods).
- `<exclude_modules>`: optional, a comma-separated list of layer names kept in full precision.

Here is quantized LoRA training for Z-Image-Turbo (full script at `examples/z_image/model_training/special/quant_training/Z-Image-Turbo-bitsandbytes_nf4.sh`):

```bash
accelerate launch examples/z_image/model_training/train.py \
  --model_id_with_origin_paths "Tongyi-MAI/Z-Image-Turbo:transformer/*.safetensors,Tongyi-MAI/Z-Image-Turbo:text_encoder/*.safetensors,Tongyi-MAI/Z-Image-Turbo:vae/diffusion_pytorch_model.safetensors" \
  --quant_options "Tongyi-MAI/Z-Image-Turbo:transformer/*.safetensors:bitsandbytes_nf4;Tongyi-MAI/Z-Image-Turbo:text_encoder/*.safetensors:bitsandbytes_nf4" \
  --lora_base_model "dit" \
  --lora_target_modules "to_q,to_k,to_v,to_out.0,w1,w2,w3" \
  --lora_rank 32 \
  --use_gradient_checkpointing \
  --output_path "./models/train/Z-Image-Turbo_quant_lora"
```

Above, NF4 quantization is enabled for both the DiT and the text encoder. Modules that do not participate in training, such as `text_encoder` and `vae`, can be quantized freely; the trained `dit` can only be quantized under LoRA training, and the method must support LoRA training β€” specifying a non-differentiable method makes training fail immediately with an error.

When training from local weights, use `--model_paths` (JSON) instead, and make `<model_string>` correspond to its entries. A model made of several files is a JSON list in `--model_paths`, and that list must be written out **as a whole** in `--quant_options`:

```bash
accelerate launch examples/z_image/model_training/train.py \
  --model_paths '[["models/Tongyi-MAI/Z-Image-Turbo/transformer/diffusion_pytorch_model-00001-of-00003.safetensors", "models/Tongyi-MAI/Z-Image-Turbo/transformer/diffusion_pytorch_model-00002-of-00003.safetensors", "models/Tongyi-MAI/Z-Image-Turbo/transformer/diffusion_pytorch_model-00003-of-00003.safetensors"], ["models/Tongyi-MAI/Z-Image-Turbo/text_encoder/model-00001-of-00003.safetensors", "models/Tongyi-MAI/Z-Image-Turbo/text_encoder/model-00002-of-00003.safetensors", "models/Tongyi-MAI/Z-Image-Turbo/text_encoder/model-00003-of-00003.safetensors"], "models/Tongyi-MAI/Z-Image-Turbo/vae/diffusion_pytorch_model.safetensors"]' \
  --tokenizer_path "models/Tongyi-MAI/Z-Image-Turbo/tokenizer/" \
  --quant_options '["models/Tongyi-MAI/Z-Image-Turbo/transformer/diffusion_pytorch_model-00001-of-00003.safetensors", "models/Tongyi-MAI/Z-Image-Turbo/transformer/diffusion_pytorch_model-00002-of-00003.safetensors", "models/Tongyi-MAI/Z-Image-Turbo/transformer/diffusion_pytorch_model-00003-of-00003.safetensors"]:bitsandbytes_nf4;["models/Tongyi-MAI/Z-Image-Turbo/text_encoder/model-00001-of-00003.safetensors", "models/Tongyi-MAI/Z-Image-Turbo/text_encoder/model-00002-of-00003.safetensors", "models/Tongyi-MAI/Z-Image-Turbo/text_encoder/model-00003-of-00003.safetensors"]:bitsandbytes_nf4' \
  --lora_base_model "dit" \
  --lora_target_modules "to_q,to_k,to_v,to_out.0,w1,w2,w3" \
  --lora_rank 32 \
  --use_gradient_checkpointing \
  --output_path "./models/train/Z-Image-Turbo_quant_lora"
```

Naming only one file of the list, or changing the file order, will not match. If `No quant option matches ...` is printed at startup, that model matched no quant option and is loaded at its original precision; compare your model string against the parsed options printed alongside it.

With `exclude_modules` to keep quantization-sensitive layers in full precision:

```bash
  --quant_options "MiniMaxAI/MiniMax-H3:FL2VA/transformer/model*.safetensors:bitsandbytes_nf4/time_embedder.proj_in,time_embedder.proj_out,video_patch_proj,audio_patch_proj"
```

> `--quant_options` always uses `mode="dynamic"` and does not expose advanced options such as `backend_config_kwargs` or mixed quantization. For those, use Approach 1: save quantized weights with `save_quantized_model` first, then train with the pre-quantized base model.

### Shared notes

- During training, the quantized base model stays frozen; only the LoRA branches are updated, so what gets saved is fp-precision LoRA weights.
- For inference, load as "quantized base model + LoRA": first load the quantized base model as described in [Loading Pre-quantized Weights](#loading-pre-quantized-weights), then `pipe.load_lora(pipe.dit, "epoch-x.safetensors")`.
- Prefer Approach 1 for large models: online quantization has to load the full fp weights first, which makes startup slow and peak memory high.

## Custom Quantization Backends

If the built-in methods don't meet your needs, you can implement your own quantization backend. See [Integrating a Quantization Backend](../Developer_Guide/Integrating_Quantization_Backend.md) for the full walkthrough with a runnable toy INT9 example, and the [`diffsynth.core.quant` API documentation](../API_Reference/core/quant.md#extension-interface-custom-backends) for the full interface signatures and contracts.

## Combining Quantization with VRAM Management

Quantization and [VRAM management](./VRAM_management.md) address different levels of the problem and can be enabled together:

- Quantization reduces **the storage size of each layer**, e.g. NF4 is about 1/4 of bf16.
- VRAM management decides **which layers stay in VRAM right now**, loading the rest from RAM/disk on demand.

Combining both can further reduce the VRAM required for inference: first compress weights to 4bit/8bit, then use `vram_limit` to split the compressed model between VRAM and RAM.

```python
from diffsynth.pipelines.z_image import ZImagePipeline, ModelConfig
from diffsynth.core.quant import QuantizeConfig
import torch

vram_config = {
    "offload_dtype": torch.bfloat16,
    "offload_device": "cpu",
    "onload_dtype": torch.bfloat16,
    "onload_device": "cpu",
    "preparing_dtype": torch.bfloat16,
    "preparing_device": "cuda",
    "computation_dtype": torch.bfloat16,
    "computation_device": "cuda",
}
pipe = ZImagePipeline.from_pretrained(
    torch_dtype=torch.bfloat16,
    device="cuda",
    model_configs=[
        ModelConfig(
            model_id="Tongyi-MAI/Z-Image", origin_file_pattern="transformer/*.safetensors",
            quantize=QuantizeConfig(method="bitsandbytes_nf4"), **vram_config,
        ),
        ModelConfig(model_id="Tongyi-MAI/Z-Image-Turbo", origin_file_pattern="text_encoder/*.safetensors", **vram_config),
        ModelConfig(model_id="Tongyi-MAI/Z-Image-Turbo", origin_file_pattern="vae/diffusion_pytorch_model.safetensors", **vram_config),
    ],
    tokenizer_config=ModelConfig(model_id="Tongyi-MAI/Z-Image-Turbo", origin_file_pattern="tokenizer/"),
    vram_limit=torch.cuda.mem_get_info("cuda")[1] / (1024 ** 3) - 0.5,
)
```

Two notes:

- Parameters such as `offload_dtype` / `onload_dtype` in `vram_config` apply to unquantized parameters; the storage precision of quantized layers is determined by the quantization method and is unaffected by these parameters.
- **Disk Offload is incompatible with online quantization.** Disk Offload reads parameters from disk layer by layer, requiring that the quantized parameters are already saved on disk, so you cannot run Disk Offload first and then quantize online. To combine quantization with Disk Offload, first save the quantized weights following the [Best Practices](#best-practices) workflow, then load the pre-quantized checkpoint.

## Best Practices

Taking MiniMax-H3 as an example, this section shows the complete workflow from saving quantized weights to loading for inference.

### Step 1: Save the Quantized Weights

The MiniMax-H3 FL2VA base model is about 66G (bf16), and online NF4 quantization would be very slow. We recommend quantizing and saving once, then loading repeatedly. `save_quantized_model` returns the hash of the saved file:

```python
from diffsynth.core.loader import ModelConfig
from diffsynth.core.quant import QuantizeConfig
from diffsynth.utils.quant.serialization import save_quantized_model

quantize = QuantizeConfig(
    method="bitsandbytes_nf4",
    mode="dynamic",
    exclude_modules=[
        "time_embedder.proj_in", "time_embedder.proj_out",
        "video_patch_proj", "audio_patch_proj", "condition_proj",
        "final_layer.video_out", "final_layer.audio_out",
    ],
)
model_config = ModelConfig(
    model_id="MiniMaxAI/MiniMax-H3",
    origin_file_pattern="FL2VA/transformer/model*.safetensors",
    quantize=quantize,
)
model_hash = save_quantized_model(model_config, "models/MiniMax-H3-NF4/minimax-h3-fl2va-nf4.safetensors")
print(model_hash)
```

`exclude_modules` lists the layers sensitive to quantization (timestep embeddings, input/output projections), kept in bf16 to preserve quality.

### Step 2: Register the Hash in the Model Config

The framework identifies the model type and quantization config by file hash. The registration entry is as follows; `quant_config` must match the `QuantizeConfig` used when saving, plus `load_prequantized: True`:

```python
config_entry = {
    # Example: ModelConfig(model_id="...", origin_file_pattern="minimax-h3-fl2va-nf4.safetensors")
    "model_hash": model_hash,
    "model_name": "minimax_h3_dit",
    "model_class": "diffsynth.models.minimax_h3_dit.MiniMaxH3DiT",
    "quant_config": {"method": "bitsandbytes_nf4", "load_prequantized": True, "exclude_modules": ["time_embedder.proj_in", "time_embedder.proj_out", "video_patch_proj", "audio_patch_proj", "condition_proj", "final_layer.video_out", "final_layer.audio_out"]},
}
```

There are two ways to register:

**Option 1: register dynamically in Python code (recommended, plug-and-play).** No framework changes needed β€” add the entry to `MODEL_CONFIGS` before loading the model, effective for the current process:

```python
from diffsynth.configs import MODEL_CONFIGS

MODEL_CONFIGS.append(config_entry)
```

**Option 2: write it into the config file (permanent).** Add the entry above to the `MODEL_CONFIGS` list in `diffsynth/configs/model_configs.py`, so you no longer need to register it manually. If your quantized weights are publicly released, you are also welcome to submit the entry to us as a PR, so other users can load them directly.

### Step 3: Load for Inference

Once registered, loading your own quantized weights works just like loading an ordinary model, without passing `quantize`:

```python
import torch
from diffsynth.pipelines.minimax_h3_audio_video import MiniMaxH3Pipeline, ModelConfig
from diffsynth.utils.data.audio_video import write_video_audio

vram_config = {
    "offload_dtype": torch.bfloat16,
    "offload_device": "cpu",
    "onload_dtype": torch.bfloat16,
    "onload_device": "cpu",
    "preparing_dtype": torch.bfloat16,
    "preparing_device": "cuda",
    "computation_dtype": torch.bfloat16,
    "computation_device": "cuda",
}
pipe = MiniMaxH3Pipeline.from_pretrained(
    torch_dtype=torch.bfloat16,
    device="cuda",
    model_configs=[
        ModelConfig(path="models/MiniMax-H3-NF4/minimax-h3-fl2va-nf4.safetensors", **vram_config),
        ModelConfig(model_id="MiniMax/MiniMax-H3", origin_file_pattern="FL2VA/text_encoder/model*.safetensors", **vram_config),
        ModelConfig(model_id="MiniMax/MiniMax-H3", origin_file_pattern="FL2VA/video_vae/source/model.safetensors", **vram_config),
        ModelConfig(model_id="MiniMax/MiniMax-H3", origin_file_pattern="FL2VA/audio_vae/model.safetensors", **vram_config),
    ],
    processor_config=ModelConfig(model_id="MiniMax/MiniMax-H3", origin_file_pattern="FL2VA/processor/"),
    vram_limit=torch.cuda.mem_get_info("cuda")[1] / (1024 ** 3) - 2,
)
prompt = "A girl is very happy, she is speaking in english: 'I enjoy working with Diffsynth-Studio, it's a perfect framework.'"
video, audio = pipe(prompt=prompt, height=480, width=832, num_frames=124, num_inference_steps=50, seed=0)
write_video_audio(video=video, audio=audio, output_path="t2va.mp4", fps=24, audio_sample_rate=32000)
```

We have uploaded the NF4 quantized weights of MiniMax-H3 to ModelScope ([DiffSynth-Studio/MiniMax-H3-NF4](https://modelscope.cn/models/DiffSynth-Studio/MiniMax-H3-NF4)), so you can use them directly without quantizing yourself. If you want to upload your own saved quantized weights to ModelScope, you can use the modelscope SDK:

```python
from modelscope.hub.api import HubApi

api = HubApi()
api.login("your_access_token")
api.create_model("your-username/MiniMax-H3-NF4", visibility=1)
api.upload_folder(
    repo_id="your-username/MiniMax-H3-NF4",
    folder_path="models/MiniMax-H3-NF4",
    repo_type="model",
)
```