From ea1cda59a756ee4ade6aad8024a5cf25bc3cf059 Mon Sep 17 00:00:00 2001 From: Severin Magel <116261790+sevmag@users.noreply.github.com> Date: Sat, 15 Aug 2026 15:26:49 -0400 Subject: [PATCH 1/2] Make EncodedEvent.cls optional Per-token pretexts (masked point modeling) read `tokens` and never need the pooled event embedding, so cls now defaults to None. The one consumer that requires it -- CURTAIN's QueryCrossAttnEncoder -- guards for None with a clear error. Existing backbones still fill cls, so behavior is unchanged. Co-Authored-By: Claude Opus 4.8 --- src/spine/backbones/base.py | 9 +++++++-- src/spine/pretrain/curtain/head.py | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/spine/backbones/base.py b/src/spine/backbones/base.py index 5a78b03..add0ad5 100644 --- a/src/spine/backbones/base.py +++ b/src/spine/backbones/base.py @@ -13,11 +13,16 @@ @dataclass class EncodedEvent: - """What every backbone returns.""" + """What every backbone returns. + + ``cls`` is optional: a backbone with no event-level readout leaves it + ``None``, and consumers that need an event embedding (the query encoder) + must guard for it. Per-token pretexts read ``tokens`` and ignore ``cls``. + """ tokens: Tensor # [B, L, D] per-pulse token embeddings token_mask: Tensor # [B, L] bool, True = real pulse (not padding) - cls: Tensor # [B, D] pooled event embedding + cls: Tensor | None = None # [B, D] pooled event embedding, or None class Backbone(nn.Module): diff --git a/src/spine/pretrain/curtain/head.py b/src/spine/pretrain/curtain/head.py index ac8f2ad..2265ee9 100644 --- a/src/spine/pretrain/curtain/head.py +++ b/src/spine/pretrain/curtain/head.py @@ -85,6 +85,11 @@ def forward(self, query_pos: Tensor, enc: EncodedEvent) -> Tensor: Returns: [B, Q, D] per-query embeddings. """ + if enc.cls is None: + raise ValueError( + "QueryCrossAttnEncoder requires an event-level `cls` embedding, " + "but the backbone returned cls=None" + ) kv = torch.cat([enc.cls.unsqueeze(1), enc.tokens], dim=1) # [B,1+L,D] ones = torch.ones( enc.token_mask.shape[0], 1, dtype=torch.bool, device=enc.token_mask.device From 08952335523d95a302053883e95e37b1d17e708c Mon Sep 17 00:00:00 2001 From: Severin Magel <116261790+sevmag@users.noreply.github.com> Date: Sat, 15 Aug 2026 15:35:42 -0400 Subject: [PATCH 2/2] Document QueryCrossAttnEncoder.forward's ValueError The cls=None guard raises ValueError; add the matching Raises section so pydoclint (DOC503) passes. Co-Authored-By: Claude Opus 4.8 --- src/spine/pretrain/curtain/head.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/spine/pretrain/curtain/head.py b/src/spine/pretrain/curtain/head.py index 2265ee9..bf31a7c 100644 --- a/src/spine/pretrain/curtain/head.py +++ b/src/spine/pretrain/curtain/head.py @@ -84,6 +84,10 @@ def forward(self, query_pos: Tensor, enc: EncodedEvent) -> Tensor: Returns: [B, Q, D] per-query embeddings. + + Raises: + ValueError: If the backbone returned no event-level embedding + (``enc.cls is None``); this encoder requires one. """ if enc.cls is None: raise ValueError(