Download code/dory/modeling.py from ProCreations/Dory-Beam-4B: direct link, hf CLI and curl.
- Browser
- Download file 14.7 kB
-
https://huggingface.co/ProCreations/Dory-Beam-4B/resolve/main/code/dory/modeling.py
- Command line
-
hf download hf://ProCreations/Dory-Beam-4B/code/dory/modeling.py
-
curl -L -o modeling.py https://huggingface.co/ProCreations/Dory-Beam-4B/resolve/main/code/dory/modeling.py
14.7 kB
| """Trainable PyTorch port of NVIDIA Dory1.0 (looped normalized-GPT). | |
| Mirrors vLLM's inference implementation (vllm-project/vllm#60149, | |
| vllm/model_executor/models/dory.py) op for op, replacing its paged attention and | |
| tensor-parallel layers with plain modules so the model can be fine-tuned: | |
| * full-sequence causal attention with per-layer sliding windows; each recurrent | |
| loop attends over that loop's own keys/values, which is exactly vLLM's | |
| "per_loop" KV-cache mode | |
| * loop count selectable per forward pass (n_loops), for elastic-loop training | |
| * per-layer gradient checkpointing and a chunked cross-entropy over the 131k vocab | |
| * packed sequences (doc_ids + per-document position_ids); attention via dense-mask | |
| SDPA (any device, used for checks) or FlexAttention block masks (CUDA training) | |
| Parameter names match the released checkpoint, so state dicts load and save as-is. | |
| """ | |
| import json | |
| import math | |
| from dataclasses import dataclass, fields | |
| from pathlib import Path | |
| import torch | |
| import torch.nn.functional as F | |
| from safetensors.torch import load_file | |
| from torch import nn | |
| from torch.utils.checkpoint import checkpoint | |
| EPS = 1e-6 | |
| class DoryConfig: | |
| vocab_size: int = 131072 | |
| hidden_size: int = 2560 | |
| num_hidden_layers: int = 72 | |
| intermediate_size: int = 7168 | |
| num_attention_heads: int = 16 | |
| num_key_value_heads: int = 4 | |
| head_dim: int = 256 | |
| initializer_range: float = 0.0177 | |
| sliding_window: int = 1024 | |
| rope_parameters: dict = None | |
| layer_types: list = None | |
| ngpt_alpha_init: float = 0.05 | |
| attention_output_gate_logit_scale_init: float = None | |
| n_input_layers: int = 16 | |
| n_recurrent_layers: int = 40 | |
| n_output_layers: int = 16 | |
| n_recurrent_loops: int = 2 | |
| def from_json(cls, path): | |
| raw = json.loads(Path(path).read_text()) | |
| known = {f.name for f in fields(cls)} | |
| cfg = cls(**{k: v for k, v in raw.items() if k in known}) | |
| assert cfg.n_input_layers + cfg.n_recurrent_layers + cfg.n_output_layers == cfg.num_hidden_layers | |
| assert not raw.get("tie_word_embeddings") and raw.get("hidden_act", "silu") == "silu" | |
| assert not raw.get("attention_bias") and not raw.get("mlp_bias") | |
| return cfg | |
| def justnorm(x, dim=-1): | |
| """nGPT normalization: x * rsqrt(sum(x^2) + eps), fp32 accumulation.""" | |
| xf = x.float() | |
| return (xf * torch.rsqrt(xf.pow(2).sum(dim=dim, keepdim=True) + EPS)).to(x.dtype) | |
| def slerp_alpha(a, b, alpha, alpha_scale): | |
| ya, yb = justnorm(a).float(), justnorm(b).float() | |
| lr = (alpha.float() * alpha_scale).abs() | |
| return justnorm(ya + lr * (yb - ya)).to(a.dtype) | |
| def rope_cos_sin(positions, head_dim, theta, device): | |
| inv_freq = 1.0 / (theta ** (torch.arange(0, head_dim, 2, device=device, dtype=torch.float32) / head_dim)) | |
| freqs = positions.to(device=device, dtype=torch.float32)[..., None] * inv_freq | |
| return freqs.cos(), freqs.sin() # [B, T, D/2] | |
| def apply_rope_neox(x, cos, sin): | |
| """x: [B, T, H, D]; cos/sin: [B, T, D/2]. NeoX style (rotate halves).""" | |
| x1, x2 = x.float().chunk(2, dim=-1) | |
| c, s = cos[:, :, None, :], sin[:, :, None, :] | |
| return torch.cat([x1 * c - x2 * s, x2 * c + x1 * s], dim=-1).to(x.dtype) | |
| def causal_mask(t, window, device, doc_ids=None): | |
| """Bool mask, True = attend: [T, T], or [B, 1, T, T] with doc_ids (packed sequences). | |
| Window w keeps keys with 0 <= i - j < w.""" | |
| i = torch.arange(t, device=device)[:, None] | |
| j = torch.arange(t, device=device)[None, :] | |
| allowed = j <= i | |
| if window is not None: | |
| allowed &= (i - j) < window | |
| if doc_ids is not None: | |
| allowed = allowed[None] & (doc_ids[:, :, None] == doc_ids[:, None, :]) | |
| allowed = allowed[:, None] | |
| return allowed | |
| _flex = None | |
| def flex_block_mask(t, window, device, doc_ids): | |
| from torch.nn.attention.flex_attention import create_block_mask | |
| def mask_mod(b, h, q, kv): | |
| ok = (kv <= q) & (doc_ids[b, q] == doc_ids[b, kv]) | |
| if window is not None: | |
| ok = ok & ((q - kv) < window) | |
| return ok | |
| return create_block_mask(mask_mod, doc_ids.shape[0], None, t, t, device=device) | |
| def flex_attention(q, k, v, block_mask, scale): | |
| global _flex | |
| if _flex is None: | |
| from torch.nn.attention.flex_attention import flex_attention as fa | |
| _flex = torch.compile(fa, dynamic=False) | |
| return _flex(q, k, v, block_mask=block_mask, scale=scale, enable_gqa=True) | |
| class DoryAttention(nn.Module): | |
| """GQA attention with nGPT qk-norm (after RoPE), sigmoid output gate and justnorm.""" | |
| def __init__(self, cfg: DoryConfig, layer_type: str): | |
| super().__init__() | |
| h, kv, d = cfg.num_attention_heads, cfg.num_key_value_heads, cfg.head_dim | |
| self.num_heads, self.num_kv_heads, self.head_dim = h, kv, d | |
| self.q_proj = nn.Linear(cfg.hidden_size, h * d, bias=False) | |
| self.k_proj = nn.Linear(cfg.hidden_size, kv * d, bias=False) | |
| self.v_proj = nn.Linear(cfg.hidden_size, kv * d, bias=False) | |
| self.g_proj = nn.Linear(cfg.hidden_size, h * d, bias=False) | |
| self.o_proj = nn.Linear(h * d, cfg.hidden_size, bias=False) | |
| self.sqk = nn.Parameter(torch.empty(h * d)) | |
| self.s_gate = nn.Parameter(torch.empty(h * d)) | |
| std = cfg.initializer_range | |
| self.sqk_factor = d**0.5 / std | |
| g0 = cfg.attention_output_gate_logit_scale_init or cfg.hidden_size**0.5 | |
| self.s_gate_factor = g0**0.5 / std | |
| self.layer_type = layer_type | |
| def forward(self, x, cos, sin, mask): | |
| b, t, _ = x.shape | |
| h, kv, d = self.num_heads, self.num_kv_heads, self.head_dim | |
| q = self.q_proj(x).view(b, t, h, d) | |
| k = self.k_proj(x).view(b, t, kv, d) | |
| v = self.v_proj(x).view(b, t, kv, d) | |
| gate = self.g_proj(x) | |
| q, k = apply_rope_neox(q, cos, sin), apply_rope_neox(k, cos, sin) | |
| sqk = (self.sqk.float() * self.sqk_factor).view(h, d) | |
| q = (sqk.square() * justnorm(q).float()).to(q.dtype) | |
| k = justnorm(k) | |
| q, k, v = (z.transpose(1, 2) for z in (q, k, v)) # [B, H, T, D] | |
| if isinstance(mask, torch.Tensor): | |
| if kv != h: | |
| k = k.repeat_interleave(h // kv, dim=1) | |
| v = v.repeat_interleave(h // kv, dim=1) | |
| out = F.scaled_dot_product_attention(q, k, v, attn_mask=mask, scale=1.0 / math.sqrt(d)) | |
| else: # FlexAttention BlockMask | |
| out = flex_attention(q, k, v, mask, 1.0 / math.sqrt(d)) | |
| out = out.transpose(1, 2).reshape(b, t, h * d) | |
| sg = (self.s_gate.float() * self.s_gate_factor).square() | |
| out = (out.float() * torch.sigmoid(gate.float() * sg)).to(out.dtype) | |
| return self.o_proj(justnorm(out)) | |
| class DoryMLP(nn.Module): | |
| """SwiGLU with nGPT suv scaling and justnorm before down_proj.""" | |
| def __init__(self, cfg: DoryConfig): | |
| super().__init__() | |
| self.gate_proj = nn.Linear(cfg.hidden_size, cfg.intermediate_size, bias=False) | |
| self.up_proj = nn.Linear(cfg.hidden_size, cfg.intermediate_size, bias=False) | |
| self.down_proj = nn.Linear(cfg.intermediate_size, cfg.hidden_size, bias=False) | |
| self.suv_gate = nn.Parameter(torch.empty(cfg.intermediate_size)) | |
| self.suv_up = nn.Parameter(torch.empty(cfg.intermediate_size)) | |
| self.suv_factor = cfg.hidden_size**0.5 | |
| def forward(self, x): | |
| g = (self.gate_proj(x).float() * (self.suv_gate.float() * self.suv_factor)).to(x.dtype) | |
| u = (self.up_proj(x).float() * (self.suv_up.float() * self.suv_factor)).to(x.dtype) | |
| m = (F.silu(g.float()) * u.float()).to(x.dtype) | |
| return self.down_proj(justnorm(m)) | |
| class DoryLayer(nn.Module): | |
| """One physical layer: a single sublayer (attention OR mlp) plus slerp residual.""" | |
| def __init__(self, cfg: DoryConfig, idx: int): | |
| super().__init__() | |
| self.layer_type = cfg.layer_types[idx] | |
| self.is_attention = self.layer_type != "mlp" | |
| self.alpha_scale = cfg.ngpt_alpha_init / cfg.initializer_range | |
| if self.is_attention: | |
| self.mixer = DoryAttention(cfg, self.layer_type) | |
| self.attn_alpha = nn.Parameter(torch.empty(cfg.hidden_size)) | |
| else: | |
| self.mixer = DoryMLP(cfg) | |
| self.mlp_alpha = nn.Parameter(torch.empty(cfg.hidden_size)) | |
| def forward(self, h, ctx): | |
| if self.is_attention: | |
| cos, sin = ctx["rope"][self.layer_type] | |
| out = self.mixer(h, cos, sin, ctx["mask"][self.layer_type]) | |
| return slerp_alpha(h, out, self.attn_alpha, self.alpha_scale) | |
| return slerp_alpha(h, self.mixer(h), self.mlp_alpha, self.alpha_scale) | |
| class DoryBackbone(nn.Module): | |
| def __init__(self, cfg: DoryConfig): | |
| super().__init__() | |
| self.cfg = cfg | |
| self.embeddings = nn.Embedding(cfg.vocab_size, cfg.hidden_size) | |
| self.layers = nn.ModuleList(DoryLayer(cfg, i) for i in range(cfg.num_hidden_layers)) | |
| self.gradient_checkpointing = False | |
| def _run(self, layer, h, ctx): | |
| if self.gradient_checkpointing and self.training and torch.is_grad_enabled(): | |
| return checkpoint(layer, h, ctx, use_reentrant=False) | |
| return layer(h, ctx) | |
| def forward(self, input_ids, position_ids=None, n_loops=None, doc_ids=None, attn_impl="sdpa"): | |
| cfg = self.cfg | |
| b, t = input_ids.shape | |
| dev = input_ids.device | |
| if position_ids is None: | |
| position_ids = torch.arange(t, device=dev).expand(b, t) | |
| if attn_impl == "flex" and doc_ids is None: | |
| doc_ids = torch.zeros(b, t, dtype=torch.int32, device=dev) | |
| ctx = {"rope": {}, "mask": {}} | |
| for lt in set(cfg.layer_types) - {"mlp"}: | |
| ctx["rope"][lt] = rope_cos_sin(position_ids, cfg.head_dim, cfg.rope_parameters[lt]["rope_theta"], dev) | |
| window = cfg.sliding_window if lt == "sliding_attention" else None | |
| if attn_impl == "flex": | |
| ctx["mask"][lt] = flex_block_mask(t, window, dev, doc_ids) | |
| else: | |
| ctx["mask"][lt] = causal_mask(t, window, dev, doc_ids) | |
| n_in, n_rec = cfg.n_input_layers, cfg.n_recurrent_layers | |
| h = self.embeddings(input_ids) | |
| for layer in self.layers[:n_in]: | |
| h = self._run(layer, h, ctx) | |
| input_h = h | |
| state = input_h | |
| for _ in range(n_loops or cfg.n_recurrent_loops): | |
| h = justnorm(state + input_h) | |
| for layer in self.layers[n_in : n_in + n_rec]: | |
| h = self._run(layer, h, ctx) | |
| state = justnorm(h) | |
| h = state | |
| for layer in self.layers[n_in + n_rec :]: | |
| h = self._run(layer, h, ctx) | |
| return h | |
| class DoryForCausalLM(nn.Module): | |
| def __init__(self, cfg: DoryConfig): | |
| super().__init__() | |
| self.config = cfg | |
| self.backbone = DoryBackbone(cfg) | |
| self.lm_head = nn.Linear(cfg.hidden_size, cfg.vocab_size, bias=False) | |
| self.lm_head.logit_scale = nn.Parameter(torch.empty(cfg.vocab_size)) | |
| def from_pretrained(cls, path, dtype=torch.bfloat16, device="cpu"): | |
| path = Path(path) | |
| cfg = DoryConfig.from_json(path / "config.json") | |
| with torch.device("meta"): | |
| model = cls(cfg) | |
| state = {} | |
| for shard in sorted(path.glob("*.safetensors")): | |
| state.update(load_file(shard)) | |
| state = {k: v.to(dtype) for k, v in state.items()} | |
| missing, unexpected = model.load_state_dict(state, strict=True, assign=True) | |
| assert not missing and not unexpected, (missing, unexpected) | |
| return model.to(device) | |
| def gradient_checkpointing_enable(self): | |
| self.backbone.gradient_checkpointing = True | |
| def normalize_matrices_(self): | |
| """Project weights back onto the unit hypersphere after an optimizer step (nGPT). | |
| The released checkpoint has every matrix unit-norm along the hidden dimension: | |
| rows of embeddings/lm_head and of maps out of the residual stream (q/k/v/g, | |
| gate/up), columns of maps into it (o_proj, down_proj). | |
| """ | |
| hidden = self.config.hidden_size | |
| for w in (self.backbone.embeddings.weight, self.lm_head.weight): | |
| w.copy_(justnorm(w, dim=1)) | |
| for mod in self.backbone.modules(): | |
| if isinstance(mod, nn.Linear): | |
| dim = 1 if mod.in_features == hidden else 0 | |
| mod.weight.copy_(justnorm(mod.weight, dim=dim)) | |
| def logits(self, hidden): | |
| scale = self.lm_head.logit_scale.float() / self.config.initializer_range | |
| return self.lm_head(hidden).float() * scale | |
| def forward(self, input_ids, labels=None, position_ids=None, n_loops=None, doc_ids=None, | |
| attn_impl="sdpa", loss_chunk=2048): | |
| """Without labels: returns float32 logits [B, T, V]. | |
| With labels (same shape as input_ids, -100 = ignore; shifted internally, so a | |
| packed batch must set -100 on each document's first token): | |
| returns (mean_loss, n_target_tokens) via a chunked, checkpointed CE.""" | |
| hidden = self.backbone(input_ids, position_ids=position_ids, n_loops=n_loops, | |
| doc_ids=doc_ids, attn_impl=attn_impl) | |
| if labels is None: | |
| return self.logits(hidden) | |
| h = hidden[:, :-1].reshape(-1, hidden.shape[-1]) | |
| y = labels[:, 1:].reshape(-1) | |
| keep = y != -100 | |
| h, y = h[keep], y[keep] | |
| total = h.new_zeros((), dtype=torch.float32) | |
| def chunk_loss(hc, yc): | |
| return F.cross_entropy(self.logits(hc), yc, reduction="sum") | |
| for s in range(0, h.shape[0], loss_chunk): | |
| hc, yc = h[s : s + loss_chunk], y[s : s + loss_chunk] | |
| if self.training and torch.is_grad_enabled(): | |
| total = total + checkpoint(chunk_loss, hc, yc, use_reentrant=False) | |
| else: | |
| total = total + chunk_loss(hc, yc) | |
| n = int(keep.sum()) | |
| return total / max(n, 1), n | |
| def generate(self, input_ids, max_new_tokens=128, n_loops=None, temperature=0.0, eos_ids=()): | |
| """Simple cache-free sampling, for correctness checks (not speed).""" | |
| ids = input_ids | |
| for _ in range(max_new_tokens): | |
| logits = self.forward(ids, n_loops=n_loops)[:, -1] | |
| if temperature > 0: | |
| nxt = torch.multinomial(torch.softmax(logits / temperature, -1), 1) | |
| else: | |
| nxt = logits.argmax(-1, keepdim=True) | |
| ids = torch.cat([ids, nxt], dim=1) | |
| if int(nxt[0, 0]) in eos_ids: | |
| break | |
| return ids[:, input_ids.shape[1] :] | |