ComfyUI Node

FID Score

Is my generated batch actually as real-looking as the training set?

By AnsteinHuynh·Created 7 days ago·Updated 6 days ago· 0
FID Score
  • model
  • target
  • reference
  • score
◄memory_managementunload model (VRAM + RAM)►
◄resize_modeclean►

FID is the metric the whole generative image field grew up on. It compares two sets of images - usually your generated batch against a real one - and answers one blunt question: do these two piles of pictures look like they came from the same distribution? Lower is better. Identical sets give roughly zero, and there's no upper bound.

This node is the only place in the pack where you get that number inside ComfyUI instead of a Python script.

Why it can't work on one image

This is the part people skip and then get angry at the numbers. FID runs both sets through InceptionV3, then compares the mean and covariance of the resulting feature vectors. Covariance is a property of a set. One image has none - the matrix is degenerate and pyiqa's sqrtm blows up on it, which is why the node refuses anything below two images per side rather than letting that crash reach you.

Two per side is the floor, not the useful range. The sample covariance of 2048-dimensional Inception features has at most rank n−1, so with a handful of images the score mostly measures how many images you wired rather than how good they are. The node still runs (you're in charge), but it tells you. The original FID protocol used 50,000 real images against 10,000 generated ones. Sixteen per side is where it starts to say something; a couple of hundred is where I'd trust a comparison between runs.

Practical consequence: only compare FID numbers across runs with similar set sizes, the same resize_mode, and the same weights (fid vs sFID vs fid_dinov2 are different metrics sharing a name). Your 12-image FID of 90 isn't better than someone else's 30,000-image FID of 40.

The inputs

model comes from (Down)load pyiqa Model with architecture FID - variants fid, sFID, fid_dinov2. Any other model is refused with a message, and in the other direction the per-image score nodes refuse these three, so the wiring can only go one sensible way.

target is the generated/processed set, reference is the real set. Both accept multiple image wires - every batch you connect joins that one set. That's how you get past the annoyance of building a single 64-image batch: wire four Load Image batches in, or loop a batch through in chunks, and they accumulate. The two sets don't need equal counts.

resize_mode is pyiqa's own option and I'd leave it on clean - clean-FID's channel-wise PIL bicubic, the CLI default. legacy_pytorch and legacy_tensorflow reproduce older pipelines. Keep both sides on the same mode; mixing them changes the number for reasons that have nothing to do with your images.

memory_management behaves as it does on every score node here: unload the InceptionV3 metric after scoring, or keep it on the GPU.

Output is one score - a plain float, not a list. FID doesn't do per-image values.

How it gets there

pyiqa's FID is folder-based, so this node writes your batches to temporary 8-bit PNG folders and calls pyiqa's own folder API - the same path as its CLI, which means the number matches a command-line run over the same images. Your images are quantised to 8-bit on the way through, exactly as if saved to disk. Don't chase sub-quantisation precision.

When you'd actually use it

LoRA and checkpoint evaluation. You train on a set of a few hundred photos; generate a few hundred images across three checkpoints or three LoRA weights; wire the real set to reference and each generated set to target; compare FID. It answers "which of these drifted less from my training data" in one number, which is more than your eyeballs will manage at image 80 of a contact sheet.

What it won't tell you is whether the images are good. A model that reproduces the training set's flaws faithfully can beat a model that produces better art. FID measures resemblance, not taste. If you also care about taste, run pyiqa NR Score or FGResQ NR Score on the same sets and read both.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/AnsteinHuynh/ComfyUI-pyiqa-toolbox
python -m pip install -r ComfyUI-pyiqa-toolbox/requirements.txt

Or ComfyUI Manager → search pyiqa-toolbox. Restart. InceptionV3 weights download on first use into ComfyUI/models/pyiqa.

The dependency note applies here in force: pyiqa resolves transformers>=5 in your ComfyUI environment, which can break other packs pinned to 4.x. Not this pack's fault, entirely this pack's install.

Common issues

"Needs at least 2 images per side." You wired one image. Use a pairwise metric - pyiqa FR Score with LPIPS/DISTS/A-FINE FR, or FGResQ FR Score - for the single-pair case.

Score jumps around between runs with the same images. Set size and set composition. Fix the seed, the count and the resize mode before comparing.

Big VRAM spike right after generation. InceptionV3 is a real model load; the pack frees ComfyUI's cache before allocating, and memory_management releases it afterwards, but a tight card will still feel it. Scoring last is the sane ordering.

Categorypyiqa/Generic

Inputs (5)

NameTypeDefaultDescription
memory_managementCOMBOunload model (VRAM + RAM)What happens to this metric after it scores. 'unload model (VRAM + RAM)': the metric is fully unloaded - gone from VRAM and RAM, only the file on disk remains; the next score reloads it (seconds for small models, ~30s for Q-ReAlign Pro). Re-queueing just works: a previously-unloaded model handed back by ComfyUI's output cache is rebuilt transparently. 'keep loaded': stays on the GPU, fastest to re-score, holds its VRAM between runs. With several score nodes sharing one loader, unload only on the LAST one - earlier nodes should 'keep loaded' or you pay a reload between them.
modelPYIQA_ANYFrom (Down)load pyiqa Model, architecture 'FID' (fid, sFID or fid_dinov2). The generic pyiqa score nodes refuse these on purpose.
targetIMAGETarget image SET (the generated/distorted side). Accepts MULTIPLE image wires - every wired batch joins one set. FID compares a feature mean AND covariance, so it needs a set: 2+ images per side is the floor, ~16+ the useful range. For a single image pair use a pairwise metric instead (LPIPS, DISTS, A-FINE FR, ... via (Down)load pyiqa Model + pyiqa FR Score).
referenceIMAGEReference image SET (real/ground-truth side). Accepts MULTIPLE image wires; the two sets need not have equal counts, but each needs >= 2 images (one image has no covariance) and ~16+ to be meaningful.
resize_modeCOMBOcleanpyiqa's FID resize mode: 'clean' (clean-fid channel-wise PIL bicubic, the CLI default), 'legacy_pytorch' (bilinear), 'legacy_tensorflow' (TF-compatible bilinear). Keep both sides on the same mode.

Outputs (1)

NameTypeDescription
scoreFLOATONE set-level score for the two image sets (not per image): the Fréchet distance between the feature distributions of the target set (your generated/processed images) and the reference set (the real images). LOWER is better, unbounded; identical sets give ~0.