"""Design C machinery: k-loop the prompt, then per-token carry generation. Structure per sequence: [prompt] [p pause tokens] [answer] 1. prefill: prompt positions looped k times through the merge (settled plan) 2. carry scan: each pause/answer position's band input is x_t = merge(e_t, s_{t-1}) where s_{t-1} is the *previous position's* final band output — the latent thought chain runs through the pause positions' workspace states (token content of pauses is inert: ). 3. suffix -> logits; CE on answer tokens only. The scan is sequential per position (RNN-style through the band); each step re-runs the band on the full sequence — causality keeps settled positions stable, provisional later positions are recomputed next step anyway. """ import torch from torch.utils.checkpoint import checkpoint PAUSE_ID = 6 # def _pos_ids(mask): return (mask.cumsum(-1) - 1).clamp(min=0) def prompt_prefill(looper, adapter, e, calls, prompt_mask, k): """k merge->band loops over prompt positions. Returns (S, X).""" with torch.no_grad(): s = looper.band(e, calls) x = e for _ in range(k): x = torch.where(prompt_mask[..., None], adapter(e, s), e) s = looper.band(x, calls) return s, x def carry_steps(looper, adapter, e, calls, S, X, step_updates, use_checkpoint=False, iter_states=None): """Sequential scan. step_updates: list of (row_idx, pos, [inplace]) index tensors. inplace=True re-iterates the SAME position, seeding from its own previous band output (internal band looping) instead of its left neighbor. iter_states: optional list — every inplace update's fresh band output at pos is appended (for lens losses).""" for upd in step_updates: rows, pos = upd[0], upd[1] inplace = len(upd) > 2 and upd[2] if rows.numel() == 0: continue seed = S[rows, pos] if inplace else S[rows, pos - 1] x_new = adapter(e[rows, pos], seed) X = X.clone() X[rows, pos] = x_new.to(X.dtype) S = (checkpoint(lambda X_: looper.band(X_, calls), X, use_reentrant=False) if use_checkpoint else looper.band(X, calls)) if inplace and iter_states is not None: iter_states.append(S[rows, pos]) return S, X def build_step_updates(prompt_lens, total_lens, device): """For right-padded batches: step j updates row b at prompt_lens[b]+j.""" max_span = int((total_lens - prompt_lens).max()) updates = [] for j in range(max_span): pos = prompt_lens + j sel = pos < total_lens rows = torch.nonzero(sel, as_tuple=False).squeeze(-1) updates.append((rows.to(device), pos[sel].to(device))) return updates def splice_inner_iters(updates, inner_iters, inner_at, prompt_lens, dev, B): """Insert inner_iters in-place band iterations at the (batch-uniform offset) anchor position, right after the scan first settles it.""" j_anchor = int((inner_at - prompt_lens).max()) rows = torch.arange(B, device=dev) inner = [(rows, inner_at.to(dev), True)] * inner_iters if j_anchor < 0: # anchor is the last PROMPT position (no pauses at all): # iterate there before the scan enters the visible tokens return inner + updates out = [] for j, u in enumerate(updates): out.append(u) if j == j_anchor: out += inner return out def sym_iterate(looper, adapter, proj, e, calls, S, X, rows, anchor, m, lens_fn, embed_w, sym_tf=None, start_id=None, topk=32, use_checkpoint=False, iter_states=None): """Item 32: discrete latent chain at the anchor. Each tick reads the previous anchor state through the lens, snaps it to a token (straight-through over top-k) or takes the teacher token (sym_tf: (B, m) ids, teacher forcing), and feeds that token's embedding back through a zero-init projector ALONGSIDE the analog carry: x_i = merge(e, s_{i-1}) + proj(E(sym)) Tick 0 uses start_id (a newline: 'a step begins').""" for i in range(m): s_prev = S[rows, anchor] if sym_tf is not None: symb = embed_w[sym_tf[:, i]] elif i == 0: symb = embed_w[torch.full((rows.shape[0],), start_id, device=e.device)] else: logits = lens_fn(s_prev).float() p, idx = torch.softmax(logits, -1).topk(topk, dim=-1) p = p / p.sum(-1, keepdim=True) soft = (p.unsqueeze(-1) * embed_w[idx].float()).sum(-2) hard = embed_w[idx[:, 0]].float() symb = hard + soft - soft.detach() x_new = (adapter(e[rows, anchor], s_prev).float() + proj(symb.float())) X = X.clone() X[rows, anchor] = x_new.to(X.dtype) S = (checkpoint(lambda X_: looper.band(X_, calls), X, use_reentrant=False) if use_checkpoint else looper.band(X, calls)) if iter_states is not None: iter_states.append(S[rows, anchor]) return S, X def carry_logits(looper, adapter, input_ids, attention_mask, prompt_lens, k, use_checkpoint=False, feedforward=False, return_states=False, inner_iters=0, inner_at=None, iter_states=None): """Teacher-forced design-C forward (right-padded batch). feedforward=True: pause-token control — same positions get the adapter as x_t = merge(e_t, e_t) in ONE band pass; no state propagates between positions. Isolates 'pause compute + weights' from the recurrence.""" dev = input_ids.device calls, _ = looper.capture(input_ids, attention_mask, logits_to_keep=1) e = looper._hin[looper.l0].detach() ar = torch.arange(input_ids.shape[1], device=dev) prompt_mask = ar[None, :] < prompt_lens[:, None].to(dev) if feedforward: touched = attention_mask.bool() x = torch.where(touched[..., None], adapter(e, e), e) S = (checkpoint(lambda x_: looper.band(x_, calls), x, use_reentrant=False) if use_checkpoint else looper.band(x, calls)) out = looper.suffix_logits(S, calls) return (out, S) if return_states else out S, X = prompt_prefill(looper, adapter, e, calls, prompt_mask, k) total_lens = attention_mask.sum(-1) updates = build_step_updates(prompt_lens.to(dev), total_lens.to(dev), dev) if inner_iters and inner_at is not None: updates = splice_inner_iters(updates, inner_iters, inner_at, prompt_lens.to(dev), dev, input_ids.shape[0]) S, X = carry_steps(looper, adapter, e, calls, S, X, updates, use_checkpoint=use_checkpoint, iter_states=iter_states) out = looper.suffix_logits(S, calls) return (out, S) if return_states else out @torch.no_grad() def generate_carry_c(looper, adapter, tok, input_ids, attention_mask, k, p, max_new_tokens=10, feedforward=False, inner_iters=0, kvmem=None, symchain=None): """Greedy design-C generation (left-padded batch, uniform positions). Appends p pause tokens, prefill-loops the prompt, carries through the pauses, then generates with per-token carry.""" B = input_ids.shape[0] dev = input_ids.device pauses = torch.full((B, p), PAUSE_ID, dtype=torch.long, device=dev) ids = torch.cat([input_ids, pauses], 1) mask = torch.cat([attention_mask, torch.ones_like(pauses)], 1) n_prompt = input_ids.shape[1] eos = {tok.eos_token_id, tok.convert_tokens_to_ids("")} done = torch.zeros(B, dtype=torch.bool, device=dev) X_store = None # merged band inputs for settled positions for step in range(max_new_tokens + 1): # step 0 = prefill + pause scan calls, _ = looper.capture(ids, mask, logits_to_keep=1, position_ids=_pos_ids(mask)) e = looper._hin[looper.l0] if feedforward: x = torch.where(mask.bool()[..., None], adapter(e, e), e) S = looper.band(x, calls) X = x elif X_store is None: ar = torch.arange(ids.shape[1], device=dev) prompt_mask = (ar[None, :] < n_prompt) & mask.bool() S, X = prompt_prefill(looper, adapter, e, calls, prompt_mask, k) updates = [(torch.arange(B, device=dev), torch.full((B,), n_prompt + j, device=dev, dtype=torch.long)) for j in range(p)] if symchain is not None and inner_iters: anchor_sc = torch.full((B,), n_prompt + p - 1, device=dev, dtype=torch.long) S, X = sym_iterate( looper, adapter, symchain["proj"], e, calls, S, X, torch.arange(B, device=dev), anchor_sc, inner_iters, symchain["lens_fn"], symchain["embed_w"], start_id=symchain["start_id"]) updates = updates # pauses (if any) already handled above elif inner_iters: anchor = torch.full((B,), n_prompt + p - 1, device=dev, dtype=torch.long) inner = [(torch.arange(B, device=dev), anchor, True) for _ in range(inner_iters)] # p=0: iterate at the last prompt position, before any # visible token — no pause tokens involved updates = (inner + updates) if p == 0 else (updates + inner) itst = [] if (kvmem is not None and inner_iters) else None S, X = carry_steps(looper, adapter, e, calls, S, X, updates, iter_states=itst) if kvmem is not None and itst: from kv_memory import arm_memory arm_memory(kvmem(torch.stack(itst, 1))) else: X = torch.cat([X_store, e[:, X_store.shape[1]:]], 1) rows = torch.arange(B, device=dev) pos = torch.full((B,), ids.shape[1] - 1, device=dev, dtype=torch.long) S = looper.band(X, calls) # settled prefix + provisional new pos S, X = carry_steps(looper, adapter, e, calls, S, X, [(rows, pos)]) X_store = X logits = looper.suffix_logits(S, calls, last_only=True) nxt = logits[:, -1].argmax(-1) nxt = torch.where(done, torch.full_like(nxt, list(eos)[0]), nxt) ids = torch.cat([ids, nxt[:, None]], 1) mask = torch.cat([mask, (~done)[:, None].long()], 1) done |= torch.tensor([t.item() in eos for t in nxt], device=dev) if done.all(): break return ids