Inspiration
A detector almost never sees a pristine image. By the time a human looks at one, it's already been through a wash cycle — resized for a feed, compressed to JPEG, blurred by some filter, re-encoded by two or three apps along the way. That wash cycle destroys exactly the high-frequency fingerprints most AI-image detectors quietly depend on.
We didn't want to find that out after submitting. So robustness became the thing we were graded on from the first commit, not a caveat bolted on at the end — and it's a good thing we did, because the single biggest finding in this project only shows up if you actually go looking for it.
What it does
BYTEPRINT scores a directory of images for AI-generation likelihood and writes out {image_path, pred} JSON, per the brief. What makes it different is how it's evaluated: against the full fifteen-rung laundering ladder from §5.2, transcribed verbatim into code and pinned by a test so it can't quietly drift out of sync with the spec. We report every rung separately instead of pooling them into one number — a decision that turned out to matter far more than we expected (see below).
The primary model fuses two frozen, independently-trained detectors — CLIP ViT-B/32 with a reactivity-delta feature and DINOv2 — through a calibrated logistic regression fit once on clean images and applied, unchanged, to every degraded version of every image. On top of that base we validated two extensions end-to-end: a training-free third expert (AEROBLADE, which scores images by diffusion-VAE reconstruction distance) and a backbone swap to SigLIP2-so400m.
| Model | Params | Mean AUC (15 rungs) | Mean TPR@1%FPR (15 rungs) |
|---|---|---|---|
| CLIP ViT-B/32 alone | 151.4M | 0.987 | 0.807 |
| DINOv2 ViT-S/14 alone | 22.0M | 0.942 | 0.371 |
| SigLIP2-so400m alone | 400.0M | 0.958 | 0.610 |
| CLIP + DINOv2 (fused) — primary model | 173.4M | 0.991 | 0.837 |
| CLIP + SigLIP2 (fused) — validated alternative | 551.4M | 0.992 | 0.856 |
The primary model runs at 173.4M parameters — under 9% of the competition's <2B budget, CPU-capable by default. We deliberately did not chase the highest number in that table; more on why below.
The finding that reorganized the project
This is our ladder, and it's the reason we insisted on reporting it this way instead of collapsing it into one score:
| Rung | CLIP AUC | Fused AUC | CLIP TPR@1% | DINO TPR@1% | Fused TPR@1% |
|---|---|---|---|---|---|
| clean | .993 | .995 | .860 | .410 | .896 |
| jpeg:30 (heaviest) | .987 | .993 | .802 | .273 | .813 |
| blur:2.0 (heaviest) | .989 | .991 | .737 | .349 | .799 |
| scale:0.25 (heaviest) | .991 | .992 | .827 | .353 | .773 |
| noise:0.10 (heaviest) | .977 | .982 | .763 | .313 | .853 |
| crop:0.8 (worst) | .951 | .975 | .335 | .425 | .648 |
| mean (15 rungs) | .987 | .991 | .807 | .371 | .837 |
If you only look at AUC, nothing seems to happen. Mean fused AUC drifts from .995 on clean images to .991 across the entire ladder — four thousandths of a point, spanning JPEG-30, 4x downscaling, blur at σ=2.0, and 10% noise. By that metric the model looks basically immune to laundering.
Then you look at TPR, and it's a different story. On crop:0.8 — an 80% center crop, arguably the gentlest transform on the whole ladder, milder than anything a social platform would do by accident — CLIP's true-positive rate at 1% FPR drops from .860 to .335. That's a 61% relative loss, on the one transform we assumed would be free. It's worse than 4x downscaling. Worse than JPEG-30. Worse than blur that wipes out the high-frequency band entirely.
None of that shows up in AUC. If we'd shipped a single pooled metric, we would have handed in a detector that silently fails on ordinary profile-picture framing and never known it. This is the finding that made us restructure the entire project around per-rung reporting instead of a headline accuracy number.
How we built it
We de-risked the dataset before trusting any number that came out of it. SID_Set's authentic images are 100% JPEG-family; its synthetic ones are 100% PNG. Re-encoding everything to a common container hides the file format but not the quantization history baked into the pixels — so the honest question was whether we were detecting generated images or just detecting JPEG. Rather than argue about it, we ran the control: pushed both classes through identical JPEG-95 compression to erase that history and re-measured. 0.9025 → 0.9022. Barely moved. The probe was reading pixels, not file provenance.
We also ran a backbone sweep with everything else held fixed, and the result surprised us a little. DINOv2-giant produced the flattest degradation curve, but language-supervised contrastive models won on raw accuracy and transfer: CLIP ViT-B/32, at a tenth of the parameters, beat DINOv2-giant (1.14B) on AUC, operating point, and transfer — and SigLIP2-so400m, at 38% of DINOv2-giant's size, beat everything, CLIP included. Pretraining objective mattered more than scale.
The primary model fuses two frozen backbones rather than tuning one harder. CLIP brings semantic, image-text-aligned features; DINOv2 brings purely visual, self-supervised ones, and the two fail on different images. A small logistic regression over their two scores — fit only on clean data — is what produces the crop:0.8 recovery in the table above: DINOv2's TPR barely moves on that rung (.425, right in its usual range) while CLIP is collapsing, and the fused model inherits DINOv2's stability exactly where it was needed.
A couple of smaller wins mattered more than we expected going in. Just restructuring the cache to one row per crop and sweeping crop count from 2 up to 8 lifted strict TPR from .58 to .69 — the single biggest gain in the whole project, and it came from nothing cleverer than looking at more of the image. And on the depth side: the standard adapter only returns the final pooled output, but tapping eleven internal depths from the same forward pass showed the final layer actively throwing away signal under heavy laundering. Shallow layers fall apart on blur, deep layers fall apart on noise, and layer 12 sits right at the crossover — layer 9 alone gets competitive strict TPR at 34% of the parameters and 2.9x the throughput.
Challenges we ran into
Most of our best ideas didn't work, and we kept the receipts instead of quietly deleting them. AEROBLADE's reconstruction error is a genuinely elegant idea and it was numerically useless on tampered images (0.49 AUC — chance) — though on the full three-expert clean-image fusion it still added a real, if modest, gain (+0.014 TPR@1%FPR on top of CLIP+DINOv2). It was also running on its weaker VGG16 fallback distance instead of proper LPIPS, because of an import failure in the GPU environment we never got to the bottom of, so its real ceiling is probably higher than what we measured. Anomaly-guided crop placement located planted edits perfectly on our own test fixture and then moved tampered-image AUC in the wrong direction on SID_Set. Max-pooling across crops just manufactured confident false positives — exactly what a strict FPR budget can't tolerate. All three are still in the repo, documented as measured negatives rather than swept under the rug.
The lesson that actually changed how we think about ensembles came from DINOv2. It's our weak expert by any summary statistic — mean TPR .371 against CLIP's .807, less than half. On most rungs it genuinely looks like dead weight, and we almost dropped it. Then look at crop:0.8: DINO scores .425 while CLIP scores .335 — the only rung on the whole ladder where the ordering flips. The strong expert's failure mode and the weak expert's failure mode turned out to be genuinely uncorrelated, and fusion recovers .648 there, well above either model alone. That one rung is where the "weak" expert earns its entire keep. Across the full ladder, adding an expert that's half as good still lifts mean TPR from .807 to .837. Judged purely on its own numbers, DINOv2 should have been cut. Judged on where it actually fails, it turned out to be essential.
We also had to make a call we suspect a lot of teams won't: after validating CLIP+SigLIP2 end-to-end across all 15 rungs and finding it beats our primary model on the mean (0.992 AUC / 0.856 TPR vs 0.991 / 0.837), we kept CLIP+DINOv2 as the headline result anyway. SigLIP2 costs 3x the parameters, most of its advantage comes from being a better standalone expert rather than a better fusion partner, and — this is the part we think is actually interesting — the fusion doesn't even inherit SigLIP2's real strength. SigLIP2 alone hits .813 TPR@1%FPR on crop:0.8; fused, that drops to .651, barely ahead of CLIP+DINOv2's .648. A fixed, clean-data-fit combiner can't shift its trust per rung, so it dilutes a much better signal instead of using it. Picking the smaller, more explainable model with a fully understood failure mode over the marginally-higher-scoring one felt like the right engineering call, not a consolation prize.
Accomplishments that we're proud of
- We ran a leakage control before anyone asked us to, and it passed: 0.9025 → 0.9022 under identical-compression normalization. Most projects never check whether their own benchmark is solvable without any real forensic skill.
- We found our own worst failure mode ourselves, and it was the one transform nobody on the team would have thought to check first.
- 0.837 mean TPR@1%FPR across all 15 rungs, reported per-rung, never pooled into one flattering number. On a platform where authentic images vastly outnumber synthetic ones, AUC alone can make an unusable detector look fine.
- A primary model at 173.4M parameters — under 9% of the competition's <2B budget, CPU-capable by default — chosen deliberately over a marginally higher-scoring, 3x-larger alternative we also fully validated.
- Five ideas we genuinely believed in got measured and then killed, qualified, or set aside — AEROBLADE on tampered images, anomaly-guided crop placement, max-pooling, the weaker-fallback LPIPS substitution, and the SigLIP2 swap — and we kept every one in the repo with its actual numbers instead of deleting the ones that didn't win.
What we learned
The metric you choose to report decides which bugs you're even able to see. Our AUC column says the wash cycle costs us four thousandths of a point. Our TPR column says one gentle crop costs us 61% of our true positives. Same model, same images, same run — and one of those numbers would have hidden the entire finding from us.
We also learned that picking experts isn't a solo decision you can make by looking at any one model's scoreboard, or any one number at all. DINOv2 looks like a mediocre expert by every summary statistic we have — and it's a genuinely great one exactly once, on the one rung where our best model breaks. SigLIP2 looks like the obvious upgrade on the mean — and quietly gives most of its advantage back the moment you actually check whether the fusion can use it. Both times, the number that mattered was hiding one level down from the headline metric.
What's next for BYTEPRINT
- Fix the crop failure properly. Our current guess is framing-dependence in the semantic backbone — crop away the composition and CLIP loses cues it was quietly leaning on. Multi-scale crop sampling and crop-augmented probe training are the two direct experiments we'd run next.
- A fusion that isn't one fixed logistic regression. The SigLIP2 result above points directly at this: a rung-aware or disagreement-gated fusion — one that shifts trust toward whichever expert is more confident when the two disagree — could plausibly beat both current pairs on the hardest rung specifically, instead of just averaging a strong signal with a weak one.
- Unseen generators are still the real bottleneck. Leave-one-generator-out transfer sits at 0.72–0.97 depending on which generator is held out (Midjourney6 is weakest), well below in-distribution performance. Domain routing narrows that gap; it doesn't close it — that's the honest ceiling on the system as it stands today.
- Tampered-image detection is our weakest axis, and all three approaches we tried for it failed outright. ELA-based localization is the untried classical route, and unlike whole-image reconstruction it's actually built for local splices rather than global generation.
- AEROBLADE under the full ladder, with the LPIPS import fixed instead of the weaker VGG16 fallback — the one validated extension that hasn't had its robustness properly tested yet.
- An evidence budget. Report how much discriminative signal actually survived a given image's specific degradation history, so the system can escalate uncertain cases instead of guessing confidently. Our own crop:0.8 result is basically the argument for building this.
Development tools used
- Claude Code (CLI) — primary development environment
- Jupyter Notebooks on Kaggle (T4 GPU) — extraction, training, and robustness-ladder evaluation
- Kaggle Datasets — packaging code and trained models for reproducible, portable GPU runs
- Git / GitHub — version control, branch-per-approach structure
- A SLURM cluster — backbone sweep and larger-scale engine experiments
Models or APIs used
- CLIP ViT-B/32 (
openai/clip-vit-base-patch32) — frozen backbone, pre-projection embeddings, extended with a reactivity-delta feature — 151.4M params - DINOv2 ViT-S/14 (
facebookresearch/dinov2) — frozen self-supervised backbone — 22.0M params - SigLIP2-so400m (
google/siglip2-so400m-patch16-naflex) — winning backbone in the sweep; fully ladder-validated as a CLIP+SigLIP2 fusion alternative (see comparison table above) — 400.0M params - EVA02-L — backbone-sweep comparison
- AEROBLADE (Ricker et al., CVPR 2024) — training-free detector via Stable Diffusion VAEs (SD1.5,
sd-vae-ft-mse, SDXL-VAE) + LPIPS/VGG16 perceptual distance
Libraries and frameworks used
PyTorch, Hugging Face Transformers, scikit-learn (LogisticRegression, StandardScaler, StratifiedKFold, ROC/AUC metrics), NumPy, Pillow, diffusers (AEROBLADE VAE loading), timm (EVA02), SciPy, pytest (TDD for the detection engine)
Datasets and assets used
- SID_Set (CC-BY-4.0, organizer-listed)
- Defactify (
Rajarshi-Roy-research/Defactify_Image_Dataset) — SD2.1/SDXL/SD3/DALL·E3/Midjourney6 generated images only (real images excluded to avoid any overlap risk with the organizer's COCO-based validation set) - Curated real/AI image pools assembled from multiple public sources
- A held-out, deterministic 443-image test split, verified disjoint from the organizer's WildFake demonstration subset (COCO val2017 + DALL·E Advanced) — never trained on
- CIFAKE was evaluated and explicitly excluded after we measured a real accuracy regression, traceable to its native 32×32 resolution — documented as a negative result rather than silently dropped
Built With
- claude-code
- clip
- dinov2
- huggingface
- jupyter
- kaggle
- pytorch
- scikit-learn
- siglip2
- vscode


Log in or sign up for Devpost to join the conversation.