Dual Model Calibration Data Collector
Two Models, One Workflow, Two Calibration Files
- model
- model_negative
- positive
- negative
- calibration_path_positive
- calibration_path_negative
Some ComfyUI workflows don't run one model - they run two, with CFG applied between them at every denoising step. That's how Ideogram 4 works in practice: the main model takes your prompt, and a second, image-only expert acts as the "negative" side. If you want to quantize that setup, you can't calibrate the two halves separately and hope the numbers still make sense together. You need both models seeing the same data, in the same dual-model arrangement, at the same time. That's exactly what this node does.
It's the sibling of the Calibration Data Collector in the same pack, and it shares all the machinery: forward hooks accumulate per-layer Hessians (H += xᵀx) and amax over a short sampling pass, nothing about the weights is modified, and it spits out .pt files for an external quantizer - the author's companion tool, int_crush_converter. The only difference is it runs the two models together and writes two calibration files.
How it works
The source is explicit: it mirrors DualModelGuider. Positive conditioning runs through model; the negative side (often image-only conditioning) runs through model_negative; and CFG is applied between them at each step. Same sampler semantics as inference, so the statistics match how the models are actually used. Two .pt files come out, one per model, each in the same schema as the single-model node.
The inputs that matter
model- the positive, conditional model.model_negative- the unconditional model. For Ideogram 4, this is the image-only expert.positive- your conditioning, wired like normal.negative- optional. Leave it disconnected and the negative model gets an image-only pass, which is the Ideogram 4 default. This is the input beginners trip on most: it genuinely expects to be empty for that workflow.cfg- the CFG value between the two models. Must be greater than 1.0 (min is 1.01); at 1.0 there's no gradient between the models and the node won't produce meaningful data for both.num_samples/num_steps- same story as the single version: more samples, smoother statistics; 4 steps per sample is plenty.hessian_format- leave it ondlr_nystrom, the recommended default. It's a Diagonal + Low-Rank Hessian built with a randomized Nyström sketch, roughly 100× faster to construct than the plain streamingdlrvariant and with the exact diagonal preserved.output_path_positiveandoutput_path_negative- where each model's file lands.
The optional set (convrot, rpbh, permuquant, piso, sigma_min/sigma_max, force_cpu_hook, timestep_channel_amax) is the same advanced rotation and clipping toolkit as the single-model node. One rule carries over: if you enable RPBH, the converter must use the same rpbh_seed (default 42) to regenerate the matching rotation for weight folding.
The outputs
Two strings: calibration_path_positive and calibration_path_negative, pointing at the saved .pt files. Like its sibling it's an output node - you run it, grab the paths, and hand both files to the converter.
Installing it
Same pack as the single-model node, same install. ComfyUI Manager (search "ComfyUI-GPTQ-Calibration"), or:
cd ComfyUI/custom_nodes
git clone https://github.com/ThunderFun/ComfyUI-GPTQ-Calibration.git
Restart, done. Dependencies are torch and numpy (already in ComfyUI). Triton is optional and only accelerates the Hadamard rotation path:
pip install triton
No model files to download - it reads whatever you load in.
Where people get burned
The same caveat that applies pack-wide: the README warns the code is not thoroughly tested - verify outputs before relying on it. It's a young, low-community pack (version 0.3, effectively no discussion out there), so treat your first quant as a beta.
Two failure modes are built into this node by design. First, the two output paths must be different - it raises a ValueError if they resolve to the same file, so don't point both at one path. Second, if cfg isn't above 1.0 you don't actually get dual-model statistics; keep it at the 4.0 default unless you know why you're changing it. And the usual suspects apply: leave negative disconnected for image-only setups, set latent_height/latent_width (128 for 1024px, 64 for 512px) so the statistics match real inference, and flip force_cpu_hook if the dual-model pass OOMs your card.
Inputs (28)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | Positive (conditional) model. | |
| model_negative | MODEL | Negative (unconditional) model. For Ideogram 4 this is the image-only expert. | |
| positive | CONDITIONING | Positive conditioning — runs through `model`. | |
| negative | CONDITIONING | Negative conditioning for the uncond model. Leave disconnected for image-only pass (default for Ideogram 4). | |
| cfg | FLOAT | 4.01.01–100 | CFG value to apply between the two models. Must be > 1.0 to calibrate both models. |
| num_steps | INT | 41–50 | — |
| num_samples | INT | 161–4096 | — |
| seed | INT | 00–4294967295 | — |
| hessian_block_size | INT | 1280–1024 | 0 = full Hessian, 128 = diagonal blocks. Ignored when hessian_format='dlr'. |
| hessian_format | COMBO | dlr_nystrom | Hessian storage format. 'dlr_nystrom' = DLR via randomized Nyström (streaming, exact diagonal, ~100× faster — recommended). 'dlr' = DLR via FrequentDirections. 'block' = diagonal blocks. 'full' = full Hessian. |
| dlr_rank | INT | 1281–4096 | Rank for DLR Hessian (only used when hessian_format is 'dlr' or 'dlr_nystrom'). |
| collect_amax | BOOLEAN | true | — |
| output_path_positive | STRING | /tmp/ComfyUI/output/calibration_positive.pt | Where to save the positive model's calibration .pt file. |
| output_path_negative | STRING | /tmp/ComfyUI/output/calibration_negative.pt | Where to save the negative model's calibration .pt file. |
| latent_heightopt | INT | 648–1024 | — |
| latent_widthopt | INT | 648–1024 | — |
| convrotopt | BOOLEAN | false | — |
| rot_sizeopt | INT | 25616–4096 | — |
| rpbhopt | BOOLEAN | false | Enable RPBH rotation. Mutually exclusive with ConvRot. Best with DLR Hessian format. |
| rpbh_seedopt | INT | 420–2147483647 | Random seed for RPBH. The converter must use the same seed. |
| rpbh_block_sizeopt | INT | 2560–65536 | RPBH Hadamard block size. 0 = auto-detect (default). Set to 256 for uniform block size. Must be a power of 2 if non-zero. |
| permuquantopt | BOOLEAN | false | — |
| pisoopt | BOOLEAN | false | — |
| 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 if you hit GPU OOM with dual-model calibration. |
| timestep_channel_amaxopt | BOOLEAN | false | Collect per-timestep-bucket per-channel amax for both models. Enables time-varying channel imbalance analysis (ViDiT-Q Section 4.2). |
| timestep_bucketsopt | INT | 41–64 | Number of timestep buckets for per-timestep channel amax. 4 = paper default. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| calibration_path_positive | STRING | — |
| calibration_path_negative | STRING | — |