Calibration Data Collector
It Doesn't Quantize Anything (and That's the Point)
- model
- conditioning
- calibration_path
The name is half a lie. This node doesn't quantize a single weight - it collects the calibration data that lets some other tool quantize your model well. That's not a bug; it's the whole design. Most people never touch this thing: they grab a GGUF or fp8 pack from civitai and move on, because someone already ran this exact pipeline for them. You reach for it when nobody has - a fine-tune or video model with no pack, or a custom bit-depth no prebuilt quant covers.
It's the first half of a two-stage pipeline. The second half is the author's companion tool, int_crush_converter, which takes the .pt file this node writes and actually does the GPTQ/OBQ/ConvRot weight compression.
How it actually works
Here's the mechanism, grounded in the source rather than the marketing. The node registers forward hooks on every layer, then runs a short sampling pass: num_samples independent denoising runs of num_steps steps each. Each time a layer's activations flow through, the hook accumulates H += xᵀx - the Gram matrix, i.e. the Hessian of the layer - plus a running amax (max |x|) if you ask for it. That Hessian is the whole trick: GPTQ uses second-order information to decide where to spend precision, instead of the naive per-row max that plain absmax quantization uses. More calibration data means the quantizer knows which channels actually matter, and compresses the ones that don't.
Nothing about the model is modified. It's read-only - the .pt it writes is the entire output.
The inputs that matter
You can leave most of these alone. The ones you actually touch:
modelandconditioning- just wire your loaded checkpoint and a CLIPTextEncode like a normal generation. It samples like a sampler, so it needs a real prompt.num_samples- independent samples to accumulate over. The tooltip says 16–128 recommended; more is smoother statistics, slower to run.num_steps- denoising steps per sample. Default 4 is fine; you're not making art, just statistics.hessian_format- leave it ondlr_nystrom. That's a Diagonal + Low-Rank Hessian built with a randomized Nyström sketch: same accuracy as the plaindlrstreaming version at roughly 100× the speed, and it stores the exact diagonal separately.blockgives you diagonal blocks sized byhessian_block_size;fullis the paper-accurate O(n²) version that memory-maps to disk and can produce multi-GB files.dlr_rank- only used in DLR modes; 128 is a good default, 64–256 the sane range.collect_amax- on by default, but weight-only GPTQ ignores it. You only need it for activation quantization.latent_height/latent_width- set 128 for a 1024px model, 64 for 512px. Get this wrong and your statistics won't match what the model actually sees at inference.
Everything else - convrot, rpbh, permuquant, piso, sigma_min/sigma_max - is advanced. The rotation flags (ConvRot Hadamard, RPBH) make Hessians more block-diagonal for better quantization; the catch is the converter must use the same rpbh_seed (default 42) to fold the rotation into the weights. sigma_min/sigma_max clip the noise range, which matters for MoE models like Wan 2.2 where different experts own different noise regimes - the README cites a boundary at sigma ≈ 0.875.
The output
One output: calibration_path (a STRING pointing at the saved .pt). It's an output node - you run the workflow, note the path (default ComfyUI/output/calibration.pt), and feed that file to the converter.
Installing it
ComfyUI Manager (search "ComfyUI-GPTQ-Calibration"), or:
cd ComfyUI/custom_nodes
git clone https://github.com/ThunderFun/ComfyUI-GPTQ-Calibration.git
Then restart ComfyUI. Dependencies are torch and numpy, which ship with ComfyUI already - no extra install. The one optional extra is Triton, which GPU-accelerates the Hadamard rotation for ConvRot; without it you get a slower CPU fallback:
pip install triton
There are no model downloads; it reads whatever you load into it.
Where people get burned
Be honest about what this pack is. The README leads with a warning: "This code has not been thoroughly tested. Verify outputs before relying on it." It's an AI-assisted version 0.3 with essentially zero community footprint - no real discussion of it anywhere as of this writing. Budget for rough edges, and sanity-check the quant against the fp16 original.
Known sharp edges from the docs and source: hessian_format='full' or hessian_block_size=0 is O(n²) and memory-mapped to a .gptq_hessian_tmp/ directory (auto-cleaned, but multi-GB territory on big models). If you hit GPU OOM during calibration, flip force_cpu_hook - the fast path only uses ~20–50 MB transient VRAM per layer, but on a tight card that's enough to hurt. And remember the rotation-seed rule: change rpbh_seed here and the converter must use the same one, or the whole quant is garbage.
Inputs (24)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | Loaded diffusion model (FP16/BF16/FP32). | |
| conditioning | CONDITIONING | Pre-encoded conditioning from CLIPTextEncode or similar. | |
| num_steps | INT | 41–50 | Denoising steps per sample. |
| num_samples | INT | 161–4096 | Independent samples to accumulate over. 16-128 recommended. |
| seed | INT | 00–4294967295 | Seed for noise and timestep sampling. |
| hessian_block_size | INT | 1280–1024 | 0 = full H (paper-accurate, auto memory-mapped to disk). 128 = diagonal blocks (default, saves RAM). Ignored when hessian_format='dlr'. |
| hessian_format | COMBO | dlr_nystrom | Hessian storage format. 'block' = diagonal blocks (use hessian_block_size). 'full' = full Hessian (memory-mapped). 'dlr' = Diagonal + Low-Rank via FrequentDirections (streaming, exact diagonal, repeated SVD). 'dlr_nystrom' = DLR via randomized Nyström (streaming, exact diagonal, ~100× faster construction — recommended default). |
| dlr_rank | INT | 1281–4096 | Rank for DLR Hessian (only used when hessian_format is 'dlr' or 'dlr_nystrom'). Same memory budget as block_size=rank. Recommended: 64-256. |
| collect_amax | BOOLEAN | true | Also collect max(abs(x)) per layer. Required for activation quantization; not used by weight-only GPTQ. |
| output_path | STRING | /tmp/ComfyUI/output/calibration.pt | Where to save the calibration .pt file. |
| latent_heightopt | INT | 648–1024 | Latent spatial height. 128 for 1024px, 64 for 512px. |
| latent_widthopt | INT | 648–1024 | Latent spatial width. 128 for 1024px, 64 for 512px. |
| convrotopt | BOOLEAN | false | Enable ConvRot Hadamard rotation. Collects Hessians in rotated space for better block-diagonal approximation. |
| rot_sizeopt | INT | 25616–4096 | Hadamard group size (must be power of 2). 256 recommended for ConvRot. |
| rpbhopt | BOOLEAN | false | Enable RPBH (Randomized Permuted Block-Hadamard) rotation. Mutually exclusive with ConvRot. Spreads outliers via random permutation + block Hadamard + sign flip. Uses per-layer auto-detected block size (largest power-of-2 dividing in_features). Best paired with DLR Hessian format — block format is lossy because RPBH's permutation destroys block-diagonal structure. Not recommended with PermuQuant. |
| rpbh_seedopt | INT | 420–2147483647 | Random seed for RPBH permutation and signs. The converter must use the same seed to regenerate matching rotation parameters for weight folding. |
| rpbh_block_sizeopt | INT | 2560–65536 | RPBH Hadamard block size. 0 = auto-detect (largest power-of-2 dividing in_features, default). Set to 256 for uniform block size across all layers — smaller blocks give finer-grained outlier spreading. Must be a power of 2 if non-zero. The converter must use the same block size. |
| permuquantopt | BOOLEAN | false | Enable PermuQuant channel reordering. Runs a second calibration pass with channels sorted by second moment for better quantization. |
| pisoopt | BOOLEAN | false | Collect Hessian diagonal for PiSO data-aware scale optimization. Adds a small overhead to store diag(X^T X) per layer, which the converter uses to compute optimal per-row scales instead of absmax. |
| sigma_minopt | FLOAT | 0.000–1 | Lower bound of the sigma range to sample. Set to 0.875 with Wan 2.2 high-noise expert, or 0.0 for full range (default). |
| sigma_maxopt | FLOAT | 1.000–1 | Upper bound of the sigma range to sample. Set to 0.875 with Wan 2.2 low-noise expert, or 1.0 for full range (default). |
| force_cpu_hookopt | BOOLEAN | false | Force hook-side processing to CPU. Enable only if you hit GPU OOM during calibration — the GPU-fast path uses ~20-50 MB of transient VRAM per layer. |
| timestep_channel_amaxopt | BOOLEAN | false | Collect per-timestep-bucket per-channel amax in addition to overall per-channel amax. Enables the converter to analyse time-varying channel imbalance for static-dynamic decomposition (ViDiT-Q Section 4.2). Automatically enables per-channel amax collection. Adds 'channel_amax_by_timestep' to the output. |
| timestep_bucketsopt | INT | 41–64 | Number of timestep buckets for per-timestep channel amax. 4 = divide denoising into 4 equal ranges (ViDiT-Q paper default). |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| calibration_path | STRING | — |