Model Compare Sheet
The Grid That Builds Itself While Your Checkpoint Sweep Runs
- images
- grid
What it is
Every comparison grid you've upvoted was assembled by hand: generate a dozen images, drag them onto a blank canvas, type twelve filenames underneath, export. The most tedious part of the hobby, and the part nobody writes tooling for.
Model Compare Sheet is that tooling. It's an output node that pairs with Checkpoint Folder Iterator: each queued run drops its image into a per-run folder as a numbered, labeled tile, and after every run it rebuilds one contact sheet from all the tiles saved so far. Halfway through the sweep you're looking at a half-full grid. After the last run, the finished thing - each frame captioned with the checkpoint that produced it, no Photoshop, no filenames to keep straight.
Comparing four models once? You don't need this. Comparing everything in a folder because a forum said CyberIllustrious is good now? This is the node.
How it works
The node writes each IMAGE in the batch into output/model_compare/<run_name>/ as NN_label.png - NN the zero-padded index it was handed, label the checkpoint name from the iterator, plus _2/_3 suffixes when a batch has more than one image. Then it lists every PNG in that folder, sorts them naturally, resizes to tile_width, and tiles them into a grid with the filename drawn underneath.
The writes are staged and then swapped in: a failed tile write leaves the previous tiles and grid untouched rather than half-erasing your comparison. Each tile carries the prompt and workflow in its PNG metadata, so dragging one back into ComfyUI restores its workflow.
grid.png in the run folder is full resolution. The node preview gets grid_preview.png, capped at 1024px wide - so don't judge sharpness by the preview, open the real file.
Inputs and the one output
images is what you'd expect: VAE Decode's output, or a batch of them.
label and index confuse people because they're input-only sockets - you can't type into them. They exist to be wired from the iterator's label and index outputs; that's how a tile knows which checkpoint it belongs to and where it sits. If the node looks broken with two empty sockets, that's why.
run_name is the subfolder inside output/model_compare, default illustrious. Nested names work (project/october/juggernaut-sweep), but absolute paths, colons and .. segments are rejected outright. On a rerun at index 0, any tiles already in that folder get moved aside to <run_name>_<timestamp> first, so a sweep you abandoned at 40% can never contaminate the new grid.
columns (default 5), tile_width (default 512) and font_size (default 28) set the layout. Grid width is columns Γ tile_width, so 5 Γ 512 gives a sensible 2560px sheet; 8 columns at 1024 builds an 8K image and charges you for it in RAM.
One output, grid, an IMAGE. You usually don't wire it anywhere - the preview is the point.
Install
Manager, search "ComfyUI-KikoTools", install, restart. Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/ComfyAssets/ComfyUI-KikoTools.git
Restart and it's under ComfyAssets β xyz-helpers, right next to the iterator. Skip the README's pip install -r requirements-dev.txt - that's the pytest/black/mypy toolchain. Runtime deps are Pillow and numpy, both already in ComfyUI.
The workflow that actually works
Iterator label β sheet label, iterator index β sheet index, decode β sheet images, iterator set to index 0 with increment. Queue a batch equal to the number of checkpoints. That's it; the sheet handles the bookkeeping.
If you want dated runs, wire the pack's Project File Path node into run_name and you get project/2026-10-19/sdxl-realistic-style folders instead of everything piling into one directory.
Where people get burned
Rerunning index 0 archives the folder. That's a feature - it's how an interrupted sweep stays out of the new grid - but "just run index 0 again to fix one tile" throws the whole set aside. To fix a single tile, re-queue that index: a nonzero index replaces only its own batch, even if the label or batch size changed.
Labels get sanitized. Anything that isn't alphanumeric, dot, dash or underscore becomes an underscore, and long labels are ellipsized to fit the tile width. Name your checkpoint files with the grid in mind.
Big sweeps make big grids. Thirty tiles at 512px in five columns is a 2560Γ~3500 image rebuilt after every single run. Fine, but not free, and it's why columns and tile_width exist.
Empty input is a hard error. A zero-length image batch raises images must contain at least one image instead of writing an empty grid. That's usually an upstream decode failing, not this node misbehaving.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | β | |
| label | STRING | β | |
| index | INT | β | |
| run_name | STRING | illustrious | Subfolder of output/model_compare for this run |
| columns | INT | 51β32 | β |
| tile_width | INT | 51264β4096 | β |
| font_size | INT | 288β256 | β |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| grid | IMAGE | β |