| """Byte-level decoder language models. |
| |
| `variant="plain"` is the P1 small Llama-style causal decoder over raw UTF-8 |
| bytes. `variant="spacebyte"` is the P2 word-boundary hierarchy: cheap local |
| byte blocks run everywhere, expensive global blocks run only on patch-boundary |
| positions, then the latest patch context is scattered back to every byte before |
| optional local output blocks. |
| |
| forward() returns (logits, loss, parts) where `parts` breaks the loss into the |
| cross-entropy term and the z-loss term for logging. |
| """ |
| from __future__ import annotations |
|
|
| import math |
| from typing import Dict, Optional, Tuple |
|
|
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
|
|
| from .config import BOS_ID, EOS_ID, VOCAB_SIZE, ByteLMConfig |
| from .layers import Block, RMSNorm, RotaryEmbedding, build_doc_attn_mask |
|
|
|
|
| |
| |
| _SPACEBYTE_WHITESPACE = (9, 10, 11, 12, 13, 32) |
|
|
|
|
| def build_spacebyte_boundary_mask( |
| x: torch.Tensor, |
| seg_ids: Optional[torch.Tensor] = None, |
| spacelike_bytes: Tuple[int, ...] = _SPACEBYTE_WHITESPACE, |
| ) -> torch.Tensor: |
| """Return [B,T] positions promoted to SpaceByte global patches. |
| |
| A text patch starts at the first byte of each document/window and at a |
| spacelike byte that is not itself preceded by another spacelike byte. BOS/EOS |
| ids are also promoted because packed byte streams use them as hard document |
| boundaries. Marking only the first byte in a run of spaces prevents wasting |
| global compute on repeated whitespace. |
| """ |
| if x.dim() != 2: |
| raise ValueError(f"x must be [B,T], got shape {tuple(x.shape)}") |
| B, T = x.shape |
| is_space = torch.zeros_like(x, dtype=torch.bool) |
| for byte in spacelike_bytes: |
| is_space |= x == byte |
|
|
| prev_space = torch.zeros_like(is_space) |
| prev_space[:, 1:] = is_space[:, :-1] |
| boundary = is_space & ~prev_space |
| boundary |= (x == BOS_ID) | (x == EOS_ID) |
|
|
| |
| |
| |
| boundary[:, 0] = True |
| if seg_ids is not None: |
| new_doc = torch.zeros_like(boundary) |
| new_doc[:, 1:] = seg_ids[:, 1:] != seg_ids[:, :-1] |
| boundary |= new_doc |
| return boundary |
|
|
|
|
| class ByteDecoder(nn.Module): |
| def __init__(self, cfg: ByteLMConfig): |
| super().__init__() |
| cfg.validate() |
| self.cfg = cfg |
| self.embed = nn.Embedding(VOCAB_SIZE, cfg.d_model) |
| self.drop = nn.Dropout(cfg.dropout) |
| self.blocks = nn.ModuleList([Block(cfg) for _ in range(cfg.n_layers)]) |
| self.norm_f = RMSNorm(cfg.d_model) |
| self.lm_head = nn.Linear(cfg.d_model, VOCAB_SIZE, bias=False) |
| self.rope = RotaryEmbedding(cfg.head_dim, cfg.rope_theta) |
| if cfg.tie_embeddings: |
| self.lm_head.weight = self.embed.weight |
|
|
| self.apply(self._init_weights) |
| |
| scale = 1.0 / math.sqrt(2 * cfg.n_layers) |
| for name, p in self.named_parameters(): |
| if name.endswith("wo.weight") or name.endswith("w_down.weight"): |
| with torch.no_grad(): |
| p.mul_(scale) |
|
|
| @staticmethod |
| def _init_weights(m: nn.Module): |
| if isinstance(m, nn.Linear): |
| nn.init.normal_(m.weight, mean=0.0, std=0.02) |
| if m.bias is not None: |
| nn.init.zeros_(m.bias) |
| elif isinstance(m, nn.Embedding): |
| nn.init.normal_(m.weight, mean=0.0, std=0.02) |
|
|
| def num_params(self, non_embedding: bool = False) -> int: |
| n = sum(p.numel() for p in self.parameters()) |
| if non_embedding and not self.cfg.tie_embeddings: |
| n -= self.embed.weight.numel() |
| return n |
|
|
| def forward( |
| self, |
| x: torch.Tensor, |
| y: Optional[torch.Tensor] = None, |
| seg_ids: Optional[torch.Tensor] = None, |
| pos_ids: Optional[torch.Tensor] = None, |
| ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Dict[str, float]]: |
| B, T = x.shape |
| if pos_ids is None: |
| pos_ids = torch.arange(T, device=x.device).unsqueeze(0).expand(B, T) |
| cos, sin = self.rope(pos_ids) |
| attn_mask = build_doc_attn_mask(seg_ids) if seg_ids is not None else None |
|
|
| h = self.drop(self.embed(x)) |
| for blk in self.blocks: |
| h = blk(h, cos, sin, attn_mask) |
| h = self.norm_f(h) |
| logits = self.lm_head(h) |
|
|
| loss, parts = _loss_parts(logits, y, self.cfg) |
| return logits, loss, parts |
|
|
| @torch.no_grad() |
| def generate(self, idx: torch.Tensor, max_new_tokens: int, |
| temperature: float = 1.0, top_k: Optional[int] = None, |
| eos_id: Optional[int] = None) -> torch.Tensor: |
| """Autoregressive sampling (no doc-mask; single growing sequence).""" |
| self.eval() |
| for _ in range(max_new_tokens): |
| idx_cond = idx[:, -self.cfg.ctx_len:] |
| logits, _, _ = self(idx_cond) |
| logits = logits[:, -1, :] / max(temperature, 1e-6) |
| if top_k is not None: |
| v, _ = torch.topk(logits, min(top_k, logits.size(-1))) |
| logits[logits < v[:, [-1]]] = float("-inf") |
| probs = F.softmax(logits, dim=-1) |
| nxt = torch.multinomial(probs, num_samples=1) |
| idx = torch.cat((idx, nxt), dim=1) |
| if eos_id is not None and (nxt == eos_id).all(): |
| break |
| return idx |
|
|
|
|
| class SpaceByteDecoder(nn.Module): |
| """SpaceByte-style hierarchy over raw bytes. |
| |
| Local-in blocks process every byte cheaply. Global blocks process only patch |
| boundary states (word/document boundaries), with causal/document masks over |
| the patch sequence. The latest available global patch state is then scattered |
| back to each byte and added as context before local-out blocks and the shared |
| LM head. Setting n_local_in=n_local_out=0 and n_global=n_layers makes this |
| reduce exactly to `ByteDecoder` when every position is a boundary. |
| """ |
|
|
| def __init__(self, cfg: ByteLMConfig): |
| super().__init__() |
| cfg.validate() |
| self.cfg = cfg |
| self.embed = nn.Embedding(VOCAB_SIZE, cfg.d_model) |
| self.drop = nn.Dropout(cfg.dropout) |
| self.local_in_blocks = nn.ModuleList([Block(cfg) for _ in range(cfg.n_local_in)]) |
| self.global_blocks = nn.ModuleList([Block(cfg) for _ in range(cfg.n_global)]) |
| self.local_out_blocks = nn.ModuleList([Block(cfg) for _ in range(cfg.n_local_out)]) |
| self.norm_f = RMSNorm(cfg.d_model) |
| self.lm_head = nn.Linear(cfg.d_model, VOCAB_SIZE, bias=False) |
| self.rope = RotaryEmbedding(cfg.head_dim, cfg.rope_theta) |
| if cfg.tie_embeddings: |
| self.lm_head.weight = self.embed.weight |
|
|
| self.apply(ByteDecoder._init_weights) |
| total_layers = max(1, cfg.n_local_in + cfg.n_global + cfg.n_local_out) |
| scale = 1.0 / math.sqrt(2 * total_layers) |
| for name, p in self.named_parameters(): |
| if name.endswith("wo.weight") or name.endswith("w_down.weight"): |
| with torch.no_grad(): |
| p.mul_(scale) |
|
|
| def num_params(self, non_embedding: bool = False) -> int: |
| n = sum(p.numel() for p in self.parameters()) |
| if non_embedding and not self.cfg.tie_embeddings: |
| n -= self.embed.weight.numel() |
| return n |
|
|
| @staticmethod |
| def gather_patches( |
| h: torch.Tensor, |
| boundary_mask: torch.Tensor, |
| pos_ids: torch.Tensor, |
| seg_ids: Optional[torch.Tensor], |
| max_patches: int, |
| ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: |
| """Gather boundary states into a padded [B,P,D] patch sequence. |
| |
| Returns `(patch_h, patch_valid, patch_pos, patch_seg, patch_ids)`, where |
| `patch_ids[b,t]` is the index of the latest patch at or before byte `t`. |
| If a sequence has more than `max_patches` boundaries, later bytes reuse |
| the last retained patch context; this keeps memory bounded and explicit. |
| """ |
| if h.dim() != 3 or boundary_mask.dim() != 2: |
| raise ValueError("h must be [B,T,D] and boundary_mask must be [B,T]") |
| B, T, D = h.shape |
| counts = boundary_mask.sum(dim=1).clamp_min(1) |
| P = int(min(max_patches, counts.max().item())) |
| device = h.device |
| gather_idx = torch.zeros((B, P), dtype=torch.long, device=device) |
| patch_valid = torch.zeros((B, P), dtype=torch.bool, device=device) |
| patch_ids = torch.empty((B, T), dtype=torch.long, device=device) |
|
|
| for b in range(B): |
| idx = torch.nonzero(boundary_mask[b], as_tuple=False).flatten() |
| if idx.numel() == 0: |
| idx = torch.zeros(1, dtype=torch.long, device=device) |
| keep = idx[:P] |
| n = keep.numel() |
| gather_idx[b, :n] = keep |
| patch_valid[b, :n] = True |
| ordinal = torch.cumsum(boundary_mask[b].to(torch.long), dim=0) - 1 |
| patch_ids[b] = ordinal.clamp(min=0, max=max(n - 1, 0)) |
|
|
| patch_h = h.gather(1, gather_idx.unsqueeze(-1).expand(B, P, D)) |
| patch_pos = pos_ids.gather(1, gather_idx) |
| if seg_ids is None: |
| patch_seg = torch.zeros((B, P), dtype=torch.long, device=device) |
| else: |
| patch_seg = seg_ids.gather(1, gather_idx) |
| return patch_h, patch_valid, patch_pos, patch_seg, patch_ids |
|
|
| @staticmethod |
| def scatter_patches(patch_h: torch.Tensor, patch_ids: torch.Tensor) -> torch.Tensor: |
| """Scatter latest patch states from [B,P,D] back to each byte [B,T,D].""" |
| B, T = patch_ids.shape |
| D = patch_h.size(-1) |
| return patch_h.gather(1, patch_ids.unsqueeze(-1).expand(B, T, D)) |
|
|
| @staticmethod |
| def _patch_attn_mask(patch_seg: torch.Tensor, patch_valid: torch.Tensor) -> torch.Tensor: |
| """Causal/document patch mask that keeps padded query rows finite.""" |
| mask = build_doc_attn_mask(patch_seg) |
| valid = patch_valid.unsqueeze(1) |
| mask = mask & valid.unsqueeze(-1) & valid.unsqueeze(-2) |
| eye = torch.eye(patch_seg.size(1), dtype=torch.bool, device=patch_seg.device) |
| |
| mask = mask | (eye.unsqueeze(0).unsqueeze(0) & ~valid.unsqueeze(-1)) |
| return mask |
|
|
| def forward( |
| self, |
| x: torch.Tensor, |
| y: Optional[torch.Tensor] = None, |
| seg_ids: Optional[torch.Tensor] = None, |
| pos_ids: Optional[torch.Tensor] = None, |
| boundary_mask: Optional[torch.Tensor] = None, |
| ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Dict[str, float]]: |
| B, T = x.shape |
| if pos_ids is None: |
| pos_ids = torch.arange(T, device=x.device).unsqueeze(0).expand(B, T) |
| byte_cos, byte_sin = self.rope(pos_ids) |
| byte_attn_mask = build_doc_attn_mask(seg_ids) if seg_ids is not None else None |
|
|
| h = self.drop(self.embed(x)) |
| for blk in self.local_in_blocks: |
| h = blk(h, byte_cos, byte_sin, byte_attn_mask) |
|
|
| if boundary_mask is None: |
| boundary_mask = build_spacebyte_boundary_mask(x, seg_ids=seg_ids) |
| patch_h, patch_valid, patch_pos, patch_seg, patch_ids = self.gather_patches( |
| h, boundary_mask, pos_ids, seg_ids, self.cfg.max_patches |
| ) |
| patch_cos, patch_sin = self.rope(patch_pos) |
| patch_attn_mask = self._patch_attn_mask(patch_seg, patch_valid) |
| for blk in self.global_blocks: |
| patch_h = blk(patch_h, patch_cos, patch_sin, patch_attn_mask) |
| patch_context = self.scatter_patches(patch_h, patch_ids) |
| if self.cfg.n_local_in == 0 and self.cfg.n_local_out == 0: |
| |
| |
| h = patch_context |
| else: |
| h = h + patch_context |
|
|
| for blk in self.local_out_blocks: |
| h = blk(h, byte_cos, byte_sin, byte_attn_mask) |
| h = self.norm_f(h) |
| logits = self.lm_head(h) |
|
|
| loss, parts = _loss_parts(logits, y, self.cfg) |
| return logits, loss, parts |
|
|
| @torch.no_grad() |
| def generate(self, idx: torch.Tensor, max_new_tokens: int, |
| temperature: float = 1.0, top_k: Optional[int] = None, |
| eos_id: Optional[int] = None) -> torch.Tensor: |
| """Autoregressive sampling with SpaceByte boundary detection per window.""" |
| self.eval() |
| for _ in range(max_new_tokens): |
| idx_cond = idx[:, -self.cfg.ctx_len:] |
| logits, _, _ = self(idx_cond) |
| logits = logits[:, -1, :] / max(temperature, 1e-6) |
| if top_k is not None: |
| v, _ = torch.topk(logits, min(top_k, logits.size(-1))) |
| logits[logits < v[:, [-1]]] = float("-inf") |
| probs = F.softmax(logits, dim=-1) |
| nxt = torch.multinomial(probs, num_samples=1) |
| idx = torch.cat((idx, nxt), dim=1) |
| if eos_id is not None and (nxt == eos_id).all(): |
| break |
| return idx |
|
|
|
|
| def _loss_parts( |
| logits: torch.Tensor, |
| y: Optional[torch.Tensor], |
| cfg: ByteLMConfig, |
| ) -> Tuple[Optional[torch.Tensor], Dict[str, float]]: |
| """Shared CE + z-loss computation for both decoder variants.""" |
| if y is None: |
| return None, {} |
| ce = F.cross_entropy( |
| logits.view(-1, VOCAB_SIZE), y.reshape(-1), |
| label_smoothing=cfg.label_smoothing, |
| ) |
| z = logits.logsumexp(dim=-1).pow(2).mean() |
| loss = ce + cfg.z_loss_weight * z |
| return loss, {"ce": ce.item(), "z": z.item(), "loss": loss.item()} |
|
|
|
|
| def build_model(cfg: ByteLMConfig) -> nn.Module: |
| if cfg.variant == "plain": |
| return ByteDecoder(cfg) |
| if cfg.variant == "spacebyte": |
| return SpaceByteDecoder(cfg) |
| raise ValueError(f"unknown variant {cfg.variant!r}") |
|
|