Download scripts/model_dna_peer_v21.py from jaivial/dna-diskchat-2b-peer-v21: direct link, hf CLI and curl.
- Browser
- Download file 8.75 kB
-
https://huggingface.co/jaivial/dna-diskchat-2b-peer-v21/resolve/main/scripts/model_dna_peer_v21.py
- Command line
-
hf download hf://jaivial/dna-diskchat-2b-peer-v21/scripts/model_dna_peer_v21.py
-
curl -L -o model_dna_peer_v21.py https://huggingface.co/jaivial/dna-diskchat-2b-peer-v21/resolve/main/scripts/model_dna_peer_v21.py
8.75 kB
| # model_dna_peer_v21.py - v21 ternary controller + SHARED-POOL PEER experts. | |
| # Changes vs model_dna_peer.py (v20), per docs/2026-07-25_v21: | |
| # P1 ONE shared expert pool used by 3 PEER layers at stride 4 (Memory Layers at | |
| # Scale, arXiv:2412.09764) instead of 16 private pools -> same params, 16x | |
| # fewer gathers. Other 13 blocks keep a cheap dense SwiGLU FFN. | |
| # P2 F.embedding_bag(mode='sum', per_sample_weights=...) for the down-projection | |
| # (numerically identical to the einsum contraction, ~3x faster). | |
| # P3 expert tables held in bf16 (nn.Embedding is autocast-EXEMPT, so the v20 code | |
| # silently gathered fp32 and then cast the whole [N,h,k,d] tensor). | |
| # P4 sparse=True on the pool -> sparse grads, no dense 507M grad / Adam state. | |
| # P7 h*k raised from 8 to 32 (heads=4, topk=8) toward PEER's recommended | |
| # granularity, plus query BatchNorm for expert usage (PEER arXiv:2407.04153). | |
| import sys; sys.path.insert(0, '/root/dna') | |
| import torch, torch.nn as nn, torch.nn.functional as F | |
| from model_dna import DnaChat, diag_scan, counts | |
| def tern(w): # BitNet b1.58 ternary STE (weight-only) | |
| s = w.abs().mean().clamp_min(1e-5) | |
| wt = (w / s).round().clamp_(-1, 1) * s | |
| return w + (wt - w).detach() | |
| class BitLinear(nn.Module): | |
| def __init__(self, i, o): | |
| super().__init__(); self.weight = nn.Parameter(torch.empty(o, i)); nn.init.normal_(self.weight, std=0.02) | |
| def forward(self, x): return F.linear(x, tern(self.weight)) | |
| class ExpertPool(nn.Module): | |
| """ONE shared bank of E = nk^2 rank-1 experts, reused by every PEER layer (P1), | |
| with sparse gradients (P4). | |
| dtype=None keeps fp32 master weights (DEFAULT). P3 (bf16 tables) halves gather | |
| bandwidth but MEASURED bf16 SparseAdam updates carry quantization noise | |
| (mean|delta| 1.8e-4 vs 1.5e-3 clean), so it is opt-in: pass dtype=torch.bfloat16.""" | |
| def __init__(self, nk, d, sparse=True, dtype=None): | |
| super().__init__() | |
| self.nk, self.E, self.d = nk, nk * nk, d | |
| self.up = nn.Embedding(self.E, d, sparse=sparse) | |
| self.down = nn.Embedding(self.E, d, sparse=sparse) | |
| nn.init.normal_(self.up.weight, std=0.02) | |
| nn.init.normal_(self.down.weight, std=0.02 / (d ** 0.5)) | |
| if dtype is not None: | |
| self.up.to(dtype); self.down.to(dtype) | |
| class PeerFFN(nn.Module): | |
| """Product-key retrieval over a SHARED expert pool. | |
| out = sum_k softmax(score)_k * Wdown_k * silu(Wup_k . x); active FLOPs ~ h*k*d.""" | |
| def __init__(self, pool, d, dk=128, topk=8, heads=4, qbn=True): | |
| super().__init__() | |
| self.pool, self.d, self.dk, self.topk, self.h = pool, d, dk, topk, heads | |
| self.nk = pool.nk | |
| self.sparse = pool.up.sparse # F.embedding/_bag need this EXPLICITLY | |
| self.q = nn.Linear(d, heads * 2 * dk, bias=False) | |
| self.Ka = nn.Parameter(torch.randn(heads, self.nk, dk) * 0.02) | |
| self.Kb = nn.Parameter(torch.randn(heads, self.nk, dk) * 0.02) | |
| # query BatchNorm (PEER 3.3 / PKM): keeps expert usage near 100% | |
| self.qbn = nn.BatchNorm1d(heads * 2 * dk) if qbn else None | |
| def route(self, x): | |
| """Product-key routing. Pure dense ops -> safe for torch.compile.""" | |
| N = x.size(0); k = self.topk; nk = self.nk; h = self.h | |
| q = self.q(x) | |
| if self.qbn is not None: q = self.qbn(q) | |
| q = q.view(N, h, 2, self.dk) | |
| qa, qb = q[:, :, 0], q[:, :, 1] # [N,h,dk] | |
| sa = torch.einsum('nhd,hkd->nhk', qa, self.Ka) # [N,h,nk] | |
| sb = torch.einsum('nhd,hkd->nhk', qb, self.Kb) | |
| va, ia = sa.topk(k, -1); vb, ib = sb.topk(k, -1) # [N,h,k] | |
| cs = (va.unsqueeze(-1) + vb.unsqueeze(-2)).reshape(N, h, -1) | |
| ci = (ia.unsqueeze(-1) * nk + ib.unsqueeze(-2)).reshape(N, h, -1) | |
| tv, ti = cs.topk(k, -1) # [N,h,k] | |
| eid = torch.gather(ci, -1, ti).reshape(N * h, k) # flatten heads into bags | |
| gate = torch.softmax(tv, -1).reshape(N * h, k) | |
| return eid, gate | |
| def gather(self, x, eid, gate): | |
| """Sparse expert gather. Kept OUT of the compiled graph: inductor cannot | |
| lower aten.embedding with sparse=True (LoweringException).""" | |
| N = x.size(0); h = self.h | |
| W = self.pool.up.weight | |
| u = F.embedding(eid, W, sparse=self.sparse) # [N*h,k,d] | |
| xr = x.unsqueeze(1).expand(N, h, self.d).reshape(N * h, self.d).to(W.dtype) | |
| hid = F.silu(torch.bmm(u, xr.unsqueeze(-1)).squeeze(-1)) # [N*h,k] | |
| # P2: weighted-sum gather == einsum('nk,nkd->nd'), ~3x faster, bit-equal | |
| out = F.embedding_bag(eid, self.pool.down.weight, mode='sum', | |
| per_sample_weights=(gate.to(W.dtype) * hid), | |
| sparse=self.sparse) | |
| return out.reshape(N, h, self.d).sum(1).to(x.dtype) / h | |
| def forward(self, x): # x: [N, d] | |
| eid, gate = self.route(x) | |
| return self.gather(x, eid, gate) | |
| class DenseFFN(nn.Module): | |
| """Cheap ternary SwiGLU FFN for the 13 non-PEER blocks.""" | |
| def __init__(self, d, ff): | |
| super().__init__(); self.up = BitLinear(d, 2 * ff); self.down = BitLinear(ff, d) | |
| def forward(self, x): | |
| a, b = self.up(x).chunk(2, -1); return self.down(F.silu(b) * a) | |
| class FastBlockTP(nn.Module): | |
| def __init__(self, d, ff, pool=None, dk=128, topk=8, heads=4): | |
| super().__init__() | |
| self.n1 = nn.LayerNorm(d); self.proj = BitLinear(d, 4 * d); self.o = BitLinear(d, d) | |
| self.decay = nn.Parameter(torch.full((d,), 2.0)) | |
| self.n2 = nn.LayerNorm(d) | |
| self.is_peer = pool is not None | |
| self.ffn = PeerFFN(pool, d, dk, topk, heads) if self.is_peer else DenseFFN(d, ff) | |
| def recur(self, x, C=16): | |
| k, v, r, g = self.proj(self.n1(x)).chunk(4, -1) | |
| g = torch.sigmoid(g + self.decay); u = (1 - g) * torch.tanh(k) | |
| s = diag_scan(g, u, C) | |
| return x + self.o(torch.sigmoid(r) * s * torch.sigmoid(v)) | |
| def forward(self, x, C=16): | |
| x = self.recur(x, C) | |
| B, T, d = x.shape | |
| h = self.n2(x).reshape(B * T, d) | |
| return x + self.ffn(h).reshape(B, T, d) | |
| class DnaPeerV21(DnaChat): | |
| """3 shared-pool PEER layers at stride 4; dense ternary FFN elsewhere.""" | |
| def __init__(self, vocab=32000, d=512, layers=16, ff=1536, a=2048, b=2048, | |
| nk=704, dk=128, topk=8, pheads=4, peer_layers=(4, 8, 12), chunk=16, | |
| pool_dtype=None): | |
| super().__init__(vocab, d, layers, ff, a, b, chunk) | |
| self.nk, self.dk, self.topk, self.pheads = nk, dk, topk, pheads | |
| self.peer_layers = tuple(peer_layers) | |
| self.pool = ExpertPool(nk, d, dtype=pool_dtype) | |
| self.blocks = nn.ModuleList([ | |
| FastBlockTP(d, ff, self.pool if i in self.peer_layers else None, dk, topk, pheads) | |
| for i in range(layers)]) | |
| def config(self): | |
| c = super().config() | |
| c.update(nk=self.nk, dk=self.dk, topk=self.topk, pheads=self.pheads, | |
| peer_layers=list(self.peer_layers)); return c | |
| def peer_counts(m): | |
| """(total controller params, active params per token).""" | |
| ctrl = sum(p.numel() for p in m.parameters()) | |
| active = 0 | |
| for blk in m.blocks: | |
| active += blk.proj.weight.numel() + blk.o.weight.numel() | |
| if blk.is_peer: | |
| active += blk.ffn.q.weight.numel() + m.pheads * m.topk * 2 * m.d | |
| else: | |
| active += blk.ffn.up.weight.numel() + blk.ffn.down.weight.numel() | |
| active += m.embed.weight.numel() | |
| active += m.ra.weight.numel() + m.rb.weight.numel() | |
| return ctrl, active | |
| def compile_(m): | |
| """Compile everything EXCEPT the sparse expert gathers. | |
| torch.compile was this project's single biggest lever (v8 T2: 3.1x), but inductor | |
| raises LoweringException on aten.embedding with sparse=True. So we compile the | |
| recurrence of every block, the dense FFNs, and the PEER routers, and leave only | |
| PeerFFN.gather (the sparse part) eager.""" | |
| for blk in m.blocks: | |
| blk.recur = torch.compile(blk.recur) | |
| if blk.is_peer: blk.ffn.route = torch.compile(blk.ffn.route) | |
| else: blk.ffn = torch.compile(blk.ffn) | |
| return m | |
| def param_groups(m): | |
| """Split params: Muon (2D recurrence), sparse pool, dense AdamW (P4/P6).""" | |
| muon, sparse, dense = [], [], [] | |
| pool_ids = {id(m.pool.up.weight), id(m.pool.down.weight)} | |
| muon_ids = set() | |
| for blk in m.blocks: | |
| muon += [blk.proj.weight, blk.o.weight] | |
| muon_ids = {id(p) for p in muon} | |
| for p in m.parameters(): | |
| if id(p) in pool_ids: sparse.append(p) | |
| elif id(p) in muon_ids: pass | |
| else: dense.append(p) | |
| return muon, sparse, dense | |