Evaluation — FID, CLIP Score, Human Preference
Every generative model leaderboard cites FID, CLIP score, and a win rate from a human-preference arena. Each number has a failure mode a determined researcher can game. If you don't know the failure modes, you can't tell a real improvement from a gaming run.
Learning Objectives
- Compute FID from Inception-style features and explain why it needs at least 10,000 samples and an ImageNet-like domain
- Score prompt adherence with CLIP score and name its failure modes: weak composition, short-prompt bias, and prompt gaming
- Aggregate pairwise human or LLM-judge preferences into Elo and explain why short streams give noisy ratings
- Assemble a four-pillar production eval report with minimum sample sizes and budget the inference cost of running it
The Problem
A generative model is judged on sample quality and conditioning adherence, and neither has a closed-form measure. Your model renders 10,000 images, something has to assign them numbers, and you have to trust those numbers across model families, resolutions, and architectures. Three metrics survived the 2014-2026 gauntlet:
- FID (Fréchet Inception Distance). A distance between two distributions, real and generated, in an Inception network's feature space. Lower is better.
- CLIP score. Cosine similarity between a generated image's CLIP-image embedding and a prompt's CLIP-text embedding. Higher is better. It measures prompt adherence.
- Human preference. Pit two models head-to-head on the same prompt, have humans (or a GPT-4-class model) pick the better one, and aggregate the picks into an Elo score.
You will also see IS (inception score, largely retired), KID, CMMD, ImageReward, PickScore, HPSv2, and MJHQ-30k. Each one patches a failure of the metric before it.
The Concept
FID: sample quality
Heusel et al. (2017). The steps:
- Extract Inception-v3 features (2048-D) for N real images and N generated.
- Fit a Gaussian to each pool: compute mean
μ_r, μ_gand covarianceΣ_r, Σ_g. - FID =
||μ_r - μ_g||² + Tr(Σ_r + Σ_g - 2 · (Σ_r · Σ_g)^0.5).
Read it as the Fréchet distance between two multivariate Gaussians in feature space. Lower means the distributions are more similar.
Failure modes:
- Biased on small N. FID is estimated from sample means and covariances, so a small N adds estimation error. Two sets drawn from the same distribution should score 0, and
main.pyprints a nonzero value that shrinks as N grows. Use at least 10,000 samples. - Inception-dependent. Inception-v3 was trained on ImageNet, so domains far from ImageNet (faces, art, text images) produce a meaningless FID. Use a domain-specific feature extractor there.
- Gaming. Overfitting to the Inception prior gives a low FID without any visual quality improvement. CMMD (below) is the counter.
CLIP score: prompt adherence
Radford et al. (2021). For a generated image + prompt:
clip_score = cos_sim( CLIP_image(x_gen), CLIP_text(prompt) )
Average over 30k generated images and you get one scalar you can compare between models.
Failure modes:
- CLIP's own blind spots. CLIP has weak compositional reasoning ("a red cube on a blue sphere" often fails). A model can rank well on CLIP score without following complex prompts.
- Short prompt bias. Short prompts have more CLIP-image matches in the wild. Longer prompts score lower mechanically.
- Prompt gaming. Including "high quality, 4k, masterpiece" in the prompt inflates CLIP score and does nothing for image-text binding.
CMMD (Jayasumana et al., 2024) fixes some of these. It uses CLIP features instead of Inception and maximum-mean discrepancy instead of a Fréchet distance, and it detects subtle quality differences better.
Human preference: the ground truth
Pick a pool of prompts. Generate with model A and model B. Show pairs to humans (or a strong LLM judge) and aggregate the wins into an Elo or Bradley-Terry score. The benchmarks:
- PartiPrompts (Google): 1,600 diverse prompts, 12 categories.
- HPSv2: 107k human annotations, widely used as automated proxy.
- ImageReward: 137k prompt-image preference pairs, MIT-licensed.
- PickScore: trained on Pick-a-Pic 2.6M preferences.
- Chatbot-Arena-style image arenas: https://imagearena.ai/ and others.
Failure modes:
- Judge variance. Non-experts prefer different images than experts do. Use both.
- Prompt distribution. Cherry-picked prompts favor one model family. Document the prompt set.
- LLM-judge reward hacking. A GPT-4 judge gets fooled by pretty-but-wrong outputs. Triangulate with human ratings.
Use together
A production eval report should include:
- FID on 10-30k samples against a held-out real distribution (sample quality).
- CLIP score or CMMD on the same samples against their prompts (adherence).
- Win rate in a blinded arena against the previous model (overall preference).
- Failure mode analysis: 50 randomly sampled outputs, flagged for known issues (hand anatomy, text rendering, consistent object count).
Any single metric is a lie. Three corroborating metrics plus a qualitative review are a claim.
Build It
main.py implements FID, a CLIP-style score, and Elo aggregation on synthetic "feature vectors" (4-D vectors stand in for Inception features). It shows three things:
- FID on identical distributions at N=50, 200, and 1000, so you can see the small-N estimation error. A second block shifts the generated mean to show FID rising with the gap.
- A "CLIP score" as cosine similarity between feature vectors, scored against image vectors centered at +1.0, +0.5, 0.0, and -0.5.
- The Elo update rule applied to a synthetic preference stream.
The snippets below are condensed from main.py (which also builds the covariance and a Jacobi matrix square root by hand).
Step 1: FID in a few lines
def fid(real_features, gen_features):
mu_r, cov_r = mean_and_cov(real_features)
mu_g, cov_g = mean_and_cov(gen_features)
mean_diff = sum((a - b) ** 2 for a, b in zip(mu_r, mu_g))
trace_term = trace(cov_r) + trace(cov_g) - 2 * sqrt_cov_product(cov_r, cov_g)
return mean_diff + trace_termStep 2: CLIP-style cosine-similarity
def clip_like(image_feat, text_feat):
dot = sum(a * b for a, b in zip(image_feat, text_feat))
norm = math.sqrt(dot_self(image_feat) * dot_self(text_feat))
return dot / max(norm, 1e-8)Step 3: Elo aggregation
def elo_update(r_a, r_b, winner, k=32):
expected_a = 1 / (1 + 10 ** ((r_b - r_a) / 400))
actual_a = 1.0 if winner == "a" else 0.0
r_a_new = r_a + k * (actual_a - expected_a)
r_b_new = r_b - k * (actual_a - expected_a)
return r_a_new, r_b_newPitfalls
- FID at N=1000. The estimate is unreliable under N=10k. Papers that report low-N FID are gaming.
- Comparing FID across resolutions. Inception's 299×299 resize changes the feature distribution, so compare at matched resolution only.
- Reporting one seed. Run 3 seeds at minimum and report the std.
- CLIP score inflation via negative prompts. Some pipelines boost CLIP by over-fitting the prompt. Check the outputs for visual saturation.
- Elo bias from prompt overlap. If both models saw a benchmark prompt during training, the Elo means nothing. Use held-out prompt sets.
- Human eval paid-crowd skew. Prolific and MTurk annotators skew younger and tech-friendly. Mix in recruited art and design experts.
Use It
The production eval protocol in 2026:
| Pillar | Minimum | Recommended |
|---|---|---|
| Sample quality | FID on 10k vs held-out real | + CMMD on 5k + FID on subset per category |
| Prompt adherence | CLIP score on 30k | + HPSv2 + ImageReward + VQA-style question answering |
| Preference | 200 blinded pairs vs baseline | + 2000 paired human + LLM-judge + Chatbot Arena |
| Failure analysis | 50 hand-flagged | 500 hand-flagged + automated safety classifier |
All four pillars in one report make a claim. Any one alone is marketing.
Ship It
Save outputs/skill-eval-report.md. The skill takes a new model checkpoint plus a baseline and outputs a full eval plan: sample sizes, metrics, failure-mode probes, and sign-off criteria.
I'd run main.py and read the first block before anything else. It scores two sets of 4-D vectors drawn from the same distribution at N=50, 200, and 1000. The true FID there is zero, so whatever prints is estimation error, and in my run it shrinks as N grows. That is the cheapest way I know to see why the Pitfalls list says to distrust low-N numbers.
The second block is where I'd slow down. FID rises with the shift between the real and generated means, which is what you want from a distance. The CLIP-like block is less flattering: it is plain cosine similarity, and the image vectors are a constant plus noise, so the +1.0 and +0.5 centers land close together. A score that only sees direction can hide a gap that you would care about.
The Elo block ends with a small rating gap after 200 pairs even though A wins 70% of them. I'd read that as a reminder that the update uses k=32 and moves on every single pair, so a short stream gives a noisy rating. All of it runs on synthetic features, so none of the numbers say anything about a real Inception or CLIP model. The code makes the formulas concrete, and the failure modes in the body are where the real judgment goes.
Exercises
- Easy. Run
main.py. Compare FID at N=50 vs N=1000 on the same synthetic distributions. Report the bias magnitude. - Medium. Implement CMMD from synthetic CLIP-style features (see Jayasumana et al., 2024 for the formula). Compare sensitivity to quality differences vs FID.
- Hard. Replicate the HPSv2 setup: take 1000 image-prompt pairs from a subset of Pick-a-Pic, fine-tune a small CLIP-based scorer on the preferences, and measure its agreement with a held-out set.
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| FID | "Fréchet Inception Distance" | Fréchet distance of Gaussian fits to real vs gen Inception features. |
| CLIP score | "Text-image similarity" | Cosine similarity between CLIP image and text embeddings. |
| CMMD | "FID's replacement" | CLIP-feature MMD; less biased, no Gaussian assumption. |
| IS | "Inception score" | Exp KL(p(y |
| HPSv2 / ImageReward / PickScore | "Learned preference proxies" | Small models trained on human preferences; used as automatic judges. |
| Elo | "Chess rating" | Bradley-Terry aggregation of pairwise wins. |
| PartiPrompts | "The benchmark prompt set" | 1,600 Google-curated prompts across 12 categories. |
| FD-DINO | "Self-sup replacement" | FD using DINOv2 features; better for out-of-ImageNet domains. |
Production note: evaluation is an inference workload too
Running FID on 10k samples means generating 10k images. For a 50-step SDXL base at 1024² on a single L4, that is ~11 hours of single-request inference. Evaluation budgets are real, and the framing is the offline-inference scenario: maximize throughput and ignore TTFT.
- Batch hard, forget latency. Offline eval is static batching at the largest size that fits in memory.
pipe(...).imageswithnum_images_per_prompt=8on an 80GB H100 runs 4-6× faster in wall-clock than single-request. - Cache the real features. Extract the Inception (FID) or CLIP (CLIP score, CMMD) features for the real reference set once and store them as a
.npz. Don't recompute them per eval.
For CI and regression gates, run FID + CLIP score on a 500-sample subset per PR (~30 min), and run the full 10k FID + HPSv2 + Elo nightly.
Further Reading
- Heusel et al. (2017). GANs Trained by a Two Time-Scale Update Rule Converge to a Local Nash Equilibrium (FID): FID paper.
- Jayasumana et al. (2024). Rethinking FID: Towards a Better Evaluation Metric for Image Generation (CMMD): CMMD.
- Radford et al. (2021). Learning Transferable Visual Models from Natural Language Supervision (CLIP): CLIP.
- Wu et al. (2023). HPSv2: A Comprehensive Human Preference Score: HPSv2.
- Xu et al. (2023). ImageReward: Learning and Evaluating Human Preferences for Text-to-Image Generation: ImageReward.
- Yu et al. (2023). Scaling Autoregressive Models for Content-Rich Text-to-Image Generation (Parti + PartiPrompts): PartiPrompts.
- Stein et al. (2023). Exposing flaws of generative model evaluation metrics: failure-mode survey.
Code
- main.py (python, 147 lines)