feat(loss): add Sinkhorn-Knopp centering and KoLeo group sizes for DINOv3 - #2005
Open
saud5150 wants to merge 4 commits into
Open
feat(loss): add Sinkhorn-Knopp centering and KoLeo group sizes for DINOv3#2005saud5150 wants to merge 4 commits into
saud5150 wants to merge 4 commits into
Conversation
DINOv2 and DINOv3 replace the mean centering of the teacher output with the Sinkhorn-Knopp centering from SwAV. DINOLoss now accepts center_mode="sinkhorn_knopp", which normalizes the teacher logits so that every prototype receives the same total weight across the batch instead of subtracting a running center. Following the reference implementation, the normalization is applied jointly over all teacher views and no center is tracked. The center buffer stays registered so that the state dict does not depend on the center mode. The number of samples is reduced across processes rather than derived from the world size, so that the shared helper also works for the iBOT loss where the number of masked tokens differs between processes.
The DINOv3 paper applies Sinkhorn-Knopp centering to both the DINO and the iBOT objective, and the reference training code asserts that centering is set to sinkhorn_knopp. IBOTPatchLoss now accepts the same center_mode as DINOLoss. Teacher normalization and the center update move into helpers so that IBOTPlusPlusPatchLoss picks up the new mode without duplicating the branch.
DINOv3 applies the KoLeo regularizer to small batches of 16 samples. The released configs reach that by running with a per-GPU batch size of 16, which is not a setting lightly users are likely to train with. KoLeoLoss now splits the batch into consecutive groups of group_size and searches nearest neighbors within each group, so the paper setting is reproducible at any batch size. topk and gather_distributed are added for parity with the reference implementation. Defaults are unchanged: without group_size the whole batch forms one group and the loss is identical to before.
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1881 — phase 1 of 3, so this does not close the issue.
Description
The three loss-level pieces for DINOv3's first training phase, scoped in #1881 (comment). Paper and reference evidence for each is in that thread; this is what changed.
center_modemirrors DINOv2/v3's owntrain.centeringkey. The sharedsinkhorn_knopp()helper sits inlightly/models/modules/center.pybesideCenter; in that mode the center buffer stays registered but unused, so checkpoints load across modes, andIBOTPlusPlusPatchLossinherits the mode via the extracted_teacher_probabilities().KoLeoLosssearches nearest neighbors within consecutive groups ofgroup_size;topkandgather_distributedcomplete the referenceKoLeoLossDistributedsurface in ~15 lines.Defaults are unchanged and no existing test was adapted.
Three things not in the issue thread:
exp(logit / 0.04)overflows fp32 at|logit| >= ~3.6. DINO heads L2-normalize before the weight-normed prototype layer, so logits stay in [-1, 1] and this cannot trigger; the reference has the identical exposure, and stabilizing under DDP needs a globally reduced max, i.e. an extra collective per step. I matched the reference.sinkhorn_knoppdoes not reuseswav_loss.py::sinkhorn, which normalizes bylocal_batch_size * world_size— wrong for iBOT, where the masked-token count differs per rank.DINOLossalready matches DINOv3's loss composition: upstream's separately-weighted global and local crop terms are algebraically the uniform mean over view pairs it already computes (12.2663984 vs 12.2663975 on identical inputs, for the pretrain defaultreweight_dino_local_loss: false).Three commits, independently reviewable; the KoLeo commit shares no file with the other two.
Tests
sinkhorn_knoppis verified against a copy of the DINOv3 reference across 9 temperature x iteration combinations, following theOriginalDINOLosspattern already intest_dino_loss.py, plus row-sum, prototype-marginal and detachment tests. The loss-level tests cover wiring rather than repeating the algorithm: Sinkhorn-Knopp applied jointly over all teacher views as upstream does, the center staying zero while remaining in the state dict, andIBOTPlusPlusPatchLossinheriting the mode.KoLeoLossgets 11 new tests, including amocker-simulatedworld_size=2gather whose loss and gradient match the non-distributed run on the concatenated global batch — a correct gathered forward can still have a wrong backward, as #1977 showed.GatherLayeritself is mocked out, so that test covers grouping and neighbor indexing keeping gradients on the right features, notGatherLayer's own backward.Run on Python 3.9 with current torch; the 3.7 and 3.12 CI legs are unexercised locally. The full run also reports 4 macOS-specific failures (
assertWarnsdict iteration, two PyAVspawnpickling, one DCL gloo mismatch) that reproduce on a cleanupstream/master.Documentation
.rstfiles).Every new parameter is documented on both the class
Attributesand the__init__Argsof the affected loss.lightly.loss.htmlis the only rendered page that changes, attached as a PDF printout.Improvements put into another issue:
KoLeoLoss(gather_distributed=True)once the shared gloo process pool from Proper distributed tests #1982 lands; currently covered by amocker-simulatedworld_size=2.DINOLosscannot express the scheduled local-crop reweighting (reweight_dino_local_loss: true) used by the DINOv3 gram-anchoring config. Not needed for phase 1; I will raise it with the refinement-phase work.Issues covering the breaking change: