W4A4 MXFP4 Quantization¶
Overview¶
W4A4 MXFP4 (Microscaling FP4) quantizes both weights and activations to FP4 (float4_e2m1fn_x2, packed 2 values per byte) using the OCP MX format: groups of 32 K-dimension elements share a single float8_e8m0fnu exponent scale.
On Ascend, single-scale MXFP4 also supports Universal Optimal Scaling (UOS, Qmax=7.25), described in the single-scale scaling section below.
vLLM-Omni provides two quantization methods with different scale structures:
| Method | Scale structure | Mode | Use case |
|---|---|---|---|
mxfp4 | Single-scale (per-32 fine only) | Online + pre-quantized single-scale weights | Online baseline, or an existing single-scale checkpoint in the layout below |
mxfp4_dualscale | Dual-scale (fine per-32 + coarse per-512 + per-channel mul_scale) | Online + Offline | W4A4 with dual-level scales; offline mode loads calibrated Smooth tensors |
Existing W4A4-only DualScale deployments
For W4A4-only deployments, the existing mxfp4_dualscale offline mode uses a pre-quantized checkpoint produced by msModelSlim. Offline checkpoints load calibrated mul_scale tensors from disk. Quality depends on the model, calibration recipe and BF16 fallback policy; evaluate the checkpoint against your quality target before deployment.
W4A8 layer/step fallback requires single-scale mxfp4, online or offline. DualScale supports W4A4 and BF16 routing only.
Online single-scale ≠ Offline dual-scale
mxfp4_dualscale offline mode uses NPUMxfp4DualScaleLinearMethod: fine scale (per-32 K), coarse scale (per-512 K), and per-input-channel mul_scale from calibration — all loaded from the checkpoint. mxfp4_dualscale online mode uses NPUMxfp4DualScaleOnlineLinearMethod: dual-level scales computed on the fly from BF16 weights; no calibration mul_scale is available. Loading an offline checkpoint with the online method (or vice versa) will produce incorrect results or shape errors.
Hardware Support¶
The following matrix describes these vLLM-Omni implementations, rather than hardware support for the MXFP4 format in general.
| Device | Single-scale online OCP | Single-scale offline | UOS / W4A8 fallback | DualScale |
|---|---|---|---|---|
| Ascend NPU (Atlas 950 A5) | ✅ | ✅ | ✅ | ✅ |
| AMD ROCm (gfx950 / MI355X) | ✅ | ❌ | ❌ | ❌ |
| Other AMD architectures | ❌ | ❌ | ❌ | ❌ |
| NVIDIA GPU | ❌ | ❌ | ❌ | ❌ |
| Intel XPU | ❌ | ❌ | ❌ | ❌ |
Legend: ✅ supported by the implementation, ❌ unsupported. ROCm uses the existing AITER online path and requires mxfp4_scale_alg=0.
Model Type Support¶
Diffusion Model (Wan2.2)¶
| Model | Online | Offline | Notes |
|---|---|---|---|
| Wan2.2-T2V-A14B | mxfp4 / mxfp4_dualscale | mxfp4 / mxfp4_dualscale | Cascade experts resolve checkpoint metadata independently; explicit global runtime policy applies to both |
| Wan2.2-I2V-A14B | mxfp4 / mxfp4_dualscale | mxfp4 / mxfp4_dualscale | Per-expert configuration uses the same interface as T2V; validate I2V checkpoints separately |
| Wan2.2-TI2V-5B | Requires validation | Requires validation | This guide does not establish checkpoint compatibility or generation quality for TI2V-5B |
Both methods can be integrated with single-transformer and cascade models. This alone does not establish support or quality for every model variant:
mxfp4: single-scale; online and pre-quantized weights, with optional W4A8 fallback on NPUmxfp4_dualscale: dual-scale; online and offline, with BF16 fallback
The existing merge_mxfp4_dualscale_checkpoint.py produces offline mxfp4_dualscale checkpoints. Single-scale weights must satisfy the separate layout described below; changing a DualScale checkpoint's method name to mxfp4 does not convert its weights. Prepare single-scale checkpoints with external tooling. Real exported checkpoint compatibility and model quality require validation of that checkpoint; the synthetic tests do not establish UOS export or video acceptance.
Per-layer BF16 fallback in offline cascade models
An A14B offline DualScale checkpoint uses quant_method: mxfp4_dualscale. Most linear layers are stored as W4A4 MXFP4 DualScale; precision-sensitive layers retain their original BF16 weights and are listed in ignored_layers inside each transformer's config.json. The two transformers may have different ignored_layers sets — the pipeline reads each transformer's own config.json and resolves the storage format and BF16 layer policy independently.
Configuration¶
W4A8 fallback at selected layers and denoising steps (Ascend NPU)¶
For single-scale mxfp4, set w4a8_fallback_steps to a list of zero-based request denoising loop indices (not scheduler timestep values). Set w4a8_fallback_layers to exact runtime Linear paths, for example ["blocks.10.attn1.to_qkv", "blocks.12.ffn.net_2"]. A selected layer uses W4A8 at every step; at a selected step, all other quantized layers also use W4A8. The rules are combined with OR. W4A8 uses MXFP8 activations with MXFP4 weights. Omitting both lists or passing [] keeps the existing W4A4 behavior. Existing ignored_layers take priority: BF16 > (layer OR step) W4A8 > W4A4. DualScale's existing leading BF16 block rule is unchanged; it does not enable W4A8.
from vllm_omni import Omni
omni = Omni(
model="<Wan2.2-T2V-A14B-Diffusers>",
quantization_config={
"method": "mxfp4",
"w4a8_fallback_layers": ["blocks.10.attn1.to_qkv"],
"w4a8_fallback_steps": [0, 1, 38, 39],
},
)
Both policies also work with offline single-scale mxfp4. Any nonempty W4A8 list with mxfp4_dualscale, online or offline, raises an explicit error at configuration construction. Empty lists preserve its existing W4A4 path. Each expert's checkpoint still determines its serialized format and BF16 layer routing. A serialized checkpoint config is the complete storage contract: ignored_layers, or its modules_to_not_convert alias, selects the BF16 layers, and omitting both means no explicit BF16 layers. A method-only checkpoint value such as "mxfp4" or {"quant_method": "mxfp4"} carries no storage policy and leaves a caller's online ignored_layers unchanged. An explicitly supplied quantization config retains its W4A8 layer and step lists when the loader rebuilds either expert's config from disk, including empty lists that disable checkpoint policies. Within an explicit runtime config, an omitted W4A8 list defaults to []; individual omitted fields do not inherit saved policies. Without an explicit runtime config, auto-detection uses the saved checkpoint policies.
Layer names are relative to each expert (blocks.10..., without a transformer. or transformer_2. prefix). A global config applies the same list to both experts; per-component transformer/transformer_2 configs can select different lists. Wan selects these expert names exactly: a transformer entry does not match transformer_2; an omitted expert uses default if set. A component mapped to null (or an unmatched component without a default) stays unquantized and requires a BF16 checkpoint; loading a quantized checkpoint for that disabled component raises an error. A top-level quantization_config=None retains checkpoint auto-detection. Names are exact, with no regex, wildcard, block expansion, or checkpoint-name remapping. Wan validates them against its runtime Linear modules and logs the selected paths/count and BF16 overrides per PP rank. Use attn1.to_qkv for fused self-attention; attn1.to_q, to_k, or to_v checkpoint names raise an error. Cross-attention attn2.to_q/to_k/to_v remains separate. Layer-only fallback does not require a denoise ForwardContext.
The text-to-video example accepts the JSON directly:
ASCEND_RT_VISIBLE_DEVICES=0,1,2,3 \
python examples/offline_inference/text_to_video/text_to_video.py \
--model /path/to/Wan2.2-T2V-A14B-Diffusers \
--quantization-config '{"method":"mxfp4","w4a8_fallback_steps":[0,2]}' \
--tensor-parallel-size 1 --ulysses-degree 2 --cfg-parallel-size 2 \
--enforce-eager --height 480 --width 832 --num-frames 81 \
--num-inference-steps 40 --seed 42 --output wan22-w4a8.mp4
Use either --quantization or --quantization-config in this example. Serving accepts the same JSON through --diffusion-quantization-config.
The list uses one request's loop index across both high- and low-noise experts, and both CFG branches see the same index. A new request starts at step zero. Indices outside the request's step range are never selected. A pipeline must publish ForwardContext.denoise_step_idx; requesting step fallback without that context raises an error instead of silently ignoring the list.
Single-scale W4A4 and W4A8 reuse the same prepared weight and weight_scale. Online loading quantizes the original BF16/FP16 weight once; offline loading packs the numeric FP4 values and reshapes the saved E8M0 scale without requantizing the weight. Only activation precision changes during forward. There is no DualScale decoding, second W4 representation or w4a8_weight / w4a8_weight_scale cache. No weight quantization runs inside the denoising loop.
The mixed FP8/FP4 GEMM uses BF16 output and a matching BF16 row bias for compatibility with the target A5 runtime. An FP16 caller receives that result converted back to FP16; its output shape and dtype are preserved.
The single-scale fallback validation target is eager Wan2.2 T2V A14B on A5. Quality scores, optimal fallback lists, real UOS exports and additional compile/offload combinations require separate evaluation. Existing W4A4-only DualScale still needs complete 512-channel input groups; use TP=1 with sequence/CFG parallelism for its initial multi-card run.
mxfp4 — Pre-Quantized Single-Scale Weights (Ascend NPU)¶
Set the following fields in each transformer's config.json when a single-scale checkpoint uses calibrated Smooth scaling:
{
"quantization_config": {
"quant_method": "mxfp4",
"is_checkpoint_mxfp4_serialized": true,
"require_smooth_scale": true,
"ignored_layers": []
}
}
Each quantized Linear must provide tensors under its model parameter name:
| Tensor | Checkpoint layout | Meaning |
|---|---|---|
weight | (N, K) numeric FP4 values | Packed to FP4 after loading |
weight_scale | (N, ceil(K / 32)), uint8 | Raw E8M0 exponent bytes, required |
mul_scale | (K,), FP32 | Smooth pre-scale, required when declared above |
Both W4A4 and W4A8 quantize x * mul_scale. The Smooth tensor is converted to the layer's activation dtype after loading. Row-parallel layers load the corresponding input-channel shard. Pre-fused QKV weights must use one shared Smooth tensor for the fused projection.
For backward compatibility, require_smooth_scale defaults to false: an absent tensor uses identity scaling, while a present tensor is still loaded. Without a declaration, an absent Smooth tensor cannot be distinguished from a legacy unsmoothed checkpoint. Calibrated checkpoints should declare require_smooth_scale: true so missing or misnamed Smooth tensors fail loading; incorrect tensor shapes, non-floating tensors, NaN/Inf and nonpositive values also fail. Values that overflow or underflow to zero in the activation dtype are rejected during weight processing. The flag requires serialized single-scale weights and is rejected for online quantization. It does not make DualScale scales optional or change missing-parameter policies for other backends. Either the checkpoint or an explicit caller can require Smooth: a true declaration is retained when per-expert metadata rebuilds the configuration, including when the other source omits the flag or sets it to false.
Prepare the canonical numeric FP4 checkpoint before inference using external tooling. Runtime loading does not parse producer-specific export formats. Retain the producer recipe to identify the weight-generation algorithm and Smooth calibration; tensor storage alone does not establish UOS provenance.
mxfp4 — Single-Scale Block Scaling (OCP / UOS)¶
mxfp4_scale_alg selects the MXFP4 block-scaling method on NPU. UOS is a scaling rule, not a separate activation dtype or an activation-only algorithm. Both choices use FP4 E2M1 values with shared E8M0 scales.
| Mode | Weight preparation | A4 activation quantization | W4A8 fallback |
|---|---|---|---|
| Online | Selected scaling method, once at loading | Selected scaling method | Same W4; OCP MXFP8 A8 |
| Offline | Load existing W4 and scales unchanged | Selected scaling method | Same W4; OCP MXFP8 A8 |
The available settings are:
| Setting | MXFP4 scaling method | NPU parameters |
|---|---|---|
0 (default) | OCP MX | scale_alg=0 |
2 | UOS (Qmax=7.25) | scale_alg=2, dst_type_max=7.25 |
Universal Optimal Scaling (UOS, Qmax=7.25) is derived in MXAttention: Data-Free Optimal Scaling and Pre-Normalization Quantization for MXFP4 Attention, Section 4.1. The value 7.25 is the boundary used to select the shared power-of-two scale; the maximum finite E2M1 value remains 6. This option applies UOS to Linear quantization. It does not implement the paper's full MXAttention pipeline or Pre-Normalization Quantization (PNQ).
Both use FP4 E2M1 with E8M0 scales, axis=-1, block_size=32, round_mode="rint". W4A8 always quantizes the activation to float8_e4m3fn with scale_alg=0 and the same grouping/rounding, without UOS parameters. A4 and A8 share the prepared W4 and scale. UOS is currently NPU-only; ROCm retains its existing AITER online algorithm.
An explicit UOS online configuration is {"method": "mxfp4", "mxfp4_scale_alg": 2}. For offline checkpoints the setting affects only A4 activations. Existing weights are never requantized: a UOS checkpoint can run UOS W4 + UOS A4 or UOS W4 + OCP A4, and both fall back to UOS W4 + OCP A8. Record the weight-generation algorithm separately from the runtime activation setting. Smooth remains active with either setting. The UOS configuration explicitly selects 2; attention computation is unchanged.
mxfp4 — Single-Scale Online Mode¶
Online mode quantizes BF16/FP16 weights once at loading and computes activation scales during forward. Each block of 32 K elements shares one E8M0 scale; no calibrated mul_scale is available. An explicit global quantization config applies to both cascade experts. Per-component configs can select different policies; checkpoint auto-detection resolves each expert independently.
from vllm_omni import Omni
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
omni = Omni(model="<your-model>", quantization="mxfp4")
outputs = omni.generate(
"A cat sitting on a windowsill",
OmniDiffusionSamplingParams(num_inference_steps=50),
)
# Single-transformer or cascade model — same command
python examples/offline_inference/text_to_video/text_to_video.py --model <your-model> --quantization mxfp4
# Online serving
vllm serve <your-model> --omni --quantization mxfp4
mxfp4_dualscale — DualScale Online Mode¶
Online DualScale mode computes both fine and coarse scales on the fly from BF16 weights using npu_dynamic_dual_level_mx_quant. Applies equally to single-transformer and cascade (A14B) models with compatible Linear layers. Compare quality and performance using the intended model and workload.
The default configuration keeps the leading 5 transformer blocks in BF16 (num_bf16_fallback_layers=5). This is an implementation default; select and validate the BF16 policy against your own quality target.
python examples/offline_inference/text_to_video/text_to_video.py --model <your-model> --quantization mxfp4_dualscale
If accuracy debugging identifies additional precision-sensitive layers, they can be pinned to BF16 via the Python API:
omni = Omni(
model="<your-model>",
quantization_config={
"method": "mxfp4_dualscale",
"ignored_layers": ["blocks.10.attn1.to_qkv"], # explicit per-layer override
},
)
BF16 fallback routing in online mode applies two rules in priority order:
ignored_layers(explicit per-layer override): a listed runtime Linear module is kept in BF16 regardless of block index. Use exact module paths, not arbitrary block prefixes or names ending in.weight.num_bf16_fallback_layers(coarse leading-block rule): the first N transformer blocks (blocks.0…blocks.N-1) fall back to BF16. Defaults to5. Layers outsideblocks.N.*are quantized unless explicitly listed inignored_layers.
mxfp4_dualscale — DualScale Offline Mode¶
Offline mode loads a pre-quantized DualScale checkpoint from msModelSlim. A preprocessing step converts the raw quantized output to the diffusers format expected by vLLM-Omni and injects the quantization config into each transformer/config.json so that vLLM-Omni auto-detects the offline path without a --quantization flag.
BF16 fallback layers may be interleaved anywhere in the transformer — they are not restricted to leading blocks. The merge script detects them from quant_model_description.json and writes their prefixes into ignored_layers inside config.json. At runtime, each layer's prefix is matched against ignored_layers to decide BF16 vs. MXFP4 DualScale.
Checkpoint tensor layout¶
Each quantized linear layer stores four tensors:
| Tensor | Shape | dtype | Description |
|---|---|---|---|
weight | (N, K) | float8_e4m3fn | Numeric FP4 values; packed into two values per byte after loading |
weight_scale | (N, K//32) | uint8 | Fine block scale (float8_e8m0fnu bit pattern) |
weight_dual_scale | (N, K//512, 1) | float32 | Coarse block scale |
mul_scale | (K,) | float32 | Per-input-channel smooth pre-scale (from calibration) |
BF16 fallback layers have no quantization tensors; only the original weight (and optional bias) are present, loaded directly from the base checkpoint.
Step 1 — Quantize with msModelSlim¶
msmodelslim quant \
--model_path /path/to/Wan2.2-T2V-A14B-Diffusers \
--save_path /path/to/wan2_2_t2v_quantized_raw \
--device npu \
--model_type Wan2_2 \
--config_path /path/to/wan2_2_w4a4_mxfp4_dualscale.yaml \
--trust_remote_code True
After this step, --save_path contains raw quantized safetensors files, scale files, and a metadata JSON (quant_model_description*.json).
For cascade MoE models (T2V-A14B, I2V-A14B), msModelSlim outputs two subdirectories: high_noise_model/ (transformer) and low_noise_model/ (transformer_2).
Step 2 — Preprocess with merge_mxfp4_dualscale_checkpoint.py¶
The script (vllm_omni/quantization/tools/merge_mxfp4_dualscale_checkpoint.py):
- Copies the original diffusers model to
--output-path(VAE, text encoder, scheduler, etc. are preserved). - Remaps tensor names from msModelSlim convention to diffusers convention and strips
.linear./.div.wrappers added by the quantization tool. - Overlays MXFP4 tensors (weight, fine/coarse scales,
mul_scale) onto the BF16 base checkpoint. Non-quantized layers keep their original BF16 weights. - Detects all linear layers that remain in BF16 and writes their prefixes into
ignored_layersinconfig.json. - Injects
quantization_configso vLLM-Omni auto-detects offline MXFP4 DualScale.
For cascade MoE models, steps 2–5 run separately for each transformer.
python vllm_omni/quantization/tools/merge_mxfp4_dualscale_checkpoint.py \
--model-type Wan2.2-T2V-A14B \
--original-model /path/to/Wan2.2-T2V-A14B-Diffusers \
--quant-path /path/to/wan2_2_t2v_quantized_raw \
--output-path /path/to/Wan2.2-T2V-A14B-MXFP4-DualScale
| Argument | Description |
|---|---|
--model-type | Model variant: Wan2.2-T2V-A14B or Wan2.2-I2V-A14B |
--original-model | Root directory of the original BF16 diffusers model |
--quant-path | Root directory of the msModelSlim quantized output |
--output-path | Output directory for the merged model (created by the script) |
The script outputs a complete diffusers model directory at --output-path, with each transformer subfolder containing:
diffusion_pytorch_model.safetensors— MXFP4 weights + scale tensors, with BF16 fallback layers from the base checkpointconfig.json— original transformer config withquantization_configinjectedquant_model_description.json— quantization metadata (reference only)
The quantization_config injected into config.json for each transformer:
{
"quant_method": "mxfp4_dualscale",
"is_checkpoint_serialized": true,
"ignored_layers": [
"blocks.0.attn1.to_qkv",
"blocks.0.attn1.to_out",
"proj_out"
]
}
ignored_layers lists every linear layer that retains its original BF16 weight, using vllm-omni runtime module paths (QKV-fused, FFN underscored, to_out unindexed). The exact entries are determined by the quantization tool (msModelSlim) and may differ between transformer and transformer_2 in a cascade model.
Step 3 — Serve¶
python examples/offline_inference/text_to_video/text_to_video.py --model /path/to/Wan2.2-T2V-A14B-MXFP4-DualScale
# Online serving
vllm serve /path/to/Wan2.2-T2V-A14B-MXFP4-DualScale --omni
Note
No --quantization flag is needed for offline mode. The preprocessing script injects quantization_config into each transformer/config.json, which vLLM-Omni reads automatically to activate the correct offline path.
Parameters¶
mxfp4 (single-scale, online + offline)¶
| Parameter | Type | Default | Description |
|---|---|---|---|
method | str | — | "mxfp4" |
mxfp4_scale_alg | int | 0 | MXFP4 block scaling: 0 OCP MX, 2 UOS (Qmax=7.25, NPU only). Online: W4 and A4; offline: A4 only. A8 remains OCP MXFP8 |
is_checkpoint_mxfp4_serialized | bool | false | Load pre-quantized single-scale weights and scales; declared in checkpoint config.json |
require_smooth_scale | bool | false | Require calibrated Smooth tensors for offline single-scale weights |
ignored_layers | list[str] | [] | Exact runtime Linear module paths to keep in BF16 |
w4a8_fallback_layers | list[str] | [] | Exact runtime Linear paths that always use A8; NPU only |
w4a8_fallback_steps | list[int] | [] | Zero-based denoise steps that use A8; NPU only |
mxfp4_dualscale (dual-scale, online + offline)¶
| Parameter | Type | Default | Description |
|---|---|---|---|
method | str | — | "mxfp4_dualscale" |
is_checkpoint_serialized | bool | False | True for offline DualScale checkpoints; auto-set from config.json when using the preprocessing script |
ignored_layers | list[str] | [] | Exact runtime Linear module paths to keep in BF16. Works in both modes: offline — populated by the merge script for interleaved sensitive layers; online — user-supplied for explicit per-layer precision override |
num_bf16_fallback_layers | int | 5 | Online mode only: leading N transformer blocks (blocks.0 … blocks.N-1) kept in BF16. Applied after ignored_layers; ignored in offline mode. Validate the default of 5 against the intended model and quality target |
w4a8_fallback_layers | list[str] | [] | Must remain empty; DualScale does not support W4A8 fallback |
w4a8_fallback_steps | list[int] | [] | Must remain empty; DualScale does not support W4A8 fallback |
BF16 fallback priority (online mode)¶
for each linear layer:
if prefix in ignored_layers → BF16 (explicit override, highest priority)
elif block_idx < num_bf16_fallback_layers → BF16 (coarse leading-block rule)
else → MXFP4 DualScale online
Layers outside blocks.N.* (e.g. condition_embedder.*) are always quantized unless they appear in ignored_layers.
Validation and Notes¶
-
Online single-scale (
mxfp4) quantizes BF16 weights at load time usingnpu_dynamic_mx_quant(single-scale). No calibrationmul_scaleis available — all output partitions receive an identity pre-scale. Offline single-scale checkpoints use the canonical numeric FP4 layout described above. -
Online dual-scale (
mxfp4_dualscale,is_checkpoint_serialized=False) quantizes BF16 weights usingnpu_dynamic_dual_level_mx_quant(fine + coarse scales computed on the fly). No calibrationmul_scale; leading blocks or explicitignored_layersstay in BF16 for accuracy. -
Offline dual-scale (
mxfp4_dualscale,is_checkpoint_serialized=True) — loads four tensors per quantized layer: FP4 weight, fine scale (uint8reinterpreted asfloat8_e8m0fnu), coarse scale (float32), and per-input-channelmul_scale(float32). BF16 fallback layers have no quantization tensors and are routed viaignored_layers. -
Scale dtype: fine scales are stored as
uint8in safetensors (same bit layout asfloat8_e8m0fnu) and reinterpreted at load time without a lossy float32 round-trip. -
Cascade model configuration: checkpoint auto-detection resolves each expert's own
config.jsonindependently; the first expert's detected config is not reused as the second expert's policy. An explicit global runtime config applies to both experts, while per-component configs select policies by the exacttransformer/transformer_2names. Serialized checkpoint metadata still determines each expert's storage format and BF16 layers. -
Self-attention QKV fusion: Q, K, V projection weights are fused into a single
QKVParallelLinearlayer at runtime.ignored_layersentries use the fused name (attn1.to_qkv), written automatically by the merge script. -
FP4 has fewer representable values than FP8, so evaluate quantization error and generation quality for the chosen recipe. Offline Smooth scaling and BF16 fallback can help with sensitive layers.
num_bf16_fallback_layersapplies only to online DualScale.
Adapting MXFP4 for a New Model¶
This section is aimed at developers who want to add MXFP4 support to a model other than Wan2.2. The three integration points are: (1) discovering the correct runtime layer names, (2) wiring ignored_layers into the model, and (3) writing a merge script for offline checkpoints.
Step 1 — Discover runtime layer names¶
ignored_layers entries use exact runtime Linear module paths, without .weight. Inspect the actual transformer instance in model initialization or a developer harness; Omni does not expose an omni.pipeline.transformer API. For a transformer instance named transformer:
from vllm.model_executor.layers.linear import LinearBase
for name, module in transformer.named_modules():
if isinstance(module, LinearBase):
print(name)
Compare these module paths against the corresponding diffusers checkpoint keys (safetensors.safe_open or torch.load) to identify any renames your model applies. Common patterns that differ in Wan2.2 (and may appear in other models):
| Diffusers checkpoint name | vllm-omni runtime name | Reason |
|---|---|---|
attn1.to_q, attn1.to_k, attn1.to_v | attn1.to_qkv | Self-attention Q/K/V fused into QKVParallelLinear |
ffn.net.0.proj | ffn.net_0.proj | Dots in sub-module names replaced with underscores |
ffn.net.2 | ffn.net_2 | Same underscore rule |
to_out.0 | to_out | Sequential index stripped |
If your model has different fusion patterns, inspect packed_modules_mapping on the model class — this dict records how checkpoint keys are mapped to fused runtime parameters.
Partial QKV fallback is not allowed
If your model fuses Q, K, V into a single layer, ignored_layers must name the fused runtime module (for Wan, attn1.to_qkv). The source checkpoint must keep all three or none in BF16. A partial fallback (e.g. to_q in BF16 but to_k, to_v quantized) cannot be expressed at runtime because they share one QKVParallelLinear. The merge script enforces this and raises an error if only some of the trio appear as non-quantized.
Step 2 — Add ignored_layers to the model¶
Online mode¶
Pass ignored_layers directly in the quantization config using the runtime names discovered in Step 1. This assumes the model already passes its quantization config and module prefixes to compatible vLLM Linear layers.
omni = Omni(
model="/path/to/your-model",
quantization_config={
"method": "mxfp4_dualscale",
"ignored_layers": [
"blocks.0.attn1.to_qkv", # runtime name, not diffusers name
"blocks.0.attn1.to_out",
"blocks.0.ffn.net_0.proj",
],
},
)
# The text-to-video example accepts list-valued fields through JSON.
python examples/offline_inference/text_to_video/text_to_video.py \
--model /path/to/your-model \
--quantization-config '{"method":"mxfp4_dualscale","ignored_layers":["blocks.0.attn1.to_qkv"]}'
# Serving accepts the same configuration.
vllm serve /path/to/your-model --omni \
--diffusion-quantization-config '{"method":"mxfp4_dualscale","ignored_layers":["blocks.0.attn1.to_qkv"]}'
The num_bf16_fallback_layers coarse rule is an alternative to listing layers individually: set it to N to keep all linear layers in blocks 0 … N-1 in BF16. The right value depends on the model's sensitivity; evaluate on a validation set and pick the smallest N that meets your accuracy target.
Offline mode¶
For offline checkpoints, ignored_layers is written into each transformer's config.json by the merge script (see Step 3). No manual editing is needed if the merge script is correct. The injected block:
{
"quant_method": "mxfp4_dualscale",
"is_checkpoint_serialized": true,
"ignored_layers": [
"blocks.0.attn1.to_qkv",
"blocks.0.attn1.to_out"
]
}
Adding an offline BF16 fallback layer requires both the original BF16 weight from the base model and a matching ignored_layers entry. Regenerate the checkpoint with the revised storage policy, restoring the original weight (and bias, if present) and removing that layer's quantization tensors. Editing config.json alone does not reconstruct BF16 weights from stored FP4 values. For fused QKV, restore all three projections together. Use runtime module paths in ignored_layers.
Step 3 — Write a merge script for offline mode¶
The merge script for a new model mirrors vllm_omni/quantization/tools/merge_mxfp4_dualscale_checkpoint.py. The four things it must do:
-
Remap tensor names from the quantization tool convention to diffusers convention (strip wrappers like
.linear.,.div.; fix any prefix differences). -
Collect ignored_layers: after loading, enumerate all
*.weightkeys that have no corresponding*.weight_scale(i.e. layers the tool left in BF16). Convert diffusers names to vllm-omni runtime names (fuse QKV, rename FFN sub-modules, etc.). Write the result toconfig.json. -
Inject
quantization_configintoconfig.json: -
Save the merged safetensors and the updated
config.json.
The key helper to implement is the diffusers-to-runtime name translator (equivalent to _diffusers_to_vllm_ignored in the Wan2.2 merge script). For each non-quantized diffusers weight key, apply your model's specific renaming rules and collect the results.
Validate before serving
After producing the offline checkpoint, load it without a --quantization flag and verify that vLLM-Omni auto-detects the correct method. Check that each intended runtime Linear module resolves to the expected quantized or BF16 method and that every BF16 entry has original BF16 weights. Account for fused modules and, in online DualScale, the leading-block fallback rule; the raw length of ignored_layers alone is not a reliable layer count.