Download src/conveyer/model.py from sumitk/diffusion-conveyer-llm: direct link, hf CLI and curl.
- Browser
- Download file 32.9 kB
-
https://huggingface.co/sumitk/diffusion-conveyer-llm/resolve/main/src/conveyer/model.py
- Command line
-
hf download hf://sumitk/diffusion-conveyer-llm/src/conveyer/model.py
-
curl -L -o model.py https://huggingface.co/sumitk/diffusion-conveyer-llm/resolve/main/src/conveyer/model.py
32.9 kB
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from typing import Optional | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from torch.utils.checkpoint import checkpoint | |
| from conveyer.config import ModelConfig | |
| class RMSNorm(nn.Module): | |
| def __init__(self, d_model: int, eps: float = 1e-6) -> None: | |
| super().__init__() | |
| self.eps = eps | |
| self.weight = nn.Parameter(torch.ones(d_model)) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| norm = x.float().pow(2).mean(dim=-1, keepdim=True) | |
| x_normed = x.float() * torch.rsqrt(norm + self.eps) | |
| return (self.weight * x_normed).type_as(x) | |
| class RotaryEmbedding(nn.Module): | |
| def __init__(self, head_dim: int, max_seq_len: int = 2048, theta: float = 10000.0) -> None: | |
| super().__init__() | |
| assert head_dim % 2 == 0, "head_dim must be even for RoPE" | |
| self.head_dim = head_dim | |
| self.max_seq_len = max_seq_len | |
| self.theta = theta | |
| inv_freq = 1.0 / (theta ** (torch.arange(0, head_dim, 2).float() / head_dim)) | |
| self.register_buffer("inv_freq", inv_freq, persistent=False) | |
| self._build_cache(max_seq_len) | |
| def _build_cache(self, seq_len: int) -> None: | |
| self.max_seq_len = max(self.max_seq_len, seq_len) | |
| t = torch.arange(self.max_seq_len, device=self.inv_freq.device, dtype=torch.float32) | |
| freqs = torch.outer(t, self.inv_freq) # (seq_len, head_dim // 2) | |
| cos = torch.cos(freqs) | |
| sin = torch.sin(freqs) | |
| self.register_buffer("cos_cached", cos, persistent=False) | |
| self.register_buffer("sin_cached", sin, persistent=False) | |
| def get_cos_sin(self, positions: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| """positions: (L,) or (N, L) integer positions.""" | |
| max_pos = int(positions.max().item()) + 1 if positions.numel() > 0 else 1 | |
| if max_pos > self.cos_cached.size(0): | |
| self._build_cache(max_pos + 256) | |
| cos = self.cos_cached[positions] | |
| sin = self.sin_cached[positions] | |
| return cos, sin | |
| def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: | |
| """ | |
| x: (N, H, L, D) | |
| cos, sin: (L, D//2) or (N, L, D//2) | |
| """ | |
| if cos.dim() == 2: | |
| cos = cos.unsqueeze(0).unsqueeze(0) # (1, 1, L, D//2) | |
| sin = sin.unsqueeze(0).unsqueeze(0) | |
| elif cos.dim() == 3: | |
| cos = cos.unsqueeze(1) # (N, 1, L, D//2) | |
| sin = sin.unsqueeze(1) | |
| d2 = x.shape[-1] // 2 | |
| x1 = x[..., :d2] | |
| x2 = x[..., d2:] | |
| out1 = x1 * cos - x2 * sin | |
| out2 = x1 * sin + x2 * cos | |
| return torch.cat([out1, out2], dim=-1).type_as(x) | |
| class SwiGLUFFN(nn.Module): | |
| def __init__(self, d_model: int, d_ff: int, dropout: float = 0.0) -> None: | |
| super().__init__() | |
| self.w_gate = nn.Linear(d_model, d_ff, bias=False) | |
| self.w_up = nn.Linear(d_model, d_ff, bias=False) | |
| self.w_down = nn.Linear(d_ff, d_model, bias=False) | |
| self.drop = nn.Dropout(dropout) if dropout > 0 else nn.Identity() | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return self.drop(self.w_down(F.silu(self.w_gate(x)) * self.w_up(x))) | |
| class PrefixKVCache: | |
| """Stores committed prefix Keys and Values per layer: list of (K, V) each (N, H, P, D_h).""" | |
| k_cache: list[torch.Tensor] | |
| v_cache: list[torch.Tensor] | |
| seq_len: int | |
| def empty(cls, n_layers: int, batch_size: int, n_heads: int, head_dim: int, device: torch.device, dtype: torch.dtype) -> "PrefixKVCache": | |
| k_cache = [torch.empty((batch_size, n_heads, 0, head_dim), device=device, dtype=dtype) for _ in range(n_layers)] | |
| v_cache = [torch.empty((batch_size, n_heads, 0, head_dim), device=device, dtype=dtype) for _ in range(n_layers)] | |
| return cls(k_cache=k_cache, v_cache=v_cache, seq_len=0) | |
| def clone(self) -> "PrefixKVCache": | |
| return PrefixKVCache( | |
| k_cache=[k.clone() for k in self.k_cache], | |
| v_cache=[v.clone() for v in self.v_cache], | |
| seq_len=self.seq_len, | |
| ) | |
| def detach(self) -> "PrefixKVCache": | |
| return PrefixKVCache( | |
| k_cache=[k.detach() for k in self.k_cache], | |
| v_cache=[v.detach() for v in self.v_cache], | |
| seq_len=self.seq_len, | |
| ) | |
| class BeltState: | |
| """Holds the 64-slot Dual-Channel + Elastic Planning dough state on the conveyor belt.""" | |
| e_soft: torch.Tensor # (N, B, D) lexical anchor soft embeddings | |
| m_thought: torch.Tensor # (N, B, D) continuous thought highway vectors | |
| tick: int # current absolute sequence position of Slot 0 | |
| probs: Optional[torch.Tensor] = None # (N, B, V) last probability distributions | |
| logits: Optional[torch.Tensor] = None # (N, B, V) last raw logits | |
| gates: Optional[torch.Tensor] = None # (N, B, D) last update gate activations | |
| shift_probs: Optional[torch.Tensor] = None # (N, B) elastic shift vs. pause probability s_k | |
| def detach(self) -> "BeltState": | |
| return BeltState( | |
| e_soft=self.e_soft.detach(), | |
| m_thought=self.m_thought.detach(), | |
| tick=self.tick, | |
| probs=self.probs.detach() if self.probs is not None else None, | |
| logits=self.logits.detach() if self.logits is not None else None, | |
| gates=self.gates.detach() if self.gates is not None else None, | |
| shift_probs=self.shift_probs.detach() if self.shift_probs is not None else None, | |
| ) | |
| class BeltStepOutput: | |
| logits: torch.Tensor # (N, B, V) raw vocabulary logits at all 64 slots | |
| probs: torch.Tensor # (N, B, V) temperature-scaled probabilities p_k | |
| e_soft: torch.Tensor # (N, B, D) expected vocab embeddings p_k^T E_vocab | |
| cand_thought: torch.Tensor # (N, B, D) candidate thought vectors c_k | |
| gate: torch.Tensor # (N, B, D) update gate g_k | |
| m_updated: torch.Tensor # (N, B, D) updated thought vectors before index shift | |
| shift_prob: torch.Tensor # (N, B) elastic shift probability s_k (1.0 in front conveyor, <1 in back 75% planning) | |
| expand_cand: torch.Tensor # (N, D) mid-belt expansion thought vector for forming new sentence clauses | |
| slot0_logits: torch.Tensor # (N, V) crisp logits at Position 0 | |
| class ConveyerAttention(nn.Module): | |
| def __init__(self, cfg: ModelConfig) -> None: | |
| super().__init__() | |
| assert cfg.d_model % cfg.n_heads == 0 | |
| self.n_heads = cfg.n_heads | |
| self.head_dim = cfg.d_model // cfg.n_heads | |
| self.d_model = cfg.d_model | |
| self.qkv = nn.Linear(cfg.d_model, 3 * cfg.d_model, bias=False) | |
| self.proj = nn.Linear(cfg.d_model, cfg.d_model, bias=False) | |
| self.dropout = cfg.dropout | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| cos: torch.Tensor, | |
| sin: torch.Tensor, | |
| attn_mask: Optional[torch.Tensor] = None, | |
| layer_kv: Optional[tuple[torch.Tensor, torch.Tensor]] = None, | |
| return_new_kv: bool = False, | |
| is_causal: bool = False, | |
| ) -> tuple[torch.Tensor, Optional[tuple[torch.Tensor, torch.Tensor]]]: | |
| """ | |
| x: (N, L, D) | |
| layer_kv: optional (K_prev, V_prev) each (N, H, P, D_h) | |
| """ | |
| bsz, seq_len, _ = x.shape | |
| qkv = self.qkv(x) | |
| q, k, v = qkv.chunk(3, dim=-1) | |
| q = q.view(bsz, seq_len, self.n_heads, self.head_dim).transpose(1, 2) | |
| k = k.view(bsz, seq_len, self.n_heads, self.head_dim).transpose(1, 2) | |
| v = v.view(bsz, seq_len, self.n_heads, self.head_dim).transpose(1, 2) | |
| q = apply_rope(q, cos, sin) | |
| k = apply_rope(k, cos, sin) | |
| new_kv = (k, v) if return_new_kv else None | |
| if layer_kv is not None and layer_kv[0].size(2) > 0: | |
| k = torch.cat([layer_kv[0].type_as(k), k], dim=2) | |
| v = torch.cat([layer_kv[1].type_as(v), v], dim=2) | |
| out = F.scaled_dot_product_attention( | |
| q, | |
| k, | |
| v, | |
| attn_mask=None if is_causal else attn_mask, | |
| dropout_p=self.dropout if self.training else 0.0, | |
| is_causal=is_causal, | |
| ) | |
| out = out.transpose(1, 2).contiguous().view(bsz, seq_len, self.d_model) | |
| return self.proj(out), new_kv | |
| class ConveyerBlock(nn.Module): | |
| def __init__(self, cfg: ModelConfig) -> None: | |
| super().__init__() | |
| self.norm1 = RMSNorm(cfg.d_model) | |
| self.attn = ConveyerAttention(cfg) | |
| self.norm2 = RMSNorm(cfg.d_model) | |
| self.ffn = SwiGLUFFN(cfg.d_model, cfg.d_ff, cfg.dropout) | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| cos: torch.Tensor, | |
| sin: torch.Tensor, | |
| attn_mask: Optional[torch.Tensor] = None, | |
| layer_kv: Optional[tuple[torch.Tensor, torch.Tensor]] = None, | |
| return_new_kv: bool = False, | |
| is_causal: bool = False, | |
| ) -> tuple[torch.Tensor, Optional[tuple[torch.Tensor, torch.Tensor]]]: | |
| attn_out, new_kv = self.attn( | |
| self.norm1(x), | |
| cos=cos, | |
| sin=sin, | |
| attn_mask=attn_mask, | |
| layer_kv=layer_kv, | |
| return_new_kv=return_new_kv, | |
| is_causal=is_causal, | |
| ) | |
| x = x + attn_out | |
| x = x + self.ffn(self.norm2(x)) | |
| return x, new_kv | |
| class ConveyerDiffusionLM(nn.Module): | |
| """ | |
| 64-Slot Dual-Channel Elastic Conveyor-Belt Diffusion Language Model. | |
| Input / Output Contract at every tick t: | |
| - Input : [All Committed Text Prefix x_{0..t-1}] + [64-Slot Conveyor/Planning Belt] | |
| - Output: [Popped-out Confirmed Token x_t at Slot 0] + [Updated 64-Slot Conveyor/Planning Belt] | |
| Two-Zone Human-Style Thinking & Speaking Belt: | |
| - Front 25% (Slots 0 .. F-1, e.g. 0..15): Immediate Articulation Stream. | |
| Shifts left by 1 slot every tick (s_k = 1.0), polishing immediate grammar toward Slot 0 (alpha_0 = 0). | |
| - Back 75% (Slots F .. B-1, e.g. 16..63): Elastic Sentence-Planning Horizon. | |
| Can pause in-place (s_k < 1.0) to hold long-range sentence goals in mind while new sentence clauses | |
| bud and expand in the middle of the belt (around Slot F). | |
| """ | |
| def __init__(self, cfg: ModelConfig) -> None: | |
| super().__init__() | |
| self.cfg = cfg | |
| self.belt_size = cfg.belt_size | |
| self.front_slots = min(cfg.front_conveyer_slots, cfg.belt_size) | |
| self.d_model = cfg.d_model | |
| self.vocab_size = cfg.vocab_size | |
| # Token embedding & weight-tied LM head | |
| self.tok_emb = nn.Embedding(cfg.vocab_size, cfg.d_model) | |
| self.lm_head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False) | |
| self.lm_head.weight = self.tok_emb.weight # weight tying | |
| # Learned oven slot embeddings for positions k = 0 .. B-1 on the conveyor belt | |
| self.slot_emb = nn.Embedding(cfg.belt_size, cfg.d_model) | |
| # Learned initial thought vector for newly spawned dough at Slot B-1 | |
| self.m_init = nn.Parameter(torch.zeros(cfg.d_model)) | |
| # Dough input RMSNorm | |
| self.dough_norm = RMSNorm(cfg.d_model) | |
| # Transformer trunk | |
| self.rope = RotaryEmbedding( | |
| head_dim=cfg.d_model // cfg.n_heads, | |
| max_seq_len=cfg.max_seq_len, | |
| theta=cfg.rope_theta, | |
| ) | |
| self.blocks = nn.ModuleList([ConveyerBlock(cfg) for _ in range(cfg.n_layers)]) | |
| self.final_norm = RMSNorm(cfg.d_model) | |
| # Channel 2: Continuous Thought Highway heads (RecurrentCubeLM style) | |
| self.thought_cand = nn.Linear(cfg.d_model, cfg.d_model, bias=False) | |
| self.thought_gate = nn.Linear(cfg.d_model, cfg.d_model, bias=True) | |
| # Elastic Planning & Mid-Belt Sentence Expansion Heads | |
| # shift_head predicts whether each back-75% planning slot shifts left or pauses in place | |
| self.shift_head = nn.Linear(cfg.d_model, 1, bias=True) | |
| # expand_head synthesizes a new phrase/clause embedding at the boundary between the | |
| # paused 75% planning zone and the moving 25% front conveyor | |
| self.expand_head = nn.Linear(2 * cfg.d_model, cfg.d_model, bias=False) | |
| # Precompute positional temperature schedule tau_k, thought mixing alpha_k, and target horizon offsets | |
| B = cfg.belt_size | |
| F_slots = self.front_slots | |
| k_idx = torch.arange(B, dtype=torch.float32) | |
| ratio = k_idx / max(B - 1, 1) | |
| tau_schedule = cfg.tau_min + (ratio ** cfg.tau_gamma) * (cfg.tau_max - cfg.tau_min) | |
| alpha_schedule = ratio.clone() # alpha_0 = 0.0, alpha_{B-1} = 1.0 | |
| # Target sequence offsets delta_k from current tick t: | |
| # - Front slots k = 0 .. F-1 map 1-to-1 to immediate tokens t + k | |
| # - Back 75% planning slots k = F .. B-1 span a wider lookahead horizon (stride >= 1) | |
| stride = max(1, cfg.plan_horizon_stride) if cfg.use_elastic_planning else 1 | |
| target_offsets = torch.arange(B, dtype=torch.long) | |
| if B > F_slots and stride > 1: | |
| back_idx = torch.arange(B - F_slots, dtype=torch.long) | |
| target_offsets[F_slots:] = F_slots + back_idx * stride | |
| # Prior bias for shift_head: front slots always shift (+6.0), back 75% slots default to ~0.35 shift (65% pause) | |
| shift_prior = torch.zeros(B, dtype=torch.float32) | |
| shift_prior[:F_slots] = 6.0 | |
| if B > F_slots: | |
| back_ratio = torch.linspace(0.0, 1.0, B - F_slots) | |
| shift_prior[F_slots:] = -0.3 - 1.2 * back_ratio # sigmoid(-0.3..-1.5) ~ 0.42 down to 0.18 | |
| self.register_buffer("tau_schedule", tau_schedule, persistent=False) | |
| self.register_buffer("alpha_schedule", alpha_schedule, persistent=False) | |
| self.register_buffer("target_offsets", target_offsets, persistent=False) | |
| self.register_buffer("shift_prior", shift_prior, persistent=False) | |
| self._init_weights() | |
| def _init_weights(self) -> None: | |
| nn.init.normal_(self.tok_emb.weight, mean=0.0, std=0.02) | |
| nn.init.normal_(self.slot_emb.weight, mean=0.0, std=0.02) | |
| nn.init.normal_(self.m_init, mean=0.0, std=0.02) | |
| for m in self.modules(): | |
| if isinstance(m, nn.Linear) and m is not self.lm_head: | |
| nn.init.normal_(m.weight, mean=0.0, std=0.02) | |
| if m.bias is not None: | |
| nn.init.zeros_(m.bias) | |
| nn.init.constant_(self.thought_gate.bias, self.cfg.gate_bias_init) | |
| nn.init.zeros_(self.shift_head.weight) | |
| nn.init.zeros_(self.shift_head.bias) | |
| def num_parameters(self, trainable_only: bool = True) -> int: | |
| return sum(p.numel() for p in self.parameters() if (p.requires_grad or not trainable_only)) | |
| def get_slot_target_offsets(self, active_slots: Optional[int] = None) -> torch.Tensor: | |
| B = active_slots if active_slots is not None else self.belt_size | |
| return self.target_offsets[:B] | |
| def init_prefix_cache(self, batch_size: int, device: torch.device, dtype: Optional[torch.dtype] = None) -> PrefixKVCache: | |
| if dtype is None: | |
| dtype = self.tok_emb.weight.dtype | |
| return PrefixKVCache.empty( | |
| n_layers=self.cfg.n_layers, | |
| batch_size=batch_size, | |
| n_heads=self.cfg.n_heads, | |
| head_dim=self.cfg.d_model // self.cfg.n_heads, | |
| device=device, | |
| dtype=dtype, | |
| ) | |
| def prefill_prefix( | |
| self, | |
| prefix_tokens: torch.Tensor, | |
| kv_cache: Optional[PrefixKVCache] = None, | |
| ) -> PrefixKVCache: | |
| """ | |
| Encodes committed prefix tokens x_{t_start .. t_end} into the causal PrefixKVCache. | |
| prefix_tokens: (N, P) long tensor. | |
| """ | |
| bsz, plen = prefix_tokens.shape | |
| device = prefix_tokens.device | |
| if kv_cache is None: | |
| kv_cache = self.init_prefix_cache(bsz, device) | |
| if plen == 0: | |
| return kv_cache | |
| start_pos = kv_cache.seq_len | |
| positions = torch.arange(start_pos, start_pos + plen, device=device, dtype=torch.long) | |
| cos, sin = self.rope.get_cos_sin(positions) | |
| slot0_bias = self.slot_emb.weight[0].view(1, 1, -1) | |
| x = self.dough_norm(self.tok_emb(prefix_tokens) + slot0_bias) | |
| total_k = start_pos + plen | |
| use_is_causal = start_pos == 0 and plen > 1 | |
| if use_is_causal or plen == 1: | |
| attn_mask = None | |
| else: | |
| causal_mask = torch.ones((plen, total_k), device=device, dtype=torch.bool).tril(diagonal=start_pos) | |
| attn_mask = causal_mask.unsqueeze(0).unsqueeze(0) | |
| new_k_list: list[torch.Tensor] = [] | |
| new_v_list: list[torch.Tensor] = [] | |
| for i, block in enumerate(self.blocks): | |
| layer_kv = (kv_cache.k_cache[i], kv_cache.v_cache[i]) | |
| x, new_kv = block( | |
| x, | |
| cos=cos, | |
| sin=sin, | |
| attn_mask=attn_mask, | |
| layer_kv=layer_kv, | |
| return_new_kv=True, | |
| is_causal=use_is_causal, | |
| ) | |
| assert new_kv is not None | |
| new_k_list.append(torch.cat([kv_cache.k_cache[i].type_as(new_kv[0]), new_kv[0]], dim=2)) | |
| new_v_list.append(torch.cat([kv_cache.v_cache[i].type_as(new_kv[1]), new_kv[1]], dim=2)) | |
| return PrefixKVCache(k_cache=new_k_list, v_cache=new_v_list, seq_len=total_k) | |
| def forward_causal_and_cache( | |
| self, | |
| seq_tokens: torch.Tensor, | |
| prefix_len: int, | |
| ) -> tuple[torch.Tensor, PrefixKVCache]: | |
| """ | |
| Runs a full causal pass over `seq_tokens` (N, L) through the shared transformer blocks: | |
| 1. Computes next-token logits `(N, L, V)` at every position so all ~160 tokens per story | |
| receive direct causal language-modeling supervision (10x more signal per step). | |
| 2. Extracts the exact causal KV cache for `seq_tokens[:, :prefix_len]` (invariant to tokens >= prefix_len) | |
| to seed the conveyor belt unroll without running a duplicate prefill pass. | |
| """ | |
| bsz, seq_len = seq_tokens.shape | |
| device = seq_tokens.device | |
| positions = torch.arange(0, seq_len, device=device, dtype=torch.long) | |
| cos, sin = self.rope.get_cos_sin(positions) | |
| slot0_bias = self.slot_emb.weight[0].view(1, 1, -1) | |
| x = self.dough_norm(self.tok_emb(seq_tokens) + slot0_bias) | |
| k_list: list[torch.Tensor] = [] | |
| v_list: list[torch.Tensor] = [] | |
| for block in self.blocks: | |
| x, new_kv = block( | |
| x, | |
| cos=cos, | |
| sin=sin, | |
| attn_mask=None, | |
| layer_kv=None, | |
| return_new_kv=True, | |
| is_causal=(seq_len > 1), | |
| ) | |
| assert new_kv is not None | |
| k_list.append(new_kv[0][:, :, :prefix_len, :].contiguous()) | |
| v_list.append(new_kv[1][:, :, :prefix_len, :].contiguous()) | |
| h = self.final_norm(x) | |
| causal_logits = self.lm_head(h) | |
| kv_cache = PrefixKVCache(k_cache=k_list, v_cache=v_list, seq_len=prefix_len) | |
| return causal_logits, kv_cache | |
| def append_committed_token( | |
| self, | |
| token_ids: torch.Tensor, | |
| kv_cache: PrefixKVCache, | |
| ) -> PrefixKVCache: | |
| if token_ids.dim() == 1: | |
| token_ids = token_ids.unsqueeze(1) | |
| return self.prefill_prefix(token_ids, kv_cache=kv_cache) | |
| def spawn_raw_dough( | |
| self, | |
| batch_size: int, | |
| num_slots: int, | |
| device: torch.device, | |
| deterministic: bool = False, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| mean_emb = self.tok_emb.weight.mean(dim=0, keepdim=True).unsqueeze(0) | |
| m_base = self.m_init.view(1, 1, -1) | |
| if deterministic or self.cfg.spawn_noise_std <= 0.0: | |
| e_new = mean_emb.expand(batch_size, num_slots, self.d_model).clone() | |
| m_new = m_base.expand(batch_size, num_slots, self.d_model).clone() | |
| else: | |
| eps_e = torch.randn((batch_size, num_slots, self.d_model), device=device, dtype=mean_emb.dtype) | |
| eps_m = torch.randn((batch_size, num_slots, self.d_model), device=device, dtype=mean_emb.dtype) | |
| e_new = mean_emb + self.cfg.spawn_noise_std * 0.02 * eps_e | |
| m_new = m_base + self.cfg.thought_noise_std * 0.02 * eps_m | |
| return e_new, m_new | |
| def init_empty_belt( | |
| self, | |
| batch_size: int, | |
| tick: int, | |
| device: torch.device, | |
| deterministic: bool = False, | |
| ) -> BeltState: | |
| e_soft, m_thought = self.spawn_raw_dough( | |
| batch_size=batch_size, | |
| num_slots=self.belt_size, | |
| device=device, | |
| deterministic=deterministic, | |
| ) | |
| return BeltState(e_soft=e_soft, m_thought=m_thought, tick=tick) | |
| def compose_dough_inputs(self, belt: BeltState, active_slots: Optional[int] = None) -> torch.Tensor: | |
| B = active_slots if active_slots is not None else self.belt_size | |
| e_soft = belt.e_soft[:, :B, :] | |
| m_thought = belt.m_thought[:, :B, :] | |
| slots = torch.arange(B, device=e_soft.device, dtype=torch.long) | |
| s_emb = self.slot_emb(slots).unsqueeze(0) | |
| if self.cfg.use_thought_highway: | |
| alpha = self.alpha_schedule[:B].view(1, B, 1) # alpha_0 == 0.0 | |
| fused = e_soft + alpha * m_thought + s_emb | |
| else: | |
| fused = e_soft + s_emb | |
| return self.dough_norm(fused) | |
| def _build_belt_mask( | |
| self, | |
| belt_len: int, | |
| prefix_len: int, | |
| device: torch.device, | |
| ) -> Optional[torch.Tensor]: | |
| total_k = prefix_len + belt_len | |
| if not self.cfg.causal_belt_only: | |
| return torch.ones((1, 1, belt_len, total_k), device=device, dtype=torch.bool) | |
| mask = torch.ones((belt_len, total_k), device=device, dtype=torch.bool).tril(diagonal=prefix_len) | |
| return mask.unsqueeze(0).unsqueeze(0) | |
| def forward_belt( | |
| self, | |
| belt: BeltState, | |
| kv_cache: Optional[PrefixKVCache] = None, | |
| active_slots: Optional[int] = None, | |
| ) -> BeltStepOutput: | |
| """ | |
| Single forward pass across all active slots on the belt: | |
| Input : [Committed Prefix KV Cache x_{0..t-1}] + [Belt Dough 0..B-1] | |
| Output: [Slot 0 Token Logits + Updated Belt State (Lexical + Thought + Elastic Shift Gate)] | |
| """ | |
| B = active_slots if active_slots is not None else self.belt_size | |
| dough = self.compose_dough_inputs(belt, active_slots=B) | |
| device = dough.device | |
| prefix_len = kv_cache.seq_len if kv_cache is not None else 0 | |
| offsets = self.get_slot_target_offsets(active_slots=B) | |
| positions = belt.tick + offsets | |
| cos, sin = self.rope.get_cos_sin(positions) | |
| attn_mask = ( | |
| None | |
| if not self.cfg.causal_belt_only | |
| else self._build_belt_mask(belt_len=B, prefix_len=prefix_len, device=device) | |
| ) | |
| h = dough | |
| use_ckpt = self.training and self.cfg.grad_checkpointing | |
| for i, block in enumerate(self.blocks): | |
| layer_kv = (kv_cache.k_cache[i], kv_cache.v_cache[i]) if kv_cache is not None else None | |
| if use_ckpt: | |
| def _run_block(inp: torch.Tensor, blk: ConveyerBlock = block, lkv: Optional[tuple[torch.Tensor, torch.Tensor]] = layer_kv) -> torch.Tensor: | |
| out_h, _ = blk(inp, cos=cos, sin=sin, attn_mask=attn_mask, layer_kv=lkv, return_new_kv=False) | |
| return out_h | |
| h = checkpoint(_run_block, h, use_reentrant=False) | |
| else: | |
| h, _ = block( | |
| h, | |
| cos=cos, | |
| sin=sin, | |
| attn_mask=attn_mask, | |
| layer_kv=layer_kv, | |
| return_new_kv=False, | |
| ) | |
| h = self.final_norm(h) # (N, B, D) | |
| # Channel 1: Lexical Anchor (Soft Probability Projection) | |
| logits = self.lm_head(h) # (N, B, V) | |
| tau = self.tau_schedule[:B].view(1, B, 1).type_as(logits) | |
| probs = F.softmax((logits / tau).float(), dim=-1).type_as(self.tok_emb.weight) | |
| e_soft = torch.matmul(probs, self.tok_emb.weight) # (N, B, D) | |
| # Channel 2: Continuous Thought Highway (Gated Residual Update) | |
| cand = self.thought_cand(h) | |
| gate = torch.sigmoid(self.thought_gate(h)) | |
| m_prev = belt.m_thought[:, :B, :] | |
| if self.cfg.use_thought_highway: | |
| m_updated = gate * m_prev + (1.0 - gate) * cand | |
| else: | |
| m_updated = torch.zeros_like(m_prev) | |
| # Elastic Planning Shift/Pause Gate & Mid-Belt Sentence Expansion | |
| F_slots = min(self.front_slots, B) | |
| if self.cfg.use_elastic_planning and B > F_slots: | |
| prior = self.shift_prior[:B].view(1, B).type_as(h) | |
| raw_shift = self.shift_head(h).squeeze(-1) + prior # (N, B) | |
| shift_prob = torch.sigmoid(raw_shift) | |
| # Front conveyor slots (0 .. F-1) always shift left at full speed (1.0) | |
| front_ones = torch.ones((h.size(0), F_slots), device=device, dtype=shift_prob.dtype) | |
| shift_prob = torch.cat([front_ones, shift_prob[:, F_slots:]], dim=1) | |
| # Mid-belt expansion thought synthesized from the boundary between Front (F-1) and Planning (F) | |
| boundary_pair = torch.cat([h[:, F_slots - 1, :], h[:, F_slots, :]], dim=-1) | |
| expand_cand = self.expand_head(boundary_pair) # (N, D) | |
| else: | |
| shift_prob = torch.ones((h.size(0), B), device=device, dtype=h.dtype) | |
| expand_cand = h[:, -1, :] | |
| return BeltStepOutput( | |
| logits=logits, | |
| probs=probs, | |
| e_soft=e_soft, | |
| cand_thought=cand, | |
| gate=gate, | |
| m_updated=m_updated, | |
| shift_prob=shift_prob, | |
| expand_cand=expand_cand, | |
| slot0_logits=logits[:, 0, :], | |
| ) | |
| def shift_belt_left( | |
| self, | |
| step_out: BeltStepOutput, | |
| current_tick: int, | |
| deterministic_spawn: bool = False, | |
| clamp_tokens: Optional[torch.Tensor] = None, | |
| clamp_mask: Optional[torch.Tensor] = None, | |
| ) -> BeltState: | |
| """ | |
| Updates the belt for tick t + 1: | |
| 1. Front Conveyor Zone (Slots 0 .. F-2): | |
| Always shifts left by 1 slot (`1..F-1 -> 0..F-2`) so fully-baked tokens flow smoothly out of Slot 0. | |
| 2. Mid-Belt Sentence Expansion Zone (Slot F-1, e.g. Slot 15): | |
| Blends the advancing plan from Slot F with the newly expanded clause (`expand_cand`), | |
| allowing new sentences and words to form in the middle while the back 75% pauses! | |
| 3. Back 75% Elastic Planning Zone (Slots F .. B-2, e.g. 16..62): | |
| Each slot k updates elastically using its learned shift probability `s_k`: | |
| e'_{k} = s_k * e_{k+1} + (1 - s_k) * e_k | |
| m'_{k} = s_k * m_{k+1} + (1 - s_k) * m_k | |
| When `s_k` is low (~0.25), the back 75% of the belt pauses in-place holding the long-range | |
| sentence plan steady while the front conveyor articulates the current sentence! | |
| 4. Slot B-1 (Slot 63): | |
| Blends fresh raw dough into the tail of the planning belt. | |
| """ | |
| bsz, B, _ = step_out.e_soft.shape | |
| device = step_out.e_soft.device | |
| F_slots = min(self.front_slots, B) | |
| e_spawn, m_spawn = self.spawn_raw_dough( | |
| batch_size=bsz, | |
| num_slots=1, | |
| device=device, | |
| deterministic=deterministic_spawn, | |
| ) | |
| if B <= 1: | |
| e_next = e_spawn | |
| m_next = m_spawn | |
| probs_next = torch.full((bsz, 1, self.vocab_size), 1.0 / self.vocab_size, device=device, dtype=step_out.probs.dtype) | |
| logits_next = torch.zeros((bsz, 1, self.vocab_size), device=device, dtype=step_out.logits.dtype) | |
| gates_next = torch.ones((bsz, 1, self.d_model), device=device, dtype=step_out.gate.dtype) | |
| elif (not self.cfg.use_elastic_planning) or B <= F_slots: | |
| # Strict 1-by-1 shift across all B slots | |
| e_next = torch.cat([step_out.e_soft[:, 1:, :], e_spawn], dim=1) | |
| m_next = torch.cat([step_out.m_updated[:, 1:, :], m_spawn], dim=1) | |
| probs_next = torch.cat( | |
| [ | |
| step_out.probs[:, 1:, :], | |
| torch.full((bsz, 1, self.vocab_size), 1.0 / self.vocab_size, device=device, dtype=step_out.probs.dtype), | |
| ], | |
| dim=1, | |
| ) | |
| logits_next = torch.cat( | |
| [ | |
| step_out.logits[:, 1:, :], | |
| torch.zeros((bsz, 1, self.vocab_size), device=device, dtype=step_out.logits.dtype), | |
| ], | |
| dim=1, | |
| ) | |
| gates_next = torch.cat( | |
| [ | |
| step_out.gate[:, 1:, :], | |
| torch.ones((bsz, 1, self.d_model), device=device, dtype=step_out.gate.dtype), | |
| ], | |
| dim=1, | |
| ) | |
| else: | |
| # Elastic Two-Zone Update: | |
| # (a) Front slots 0 .. F-2 shift strictly from 1 .. F-1 | |
| e_front = step_out.e_soft[:, 1:F_slots, :] | |
| m_front = step_out.m_updated[:, 1:F_slots, :] | |
| # (b) Mid-Belt Expansion Slot (F-1): where paused back-belt plans expand into new active words | |
| s_mid = step_out.shift_prob[:, F_slots : F_slots + 1].unsqueeze(-1) # (N, 1, 1) | |
| e_FromPlan = step_out.e_soft[:, F_slots : F_slots + 1, :] | |
| m_FromPlan = step_out.m_updated[:, F_slots : F_slots + 1, :] | |
| m_expand = step_out.expand_cand.unsqueeze(1) # (N, 1, D) | |
| e_mid = e_FromPlan | |
| m_mid = s_mid * m_FromPlan + (1.0 - s_mid) * m_expand | |
| # (c) Back 75% Planning Slots (F .. B-2): elastic interpolation between staying (k) and shifting (k+1) | |
| s_back = step_out.shift_prob[:, F_slots : B - 1].unsqueeze(-1) # (N, B - 1 - F, 1) | |
| e_stay = step_out.e_soft[:, F_slots : B - 1, :] | |
| e_adv = step_out.e_soft[:, F_slots + 1 : B, :] | |
| e_back = s_back * e_adv + (1.0 - s_back) * e_stay | |
| m_stay = step_out.m_updated[:, F_slots : B - 1, :] | |
| m_adv = step_out.m_updated[:, F_slots + 1 : B, :] | |
| m_back = s_back * m_adv + (1.0 - s_back) * m_stay | |
| # (d) Slot B-1 (Tail of planning belt): elastic blend between holding Slot B-1 and fresh raw dough | |
| s_tail = step_out.shift_prob[:, B - 1 : B].unsqueeze(-1) | |
| e_tail = s_tail * e_spawn + (1.0 - s_tail) * step_out.e_soft[:, B - 1 : B, :] | |
| m_tail = s_tail * m_spawn + (1.0 - s_tail) * step_out.m_updated[:, B - 1 : B, :] | |
| e_next = torch.cat([e_front, e_mid, e_back, e_tail], dim=1) | |
| m_next = torch.cat([m_front, m_mid, m_back, m_tail], dim=1) | |
| probs_next = torch.cat( | |
| [ | |
| step_out.probs[:, 1:, :], | |
| torch.full((bsz, 1, self.vocab_size), 1.0 / self.vocab_size, device=device, dtype=step_out.probs.dtype), | |
| ], | |
| dim=1, | |
| ) | |
| logits_next = torch.cat( | |
| [ | |
| step_out.logits[:, 1:, :], | |
| torch.zeros((bsz, 1, self.vocab_size), device=device, dtype=step_out.logits.dtype), | |
| ], | |
| dim=1, | |
| ) | |
| gates_next = torch.cat( | |
| [ | |
| step_out.gate[:, 1:, :], | |
| torch.ones((bsz, 1, self.d_model), device=device, dtype=step_out.gate.dtype), | |
| ], | |
| dim=1, | |
| ) | |
| if clamp_tokens is not None and clamp_mask is not None: | |
| clamped_emb = self.tok_emb(clamp_tokens) | |
| mask_exp = clamp_mask.unsqueeze(-1) | |
| e_next = torch.where(mask_exp, clamped_emb, e_next) | |
| return BeltState( | |
| e_soft=e_next, | |
| m_thought=m_next, | |
| tick=current_tick + 1, | |
| probs=probs_next, | |
| logits=logits_next, | |
| gates=gates_next, | |
| shift_probs=step_out.shift_prob, | |
| ) | |