Skip to content

(prototype) Move the NT-Xent equation into lightly.functional - #2035

Draft
gabrielfruet wants to merge 1 commit into
simclr-v2-02-backbonesfrom
simclr-v2-03-ntxent
Draft

(prototype) Move the NT-Xent equation into lightly.functional#2035
gabrielfruet wants to merge 1 commit into
simclr-v2-02-backbonesfrom
simclr-v2-03-ntxent

Conversation

@gabrielfruet

@gabrielfruet gabrielfruet commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Prototype. Do not merge. This is a design exploration for the LightlySSL 2.0
refactor, opened to be read and argued with, not to land on master. The whole
stack goes together or not at all, and the shape is still open.

3 of 7 in a stack. Base: #2034.

NTXentLoss.forward holds the memory bank, the distributed gather and the equation in one 75-line method. This moves the equation out to lightly/functional/ntxent.py as a pure function, and leaves the module holding the state. No maths is duplicated: the module calls the function.

The gather stays in the module, which is where it belongs, and reaches the function as z0_all, z1_all and positives_at. On one rank those default to the local tensors and the mask is torch.eye, which is what the old code built.

DistributedKind is the other half, and it is a declaration rather than a mechanism today. DDP averages the per-rank loss and gradient, and whether that is correct depends on the loss. torch.distributed.all_reduce has no backward, so SIGReg shipped a correct forward with a gradient off by exactly 1/world_size (#1920), and the same bug came back against BarlowTwinsLoss (#1977). Both were fixed by hand and the fixes are comments. NTXentLoss now declares GATHER_FOR_NEGATIVES. Nothing dispatches on the kind yet; the test selector belongs with its own PR.

Testing: pytest tests/loss/test_ntx_ent_loss.py unchanged at 246 cases, plus golden values pinning ntxent at seed 11.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69200af70c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

logits_11 = torch.einsum("nc,mc->nm", z1, z1_all) / temperature

logits_00 = logits_00[~diagonal].view(batch_size, -1)
logits_11 = logits_11[~diagonal].view(batch_size, -1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require gathered views together or mask them independently

When a caller supplies only one of z0_all or z1_all—which the independently optional arguments and documented defaults permit—the gathered tensor can have B * W rows while the other defaults to B. The diagonal mask is sized exclusively from z0_all, so applying it to logits_11 raises a shape-mismatch IndexError for either one-sided case. Either reject calls that do not provide both gathered views or build the self-similarity masks independently.

Useful? React with 👍 / 👎.

@gabrielfruet gabrielfruet changed the title Move the NT-Xent equation into lightly.functional (prototype) Move the NT-Xent equation into lightly.functional Aug 16, 2026
@gabrielfruet
gabrielfruet marked this pull request as draft August 16, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant