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.
Difference between quantization and FP8 in VRAM management
The FP8 in VRAM management 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 fornn.Linearlayers, 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.
Different quantization backends require the corresponding third-party libraries:
| Backend | Install Command | Project Page |
|---|---|---|
| bitsandbytes | pip install bitsandbytes |
bitsandbytes |
| torchao | pip install torchao>=0.16 |
torchao |
| comfy-kitchen | pip install comfy-kitchen |
comfy-kitchen |
Install all at once: pip install "diffsynth[quant]"
Pass quantize to any ModelConfig to enable online quantization for that model. The following code loads Z-Image's DiT with NF4 quantization:
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")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 documentation for compatibility details.
You can query all available methods and their parameters in code:
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 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 fpnn.Linearonce (keeping the quantization error). Suitable for scenarios that need standardnn.Linear; no longer saves VRAM.
target_modules/exclude_modules: filter thenn.Linearlayers 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"matchestransformer_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). Usedescribe_quant_method(method)to query the available parameters.load_prequantized: set toTruewhen the checkpoint already holds quantized weights, so they are loaded directly (see below).
Example: exclude quantization-sensitive layers and adjust NF4 backend parameters:
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:
quantize = QuantizeConfig(method="comfy_kitchen_int8_w8a8", backend_config_kwargs={"convrot_groupsize": 128})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:
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:
from diffsynth.core.quant import QuantizeConfig
ModelConfig(
path="models/z-image-nf4/transformer.safetensors",
quantize=QuantizeConfig(method="bitsandbytes_nf4", load_prequantized=True),
)To save the result of an online quantization for reuse, use save_quantized_model:
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.
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:
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=Trueon theMixedQuantizeConfigitself, not on the sub-configs.
A quantized model can have LoRA injected and trained directly, 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. The typical practice is to train with a pre-quantized base model: the training script points --model_id_with_origin_paths at the pre-quantized model:
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"- 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, then
pipe.load_lora(pipe.dit, "epoch-x.safetensors").
If the built-in methods don't meet your needs, you can implement your own quantization backend. See Integrating a Quantization Backend for the full walkthrough with a runnable toy INT9 example, and the diffsynth.core.quant API documentation for the full interface signatures and contracts.
Quantization and VRAM management 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.
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_dtypeinvram_configapply 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 workflow, then load the pre-quantized checkpoint.
Taking MiniMax-H3 as an example, this section shows the complete workflow from saving quantized weights to loading for inference.
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:
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.
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:
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:
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.
Once registered, loading your own quantized weights works just like loading an ordinary model, without passing quantize:
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), 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:
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",
)