Zero-shot calibration of frozen vision features on HyperKvasir, before vs. after self-supervised pretraining on GastroNet-5M — part of the Surgical Vision-Language Foundation Model collaboration.
For four model settings — {LeJEPA, DINO v1} × {SigLIP-2 ViT-B/16 init, ImageNet ViT-B/16 init} — measure zero-shot calibration on HyperKvasir (weighted k-NN on frozen features, k = 20), then repeat after in-domain self-supervised pretraining on GastroNet-5M, and report the change in ECE and NLL across five downstream tasks (four classification, one zero-shot polyp segmentation).
Every setting below ran to completion — full hyperparameters, loss/LR curves and per-step timing for all four are logged live to the W&B project. The table is the fixed part shared by every run; what varies is only the objective and the initialization.
| Backbone | ViT-B/16, 768-d · feature = LayerNorm(mean patch token), non-affine |
| Initializations | SigLIP-2 (timm tag, open_clip fallback) · ImageNet-1k supervised (torchvision → timm remap) |
| SSL objectives | DINO v1 (centering + sharpening, teacher momentum 0.996 → 1) · LeJEPA (SIGReg isotropy + variance hinge + stop-grad prediction) |
| Optimizer | AdamW, cosine schedule, base LR 1e-4 · layer-wise LR decay 0.75 · grad clip 3.0 |
| EMA | LeJEPA backbone EMA decay 0.9995 · DINO teacher momentum cosine 0.996 → 1.0 |
| Batch size · precision | 128 · bf16 mixed, gradient checkpointing |
| Schedule | 3 epochs on the curated tier · 14,061 steps per setting, verified by checkpoint step count |
| Augmentation | Endoscopy-safe: rotation, colour jitter, blur — no flips or grayscale (laterality and colour are both diagnostic) |
| Zero-shot eval | Weighted k-NN, k = 20, τ = 0.07, cosine similarity on L2-normalized features |
| Calibration | ECE (15 equal-width + 15 adaptive bins), NLL, Brier · scalar temperature via LBFGS |
| Hardware | Single GPU per run, shared H100 94GB / A100‑SXM4‑80GB cluster, SLURM-scheduled |
Each backbone frozen at its public initialization, no in-domain SSL yet. Weighted k-NN over a 60% reference split, temperature fit on a 15% cal split, metrics on the held-out query split (~2,650 images per classification task; 900 query masks for segmentation). The point of this baseline is the contrast it draws with the post-SSL tables below.
Query accuracy (seg: Dice) · higher is better
NLL (pre-temperature) · lower is better
| task | init | T | acc | bal-acc | AUROC | ECE ew | ECE ew·T | NLL | NLL·T |
|---|---|---|---|---|---|---|---|---|---|
| tract | imagenet ⚠ | 1.38 | .9947 | .9955 | .9990 | .0023 | .0064 | .0233 | .0229 |
| tract | siglip2 ⚠ | 1.04 | .9872 | .9862 | .9977 | .0061 | .0038 | .0519 | .0515 |
| pathology | imagenet | 1.33 | .9416 | .9013 | .9826 | .0170 | .0342 | .1522 | .1581 |
| pathology | siglip2 | 0.91 | .9084 | .8512 | .9565 | .0338 | .0260 | .2555 | .2547 |
| category | imagenet | 1.19 | .9397 | .9434 | .9924 | .0258 | .0381 | .1772 | .1822 |
| category | siglip2 | 0.82 | .8968 | .9000 | .9802 | .0589 | .0330 | .3185 | .3099 |
| findings | imagenet | 1.61 | .8455 | .6148 | .9474 | .0480 | .1114 | .5567 | .5547 |
| findings | siglip2 | 1.34 | .7628 | .5027 | .9232 | .0843 | .1438 | .9259 | .9122 |
| segmentation | imagenet | – | Dice .667 | mIoU .541 | – | .0093 | – | .2384 | – |
| segmentation | siglip2 | – | Dice .314 | mIoU .214 | – | .0140 | – | .3486 | – |
ImageNet init beats SigLIP-2 on every task — the opposite of the pre-registered guess that a web-scale contrastive model would transfer better. For endoscopy k-NN it does not: supervised ImageNet features are more linearly separable here, and their patch tokens carry far more local structure (segmentation Dice 0.67 vs 0.31).
The tract task is flagged — confirmed structural, not leakage. A dedicated ontology audit found 23 of 23 HyperKvasir findings are tract-exclusive (each occurs in upper-GI or lower-GI images only, never both), so upper-vs-lower-GI is recoverable from coarse image content almost by construction, independent of the accuracy number itself. Recorded as an interpretation caveat rather than treated as a leak.
Temperature scaling is not uniformly helpful: for ImageNet, T > 1 and post-T ECE sometimes rises (findings: .048 → .111) — the k-NN “logits” are not true logits, and a scalar fit on a 15% cal split can over-correct. The pre-vs-post-SSL delta below is the intended read, not the absolute baseline value.
DINO is a reliable in-domain adapter — it improved calibration and/or segmentation from both initializations, with no accuracy cost anywhere. LeJEPA is powerful but init-sensitive: from the weaker SigLIP-2 init it trades some accuracy for markedly better calibration; applied to the already-strong ImageNet init, its aggressive distributional regularizer is destructive rather than helpful. The practical takeaway: match the SSL objective's aggressiveness to how much the starting representation actually needs to move.
Weighted k-NN, k = 20, on a perceptual-hash-deduplicated split (near-duplicate endoscopy frames between the k-NN reference set and the held-out query set were removed first — 17% of query images). Negative ΔECE / ΔNLL = SSL improved that metric. Green = SSL helped, red = SSL hurt, grey = within noise.
DINO v1 / ImageNet init — effective rank 51.7, no collapse
| task | ΔECE ew | ΔECE·T | ΔNLL | ΔNLL·T | Δacc | Δbal-acc | ΔAUROC | ΔDice |
|---|---|---|---|---|---|---|---|---|
| findings (20-way) | −.0155 | −.0162 | −.045 | −.076 | +.021 | +.035 | +.009 | – |
| category (4-way) | −.0095 | −.0032 | +.024 | +.004 | −.002 | −.004 | −.002 | – |
| pathology (2-way) | +.0003 | +.0080 | +.023 | +.012 | −.003 | −.008 | −.002 | – |
| tract (2-way) | +.0007 | −.0020 | −.008 | −.007 | +.001 | +.001 | +.001 | – |
| segmentation | +.0035 | – | −.026 | – | – | – | – | +.061 |
DINO v1 / SigLIP-2 init — effective rank 46.7, no collapse
| task | ΔECE ew | ΔECE·T | ΔNLL | ΔNLL·T | Δacc | Δbal-acc | ΔAUROC | ΔDice |
|---|---|---|---|---|---|---|---|---|
| findings (20-way) | −.0646 | −.0687 | −.413 | −.438 | +.092 | +.144 | +.015 | – |
| category (4-way) | −.0492 | −.0103 | −.153 | −.152 | +.047 | +.055 | +.014 | – |
| pathology (2-way) | −.0247 | −.0014 | −.111 | −.111 | +.037 | +.053 | +.032 | – |
| tract (2-way) | −.0028 | −.0003 | −.038 | −.039 | +.010 | +.010 | +.002 | – |
| segmentation | +.0035 | – | −.131 | – | – | – | – | +.402 |
LeJEPA / SigLIP-2 init — effective rank 25.8, no collapse
| task | ΔECE ew | ΔECE·T | ΔNLL | ΔNLL·T | Δacc | Δbal-acc | ΔAUROC | ΔDice |
|---|---|---|---|---|---|---|---|---|
| findings (20-way) | −.0521 | −.0724 | +.126 | −.000 | −.058 | −.014 | −.036 | – |
| category (4-way) | −.0374 | −.0149 | +.068 | +.068 | −.037 | −.025 | −.009 | – |
| pathology (2-way) | −.0189 | −.0053 | +.054 | +.046 | −.030 | −.051 | −.023 | – |
| tract (2-way) | +.0087 | +.0034 | +.072 | +.072 | −.030 | −.039 | −.006 | – |
| segmentation | +.0222 | – | +.138 | – | – | – | – | +.128 |
LeJEPA / ImageNet init — severe regression, effective rank 33.9 (not collapsed)
| task | ΔECE ew | ΔECE·T | ΔNLL | ΔNLL·T | Δacc | Δbal-acc | ΔAUROC | ΔDice |
|---|---|---|---|---|---|---|---|---|
| findings (20-way) | +.367 | −.072 | +6.91 | +2.07 | −.625 | −.447 | −.273 | – |
| category (4-way) | +.183 | +.116 | +1.94 | +.750 | −.278 | −.423 | −.234 | – |
| pathology (2-way) | +.147 | +.127 | +1.30 | +.450 | −.188 | −.343 | −.410 | – |
| tract (2-way) | +.075 | +.023 | +.592 | +.529 | −.292 | −.364 | −.238 | – |
| segmentation | +.008 | – | +.095 | – | – | – | – | −.136 |
Query accuracy on the 20-way task collapsed from 82.4% pre-SSL to 19.9% post-SSL. Checked against a pipeline error and ruled out: the pre-SSL baselines in the same sweep are normal, the other three settings in the same sweep are normal, and SSL training itself shows no instability — loss fell smoothly, no non-finite-loss skips, effective rank stayed healthy (33.9; collapse reads ~1). AUROC staying above 0.5 confirms the features still carry some signal. Reading: LeJEPA's SIGReg term reshapes the batch toward an isotropic Gaussian; applied full-backbone to an already highly class-structured supervised embedding, it appears to overwrite that structure faster than the layer-wise LR decay can protect it. A sweep over lower LR / stronger layer decay is in progress to see whether gentler optimization avoids this.
The story across all four settings: DINO is the reliable adapter — it improved calibration and/or segmentation from both initializations, with no accuracy cost anywhere. LeJEPA is powerful but init-sensitive — from SigLIP-2 (room to improve) it trades accuracy for markedly better calibration and a strong segmentation gain; from ImageNet (already excellent) it is destructive, since the aggressive regularizer has nothing to gain and a great deal to lose. Findings and segmentation are where SSL matters most for the three well-behaved settings; the near-saturated tract/pathology tasks barely move either way. Practical takeaway: if the deployment init is a strong supervised backbone, prefer a gentle self-distillation objective (DINO) for in-domain adaptation, and reserve an aggressive distributional regularizer (LeJEPA) for weaker starting points.
| setting | GPU | wall time |
|---|---|---|
| LeJEPA / SigLIP-2 | A100‑SXM4‑80GB | 1h 30m |
| DINO / ImageNet | H100 94GB | 3h 43m |
| DINO / SigLIP-2 | H100 94GB | 2h 54m |
| LeJEPA / ImageNet | H100 94GB | 1h 04m |
The first full-scale training job started on an A100 the morning of 2026-09-08. Watching its scalar dashboard live caught something the smoke tests could not: it was not training at all.
Learning rate flat at 0.000e+00 for over 10,000 steps. Loss flat at
0.737 over the same span. The variance-hinge loss term pinned at its floor.
Zero checkpoints written to the run directory.
Two independent bugs compounded:
.step() instead of the wrapped one — a
workaround for an intermittent CPU hang on a login node, left in place on GPU where it
was never needed. On GPU that means the step counter stays at 0: the cosine LR schedule,
keyed on that counter, sits at its warmup-step-zero value — LR ≡ 0, so every
optimizer step is a no-op — and the checkpoint trigger, keyed on the same counter,
never fires either. A crash-and-resume would have restarted from scratch, forever.Together: the run could produce nothing usable, and would have looped indefinitely, silently, across every restart.
1. Use the wrapped optimizer step on GPU (the raw bypass kept only for CPU smoke tests) — the step counter advances, so the schedule and the checkpoint trigger both work.
2. Move the epoch-length setting to apply after the parallel workers are merged back into one stream, not before — verified directly: request N batches, get exactly N, stable across epochs and worker counts.
The GPU smoke test had passed because a handful of steps at a tiny batch size sits entirely inside LR warmup — the rate is near-zero there whether or not the counter moves, and the smoke test only checks that a checkpoint file appears and that resuming from it works, which the epoch-end trigger did handle at that scale. The bug only showed up under a full-scale run, which is exactly why the run was being watched live rather than fired and checked the next day.