Text Generation
Transformers
Safetensors
PyTorch
Kashmiri
ksbyte
kashmiri
byte-level
causal-lm
spacebyte
custom_code
Eval Results (legacy)
Instructions to use Omarrran/ks-byte-lm-spacebyte-transformers with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Omarrran/ks-byte-lm-spacebyte-transformers with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="Omarrran/ks-byte-lm-spacebyte-transformers", trust_remote_code=True)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("Omarrran/ks-byte-lm-spacebyte-transformers", trust_remote_code=True, dtype="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use Omarrran/ks-byte-lm-spacebyte-transformers with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "Omarrran/ks-byte-lm-spacebyte-transformers" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Omarrran/ks-byte-lm-spacebyte-transformers", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/Omarrran/ks-byte-lm-spacebyte-transformers
- SGLang
How to use Omarrran/ks-byte-lm-spacebyte-transformers with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "Omarrran/ks-byte-lm-spacebyte-transformers" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Omarrran/ks-byte-lm-spacebyte-transformers", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "Omarrran/ks-byte-lm-spacebyte-transformers" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Omarrran/ks-byte-lm-spacebyte-transformers", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use Omarrran/ks-byte-lm-spacebyte-transformers with Docker Model Runner:
docker model run hf.co/Omarrran/ks-byte-lm-spacebyte-transformers
| from __future__ import annotations | |
| from typing import Optional | |
| import torch | |
| import torch.nn.functional as F | |
| from transformers.modeling_utils import PreTrainedModel | |
| from transformers.generation import GenerationMixin | |
| from transformers.modeling_outputs import CausalLMOutput | |
| from .configuration_ksbyte import KsByteConfig | |
| # ---- Inlined ksbyte.config.py ------------------------------------------------- | |
| """Single source of truth for every tunable knob in ks_byte_lm. | |
| Mirrors the philosophy of the ks_diacritizer DiacConfig: nothing else in the | |
| codebase hard-codes a hyper-parameter. Override fields from the CLI / Modal / | |
| run_local by passing a dict to `.merge(...)`. | |
| A frozen copy of this config is embedded in every checkpoint, so a model can be | |
| rebuilt and resumed on any machine without the original launch command. | |
| """ | |
| from dataclasses import asdict, dataclass, field, replace | |
| from typing import List, Optional | |
| # ----- byte vocabulary ------------------------------------------------------- | |
| # 0..255 raw UTF-8 bytes, then three control ids. Stored as uint16 on disk. | |
| BYTE_VOCAB = 256 | |
| BOS_ID = 256 | |
| EOS_ID = 257 | |
| PAD_ID = 258 | |
| VOCAB_SIZE = 259 | |
| class ByteLMConfig: | |
| # ------------------------------ dataset -------------------------------- | |
| hf_dataset: str = "Omarrran/KS-PRET-5M_5_million_kashmiri_Pretrainning_LLM_dataset_12M_tokens_2026" | |
| hf_revision: Optional[str] = None | |
| text_col: str = "auto" # "auto" => pick the longest string column | |
| local_text_file: Optional[str] = None # bypass HF: a local UTF-8 .txt path | |
| min_ks_ratio: float = 0.90 # drop records below this script purity | |
| min_chars: int = 2 | |
| max_chars: int = 100_000 # guard against pathological mega-rows | |
| keep_mixed_script: bool = True # keep the ~18% non-Nastaliq content | |
| # ------------------------- normalization policy ------------------------ | |
| zwnj_policy: str = "keep" # keep | to_space | strip | |
| digit_policy: str = "keep_native" # keep_native | to_ascii | |
| remove_diacritics: bool = False # NEVER true for the foundation model | |
| # ------------------------------ split ---------------------------------- | |
| val_frac: float = 0.05 | |
| test_frac: float = 0.05 | |
| split_seed: int = 13 # deterministic, content-hash based | |
| dedup: bool = True # exact-hash dedup before splitting | |
| # ----------------------------- data cache ------------------------------ | |
| data_dir: str = "data" # where {train,val,test}.bin + meta.json live | |
| rebuild_data: bool = False # force re-running data_prep | |
| # ------------------------------ model ---------------------------------- | |
| variant: str = "plain" # "plain" (implemented) | "spacebyte" (P2) | |
| d_model: int = 384 | |
| n_layers: int = 6 # used by "plain" | |
| n_heads: int = 6 | |
| n_kv_heads: int = 2 # GQA; must divide n_heads | |
| mlp_ratio: float = 2.6667 # SwiGLU hidden = round(mlp_ratio * d_model) | |
| ctx_len: int = 2048 # bytes per window | |
| rope_theta: float = 10_000.0 | |
| dropout: float = 0.1 # ON: 48MB overfits a transformer fast | |
| qk_norm: bool = True # LayerNorm on Q,K -> tames attention logits | |
| tie_embeddings: bool = True | |
| # SpaceByte-only (reserved; "spacebyte" variant lands in P2) | |
| n_local_in: int = 2 | |
| n_global: int = 6 | |
| n_local_out: int = 2 | |
| max_patches: int = 320 | |
| # ---------------------------- optimisation ----------------------------- | |
| epochs: float = 4.0 # Muennighoff: <=4 epochs ~ free on repeats | |
| lr: float = 4e-4 | |
| min_lr_ratio: float = 0.1 # cosine floor = lr * min_lr_ratio | |
| warmup_ratio: float = 0.03 | |
| weight_decay: float = 0.1 | |
| beta1: float = 0.9 | |
| beta2: float = 0.95 | |
| grad_clip: float = 1.0 | |
| z_loss_weight: float = 1e-4 # auxiliary logit-norm regularizer | |
| label_smoothing: float = 0.0 | |
| batch_size: int = 16 # sequences per micro-step | |
| grad_accum: int = 4 # effective batch = batch_size * grad_accum | |
| doc_attention_mask: bool = True # block cross-document attention + RoPE reset | |
| # --------------------------- system / perf ----------------------------- | |
| device: str = "auto" # auto | cuda | cpu | |
| bf16: bool = True # L4 (Ada) supports bf16 | |
| tf32: bool = True | |
| torch_compile: bool = False # optional; can be flaky -> off by default | |
| num_workers: int = 4 | |
| seed: int = 42 | |
| # ------------------------------- eval ---------------------------------- | |
| eval_interval: int = 500 # optimiser steps between evals | |
| eval_iters: int = 100 # micro-batches per eval estimate | |
| log_interval: int = 20 | |
| generate_every: int = 1000 # emit a sample generation every N steps | |
| generate_tokens: int = 160 | |
| early_stop_patience: int = 6 # evals without val-BPB improvement | |
| max_steps: Optional[int] = None # hard cap (None => derive from epochs) | |
| # --------------------------- io / logging ------------------------------ | |
| output_dir: str = "outputs" | |
| run_name: str = "ksbyte-plain-10m" | |
| resume: str = "auto" # auto | never | <path-to-checkpoint> | |
| save_interval: int = 500 # also saves best-on-eval regardless | |
| save_total_limit: int = 3 | |
| use_wandb: bool = False | |
| wandb_project: str = "ks-byte-lm" | |
| # ------------------------------ helpers -------------------------------- | |
| def run_dir(self) -> str: | |
| return f"{self.output_dir.rstrip('/')}/{self.run_name}" | |
| def effective_batch(self) -> int: | |
| return self.batch_size * self.grad_accum | |
| def head_dim(self) -> int: | |
| if self.d_model % self.n_heads != 0: | |
| raise ValueError(f"d_model {self.d_model} not divisible by n_heads {self.n_heads}") | |
| return self.d_model // self.n_heads | |
| def ffn_hidden(self) -> int: | |
| return int(round(self.mlp_ratio * self.d_model)) | |
| def validate(self) -> "ByteLMConfig": | |
| """Fail fast on inconsistent settings (called by train/data entrypoints).""" | |
| errs: List[str] = [] | |
| if self.n_heads % self.n_kv_heads != 0: | |
| errs.append(f"n_heads {self.n_heads} not divisible by n_kv_heads {self.n_kv_heads}") | |
| if self.d_model % self.n_heads != 0: | |
| errs.append(f"d_model {self.d_model} not divisible by n_heads {self.n_heads}") | |
| if self.variant not in ("plain", "spacebyte"): | |
| errs.append(f"unknown variant {self.variant!r}") | |
| if self.zwnj_policy not in ("keep", "to_space", "strip"): | |
| errs.append(f"unknown zwnj_policy {self.zwnj_policy!r}") | |
| if self.digit_policy not in ("keep_native", "to_ascii"): | |
| errs.append(f"unknown digit_policy {self.digit_policy!r}") | |
| if not (0.0 <= self.val_frac + self.test_frac < 0.9): | |
| errs.append("val_frac + test_frac must be in [0, 0.9)") | |
| if self.ctx_len < 8: | |
| errs.append("ctx_len too small") | |
| if self.variant == "spacebyte": | |
| if self.n_local_in < 0 or self.n_global < 0 or self.n_local_out < 0: | |
| errs.append("spacebyte layer counts must be non-negative") | |
| if self.n_global < 1: | |
| errs.append("spacebyte requires at least one global block") | |
| if self.max_patches < 1: | |
| errs.append("spacebyte max_patches must be >= 1") | |
| if errs: | |
| raise ValueError("Invalid ByteLMConfig:\n - " + "\n - ".join(errs)) | |
| return self | |
| def merge(self, overrides: Optional[dict]) -> "ByteLMConfig": | |
| """Return a copy with given keys replaced; warns (not fails) on unknown keys.""" | |
| if not overrides: | |
| return replace(self) | |
| known = {k: v for k, v in overrides.items() if k in self.__dataclass_fields__} | |
| unknown = set(overrides) - set(known) | |
| if unknown: | |
| print(f"[config] WARNING ignoring unknown overrides: {sorted(unknown)}") | |
| return replace(self, **known) | |
| def to_dict(self) -> dict: | |
| return asdict(self) | |
| def from_dict(cls, d: dict) -> "ByteLMConfig": | |
| known = {k: v for k, v in d.items() if k in cls.__dataclass_fields__} | |
| return cls(**known) | |
| # ---- Inlined ksbyte.layers.py ------------------------------------------------- | |
| """Modern Llama-style building blocks, isolated so architectures swap easily. | |
| Components: RMSNorm (pre-norm), RoPE (position ids supplied by the loader so it | |
| resets per document), GQA attention with optional QK-Norm and document-block | |
| attention masking, and a SwiGLU MLP. | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| class RMSNorm(nn.Module): | |
| def __init__(self, dim: int, eps: float = 1e-6): | |
| super().__init__() | |
| self.eps = eps | |
| self.weight = nn.Parameter(torch.ones(dim)) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| dtype = x.dtype | |
| x = x.float() | |
| x = x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) | |
| return (x * self.weight).to(dtype) | |
| class RotaryEmbedding(nn.Module): | |
| """Builds cos/sin tables from explicit position ids ([B,T]).""" | |
| def __init__(self, head_dim: int, theta: float = 10_000.0): | |
| super().__init__() | |
| if head_dim % 2 != 0: | |
| raise ValueError("head_dim must be even for RoPE") | |
| inv_freq = 1.0 / (theta ** (torch.arange(0, head_dim, 2).float() / head_dim)) | |
| self.register_buffer("inv_freq", inv_freq, persistent=False) | |
| def forward(self, pos_ids: torch.Tensor): | |
| # pos_ids: [B,T] -> freqs [B,T,head_dim/2] | |
| freqs = pos_ids.float().unsqueeze(-1) * self.inv_freq.to(pos_ids.device) | |
| emb = torch.cat((freqs, freqs), dim=-1) # [B,T,head_dim] | |
| return emb.cos().unsqueeze(1), emb.sin().unsqueeze(1) # [B,1,T,head_dim] | |
| def _rotate_half(x: torch.Tensor) -> torch.Tensor: | |
| x1, x2 = x.chunk(2, dim=-1) | |
| return torch.cat((-x2, x1), dim=-1) | |
| def apply_rope(q: torch.Tensor, k: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor): | |
| q = (q * cos) + (_rotate_half(q) * sin) | |
| k = (k * cos) + (_rotate_half(k) * sin) | |
| return q, k | |
| class Attention(nn.Module): | |
| def __init__(self, cfg): | |
| super().__init__() | |
| self.n_heads = cfg.n_heads | |
| self.n_kv_heads = cfg.n_kv_heads | |
| self.head_dim = cfg.head_dim | |
| self.dropout = cfg.dropout | |
| self.qk_norm = cfg.qk_norm | |
| d = cfg.d_model | |
| self.wq = nn.Linear(d, self.n_heads * self.head_dim, bias=False) | |
| self.wk = nn.Linear(d, self.n_kv_heads * self.head_dim, bias=False) | |
| self.wv = nn.Linear(d, self.n_kv_heads * self.head_dim, bias=False) | |
| self.wo = nn.Linear(self.n_heads * self.head_dim, d, bias=False) | |
| if self.qk_norm: | |
| self.q_norm = RMSNorm(self.head_dim) | |
| self.k_norm = RMSNorm(self.head_dim) | |
| def forward(self, x, cos, sin, attn_mask): | |
| B, T, _ = x.shape | |
| q = self.wq(x).view(B, T, self.n_heads, self.head_dim) | |
| k = self.wk(x).view(B, T, self.n_kv_heads, self.head_dim) | |
| v = self.wv(x).view(B, T, self.n_kv_heads, self.head_dim) | |
| if self.qk_norm: | |
| q = self.q_norm(q) | |
| k = self.k_norm(k) | |
| q = q.transpose(1, 2) # [B,H,T,D] | |
| k = k.transpose(1, 2) # [B,KV,T,D] | |
| v = v.transpose(1, 2) | |
| q, k = apply_rope(q, k, cos, sin) | |
| # GQA: expand kv heads to match query heads. | |
| if self.n_kv_heads != self.n_heads: | |
| rep = self.n_heads // self.n_kv_heads | |
| k = k.repeat_interleave(rep, dim=1) | |
| v = v.repeat_interleave(rep, dim=1) | |
| dp = self.dropout if self.training else 0.0 | |
| if attn_mask is None: | |
| out = F.scaled_dot_product_attention(q, k, v, is_causal=True, dropout_p=dp) | |
| else: | |
| # boolean mask [B,1,T,T]: True => attend. | |
| out = F.scaled_dot_product_attention(q, k, v, attn_mask=attn_mask, dropout_p=dp) | |
| out = out.transpose(1, 2).contiguous().view(B, T, self.n_heads * self.head_dim) | |
| return self.wo(out) | |
| class SwiGLU(nn.Module): | |
| def __init__(self, cfg): | |
| super().__init__() | |
| d, h = cfg.d_model, cfg.ffn_hidden | |
| self.w_gate = nn.Linear(d, h, bias=False) | |
| self.w_up = nn.Linear(d, h, bias=False) | |
| self.w_down = nn.Linear(h, d, bias=False) | |
| def forward(self, x): | |
| return self.w_down(F.silu(self.w_gate(x)) * self.w_up(x)) | |
| class Block(nn.Module): | |
| def __init__(self, cfg): | |
| super().__init__() | |
| self.norm1 = RMSNorm(cfg.d_model) | |
| self.attn = Attention(cfg) | |
| self.norm2 = RMSNorm(cfg.d_model) | |
| self.mlp = SwiGLU(cfg) | |
| self.drop = nn.Dropout(cfg.dropout) | |
| def forward(self, x, cos, sin, attn_mask): | |
| x = x + self.drop(self.attn(self.norm1(x), cos, sin, attn_mask)) | |
| x = x + self.drop(self.mlp(self.norm2(x))) | |
| return x | |
| def build_doc_attn_mask(seg_ids: torch.Tensor) -> torch.Tensor: | |
| """Boolean [B,1,T,T] mask: position i may attend to j iff j<=i (causal) and | |
| they share a document segment. True => attend.""" | |
| B, T = seg_ids.shape | |
| causal = torch.tril(torch.ones(T, T, dtype=torch.bool, device=seg_ids.device)) | |
| same_doc = seg_ids.unsqueeze(2) == seg_ids.unsqueeze(1) # [B,T,T] | |
| return (causal.unsqueeze(0) & same_doc).unsqueeze(1) | |
| # ---- Inlined ksbyte.model.py -------------------------------------------------- | |
| """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. | |
| """ | |
| import math | |
| from typing import Dict, Optional, Tuple | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| # ASCII whitespace bytes that delimit Kashmiri words in the normalized corpus. | |
| # UTF-8 non-ASCII punctuation is deliberately not treated as a patch boundary. | |
| _SPACEBYTE_WHITESPACE = (9, 10, 11, 12, 13, 32) # \t \n \v \f \r space | |
| 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) | |
| # Every sampled window needs at least one patch. When segment ids are known, | |
| # also seed each new document segment so document-aware global attention has | |
| # a patch before the first visible space. | |
| 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 residual-path projections by 1/sqrt(2*n_layers) for stable depth. | |
| 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) | |
| 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 | |
| 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 | |
| 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 | |
| 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)) | |
| 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) | |
| # Padded rows are ignored later, but SDPA still needs at least one True. | |
| 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: | |
| # Degenerate hierarchy: the global stream is the whole residual | |
| # stream, so all-boundary inputs exactly match the plain decoder. | |
| 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 | |
| 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}") | |
| def _to_byte_lm_config(config: KsByteConfig) -> ByteLMConfig: | |
| keys = ByteLMConfig.__dataclass_fields__.keys() | |
| return ByteLMConfig.from_dict({k: getattr(config, k) for k in keys if hasattr(config, k)}) | |
| class KsByteForCausalLM(PreTrainedModel, GenerationMixin): | |
| config_class = KsByteConfig | |
| base_model_prefix = "model" | |
| main_input_name = "input_ids" | |
| supports_gradient_checkpointing = False | |
| _supports_cache_class = False | |
| _tied_weights_keys = ["model.lm_head.weight"] | |
| # Some Transformers releases expect this to be a dict and call .keys(); | |
| # others only need membership. Keep it dict-shaped for Colab compatibility. | |
| all_tied_weights_keys = {"model.lm_head.weight": "model.embed.weight"} | |
| def __init__(self, config: KsByteConfig): | |
| super().__init__(config) | |
| self.model = build_model(_to_byte_lm_config(config)) | |
| def get_input_embeddings(self): | |
| return self.model.embed | |
| def set_input_embeddings(self, value): | |
| self.model.embed = value | |
| if getattr(self.config, "tie_embeddings", False): | |
| self.model.lm_head.weight = self.model.embed.weight | |
| def get_output_embeddings(self): | |
| return self.model.lm_head | |
| def set_output_embeddings(self, new_embeddings): | |
| self.model.lm_head = new_embeddings | |
| def prepare_inputs_for_generation(self, input_ids, **kwargs): | |
| if input_ids.shape[1] > self.config.ctx_len: | |
| input_ids = input_ids[:, -self.config.ctx_len:] | |
| return {"input_ids": input_ids} | |
| def forward( | |
| self, | |
| input_ids: torch.LongTensor, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| labels: Optional[torch.LongTensor] = None, | |
| **kwargs, | |
| ): | |
| logits, _, _ = self.model(input_ids) | |
| loss = None | |
| if labels is not None: | |
| shift_logits = logits[:, :-1, :].contiguous() | |
| shift_labels = labels[:, 1:].contiguous() | |
| loss = F.cross_entropy( | |
| shift_logits.view(-1, shift_logits.size(-1)), | |
| shift_labels.view(-1), | |
| ignore_index=-100, | |
| ) | |
| return CausalLMOutput(loss=loss, logits=logits) | |
| def generate_bytes(self, input_ids, max_new_tokens=200, temperature=0.8, top_k=50): | |
| return self.model.generate( | |
| input_ids, | |
| max_new_tokens=max_new_tokens, | |
| temperature=temperature, | |
| top_k=top_k, | |
| eos_id=self.config.eos_token_id, | |
| ) | |