Instructions to use Codeseys/composer-replication-framework with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Codeseys/composer-replication-framework with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Codeseys/composer-replication-framework", dtype="auto") - Notebooks
- Google Colab
- Kaggle
SDPO Alignment Indices: Canonical Collator Design
Status: Recommendation (Option B with ragged-K safety)
Date: 2026-05-29
Context: Cross-family review found the SDPO loss alignment guard was shape-only; the fix (commit 2026-05-29) now requires student_response_idx / teacher_response_idx LongTensors from the collator. The production collator (composer_replication/trainer/data_collator.py) does not yet emit them, so strict SDPO raises.
1. What the loss now demands
ComposerReplicationTrainer._compute_sdpo_loss (lines 184–242 of composer_trainer.py) does:
s_idx = inputs.get("student_response_idx") # (B, K) LongTensor
t_idx = inputs.get("teacher_response_idx") # (B, K) LongTensor
# … guard: raise if strict + missing …
vocab = student_logits.size(-1)
s_gather = s_idx.unsqueeze(-1).expand(-1, -1, vocab) # (B, K, V)
t_gather = t_idx.unsqueeze(-1).expand(-1, -1, vocab)
student_aligned = torch.gather(student_logits, 1, s_gather) # (B, K, V)
teacher_aligned = torch.gather(teacher_logits, 1, t_gather) # (B, K, V)
# → generalized_jsd_loss(student_aligned, teacher_aligned, …)
It expects per-row indices into the sequence dimension (dim=1) selecting K aligned post-hint response-token positions. K is the number of error-turn response tokens in that row.
2. Option A vs Option B — Recommendation
Option A: derive indices from the existing equal-length mask
Since the collator already builds same-length student/teacher sequences via _build_aligned_student_for_sdpo (placeholder system-message of identical token length at the hint-slot), both sdpo_loss_mask (teacher-side) and response_mask (student-side) mark the exact same token positions. We could simply do:
student_response_idx = torch.nonzero(response_mask == 1, as_tuple=False) # per-row
teacher_response_idx = torch.nonzero(sdpo_loss_mask == 1, as_tuple=False) # identical
Pros: zero new collator complexity; the mask is already correct.
Cons: (a) couples the loss to an implementation detail (the placeholder trick) — if the collator ever drops same-length alignment, all rows silently break; (b) the mask selects a subset of the response tokens (only assistant content), while the indices could select all post-hint tokens including chat-template scaffolding; (c) torch.gather on equal-length identical indices is mathematically a no-op that wastes memory — the loss should just take a mask path when alignment is trivial.
Verdict: Reject. The mask path should remain as a fallback inside generalized_jsd_loss when the collator hasn't been upgraded, but the canonical emission is distinct indices.
Option B (RECOMMENDED): emit distinct indices unconditionally
The collator computes both student_response_idx and teacher_response_idx explicitly during _build_aligned_student_for_sdpo / _build_sdpo_fields. Even when sequences are same-length, emitting the indices:
- Proves to the loss that alignment was deliberately solved (not accidentally same-length)
- Survives a future where the placeholder trick is replaced by dynamic padding
- Allows
teacher_response_idxto differ fromstudent_response_idxin any future generalization (e.g., the teacher omits some non-content tokens)
This is the canonical design. The remainder of this document specifies the exact tensor construction.
3. Design: constructing the indices from the existing mask
The collator already computes:
sdpo_loss_mask: (B, T) with 1 at teacher post-hint error-turn content tokens,ignore_index(-100) elsewhere.response_mask: (B, T) with 1 at student assistant-content tokens (incl. post-hint), 0 elsewhere.
Because the collator's _build_chat_aligned_mask uses per-message apply_chat_template prefix deltas to place mask bits exactly on content tokens regardless of scaffolding, the 1-positions in both masks correspond to the same logical token in an aligned comparison.
Step 1: per-row nonzero positions
def _build_response_indices(
mask: torch.Tensor, # (B, T), 1=response, 0=ignore
pad_sentinel: int = -1,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Convert a per-row response mask to padded index tensors.
Returns:
idx: (B, K_max) LongTensor — position indices, padded with sentinel
valid: (B, K_max) BoolTensor — True where idx is a real position
"""
B, T = mask.shape
rows = []
for b in range(B):
pos = torch.nonzero(mask[b] == 1, as_tuple=True)[0] # (K_b,)
rows.append(pos)
K_max = max(r.numel() for r in rows) if rows else 0
if K_max == 0:
# No error sites in this batch — return empty sentinels
return (
torch.full((B, 0), pad_sentinel, dtype=torch.long, device=mask.device),
torch.zeros(B, 0, dtype=torch.bool, device=mask.device),
)
idx = torch.full((B, K_max), pad_sentinel, dtype=torch.long, device=mask.device)
valid = torch.zeros(B, K_max, dtype=torch.bool, device=mask.device)
for b, row in enumerate(rows):
k = row.numel()
idx[b, :k] = row
valid[b, :k] = True
return idx, valid
Step 2: unified emission in the collator's __call__
Inside ComposerDataCollator.__call__ (after the _build_aligned_student_for_sdpo block, around line 191), add:
# --- Emit SDPO alignment indices (ADR-008 gate) ---
if "sdpo_loss_mask" in out and "response_mask" in out:
# Teacher-side: where does sdpo_loss_mask == 1?
# Note: sdpo_loss_mask uses ignore_index (-100) for non-loss tokens.
# We want positions where the value is exactly 1 (the in-loss marker).
t_mask = (out["sdpo_loss_mask"] == 1) # (B, T)
t_idx, t_valid = _build_response_indices(t_mask)
# Student-side: where does response_mask == 1?
# response_mask is 0/1; 1 means assistant-response token.
s_mask = (out["response_mask"] == 1) # (B, T)
s_idx, s_valid = _build_response_indices(s_mask)
# When sequences are same-length and aligned by the placeholder trick,
# s_idx will equal t_idx for every valid position. The loss can
# optionally assert this in debug mode, but the canonical contract
# is that the two index tensors describe the alignment, and they
# MAY differ in future collator versions.
out["student_response_idx"] = s_idx
out["teacher_response_idx"] = t_idx
out["student_response_valid"] = s_valid # (B, K_max)
out["teacher_response_valid"] = t_valid # (B, K_max) — same max-K
Step 3: sentinel handling in the loss
The loss currently does torch.gather unconditionally. The sentinel value (-1) would wrap around and select the last token — harmless but wasteful. Better: the loss should mask sentinel positions. Update _compute_sdpo_loss:
# After gather:
student_aligned = torch.gather(student_logits, 1, s_gather) # (B, K, V)
teacher_aligned = torch.gather(teacher_logits, 1, t_gather) # (B, K, V)
# Build a (B, K) mask: True where BOTH indices are valid (not sentinel).
# When the collator guarantees s_valid == t_valid, use either.
if "student_response_valid" in inputs:
aligned_mask = inputs["student_response_valid"] # (B, K), already BoolTensor
else:
aligned_mask = (s_idx >= 0) & (t_idx >= 0) # sentinel=-1 guard
# Pass this as the labels mask to generalized_jsd_loss.
# The loss already handles labels != -100 masking; we repurpose it:
# labels[b, k] = 1 if aligned_mask[b, k] else -100
aligned_labels = torch.where(
aligned_mask,
torch.ones_like(s_idx, dtype=torch.long),
torch.full_like(s_idx, -100, dtype=torch.long),
)
return generalized_jsd_loss(
student_logits=student_aligned,
teacher_logits=teacher_aligned,
labels=aligned_labels,
…
)
4. Why this is canonical
| Property | How this design provides it |
|---|---|
| Token-level alignment | Indices are derived from _build_chat_aligned_mask, which uses per-message prefix deltas to locate content tokens inside the full chat-template tokenization — not naive segment concatenation. |
| Ragged-K safety | Pad to K_max with sentinel -1; emit a *_valid BoolTensor. The loss masks sentinels via labels=-100 (standard HF ignore convention). No silent padding-token contribution. |
| No additional forward passes | The indices are computed from existing mask tensors inside __call__ — zero extra tokenizer calls. |
| Forward-compatible | Emitting distinct student/teacher indices survives a future where the placeholder trick is replaced by dynamic-length alignment. |
| Auditable | A one-line assertion (s_idx == t_idx).all() in a debug build verifies the placeholder trick is still intact. |
5. Code sketch (full emission path)
# === In ComposerDataCollator.__call__, after line 191 ===
def _mask_to_padded_indices(
mask: torch.Tensor, # (B, T) where 1 = valid position
pad_sentinel: int = -1,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Convert (B,T) boolean mask → (B,K_max) index tensor + (B,K_max) validity mask."""
B, T = mask.shape
# Per-row nonzero — torch.nonzero on a 2D bool tensor gives (N,2); reshape.
nz = torch.nonzero(mask, as_tuple=False) # (total_K, 2)
# Group by row:
counts = mask.sum(dim=1).long() # (B,) — K per row
K_max = int(counts.max().item()) if counts.numel() else 0
if K_max == 0:
return (
torch.full((B, 0), pad_sentinel, dtype=torch.long, device=mask.device),
torch.zeros(B, 0, dtype=torch.bool, device=mask.device),
)
idx = torch.full((B, K_max), pad_sentinel, dtype=torch.long, device=mask.device)
valid = torch.zeros(B, K_max, dtype=torch.bool, device=mask.device)
# nz[:, 0] are batch indices, nz[:, 1] are position indices
batch_idx = nz[:, 0] # (total_K,)
pos_idx = nz[:, 1] # (total_K,)
# Build a per-batch write offset using cumsum
offsets = torch.zeros(B + 1, dtype=torch.long, device=mask.device)
offsets[1:] = counts.cumsum(dim=0)
for b in range(B):
start, end = offsets[b].item(), offsets[b + 1].item()
k = end - start
if k > 0:
idx[b, :k] = pos_idx[start:end]
valid[b, :k] = True
return idx, valid
# --- Emission ---
if "sdpo_loss_mask" in out and "response_mask" in out:
t_mask = (out["sdpo_loss_mask"] == 1)
s_mask = (out["response_mask"] == 1)
t_idx, t_valid = _mask_to_padded_indices(t_mask)
s_idx, s_valid = _mask_to_padded_indices(s_mask)
out["student_response_idx"] = s_idx
out["teacher_response_idx"] = t_idx
out["student_response_valid"] = s_valid
out["teacher_response_valid"] = t_valid
6. Migration path
- Add
_mask_to_padded_indicesand the emission block todata_collator.py. - The existing
_compute_sdpo_lossincomposer_trainer.pyalready handles the index path (lines 184–242); it only needs the sentinel-mask addition described in §3 Step 3. - Update
_compute_sdpo_lossto prefer the aligned index path even when shapes match — remove the legacy shape-only fallback from the strict path entirely. - The non-strict path (
strict_sdpo_alignment=False) can fall back totorch.nonzero(sdpo_loss_mask == 1)as a convenience for ad-hoc scripts, but the canonical production path is the explicit indices.