"""~1B dense reasoning LLM.
Components: RMSNorm (pre-norm), partial RoPE, QK-norm, GQA, SwiGLU, tied embeddings,
attention + final logit soft-cap, depth-scaled residual init.
Our twists live here:
- hybrid attention: local (sliding-window) layers + global layers in the top third
- NoPE on global layers
- Tier A: z-loss, value-residual (ResFormer), partial RoPE
- optional MTP-2 auxiliary head (mid-training only)
Attention uses flash-attn: flash_attn_func supports GQA, causal masking,
sliding windows, and attention-logit softcapping natively.
"""
from __future__ import annotations
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from config import ModelConfig
try:
from flash_attn import flash_attn_func
except Exception:
flash_attn_func = None
_HAS_FLASH = flash_attn_func is not None
# ----------------------------------------------------------------------------- norm
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:
dt = x.dtype
x = x.float()
x = x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
return (x * self.weight.float()).to(dt)
# ----------------------------------------------------------------------------- rope
def build_rope_cache(seq_len: int, rope_dim: int, theta: float, device, dtype):
inv_freq = 1.0 / (theta ** (torch.arange(0, rope_dim, 2, device=device).float() / rope_dim))
t = torch.arange(seq_len, device=device).float()
freqs = torch.outer(t, inv_freq) # (S, rope_dim/2)
return torch.cos(freqs).to(dtype), torch.sin(freqs).to(dtype)
def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, rope_dim: int) -> torch.Tensor:
# x: (B, S, H, D). Rotate only the first rope_dim dims (partial RoPE); pass the rest through.
xr, xp = x[..., :rope_dim], x[..., rope_dim:]
x1, x2 = xr[..., 0::2], xr[..., 1::2]
cos = cos[None, :, None, :]
sin = sin[None, :, None, :]
o1 = x1 * cos - x2 * sin
o2 = x1 * sin + x2 * cos
xr = torch.stack((o1, o2), dim=-1).flatten(-2)
return torch.cat([xr, xp], dim=-1).to(x.dtype)
# ----------------------------------------------------------------------- think mask
def compute_think_mask(idx: torch.Tensor, start_id: int, end_id: int) -> torch.Tensor:
"""Bool (B,S): True for tokens strictly inside a ... span.
depth = cumsum(open - close); a position is 'inside' where depth>0. The
opener itself gets depth 1 (inside), the closer gets depth 0 (outside)."""
opens = (idx == start_id).int()
closes = (idx == end_id).int()
depth = torch.cumsum(opens - closes, dim=1)
return depth > 0
# ------------------------------------------------------------------------- attention
class Attention(nn.Module):
def __init__(self, cfg: ModelConfig, layer_idx: int, is_global: bool, rope_dim: int):
super().__init__()
self.cfg = cfg
self.layer_idx = layer_idx
self.is_global = is_global
self.rope_dim = rope_dim
self.use_rope = not (is_global and cfg.nope_on_global) # our twist: NoPE on global
self.nh, self.nkv, self.hd = cfg.n_heads, cfg.n_kv_heads, cfg.head_dim
self.wq = nn.Linear(cfg.d_model, self.nh * self.hd, bias=False)
self.wk = nn.Linear(cfg.d_model, self.nkv * self.hd, bias=False)
self.wv = nn.Linear(cfg.d_model, self.nkv * self.hd, bias=False)
self.wo = nn.Linear(self.nh * self.hd, cfg.d_model, bias=False)
if cfg.qk_norm:
self.q_norm = RMSNorm(self.hd, cfg.rms_eps)
self.k_norm = RMSNorm(self.hd, cfg.rms_eps)
else:
self.q_norm = self.k_norm = None
# value-residual mix gate: blend layer-0 value into this layer (ResFormer)
self.value_residual = cfg.value_residual and layer_idx > 0
if self.value_residual:
self.v_lambda = nn.Parameter(torch.tensor(float(cfg.value_residual_init)))
self.window = (-1, -1) if is_global else (cfg.sliding_window - 1, 0)
self.softcap = cfg.attn_logit_softcap
def forward(self, x, cos, sin, v_first):
B, S, _ = x.shape
q = self.wq(x).view(B, S, self.nh, self.hd)
k = self.wk(x).view(B, S, self.nkv, self.hd)
v = self.wv(x).view(B, S, self.nkv, self.hd)
raw_v = v # pre-blend value (layer 0 exports this)
if self.q_norm is not None: # QK-norm before RoPE
q = self.q_norm(q)
k = self.k_norm(k)
if self.use_rope:
q = apply_rope(q, cos, sin, self.rope_dim)
k = apply_rope(k, cos, sin, self.rope_dim)
if self.value_residual and v_first is not None:
lam = torch.sigmoid(self.v_lambda)
v = lam * v + (1.0 - lam) * v_first
if _HAS_FLASH:
out = flash_attn_func(
q, k, v,
causal=True,
window_size=self.window,
softcap=self.softcap if self.softcap else 0.0,
)
out = out.reshape(B, S, self.nh * self.hd)
else:
# SDPA fallback (no flash_attn package): torch's built-in flash backend.
# Full causal == our sliding window whenever seq_len <= window (true for
# base pretrain at 2k/4k). GQA handled by expanding KV heads. Attn-softcap
# is dropped here (QK-norm + final softcap + z-loss keep things stable).
rep = self.nh // self.nkv
qs = q.transpose(1, 2) # (B, H, S, D)
ks = k.transpose(1, 2).repeat_interleave(rep, dim=1)
vs = v.transpose(1, 2).repeat_interleave(rep, dim=1)
out = F.scaled_dot_product_attention(qs, ks, vs, is_causal=True)
out = out.transpose(1, 2).reshape(B, S, self.nh * self.hd)
return self.wo(out), raw_v
# ------------------------------------------------------------------------------- mlp
class SwiGLU(nn.Module):
def __init__(self, cfg: ModelConfig):
super().__init__()
self.w_gate = nn.Linear(cfg.d_model, cfg.d_ff, bias=False)
self.w_up = nn.Linear(cfg.d_model, cfg.d_ff, bias=False)
self.w_down = nn.Linear(cfg.d_ff, cfg.d_model, bias=False)
def forward(self, x):
return self.w_down(F.silu(self.w_gate(x)) * self.w_up(x))
# ----------------------------------------------------------------------------- block
class Block(nn.Module):
def __init__(self, cfg: ModelConfig, layer_idx: int, rope_dim: int):
super().__init__()
is_global = layer_idx in cfg.global_layers()
self.attn_norm = RMSNorm(cfg.d_model, cfg.rms_eps)
self.attn = Attention(cfg, layer_idx, is_global, rope_dim)
self.mlp_norm = RMSNorm(cfg.d_model, cfg.rms_eps)
self.mlp = SwiGLU(cfg)
def forward(self, x, cos, sin, v_first):
a, raw_v = self.attn(self.attn_norm(x), cos, sin, v_first)
x = x + a
x = x + self.mlp(self.mlp_norm(x))
return x, raw_v
# ----------------------------------------------------------------------------- model
class LLM(nn.Module):
def __init__(self, cfg: ModelConfig):
super().__init__()
self.cfg = cfg
# partial-RoPE dim (even)
self.rope_dim = int(cfg.head_dim * cfg.rope_fraction)
self.rope_dim -= self.rope_dim % 2
self.tok_emb = nn.Embedding(cfg.vocab_size, cfg.d_model)
self.blocks = nn.ModuleList([Block(cfg, i, self.rope_dim) for i in range(cfg.n_layers)])
self.final_norm = RMSNorm(cfg.d_model, cfg.rms_eps)
self.lm_head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False)
if cfg.tie_embeddings:
self.lm_head.weight = self.tok_emb.weight
# MTP head (our twist): predicts token t+offset. Cheap: norm + reuse lm_head.
self.mtp_norm = RMSNorm(cfg.d_model, cfg.rms_eps) if cfg.use_mtp else None
# Think-Gated Adaptive Compute: per-token halting head over the looped core.
self.ponder_halt = nn.Linear(cfg.d_model, 1, bias=True) if cfg.adaptive_compute else None
# Fused linear cross-entropy (Liger): computes LM loss WITHOUT materializing the
# full (B*S, vocab) fp32 logits -> massive memory drop on the big-vocab tail,
# with softcap + z-loss folded in. Base-pretrain hot path. Falls back if absent.
self.fused_ce = None
try:
from liger_kernel.transformers.fused_linear_cross_entropy import (
LigerFusedLinearCrossEntropyLoss)
self.fused_ce = LigerFusedLinearCrossEntropyLoss(
ignore_index=-100,
lse_square_scale=cfg.z_loss_weight,
softcap=cfg.final_logit_softcap if cfg.final_logit_softcap else None)
except Exception as e:
print(f"[model] Liger fused CE unavailable ({e}); using full-logits loss", flush=True)
self._rope_cache = None
self.apply(self._init_weights)
if cfg.depth_scaled_residual:
self._scale_residual_branches()
print(f"[model] attention backend: {'flash_attn' if _HAS_FLASH else 'torch SDPA (no flash pkg)'}",
flush=True)
# --- init ---
def _init_weights(self, m):
if isinstance(m, nn.Linear):
nn.init.normal_(m.weight, mean=0.0, std=self.cfg.init_std)
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=self.cfg.init_std)
def _scale_residual_branches(self):
# depth-scaled: shrink output projections that write into the residual stream
scale = 1.0 / math.sqrt(2 * self.cfg.n_layers)
for blk in self.blocks:
with torch.no_grad():
blk.attn.wo.weight.mul_(scale)
blk.mlp.w_down.weight.mul_(scale)
def _rope(self, S, device, dtype):
if self._rope_cache is None or self._rope_cache[0].shape[0] < S:
self._rope_cache = build_rope_cache(
max(S, self.cfg.max_seq_len), self.rope_dim, self.cfg.rope_theta, device, dtype)
cos, sin = self._rope_cache
return cos[:S], sin[:S]
def _softcap(self, logits):
c = self.cfg.final_logit_softcap
return c * torch.tanh(logits / c) if c else logits
def _ponder(self, x, cos, sin, v_first, think_mask):
"""Loop the shared reasoning core [core_start, core_end) up to ponder_max_steps
times. PonderNet halting decides per-token depth; outside every token
halts at step 1 (== plain single pass). Returns (weighted_hidden, ponder_loss)."""
cfg = self.cfg
core = self.blocks[cfg.core_start:cfg.core_end]
N = cfg.ponder_max_steps
hs, lambdas = [], []
cur = x
for step in range(N):
for blk in core:
cur, _ = blk(cur, cos, sin, v_first)
hs.append(cur)
lam = torch.sigmoid(self.ponder_halt(cur).squeeze(-1)) # (B,S) halt-prob
lambdas.append(lam)
# PonderNet halting distribution p_n (last step takes all remaining mass)
ps, remain = [], torch.ones_like(lambdas[0])
for n in range(N):
lam = torch.ones_like(lambdas[n]) if n == N - 1 else lambdas[n]
ps.append(remain * lam)
remain = remain * (1.0 - lam)
# think-gate: non-think tokens halt at step 0 (depth 1 == baseline)
if think_mask is not None:
tm = think_mask.to(ps[0].dtype)
ps[0] = ps[0] * tm + (1.0 - tm)
for n in range(1, N):
ps[n] = ps[n] * tm
out = sum(ps[n].unsqueeze(-1) * hs[n] for n in range(N))
# ponder cost: KL(halting || geometric prior), over think tokens only
ponder_loss = x.new_zeros(())
if think_mask is not None and think_mask.any():
p = torch.stack(ps, dim=-1).clamp_min(1e-8) # (B,S,N)
n_idx = torch.arange(N, device=x.device)
prior = (cfg.ponder_prior * (1 - cfg.ponder_prior) ** n_idx)
prior = (prior / prior.sum()).clamp_min(1e-8) # (N,)
kl = (p * (p.log() - prior.log())).sum(-1) # (B,S)
ponder_loss = kl[think_mask].mean()
return out, ponder_loss
def forward(self, idx: torch.Tensor, targets: torch.Tensor | None = None,
think_mask: torch.Tensor | None = None):
cfg = self.cfg
B, S = idx.shape
x = self.tok_emb(idx)
cos, sin = self._rope(S, idx.device, x.dtype)
ponder_loss = None
if cfg.adaptive_compute:
if think_mask is None:
think_mask = compute_think_mask(idx, cfg.think_start_id, cfg.think_end_id)
v_first = None
for i in range(cfg.core_start): # pre-core
x, raw_v = self.blocks[i](x, cos, sin, v_first)
if i == 0:
v_first = raw_v
x, ponder_loss = self._ponder(x, cos, sin, v_first, think_mask) # looped core
for i in range(cfg.core_end, cfg.n_layers): # post-core
x, _ = self.blocks[i](x, cos, sin, v_first)
else:
v_first = None
for i, blk in enumerate(self.blocks):
x, raw_v = blk(x, cos, sin, v_first)
if i == 0:
v_first = raw_v # value-residual anchor
h = self.final_norm(x)
if targets is None:
return self._softcap(self.lm_head(h[:, -1:]))
# memory-efficient base path: fused CE (no full-logits materialization).
# only when we don't need explicit logits (MTP / lookahead are reasoning-phase).
need_full = (self.mtp_norm is not None) or cfg.lookahead_consistency or (self.fused_ce is None)
if not need_full:
D = h.size(-1)
loss = self.fused_ce(self.lm_head.weight, h.reshape(-1, D), targets.reshape(-1))
if ponder_loss is not None:
loss = loss + cfg.ponder_loss_weight * ponder_loss
return None, loss
logits = self._softcap(self.lm_head(h))
loss = F.cross_entropy(
logits.view(-1, logits.size(-1)).float(), targets.view(-1), ignore_index=-100)
# z-loss: penalize drift of logsumexp (stabilizes logits, allows higher LR)
if cfg.z_loss_weight:
valid = (targets.view(-1) != -100)
lse = torch.logsumexp(logits.view(-1, logits.size(-1)).float(), dim=-1)
if valid.any():
loss = loss + cfg.z_loss_weight * (lse[valid] ** 2).mean()
mtp_logits = None
if self.mtp_norm is not None: # MTP aux loss (predict t+offset)
off = cfg.mtp_offset
mtp_logits = self._softcap(self.lm_head(self.mtp_norm(h)))
mtp_loss = F.cross_entropy(
mtp_logits[:, :-off].reshape(-1, mtp_logits.size(-1)).float(),
idx[:, off:].reshape(-1))
loss = loss + cfg.mtp_loss_weight * mtp_loss
# lookahead-consistency: MTP's t+off pred must match the more-informed t+1 pred
if cfg.lookahead_consistency and mtp_logits is not None:
sh = cfg.mtp_offset - 1
if sh >= 1 and S > sh:
teacher = F.softmax(logits[:, sh:].detach().float(), dim=-1)
student = F.log_softmax(mtp_logits[:, :-sh].float(), dim=-1)
kl = F.kl_div(student, teacher, reduction="batchmean")
loss = loss + cfg.lookahead_weight * kl
if ponder_loss is not None:
loss = loss + cfg.ponder_loss_weight * ponder_loss
return logits, loss
def num_params(self, non_embedding=False):
n = sum(p.numel() for p in self.parameters())
if non_embedding and self.cfg.tie_embeddings:
n -= self.tok_emb.weight.numel()
return n
if __name__ == "__main__":
print("=== budgie-1b (full) ===")
cfg = ModelConfig()
print("global layers:", sorted(cfg.global_layers()))
print(f"reasoning core span: [{cfg.core_start}, {cfg.core_end})")
m = LLM(cfg)
print(f"rope_dim (partial): {m.rope_dim}/{cfg.head_dim}")
print(f"total params: {m.num_params()/1e9:.3f}B")
print(f"non-embedding params: {m.num_params(non_embedding=True)/1e9:.3f}B")
print("\n=== budgie-mini (ablation, signature ON) ===")
mc = ModelConfig.mini(use_mtp=True, adaptive_compute=True, lookahead_consistency=True)
print("global layers:", sorted(mc.global_layers()), "core:", (mc.core_start, mc.core_end))
mm = LLM(mc)
print(f"total params: {mm.num_params()/1e6:.1f}M")
# smoke test the forward on both branches if a GPU + flash-attn are available
if torch.cuda.is_available() and _HAS_FLASH:
dev = "cuda"
mm = mm.to(dev).to(torch.bfloat16)
idx = torch.randint(0, mc.vocab_size, (2, 128), device=dev)
# inject a span so adaptive compute + lookahead paths execute
idx[:, 10] = mc.think_start_id
idx[:, 40] = mc.think_end_id
tgt = torch.randint(0, mc.vocab_size, (2, 128), device=dev)
logits, loss = mm(idx, tgt)
print(f"mini forward OK: logits {tuple(logits.shape)}, loss {loss.item():.3f}")