Upload folder using huggingface_hub
Browse files- README.md +16 -0
- config.json +38 -0
- gdn.py +273 -0
- model.safetensors +3 -0
- model4.py +597 -0
- segmm.py +393 -0
- tokenizer.json +0 -0
README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
---
|
| 4 |
+
# Foundry-4
|
| 5 |
+
|
| 6 |
+
Kernel-specialized hybrid LM: 5.02B total / ~0.58B active params.
|
| 7 |
+
18 GDN (gated DeltaNet, fla) + 6 RoPE attention layers, 384-expert
|
| 8 |
+
sigmoid-routed dropless MoE (top-8, packed Triton segmm), KernelGram
|
| 9 |
+
n-gram tables, 4-iteration shared-weight latent ponder loop, JEV-style
|
| 10 |
+
judge head.
|
| 11 |
+
|
| 12 |
+
Trained 2000 steps on a curated kernel-optimization corpus
|
| 13 |
+
(CUDA/Triton episodes with measured RESULT/BEST/HISTORY feedback) plus
|
| 14 |
+
GPU-verified synthetic optimization ladders (synth4.py).
|
| 15 |
+
|
| 16 |
+
Code: model4.py / gdn.py / segmm.py in this repo.
|
config.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architecture": "Foundry4",
|
| 3 |
+
"config": {
|
| 4 |
+
"vocab_size": 65536,
|
| 5 |
+
"n_specials": 16,
|
| 6 |
+
"d_model": 1280,
|
| 7 |
+
"n_layers": 24,
|
| 8 |
+
"n_heads": 10,
|
| 9 |
+
"head_dim": 128,
|
| 10 |
+
"seq_len": 4096,
|
| 11 |
+
"dropout": 0.0,
|
| 12 |
+
"rope_theta": 10000.0,
|
| 13 |
+
"qk_norm": true,
|
| 14 |
+
"attn_every": 4,
|
| 15 |
+
"gdn_heads": 10,
|
| 16 |
+
"gdn_head_k": 128,
|
| 17 |
+
"gdn_head_v": 128,
|
| 18 |
+
"gdn_chunk": 32,
|
| 19 |
+
"n_experts": 384,
|
| 20 |
+
"expert_hidden": 128,
|
| 21 |
+
"moe_topk": 8,
|
| 22 |
+
"shared_hidden": 640,
|
| 23 |
+
"lb_weight": 0.01,
|
| 24 |
+
"kg_buckets": 262144,
|
| 25 |
+
"kg_dim": 80,
|
| 26 |
+
"kg_max_n": 4,
|
| 27 |
+
"kg_probes": 2,
|
| 28 |
+
"ponder_iters": 4,
|
| 29 |
+
"ponder_hidden": 2560,
|
| 30 |
+
"ponder_iter_weight_decay": 1.0,
|
| 31 |
+
"judge_hidden": 512,
|
| 32 |
+
"use_mtp": true,
|
| 33 |
+
"mtp_weight": 0.05,
|
| 34 |
+
"moe_ckpt": false
|
| 35 |
+
},
|
| 36 |
+
"trained_steps": 2000,
|
| 37 |
+
"dtype": "bfloat16"
|
| 38 |
+
}
|
gdn.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Gated DeltaNet mixer for Foundry-4.
|
| 2 |
+
|
| 3 |
+
Recurrence (per head), GDN convention:
|
| 4 |
+
|
| 5 |
+
S_t = exp(g_t) * (I - beta_t k_t k_t^T) S_{t-1} + beta_t k_t v_t^T
|
| 6 |
+
o_t = q_t^T S_t
|
| 7 |
+
|
| 8 |
+
with S in R^{dk x dv} — keys address, values retrieved (o ~ v when
|
| 9 |
+
q ~ k). g <= 0 is log-decay per head per step, beta in (0,1) the write
|
| 10 |
+
strength.
|
| 11 |
+
|
| 12 |
+
Implementations:
|
| 13 |
+
|
| 14 |
+
- ``gdn_sequential`` — per-token recurrence. Obviously correct oracle
|
| 15 |
+
for the unit test; too slow for training.
|
| 16 |
+
- ``gdn_chunk`` — chunkwise WY form (Yang et al. 2024, "Gated Delta
|
| 17 |
+
Networks"). All within-chunk work is batched bmms + one triangular
|
| 18 |
+
solve; the only sequential loop is the inter-chunk state scan over
|
| 19 |
+
T/C chunks of cheap [dk x dv] ops.
|
| 20 |
+
|
| 21 |
+
Derivation (verified against the N=2 expansion by hand, and against
|
| 22 |
+
gdn_sequential in test_gdn.py):
|
| 23 |
+
|
| 24 |
+
within chunk, G_i = sum_{j<=i} g_j, Ghat_i = G_i - G_N (<= 0)
|
| 25 |
+
W = beta * K, U = beta * V
|
| 26 |
+
M[i,j] = exp(G_i - G_j) for i >= j, else 0
|
| 27 |
+
A_k[i,l] = beta_l <k_i, k_l> for i > l (strict lower)
|
| 28 |
+
A_v[i,j] = exp(G_i - G_j) beta_i <k_i, k_j> for i > j (strict lower)
|
| 29 |
+
Kt = (I + A_k)^{-1} K transformed keys (forward solve —
|
| 30 |
+
prefix property: leading submatrix
|
| 31 |
+
is the restricted solve)
|
| 32 |
+
Ut = (I + A_v)^{-1} U transformed values (decayed)
|
| 33 |
+
Kd = K * exp(G_N - G_j) decay-safe anti-decayed keys
|
| 34 |
+
|
| 35 |
+
removal product: prod_{j<=i}(I - w_j k_j^T) = I - W^T Kt (prefix
|
| 36 |
+
property lets the same Kt serve every intra-chunk position)
|
| 37 |
+
|
| 38 |
+
o_i = e^{G_i} q_i S_in
|
| 39 |
+
- e^{G_i} sum_{l<=i} <q_i, W_l> (Kt_l S_in)
|
| 40 |
+
+ sum_{j<=i} M[i,j] <q_i, k_j> Ut_j
|
| 41 |
+
|
| 42 |
+
S_out = e^{G_N} ( S_in - W^T (Kt S_in) ) + Kd^T Ut
|
| 43 |
+
"""
|
| 44 |
+
from __future__ import annotations
|
| 45 |
+
|
| 46 |
+
import os
|
| 47 |
+
|
| 48 |
+
import torch
|
| 49 |
+
import torch.nn as nn
|
| 50 |
+
import torch.nn.functional as F
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# ---------------------------------------------------------------------------
|
| 54 |
+
# sequential oracle
|
| 55 |
+
# ---------------------------------------------------------------------------
|
| 56 |
+
|
| 57 |
+
def gdn_sequential(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor,
|
| 58 |
+
g: torch.Tensor, beta: torch.Tensor,
|
| 59 |
+
S0: torch.Tensor | None = None):
|
| 60 |
+
"""q,k [B,T,H,dk]; v [B,T,H,dv]; g,beta [B,T,H] -> (o [B,T,H,dv], S).
|
| 61 |
+
|
| 62 |
+
fp32 oracle — the unit test compares gdn_chunk against this.
|
| 63 |
+
"""
|
| 64 |
+
B, T, H, dk = q.shape
|
| 65 |
+
dv = v.shape[-1]
|
| 66 |
+
S = (S0 if S0 is not None else
|
| 67 |
+
q.new_zeros(B, H, dk, dv)).float()
|
| 68 |
+
o = q.new_zeros(B, T, H, dv, dtype=torch.float32)
|
| 69 |
+
qf, kf, vf = q.float(), k.float(), v.float()
|
| 70 |
+
gf, bf = g.float(), beta.float()
|
| 71 |
+
for t in range(T):
|
| 72 |
+
gt = gf[:, t].view(B, H, 1, 1).exp()
|
| 73 |
+
bt = bf[:, t].view(B, H, 1, 1)
|
| 74 |
+
kt, vt, qt = kf[:, t], vf[:, t], qf[:, t]
|
| 75 |
+
kTS = torch.einsum("bhd,bhdv->bhv", kt, S) # k^T S
|
| 76 |
+
S = gt * (S - bt * torch.einsum("bhd,bhv->bhdv", kt, kTS)) \
|
| 77 |
+
+ bt * torch.einsum("bhd,bhv->bhdv", kt, vt)
|
| 78 |
+
o[:, t] = torch.einsum("bhd,bhdv->bhv", qt, S)
|
| 79 |
+
return o, S
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
# ---------------------------------------------------------------------------
|
| 83 |
+
# chunked WY implementation
|
| 84 |
+
# ---------------------------------------------------------------------------
|
| 85 |
+
|
| 86 |
+
def gdn_chunk(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor,
|
| 87 |
+
g: torch.Tensor, beta: torch.Tensor,
|
| 88 |
+
S0: torch.Tensor | None = None, chunk_size: int = 32,
|
| 89 |
+
matmul_dtype: torch.dtype | None = None):
|
| 90 |
+
"""Chunkwise gated delta rule, vectorized across chunks.
|
| 91 |
+
|
| 92 |
+
Only the [dk x dv] state scan is sequential (T/C cheap iterations).
|
| 93 |
+
Gram/output matmuls run in ``matmul_dtype`` (bf16 on GPU = tensor
|
| 94 |
+
cores); the triangular solves and the state recurrence stay fp32.
|
| 95 |
+
"""
|
| 96 |
+
B, T, H, dk = q.shape
|
| 97 |
+
dv = v.shape[-1]
|
| 98 |
+
C = chunk_size
|
| 99 |
+
NC = (T + C - 1) // C
|
| 100 |
+
pad = NC * C - T
|
| 101 |
+
if pad: # pad tail chunk
|
| 102 |
+
zp = lambda t: F.pad(t, (0, 0, 0, 0, 0, pad)) # [B,T,H,*]
|
| 103 |
+
q, k, v = zp(q), zp(k), zp(v)
|
| 104 |
+
g = F.pad(g, (0, 0, 0, pad)) # g=0 no decay
|
| 105 |
+
beta = F.pad(beta, (0, 0, 0, pad))
|
| 106 |
+
|
| 107 |
+
mdt = matmul_dtype or torch.float32 # big matmuls
|
| 108 |
+
dt = torch.float32 # solves + state
|
| 109 |
+
dev = q.device
|
| 110 |
+
# -> [B,H,NC,C,*]
|
| 111 |
+
qc = q.float().view(B, NC, C, H, dk).permute(0, 3, 1, 2, 4)
|
| 112 |
+
kc = k.float().view(B, NC, C, H, dk).permute(0, 3, 1, 2, 4)
|
| 113 |
+
vc = v.float().view(B, NC, C, H, dv).permute(0, 3, 1, 2, 4)
|
| 114 |
+
gc = g.float().view(B, NC, C, H).permute(0, 3, 1, 2)
|
| 115 |
+
bc = beta.float().view(B, NC, C, H).permute(0, 3, 1, 2)
|
| 116 |
+
|
| 117 |
+
G = gc.cumsum(-1) # [B,H,NC,C]
|
| 118 |
+
Gh = G - G[..., -1:] # <=0
|
| 119 |
+
EG = Gh.exp() # e^{Ghat_i}
|
| 120 |
+
EGN = G[..., -1].exp() # e^{G_N} [B,H,NC]
|
| 121 |
+
|
| 122 |
+
tril = torch.tril(torch.ones(C, C, device=dev, dtype=torch.bool))
|
| 123 |
+
tril_m1 = torch.tril(torch.ones(C, C, device=dev, dtype=torch.bool),
|
| 124 |
+
diagonal=-1)
|
| 125 |
+
|
| 126 |
+
# M[i,j] = e^{G_i - G_j} on i >= j (<=1, stable)
|
| 127 |
+
M = torch.where(tril, (G.unsqueeze(-1) - G.unsqueeze(-2)).exp(),
|
| 128 |
+
G.new_zeros(()))
|
| 129 |
+
# ---- fp32 triangular solves (bounded by chunk size C) --------------
|
| 130 |
+
kbc32 = kc * bc.unsqueeze(-1) # W = beta k
|
| 131 |
+
vbc32 = vc * bc.unsqueeze(-1) # U = beta v
|
| 132 |
+
A_k = torch.where(tril_m1, kc @ kbc32.transpose(-1, -2),
|
| 133 |
+
kc.new_zeros(())) # beta_l<k_i,k_l>
|
| 134 |
+
A_v = torch.where(tril_m1, (kbc32 @ kc.transpose(-1, -2)) * M,
|
| 135 |
+
kc.new_zeros(())) # e^{Gi-Gj} b_i<k_i,k_j>
|
| 136 |
+
eye = torch.eye(C, device=dev, dtype=dt).view(
|
| 137 |
+
1, 1, 1, C, C).expand(B, H, NC, C, C)
|
| 138 |
+
Kt = torch.linalg.solve_triangular(eye + A_k, kc, upper=False)
|
| 139 |
+
Ut = torch.linalg.solve_triangular(eye + A_v, vbc32, upper=False)
|
| 140 |
+
|
| 141 |
+
# ---- batch the matmul dtype: Gram rows, outputs, Kd----------------
|
| 142 |
+
kbc = kbc32.to(mdt)
|
| 143 |
+
Ut_m = Ut.to(mdt)
|
| 144 |
+
qc_m, kc_m = qc.to(mdt), kc.to(mdt)
|
| 145 |
+
M_m = M.to(mdt)
|
| 146 |
+
Kd = kc * (G[..., -1:] - G).exp().unsqueeze(-1) # e^{G_N-G_j} k
|
| 147 |
+
Kd_m = Kd.to(mdt)
|
| 148 |
+
EG_m, EGN_m = EG.to(mdt), EGN.to(mdt)
|
| 149 |
+
QW = torch.where(tril, qc_m @ kbc.transpose(-1, -2),
|
| 150 |
+
qc_m.new_zeros(())) # <q_i,W_l> l<=i
|
| 151 |
+
QWd = QW * EG_m.unsqueeze(-1) # e^{Ghat_i} rows
|
| 152 |
+
QK_m = (qc_m @ kc_m.transpose(-1, -2)) * M_m # tril by M
|
| 153 |
+
o_loc_all = QK_m @ Ut_m # batchable term
|
| 154 |
+
|
| 155 |
+
# scan states over chunks: o_inter needs S_in, so sequential here.
|
| 156 |
+
S = (S0.float() if S0 is not None else
|
| 157 |
+
q.new_zeros(B, H, dk, dv, dtype=dt))
|
| 158 |
+
o = q.new_zeros(B, H, NC, C, dv, dtype=dt)
|
| 159 |
+
for c in range(NC):
|
| 160 |
+
KS_c = Kt[:, :, c].to(mdt) @ S.to(mdt) # Kt_l S_in [C,dv]
|
| 161 |
+
o_in = (qc_m[:, :, c] * EG_m[:, :, c].unsqueeze(-1)) @ S.to(mdt) \
|
| 162 |
+
- QWd[:, :, c] @ KS_c
|
| 163 |
+
o_in = o_in * EGN_m[:, :, c].view(B, H, 1, 1)
|
| 164 |
+
o[:, :, c] = (o_in + o_loc_all[:, :, c]).float()
|
| 165 |
+
S = EGN[:, :, c].view(B, H, 1, 1) * (
|
| 166 |
+
S - kbc[:, :, c].transpose(-1, -2).float() @ KS_c.float()) \
|
| 167 |
+
+ (Kd_m[:, :, c].transpose(-1, -2) @ Ut_m[:, :, c]).float()
|
| 168 |
+
|
| 169 |
+
o = o.permute(0, 2, 3, 1, 4).reshape(B, NC * C, H, dv)[:, :T]
|
| 170 |
+
return o.to(q.dtype), S
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# ---------------------------------------------------------------------------
|
| 174 |
+
# nn.Module: causal conv1d + projections + gated delta + output gate
|
| 175 |
+
# (the FLA/Qwen3-Next layer shape: short conv on qkv, per-head decay/beta,
|
| 176 |
+
# normalized output gate)
|
| 177 |
+
# ---------------------------------------------------------------------------
|
| 178 |
+
|
| 179 |
+
# fla provides the production Triton kernel (verified vs gdn_sequential
|
| 180 |
+
# in test_gdn: rel ~1e-6 on ROCm). Fall back to gdn_chunk elsewhere.
|
| 181 |
+
try:
|
| 182 |
+
from fla.ops.gated_delta_rule import chunk_gated_delta_rule as _fla_gdr
|
| 183 |
+
except Exception:
|
| 184 |
+
_fla_gdr = None
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
class GatedDeltaNet(nn.Module):
|
| 188 |
+
"""Linear-attention mixer. h: [B,T,d_model] -> [B,T,d_model]."""
|
| 189 |
+
|
| 190 |
+
def __init__(self, d_model: int, n_heads: int, head_k: int = 128,
|
| 191 |
+
head_v: int = 128, conv_k: int = 4,
|
| 192 |
+
chunk_size: int = 32):
|
| 193 |
+
super().__init__()
|
| 194 |
+
self.h = n_heads
|
| 195 |
+
self.dk, self.dv = head_k, head_v
|
| 196 |
+
self.chunk_size = chunk_size
|
| 197 |
+
dk, dv = n_heads * head_k, n_heads * head_v
|
| 198 |
+
self.conv_k = conv_k
|
| 199 |
+
# q,k share dk side; v on dv side; conv covers all three
|
| 200 |
+
self.qkv = nn.Linear(d_model, 2 * dk + dv, bias=False)
|
| 201 |
+
self.conv = nn.Conv1d(2 * dk + dv, 2 * dk + dv, conv_k,
|
| 202 |
+
groups=2 * dk + dv, padding=conv_k - 1,
|
| 203 |
+
bias=True)
|
| 204 |
+
self.a_proj = nn.Linear(d_model, n_heads, bias=True) # decay pre-activ
|
| 205 |
+
self.b_proj = nn.Linear(d_model, n_heads, bias=True) # beta
|
| 206 |
+
self.g_out = nn.Linear(d_model, dv, bias=False) # output gate
|
| 207 |
+
self.out_norm = nn.GroupNorm(n_heads, dv) # per-head rms-ish
|
| 208 |
+
self.o_proj = nn.Linear(dv, d_model, bias=False)
|
| 209 |
+
nn.init.normal_(self.o_proj.weight, std=0.02)
|
| 210 |
+
|
| 211 |
+
def forward(self, x: torch.Tensor, S=None, use_cache=False,
|
| 212 |
+
conv_prev: torch.Tensor | None = None):
|
| 213 |
+
B, T, D = x.shape
|
| 214 |
+
qkv = self.qkv(x).transpose(1, 2) # [B,C,T]
|
| 215 |
+
if use_cache:
|
| 216 |
+
# decode path: the causal conv needs the previous k-1
|
| 217 |
+
# columns of qkv-projected input as its left context.
|
| 218 |
+
# conv_tail must be taken from the CONCATENATED context —
|
| 219 |
+
# a short chunk can carry fewer than k-1 new columns.
|
| 220 |
+
k1 = self.conv_k - 1
|
| 221 |
+
p_len = 0 if conv_prev is None else conv_prev.size(-1)
|
| 222 |
+
if conv_prev is not None:
|
| 223 |
+
qkv = torch.cat([conv_prev, qkv], -1)
|
| 224 |
+
conv_tail = qkv[..., -k1:]
|
| 225 |
+
# out[i] covers cat_in[i-k1 .. i]; new token t sits at cat
|
| 226 |
+
# index p_len+t -> want out[p_len : p_len+T]
|
| 227 |
+
qkv = self.conv(qkv)[..., p_len:p_len + T]
|
| 228 |
+
qkv = F.silu(qkv).transpose(1, 2) # [B,T,C]
|
| 229 |
+
else:
|
| 230 |
+
conv_tail = None
|
| 231 |
+
qkv = self.conv(qkv)[..., :T] # causal conv
|
| 232 |
+
qkv = F.silu(qkv).transpose(1, 2) # [B,T,C]
|
| 233 |
+
dk_t = self.h * self.dk
|
| 234 |
+
q = qkv[..., :dk_t].view(B, T, self.h, self.dk)
|
| 235 |
+
kk = qkv[..., dk_t:2 * dk_t].view(B, T, self.h, self.dk)
|
| 236 |
+
vv = qkv[..., 2 * dk_t:].view(B, T, self.h, self.dv)
|
| 237 |
+
# F.normalize upcasts to fp32 under autocast — fla's triton
|
| 238 |
+
# kernels need all main operands in ONE dtype, so normalize in
|
| 239 |
+
# fp32 for accuracy then cast back to the input dtype.
|
| 240 |
+
kk = F.normalize(kk.float(), p=2, dim=-1).to(vv.dtype)
|
| 241 |
+
a = self.a_proj(x) # [B,T,H]
|
| 242 |
+
b = self.b_proj(x)
|
| 243 |
+
g_log = -F.softplus(a).clamp(1e-4, 8.0) # log decay <=0
|
| 244 |
+
beta = torch.sigmoid(b).clamp(1e-4, 1 - 1e-4)
|
| 245 |
+
if _fla_gdr is not None and x.is_cuda:
|
| 246 |
+
# fla accepts bf16 operands + fp32 gates on ROCm (verified —
|
| 247 |
+
# the earlier fp32-only workaround was stale). GDN_FP32=1
|
| 248 |
+
# forces the whole call fp32 — fallback if bf16 fla
|
| 249 |
+
# instability is ever suspected in a training divergence.
|
| 250 |
+
if os.environ.get("GDN_FP32") == "1":
|
| 251 |
+
q, kk, vv, beta = (q.float(), kk.float(), vv.float(),
|
| 252 |
+
beta.float())
|
| 253 |
+
res = _fla_gdr(q, kk, vv,
|
| 254 |
+
g=g_log.float(), beta=beta, scale=1.0,
|
| 255 |
+
initial_state=(S.to(q.dtype) if S is not None
|
| 256 |
+
else None),
|
| 257 |
+
output_final_state=use_cache)
|
| 258 |
+
o = res[0].to(x.dtype)
|
| 259 |
+
S = res[1] if (use_cache and len(res) > 1) else None
|
| 260 |
+
else:
|
| 261 |
+
mdt = x.dtype if x.dtype in (torch.bfloat16,
|
| 262 |
+
torch.float16) else None
|
| 263 |
+
o, S = gdn_chunk(q, kk, vv, g_log, beta, S0=S,
|
| 264 |
+
chunk_size=self.chunk_size,
|
| 265 |
+
matmul_dtype=mdt)
|
| 266 |
+
# per-head GroupNorm over dv: reshape so channels = H*dv
|
| 267 |
+
o = self.out_norm(o.reshape(B * T, self.h * self.dv, 1))
|
| 268 |
+
o = o.reshape(B, T, -1)
|
| 269 |
+
o = o * torch.sigmoid(self.g_out(x))
|
| 270 |
+
out = self.o_proj(o)
|
| 271 |
+
if use_cache:
|
| 272 |
+
return out, S, conv_tail
|
| 273 |
+
return out
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4e9798dedae530810fc606a77ad8cbb31763700e3927d20b660d69398af55049
|
| 3 |
+
size 10202500036
|
model4.py
ADDED
|
@@ -0,0 +1,597 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Foundry-4: sparse hybrid kernel-optimization model.
|
| 2 |
+
|
| 3 |
+
Architecture (see DESIGN notes at bottom):
|
| 4 |
+
|
| 5 |
+
- Embedding + KernelGram n-gram memory (hashed bigram..5-gram tables,
|
| 6 |
+
zero-init gates — proven on Foundry-2/3 to carry ~half the val loss).
|
| 7 |
+
- Hybrid trunk: GatedDeltaNet linear-attention blocks everywhere except
|
| 8 |
+
every ``attn_every``-th layer which is full causal attention (RoPE +
|
| 9 |
+
QK-norm + SDPA). Linear layers handle the stream; attention layers
|
| 10 |
+
give exact long-range recall for pointer names / block sizes.
|
| 11 |
+
- Sparse MoE FFN on every block: 1 shared expert + N fine-grained
|
| 12 |
+
routed experts, top-k sigmoid router (DeepSeekMoE-style), Switch-style
|
| 13 |
+
load-balance aux loss.
|
| 14 |
+
- PonderLoop: a recurrent block (shared weights, iteration embedding)
|
| 15 |
+
applied K times to the trunk output. Per-iteration deep supervision
|
| 16 |
+
(weighted CE on each iteration's logits) so every loop step is a
|
| 17 |
+
usable state — the substrate for latent "try many paths" reasoning
|
| 18 |
+
and later RL-driven non-lazy search.
|
| 19 |
+
- JudgmentHead (Jev-like): small MLP over the pooled latent state that
|
| 20 |
+
emits typed verdicts — p(compiles), p(correct), expected speedup,
|
| 21 |
+
halt. In pretrain it is self-supervised on a signal we already have:
|
| 22 |
+
per-iteration LM improvement. In RL it is re-pointed at real harness
|
| 23 |
+
outcomes and becomes the best-of-loop selector / idea filter.
|
| 24 |
+
- Tied 65k-token embedding + chunked CE scoring (never materialise
|
| 25 |
+
[B,T,V] logits at once), optional MTP head.
|
| 26 |
+
|
| 27 |
+
Invariants (same discipline as Foundry-1/2 — do not break):
|
| 28 |
+
1. Trunk is causal. Attention uses the explicit -inf mask; GDN is
|
| 29 |
+
causal by construction; the ponder block uses the same mask.
|
| 30 |
+
2. Rewards/judgments are measured or self-supervised, never faked.
|
| 31 |
+
3. save_checkpoint stage->verify->copy->verify on every write.
|
| 32 |
+
"""
|
| 33 |
+
from __future__ import annotations
|
| 34 |
+
|
| 35 |
+
import math
|
| 36 |
+
from dataclasses import dataclass
|
| 37 |
+
|
| 38 |
+
import torch
|
| 39 |
+
import torch.nn as nn
|
| 40 |
+
import torch.nn.functional as F
|
| 41 |
+
import torch.utils.checkpoint
|
| 42 |
+
|
| 43 |
+
from gdn import GatedDeltaNet
|
| 44 |
+
from segmm import seg_mm, wscatter
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
# ---------------------------------------------------------------------------
|
| 48 |
+
# config
|
| 49 |
+
# ---------------------------------------------------------------------------
|
| 50 |
+
|
| 51 |
+
@dataclass
|
| 52 |
+
class Foundry4Config:
|
| 53 |
+
vocab_size: int = 65536 # bpe2_65k_v2 + specials 0..15
|
| 54 |
+
n_specials: int = 16
|
| 55 |
+
d_model: int = 1280
|
| 56 |
+
n_layers: int = 24
|
| 57 |
+
n_heads: int = 10 # attn heads, head_dim 128
|
| 58 |
+
head_dim: int = 128
|
| 59 |
+
seq_len: int = 4096
|
| 60 |
+
dropout: float = 0.0
|
| 61 |
+
rope_theta: float = 10000.0
|
| 62 |
+
qk_norm: bool = True
|
| 63 |
+
attn_every: int = 4 # layer i%4==3 -> full attention
|
| 64 |
+
# gated deltanet
|
| 65 |
+
gdn_heads: int = 10
|
| 66 |
+
gdn_head_k: int = 128
|
| 67 |
+
gdn_head_v: int = 128
|
| 68 |
+
gdn_chunk: int = 32 # C=64 can overflow the WY solve (2^C growth)
|
| 69 |
+
# sparse MoE
|
| 70 |
+
n_experts: int = 384
|
| 71 |
+
expert_hidden: int = 128
|
| 72 |
+
moe_topk: int = 8
|
| 73 |
+
shared_hidden: int = 640 # always-on shared expert
|
| 74 |
+
lb_weight: float = 0.01
|
| 75 |
+
# kernelgram
|
| 76 |
+
kg_buckets: int = 262144
|
| 77 |
+
kg_dim: int = 80
|
| 78 |
+
kg_max_n: int = 4 # n-gram orders 2..4
|
| 79 |
+
kg_probes: int = 2 # salted probes per order
|
| 80 |
+
# ponder loop (latent reasoning)
|
| 81 |
+
ponder_iters: int = 4
|
| 82 |
+
ponder_hidden: int = 2560 # dense SwiGLU inside the loop block
|
| 83 |
+
ponder_iter_weight_decay: float = 1.0 # loss weight ramp exponent
|
| 84 |
+
# judgment head
|
| 85 |
+
judge_hidden: int = 512
|
| 86 |
+
# mtp
|
| 87 |
+
use_mtp: bool = True
|
| 88 |
+
mtp_weight: float = 0.05
|
| 89 |
+
# memory
|
| 90 |
+
moe_ckpt: bool = False # recompute MoE dispatch in backward
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def beast_config() -> Foundry4Config:
|
| 94 |
+
"""~5.1B total / ~570M active. The target."""
|
| 95 |
+
return Foundry4Config()
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def dev_config() -> Foundry4Config:
|
| 99 |
+
"""~700M total / ~120M active — smoke + kernel-tuning config."""
|
| 100 |
+
return Foundry4Config(
|
| 101 |
+
d_model=768, n_layers=12, n_heads=6, head_dim=128,
|
| 102 |
+
gdn_heads=6, gdn_head_k=128, gdn_head_v=128,
|
| 103 |
+
n_experts=32, expert_hidden=192, moe_topk=4,
|
| 104 |
+
shared_hidden=512, seq_len=2048,
|
| 105 |
+
kg_buckets=65536, kg_dim=64, kg_max_n=4, kg_probes=2,
|
| 106 |
+
ponder_iters=3, ponder_hidden=1536,
|
| 107 |
+
judge_hidden=256)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
# ---------------------------------------------------------------------------
|
| 111 |
+
# KernelGram — identical semantics to model2 (proven); kept self-contained
|
| 112 |
+
# ---------------------------------------------------------------------------
|
| 113 |
+
|
| 114 |
+
class KernelGram(nn.Module):
|
| 115 |
+
_C1 = 0x9E3779B1
|
| 116 |
+
_C2 = 0x85EBCA77
|
| 117 |
+
_SEEDS = (0x0, 0x9E3779B1, 0x85EBCA77, 0xC2B2AE3D)
|
| 118 |
+
|
| 119 |
+
def __init__(self, cfg: Foundry4Config):
|
| 120 |
+
super().__init__()
|
| 121 |
+
self.cfg = cfg
|
| 122 |
+
self.n_grams = cfg.kg_max_n - 1
|
| 123 |
+
self.n_probes = cfg.kg_probes
|
| 124 |
+
self.n_slots = self.n_grams * self.n_probes
|
| 125 |
+
self.tables = nn.ParameterList([
|
| 126 |
+
nn.Parameter(torch.randn(cfg.kg_buckets, cfg.kg_dim)
|
| 127 |
+
/ math.sqrt(cfg.kg_dim))
|
| 128 |
+
for _ in range(self.n_slots)])
|
| 129 |
+
self.proj = nn.ParameterList([
|
| 130 |
+
nn.Parameter(torch.empty(cfg.kg_dim, cfg.d_model))
|
| 131 |
+
for _ in range(self.n_slots)])
|
| 132 |
+
for p in self.proj:
|
| 133 |
+
nn.init.normal_(p, std=0.02)
|
| 134 |
+
self.gates = nn.Parameter(torch.zeros(self.n_slots))
|
| 135 |
+
|
| 136 |
+
def _hashes(self, ids: torch.Tensor) -> list[torch.Tensor]:
|
| 137 |
+
B, T = ids.shape
|
| 138 |
+
out = []
|
| 139 |
+
for k_ in range(self.n_grams):
|
| 140 |
+
n = k_ + 2
|
| 141 |
+
for p in range(self.n_probes):
|
| 142 |
+
h = torch.zeros(B, T, dtype=torch.long, device=ids.device)
|
| 143 |
+
if p:
|
| 144 |
+
h[:, n - 1:] = self._SEEDS[p % len(self._SEEDS)]
|
| 145 |
+
for j in range(n):
|
| 146 |
+
tok = ids[:, j:T - n + 1 + j]
|
| 147 |
+
h[:, n - 1:] = (h[:, n - 1:] * self._C1
|
| 148 |
+
+ tok * self._C2)
|
| 149 |
+
out.append(h % self.cfg.kg_buckets)
|
| 150 |
+
return out
|
| 151 |
+
|
| 152 |
+
def forward(self, ids, x_emb):
|
| 153 |
+
for s, (h, tab, pr) in enumerate(
|
| 154 |
+
zip(self._hashes(ids), self.tables, self.proj)):
|
| 155 |
+
g = torch.tanh(self.gates[s])
|
| 156 |
+
x_emb = x_emb + g * (F.embedding(h, tab) @ pr)
|
| 157 |
+
return x_emb
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
# ---------------------------------------------------------------------------
|
| 161 |
+
# Sparse MoE — shared expert + fine-grained routed experts (sigmoid router,
|
| 162 |
+
# DeepSeek-style top-k with weight renormalisation; bucketed bmm dispatch)
|
| 163 |
+
# ---------------------------------------------------------------------------
|
| 164 |
+
|
| 165 |
+
class SparseMoE(nn.Module):
|
| 166 |
+
def __init__(self, cfg: Foundry4Config):
|
| 167 |
+
super().__init__()
|
| 168 |
+
d, h = cfg.d_model, cfg.expert_hidden
|
| 169 |
+
E = cfg.n_experts
|
| 170 |
+
self.cfg = cfg
|
| 171 |
+
self.E, self.k = E, cfg.moe_topk
|
| 172 |
+
self.router = nn.Linear(d, E, bias=False)
|
| 173 |
+
self.w_g = nn.Parameter(torch.empty(E, d, h))
|
| 174 |
+
self.w_w = nn.Parameter(torch.empty(E, d, h))
|
| 175 |
+
self.w_o = nn.Parameter(torch.empty(E, h, d))
|
| 176 |
+
for w in (self.w_g, self.w_w, self.w_o):
|
| 177 |
+
nn.init.normal_(w, std=0.02)
|
| 178 |
+
# shared expert (always on)
|
| 179 |
+
self.sh_g = nn.Linear(d, cfg.shared_hidden, bias=False)
|
| 180 |
+
self.sh_w = nn.Linear(d, cfg.shared_hidden, bias=False)
|
| 181 |
+
self.sh_o = nn.Linear(cfg.shared_hidden, d, bias=False)
|
| 182 |
+
|
| 183 |
+
def _swiglu(self, x, wg, ww, wo):
|
| 184 |
+
return (F.silu(x @ wg) * (x @ ww)) @ wo
|
| 185 |
+
|
| 186 |
+
def _dispatch(self, xf, top_i, w):
|
| 187 |
+
"""Packed-segment expert compute (segmm.py Triton grouped GEMM).
|
| 188 |
+
DROPLESS (causality invariant): every routed token keeps all k
|
| 189 |
+
experts — a capacity mask would couple position i's output to
|
| 190 |
+
future tokens' routing and break KV-cache semantics. Packed
|
| 191 |
+
layout: memory stays at Nk rows regardless of routing skew, and
|
| 192 |
+
there is no host<->device sync at all."""
|
| 193 |
+
N, d = xf.shape
|
| 194 |
+
counts = torch.bincount(
|
| 195 |
+
top_i.reshape(-1), minlength=self.E)
|
| 196 |
+
order = torch.argsort(top_i.reshape(-1)) # stable sort
|
| 197 |
+
flat_t = (order // self.k) # token ids
|
| 198 |
+
flat_w = w.reshape(-1)[order]
|
| 199 |
+
offs = torch.zeros(self.E + 1, dtype=torch.int64,
|
| 200 |
+
device=xf.device)
|
| 201 |
+
offs[1:] = torch.cumsum(counts, 0)
|
| 202 |
+
xs = xf[flat_t] # [Nk,d] packed
|
| 203 |
+
dt = xs.dtype
|
| 204 |
+
wg = self.w_g.to(dt) # cast: same-dtype
|
| 205 |
+
ww = self.w_w.to(dt) # tl.dot operands
|
| 206 |
+
wo = self.w_o.to(dt)
|
| 207 |
+
h1 = seg_mm(xs, wg, offs)
|
| 208 |
+
h2 = seg_mm(xs, ww, offs)
|
| 209 |
+
yp = seg_mm(F.silu(h1) * h2, wo, offs) # [Nk,d]
|
| 210 |
+
out = xf.new_zeros(N, d)
|
| 211 |
+
out.index_add_(0, flat_t,
|
| 212 |
+
(yp * flat_w.unsqueeze(-1)).to(out.dtype))
|
| 213 |
+
return out
|
| 214 |
+
|
| 215 |
+
def forward(self, x: torch.Tensor):
|
| 216 |
+
"""x [B,T,d] -> out [B,T,d], lb_loss scalar."""
|
| 217 |
+
B, T, d = x.shape
|
| 218 |
+
xf = x.reshape(-1, d)
|
| 219 |
+
N = xf.size(0)
|
| 220 |
+
|
| 221 |
+
logits = self.router(xf) # [N,E]
|
| 222 |
+
scores = torch.sigmoid(logits)
|
| 223 |
+
top_s, top_i = scores.topk(self.k, dim=-1) # [N,k]
|
| 224 |
+
w = top_s / top_s.sum(-1, keepdim=True).clamp_min(1e-6)
|
| 225 |
+
|
| 226 |
+
if self.cfg.moe_ckpt and self.training and xf.requires_grad:
|
| 227 |
+
out = torch.utils.checkpoint.checkpoint(
|
| 228 |
+
self._dispatch, xf, top_i, w,
|
| 229 |
+
use_reentrant=False)
|
| 230 |
+
else:
|
| 231 |
+
out = self._dispatch(xf, top_i, w)
|
| 232 |
+
|
| 233 |
+
out = out + self.sh_o(F.silu(self.sh_g(xf)) * self.sh_w(xf))
|
| 234 |
+
out = out.view(B, T, d)
|
| 235 |
+
|
| 236 |
+
# Switch-style load balance on the full softmax
|
| 237 |
+
p = F.softmax(logits, -1)
|
| 238 |
+
f = torch.zeros_like(p).scatter_(
|
| 239 |
+
1, top_i, 1.0 / self.k).mean(0)
|
| 240 |
+
lb = self.E * (f * p.mean(0)).sum()
|
| 241 |
+
return out, lb
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
# ---------------------------------------------------------------------------
|
| 245 |
+
# attention mixer + block
|
| 246 |
+
# ---------------------------------------------------------------------------
|
| 247 |
+
|
| 248 |
+
class RMSNorm(nn.Module):
|
| 249 |
+
def __init__(self, d: int, eps: float = 1e-6):
|
| 250 |
+
super().__init__()
|
| 251 |
+
self.w = nn.Parameter(torch.ones(d))
|
| 252 |
+
self.eps = eps
|
| 253 |
+
|
| 254 |
+
def forward(self, x):
|
| 255 |
+
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True)
|
| 256 |
+
+ self.eps) * self.w
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
def _rope_cos_sin(T, hd, theta, device):
|
| 260 |
+
half = hd // 2
|
| 261 |
+
fr = 1.0 / (theta ** (torch.arange(0, half, device=device,
|
| 262 |
+
dtype=torch.float32) / half))
|
| 263 |
+
t = torch.arange(T, device=device, dtype=torch.float32)
|
| 264 |
+
ang = torch.outer(t, fr)
|
| 265 |
+
return ang.cos(), ang.sin()
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def _rope(x, cos, sin):
|
| 269 |
+
half = x.size(-1) // 2
|
| 270 |
+
x1, x2 = x[..., :half], x[..., half:]
|
| 271 |
+
c = cos[None, None]
|
| 272 |
+
s = sin[None, None]
|
| 273 |
+
return torch.cat([x1 * c - x2 * s, x1 * s + x2 * c], -1)
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
class Attention(nn.Module):
|
| 277 |
+
def __init__(self, cfg: Foundry4Config):
|
| 278 |
+
super().__init__()
|
| 279 |
+
d = cfg.d_model
|
| 280 |
+
self.h, self.hd = cfg.n_heads, cfg.head_dim
|
| 281 |
+
self.wq = nn.Linear(d, self.h * self.hd, bias=False)
|
| 282 |
+
self.wk = nn.Linear(d, self.h * self.hd, bias=False)
|
| 283 |
+
self.wv = nn.Linear(d, self.h * self.hd, bias=False)
|
| 284 |
+
self.wo = nn.Linear(self.h * self.hd, d, bias=False)
|
| 285 |
+
self.qn = RMSNorm(self.hd) if cfg.qk_norm else None
|
| 286 |
+
self.kn = RMSNorm(self.hd) if cfg.qk_norm else None
|
| 287 |
+
|
| 288 |
+
def forward(self, x, cos, sin, causal):
|
| 289 |
+
B, T, _ = x.shape
|
| 290 |
+
q = self.wq(x).view(B, T, self.h, self.hd).transpose(1, 2)
|
| 291 |
+
k = self.wk(x).view(B, T, self.h, self.hd).transpose(1, 2)
|
| 292 |
+
v = self.wv(x).view(B, T, self.h, self.hd).transpose(1, 2)
|
| 293 |
+
if self.qn is not None:
|
| 294 |
+
q, k = self.qn(q), self.kn(k)
|
| 295 |
+
q, k = _rope(q, cos, sin), _rope(k, cos, sin)
|
| 296 |
+
a = F.scaled_dot_product_attention(q, k, v, is_causal=True)
|
| 297 |
+
return self.wo(a.transpose(1, 2).reshape(B, T, -1))
|
| 298 |
+
|
| 299 |
+
def forward_cached(self, x, cos, sin, past=None):
|
| 300 |
+
"""Decode-time path. past = (k, v) post-rope from earlier calls.
|
| 301 |
+
Returns (out, (k_all, v_all)) — the returned tensors become the
|
| 302 |
+
next call's `past`. cos/sin must cover only the NEW positions."""
|
| 303 |
+
B, T, _ = x.shape
|
| 304 |
+
q = self.wq(x).view(B, T, self.h, self.hd).transpose(1, 2)
|
| 305 |
+
k = self.wk(x).view(B, T, self.h, self.hd).transpose(1, 2)
|
| 306 |
+
v = self.wv(x).view(B, T, self.h, self.hd).transpose(1, 2)
|
| 307 |
+
if self.qn is not None:
|
| 308 |
+
q, k = self.qn(q), self.kn(k)
|
| 309 |
+
q, k = _rope(q, cos, sin), _rope(k, cos, sin)
|
| 310 |
+
if past is not None:
|
| 311 |
+
k = torch.cat([past[0], k], 2)
|
| 312 |
+
v = torch.cat([past[1], v], 2)
|
| 313 |
+
if past is None or T == k.size(2):
|
| 314 |
+
a = F.scaled_dot_product_attention(q, k, v, is_causal=True)
|
| 315 |
+
else:
|
| 316 |
+
P = k.size(2) - T
|
| 317 |
+
m = torch.ones(T, k.size(2), dtype=torch.bool,
|
| 318 |
+
device=x.device)
|
| 319 |
+
m[:, P:] = torch.tril(torch.ones(T, T, dtype=torch.bool,
|
| 320 |
+
device=x.device))
|
| 321 |
+
a = F.scaled_dot_product_attention(q, k, v, attn_mask=m)
|
| 322 |
+
return self.wo(a.transpose(1, 2).reshape(B, T, -1)), (k, v)
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
class Block(nn.Module):
|
| 326 |
+
"""mixer (GDN or attention) + sparse MoE, pre-norm."""
|
| 327 |
+
|
| 328 |
+
def __init__(self, cfg: Foundry4Config, use_attn: bool):
|
| 329 |
+
super().__init__()
|
| 330 |
+
self.use_attn = use_attn
|
| 331 |
+
d = cfg.d_model
|
| 332 |
+
self.norm1 = RMSNorm(d)
|
| 333 |
+
self.norm2 = RMSNorm(d)
|
| 334 |
+
if use_attn:
|
| 335 |
+
self.mixer = Attention(cfg)
|
| 336 |
+
else:
|
| 337 |
+
self.mixer = GatedDeltaNet(
|
| 338 |
+
d, cfg.gdn_heads, cfg.gdn_head_k, cfg.gdn_head_v,
|
| 339 |
+
chunk_size=cfg.gdn_chunk)
|
| 340 |
+
self.ffn = SparseMoE(cfg)
|
| 341 |
+
|
| 342 |
+
def forward(self, h, cos, sin, causal):
|
| 343 |
+
if self.use_attn:
|
| 344 |
+
h = h + self.mixer(self.norm1(h), cos, sin, causal)
|
| 345 |
+
else:
|
| 346 |
+
h = h + self.mixer(self.norm1(h))
|
| 347 |
+
ff, lb = self.ffn(self.norm2(h))
|
| 348 |
+
h = h + ff
|
| 349 |
+
return h, lb
|
| 350 |
+
|
| 351 |
+
def forward_cached(self, h, cos, sin, cache):
|
| 352 |
+
"""cache: dict — {'kv': (k,v)} for attn, {'S': state} for GDN."""
|
| 353 |
+
if self.use_attn:
|
| 354 |
+
o, kv = self.mixer.forward_cached(self.norm1(h), cos, sin,
|
| 355 |
+
cache.get("kv"))
|
| 356 |
+
cache["kv"] = kv
|
| 357 |
+
h = h + o
|
| 358 |
+
else:
|
| 359 |
+
o, S, ct = self.mixer(self.norm1(h), S=cache.get("S"),
|
| 360 |
+
use_cache=True,
|
| 361 |
+
conv_prev=cache.get("conv"))
|
| 362 |
+
cache["S"] = S
|
| 363 |
+
cache["conv"] = ct
|
| 364 |
+
h = h + o
|
| 365 |
+
ff, _ = self.ffn(self.norm2(h))
|
| 366 |
+
return h + ff
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
# ---------------------------------------------------------------------------
|
| 370 |
+
# PonderLoop — the latent reasoning core
|
| 371 |
+
# ---------------------------------------------------------------------------
|
| 372 |
+
|
| 373 |
+
class PonderBlock(nn.Module):
|
| 374 |
+
"""One shared-weight recurrent step: causal attn + dense SwiGLU."""
|
| 375 |
+
|
| 376 |
+
def __init__(self, cfg: Foundry4Config):
|
| 377 |
+
super().__init__()
|
| 378 |
+
d = cfg.d_model
|
| 379 |
+
self.norm1 = RMSNorm(d)
|
| 380 |
+
self.norm2 = RMSNorm(d)
|
| 381 |
+
self.attn = Attention(cfg)
|
| 382 |
+
self.ff_g = nn.Linear(d, cfg.ponder_hidden, bias=False)
|
| 383 |
+
self.ff_w = nn.Linear(d, cfg.ponder_hidden, bias=False)
|
| 384 |
+
self.ff_o = nn.Linear(cfg.ponder_hidden, d, bias=False)
|
| 385 |
+
self.iter_emb = nn.Parameter(torch.zeros(cfg.ponder_iters, d))
|
| 386 |
+
|
| 387 |
+
def forward(self, h, it, cos, sin, causal):
|
| 388 |
+
h = h + self.iter_emb[it]
|
| 389 |
+
h = h + self.attn(self.norm1(h), cos, sin, causal)
|
| 390 |
+
x = self.norm2(h)
|
| 391 |
+
h = h + self.ff_o(F.silu(self.ff_g(x)) * self.ff_w(x))
|
| 392 |
+
return h
|
| 393 |
+
|
| 394 |
+
def forward_cached(self, h, it, cos, sin, past):
|
| 395 |
+
o, kv = self.attn.forward_cached(
|
| 396 |
+
self.norm1(h + self.iter_emb[it]), cos, sin, past)
|
| 397 |
+
h = h + self.iter_emb[it] + o
|
| 398 |
+
x = self.norm2(h)
|
| 399 |
+
return h + self.ff_o(F.silu(self.ff_g(x)) * self.ff_w(x)), kv
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
class JudgmentHead(nn.Module):
|
| 403 |
+
"""Typed verdicts over the pooled latent state.
|
| 404 |
+
|
| 405 |
+
Outputs: p_compile, p_correct logits, log-speedup regression,
|
| 406 |
+
halt logit. Trained self-supervised in pretrain (per-iter LM
|
| 407 |
+
improvement); re-pointed at harness rewards in SFT/RL.
|
| 408 |
+
"""
|
| 409 |
+
|
| 410 |
+
def __init__(self, cfg: Foundry4Config):
|
| 411 |
+
super().__init__()
|
| 412 |
+
d = cfg.d_model
|
| 413 |
+
self.net = nn.Sequential(
|
| 414 |
+
nn.Linear(d, cfg.judge_hidden), nn.SiLU(),
|
| 415 |
+
nn.Linear(cfg.judge_hidden, 4))
|
| 416 |
+
|
| 417 |
+
def forward(self, h): # h [B,T,d] -> [B,4]
|
| 418 |
+
pooled = h.mean(1)
|
| 419 |
+
return self.net(pooled)
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
# ---------------------------------------------------------------------------
|
| 423 |
+
# model
|
| 424 |
+
# ---------------------------------------------------------------------------
|
| 425 |
+
|
| 426 |
+
class Foundry4Model(nn.Module):
|
| 427 |
+
def __init__(self, cfg: Foundry4Config | None = None):
|
| 428 |
+
super().__init__()
|
| 429 |
+
self.cfg = cfg = cfg or Foundry4Config()
|
| 430 |
+
d = cfg.d_model
|
| 431 |
+
self.tok_emb = nn.Embedding(cfg.vocab_size, d)
|
| 432 |
+
self.kg = KernelGram(cfg)
|
| 433 |
+
self.blocks = nn.ModuleList([
|
| 434 |
+
Block(cfg, use_attn=((i + 1) % cfg.attn_every == 0))
|
| 435 |
+
for i in range(cfg.n_layers)])
|
| 436 |
+
self.norm = RMSNorm(d)
|
| 437 |
+
self.ponder = PonderBlock(cfg)
|
| 438 |
+
self.judge = JudgmentHead(cfg)
|
| 439 |
+
self.head = nn.Linear(d, cfg.vocab_size, bias=False)
|
| 440 |
+
self.head.weight = self.tok_emb.weight # tied
|
| 441 |
+
self.mtp = (nn.Linear(d, d, bias=False)
|
| 442 |
+
if cfg.use_mtp else None)
|
| 443 |
+
self.apply(self._init)
|
| 444 |
+
|
| 445 |
+
@staticmethod
|
| 446 |
+
def _init(m):
|
| 447 |
+
if isinstance(m, nn.Linear):
|
| 448 |
+
nn.init.normal_(m.weight, std=0.02)
|
| 449 |
+
elif isinstance(m, nn.Embedding):
|
| 450 |
+
nn.init.normal_(m.weight, std=0.02)
|
| 451 |
+
|
| 452 |
+
# -- trunk -------------------------------------------------------------
|
| 453 |
+
|
| 454 |
+
def trunk(self, ids):
|
| 455 |
+
x = self.kg(ids, self.tok_emb(ids))
|
| 456 |
+
dev = ids.device
|
| 457 |
+
T = ids.size(1)
|
| 458 |
+
cos, sin = _rope_cos_sin(T, self.cfg.head_dim,
|
| 459 |
+
self.cfg.rope_theta, dev)
|
| 460 |
+
h, lb = x, x.new_zeros(())
|
| 461 |
+
for blk in self.blocks:
|
| 462 |
+
h, l = blk(h, cos, sin, None)
|
| 463 |
+
lb = lb + l
|
| 464 |
+
return self.norm(h), lb
|
| 465 |
+
|
| 466 |
+
# -- forward: trunk -> ponder loop -> per-iter hidden states -----------
|
| 467 |
+
|
| 468 |
+
def forward(self, ids, return_aux=False):
|
| 469 |
+
cfg = self.cfg
|
| 470 |
+
h, lb = self.trunk(ids)
|
| 471 |
+
T = ids.size(1)
|
| 472 |
+
cos, sin = _rope_cos_sin(T, cfg.head_dim, cfg.rope_theta,
|
| 473 |
+
ids.device)
|
| 474 |
+
h_iters, judge_per_iter = [h], []
|
| 475 |
+
for it in range(cfg.ponder_iters):
|
| 476 |
+
h = self.ponder(h, it, cos, sin, None)
|
| 477 |
+
h_iters.append(h)
|
| 478 |
+
if return_aux:
|
| 479 |
+
judge_per_iter.append(self.judge(h))
|
| 480 |
+
if return_aux:
|
| 481 |
+
# training path: h_iters drive chunked/fused CE — never
|
| 482 |
+
# materialise the [B,T,V] logits here (saves ~4GB + backward)
|
| 483 |
+
aux = {"lb_loss": lb * cfg.lb_weight,
|
| 484 |
+
"judge": torch.stack(judge_per_iter, 1),
|
| 485 |
+
"h_iters": h_iters}
|
| 486 |
+
if self.mtp is not None:
|
| 487 |
+
aux["mtp_h"] = self.mtp(h)
|
| 488 |
+
return None, aux
|
| 489 |
+
return self.head(h) # final iter
|
| 490 |
+
|
| 491 |
+
# -- cached decode ------------------------------------------------------
|
| 492 |
+
|
| 493 |
+
def new_cache(self):
|
| 494 |
+
return {"blocks": [{} for _ in self.blocks],
|
| 495 |
+
"ponder": [None] * self.cfg.ponder_iters,
|
| 496 |
+
"tail": None} # last kg_max_n-1 ids
|
| 497 |
+
|
| 498 |
+
def forward_cached(self, ids, pos0, cache):
|
| 499 |
+
"""Decode/prefill for generation. ids [B,Tn] at absolute
|
| 500 |
+
positions pos0..pos0+Tn-1. Returns logits [B,Tn,V]; cache is
|
| 501 |
+
updated in place. Matches forward() token-for-token (causality
|
| 502 |
+
invariant), just incremental."""
|
| 503 |
+
cfg = self.cfg
|
| 504 |
+
B, Tn = ids.shape
|
| 505 |
+
dev = ids.device
|
| 506 |
+
# KernelGram needs the trailing n-1 ids for the n-gram window
|
| 507 |
+
tail = cache["tail"]
|
| 508 |
+
kg_in = ids if tail is None else torch.cat([tail, ids], 1)
|
| 509 |
+
x = self.kg(kg_in, self.tok_emb(kg_in))[:, -Tn:]
|
| 510 |
+
cache["tail"] = kg_in[:, -(cfg.kg_max_n - 1):]
|
| 511 |
+
cos, sin = _rope_cos_sin(pos0 + Tn, cfg.head_dim,
|
| 512 |
+
cfg.rope_theta, dev)
|
| 513 |
+
cos, sin = cos[pos0:], sin[pos0:]
|
| 514 |
+
h = x
|
| 515 |
+
for blk, bc in zip(self.blocks, cache["blocks"]):
|
| 516 |
+
h = blk.forward_cached(h, cos, sin, bc)
|
| 517 |
+
h = self.norm(h)
|
| 518 |
+
for it in range(cfg.ponder_iters):
|
| 519 |
+
h, cache["ponder"][it] = self.ponder.forward_cached(
|
| 520 |
+
h, it, cos, sin, cache["ponder"][it])
|
| 521 |
+
return self.head(h)
|
| 522 |
+
|
| 523 |
+
|
| 524 |
+
# ---------------------------------------------------------------------------
|
| 525 |
+
# parameter accounting + checkpointing (same discipline as model2)
|
| 526 |
+
# ---------------------------------------------------------------------------
|
| 527 |
+
|
| 528 |
+
def param_report(model: Foundry4Model) -> dict:
|
| 529 |
+
cfg = model.cfg
|
| 530 |
+
total = sum(p.numel() for p in model.parameters())
|
| 531 |
+
|
| 532 |
+
emb = cfg.vocab_size * cfg.d_model # tied
|
| 533 |
+
kg = sum(p.numel() for p in model.kg.parameters())
|
| 534 |
+
experts = cfg.n_layers * cfg.n_experts * 3 * cfg.d_model \
|
| 535 |
+
* cfg.expert_hidden
|
| 536 |
+
|
| 537 |
+
# active = everything except the cold (non-selected) routed-expert
|
| 538 |
+
# capacity. Embedding lookup counts once (tied head reuses it for
|
| 539 |
+
# logits, so it's active anyway).
|
| 540 |
+
routed = cfg.n_layers * cfg.moe_topk * 3 * cfg.d_model \
|
| 541 |
+
* cfg.expert_hidden
|
| 542 |
+
active = total - experts + routed
|
| 543 |
+
n_attn = cfg.n_layers // cfg.attn_every
|
| 544 |
+
n_gdn = cfg.n_layers - n_attn
|
| 545 |
+
return {"total": total, "active": active,
|
| 546 |
+
"experts": experts, "kg": kg, "emb": emb,
|
| 547 |
+
"n_attn": n_attn, "n_gdn": n_gdn}
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
def save_checkpoint(obj: dict, out_path) -> None:
|
| 551 |
+
"""Stage-write-verify-copy-verify; survives flaky filesystems."""
|
| 552 |
+
import io
|
| 553 |
+
import shutil
|
| 554 |
+
import tempfile
|
| 555 |
+
from pathlib import Path
|
| 556 |
+
|
| 557 |
+
out_path = Path(out_path)
|
| 558 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
| 559 |
+
buf = io.BytesIO()
|
| 560 |
+
torch.save(obj, buf)
|
| 561 |
+
data = buf.getvalue()
|
| 562 |
+
with tempfile.NamedTemporaryFile(
|
| 563 |
+
dir=out_path.parent, delete=False) as f:
|
| 564 |
+
f.write(data)
|
| 565 |
+
tmp = Path(f.name)
|
| 566 |
+
# verify the staged file loads before it replaces anything
|
| 567 |
+
torch.load(tmp, map_location="cpu", weights_only=False)
|
| 568 |
+
shutil.copy2(tmp, out_path)
|
| 569 |
+
torch.load(out_path, map_location="cpu", weights_only=False)
|
| 570 |
+
tmp.unlink()
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
# ---------------------------------------------------------------------------
|
| 574 |
+
# FLOP model for MFU accounting
|
| 575 |
+
# ---------------------------------------------------------------------------
|
| 576 |
+
|
| 577 |
+
def flops_per_token(cfg: Foundry4Config) -> float:
|
| 578 |
+
"""Forward+backward FLOPs per token (~6N_active + attention terms)."""
|
| 579 |
+
d = cfg.d_model
|
| 580 |
+
n_attn = cfg.n_layers // cfg.attn_every
|
| 581 |
+
n_gdn = cfg.n_layers - n_attn
|
| 582 |
+
# dense per-token params: mixers + shared expert + topk experts +
|
| 583 |
+
# router + ponder(iters) + heads — times 6
|
| 584 |
+
mixer = n_attn * 4 * d * cfg.n_heads * cfg.head_dim \
|
| 585 |
+
+ n_gdn * d * (4 * cfg.gdn_heads * cfg.gdn_head_k)
|
| 586 |
+
ffn = cfg.n_layers * 3 * d * (cfg.shared_hidden
|
| 587 |
+
+ cfg.moe_topk * cfg.expert_hidden)
|
| 588 |
+
ponder = cfg.ponder_iters * (4 * d * cfg.n_heads * cfg.head_dim
|
| 589 |
+
+ 3 * d * cfg.ponder_hidden)
|
| 590 |
+
heads = cfg.vocab_size * d # tied head proj
|
| 591 |
+
n_active = mixer + ffn + ponder + heads
|
| 592 |
+
# attention QK/AV term: ~4 * T * d * n_attn_heads-ish; and GDN chunk
|
| 593 |
+
# work ~= O(C * dk * dv) per token — lump as seq term:
|
| 594 |
+
seq_term = n_attn * 12 * cfg.seq_len * d \
|
| 595 |
+
+ n_gdn * 2 * cfg.gdn_chunk * cfg.gdn_heads \
|
| 596 |
+
* cfg.gdn_head_v
|
| 597 |
+
return 6.0 * n_active + seq_term
|
segmm.py
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""segmm.py — segmented/grouped GEMM for dropless MoE dispatch.
|
| 2 |
+
|
| 3 |
+
Tokens are gathered into expert-sorted order once: xs [M, K] where the
|
| 4 |
+
rows for expert e occupy the contiguous slice [offs[e], offs[e+1]).
|
| 5 |
+
Each expert multiplies its slice by its own weight w[e] [K, N].
|
| 6 |
+
|
| 7 |
+
Why not torch.bmm + padding? Dropless padding materialises
|
| 8 |
+
[E, max_count, *] — on skewed routing max_count spikes (we measured
|
| 9 |
+
cap ~7600 on real data -> multi-GB buffers per layer, OOM). Packed
|
| 10 |
+
segments keep memory at exactly M rows regardless of skew, and the
|
| 11 |
+
same slices power a dropless (causality-safe) dispatch.
|
| 12 |
+
|
| 13 |
+
Why not torch._grouped_mm? On this ROCm build it measured ~10 TFLOP/s
|
| 14 |
+
(a scalar fallback path) vs ~300 TFLOP/s for bmm. This kernel is a
|
| 15 |
+
straight tile loop and hits bmm-class throughput.
|
| 16 |
+
|
| 17 |
+
Forward: c[rows_e] = a[rows_e] @ w[e]
|
| 18 |
+
Backward: da[rows_e] = dc[rows_e] @ w[e]^T (same kernel, w
|
| 19 |
+
loaded transposed via strides)
|
| 20 |
+
dw[e] = a[rows_e]^T @ dc[rows_e] (reduction kernel)
|
| 21 |
+
"""
|
| 22 |
+
import torch
|
| 23 |
+
import triton
|
| 24 |
+
import triton.language as tl
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@triton.autotune(
|
| 28 |
+
configs=[
|
| 29 |
+
# BM must stay 64 — _tile_meta partitions tiles with this size
|
| 30 |
+
triton.Config({"BM": 64, "BK": 64, "BN": 64}, num_warps=4,
|
| 31 |
+
num_stages=3),
|
| 32 |
+
triton.Config({"BM": 64, "BK": 128, "BN": 64}, num_warps=4,
|
| 33 |
+
num_stages=4),
|
| 34 |
+
triton.Config({"BM": 64, "BK": 64, "BN": 128}, num_warps=8,
|
| 35 |
+
num_stages=3),
|
| 36 |
+
triton.Config({"BM": 64, "BK": 128, "BN": 128}, num_warps=8,
|
| 37 |
+
num_stages=4),
|
| 38 |
+
triton.Config({"BM": 64, "BK": 256, "BN": 64}, num_warps=8,
|
| 39 |
+
num_stages=3),
|
| 40 |
+
],
|
| 41 |
+
key=["K", "N", "E"],
|
| 42 |
+
)
|
| 43 |
+
@triton.jit
|
| 44 |
+
def _seg_mm_fwd(a_ptr, w_ptr, c_ptr, offs_ptr, mt_starts_ptr, n_tiles_ptr,
|
| 45 |
+
gidx_ptr,
|
| 46 |
+
K, N,
|
| 47 |
+
sa_m, sa_k, sw_e, sw_k, sw_n, sc_m, sc_n,
|
| 48 |
+
E: tl.constexpr, BE: tl.constexpr,
|
| 49 |
+
HAS_IDX: tl.constexpr,
|
| 50 |
+
BM: tl.constexpr, BK: tl.constexpr, BN: tl.constexpr):
|
| 51 |
+
pid_m = tl.program_id(0) # global m-tile across all experts
|
| 52 |
+
pid_n = tl.program_id(1)
|
| 53 |
+
total = tl.load(n_tiles_ptr) # scalar: total m-tiles
|
| 54 |
+
if pid_m >= total:
|
| 55 |
+
return
|
| 56 |
+
# expert = last e with mt_starts[e] <= pid_m (linear scan, E small)
|
| 57 |
+
e_off = tl.arange(0, BE)
|
| 58 |
+
starts = tl.load(mt_starts_ptr + e_off, mask=e_off < E, other=1 << 30)
|
| 59 |
+
e = tl.sum((starts <= pid_m).to(tl.int32)) - 1
|
| 60 |
+
mt_local = pid_m - tl.load(mt_starts_ptr + e)
|
| 61 |
+
row0 = tl.load(offs_ptr + e)
|
| 62 |
+
row1 = tl.load(offs_ptr + e + 1)
|
| 63 |
+
rows = row0 + mt_local * BM + tl.arange(0, BM)
|
| 64 |
+
rmask = rows < row1
|
| 65 |
+
if HAS_IDX:
|
| 66 |
+
a_rows = tl.load(gidx_ptr + rows, mask=rmask, other=0)
|
| 67 |
+
else:
|
| 68 |
+
a_rows = rows
|
| 69 |
+
cols = pid_n * BN + tl.arange(0, BN)
|
| 70 |
+
cmask = cols < N
|
| 71 |
+
acc = tl.zeros((BM, BN), dtype=tl.float32)
|
| 72 |
+
for k0 in range(0, K, BK):
|
| 73 |
+
kk = k0 + tl.arange(0, BK)
|
| 74 |
+
a = tl.load(a_ptr + a_rows[:, None] * sa_m + kk[None, :] * sa_k,
|
| 75 |
+
mask=rmask[:, None] & (kk < K)[None, :], other=0.0)
|
| 76 |
+
w = tl.load(w_ptr + e * sw_e + kk[:, None] * sw_k
|
| 77 |
+
+ cols[None, :] * sw_n,
|
| 78 |
+
mask=(kk < K)[:, None] & cmask[None, :], other=0.0)
|
| 79 |
+
acc += tl.dot(a, w)
|
| 80 |
+
tl.store(c_ptr + rows[:, None] * sc_m + cols[None, :] * sc_n,
|
| 81 |
+
acc.to(c_ptr.dtype.element_ty),
|
| 82 |
+
mask=rmask[:, None] & cmask[None, :])
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
@triton.jit
|
| 86 |
+
def _seg_mm_dw(a_ptr, dy_ptr, dw_ptr, offs_ptr, gidx_ptr,
|
| 87 |
+
K, N,
|
| 88 |
+
sa_m, sa_k, sd_m, sd_n, sw_e, sw_k, sw_n,
|
| 89 |
+
HAS_IDX: tl.constexpr,
|
| 90 |
+
BR: tl.constexpr, BK: tl.constexpr, BN: tl.constexpr):
|
| 91 |
+
# grid: (E, ceil(K/BK), ceil(N/BN)) flattened into pid via program_id
|
| 92 |
+
e = tl.program_id(0)
|
| 93 |
+
pid_k = tl.program_id(1)
|
| 94 |
+
pid_n = tl.program_id(2)
|
| 95 |
+
row0 = tl.load(offs_ptr + e)
|
| 96 |
+
row1 = tl.load(offs_ptr + e + 1)
|
| 97 |
+
kk = pid_k * BK + tl.arange(0, BK)
|
| 98 |
+
nn = pid_n * BN + tl.arange(0, BN)
|
| 99 |
+
kmask = kk < K
|
| 100 |
+
nmask = nn < N
|
| 101 |
+
acc = tl.zeros((BK, BN), dtype=tl.float32)
|
| 102 |
+
for r0 in range(row0, row1, BR):
|
| 103 |
+
rows = r0 + tl.arange(0, BR)
|
| 104 |
+
rmask = rows < row1
|
| 105 |
+
if HAS_IDX:
|
| 106 |
+
a_rows = tl.load(gidx_ptr + rows, mask=rmask, other=0)
|
| 107 |
+
else:
|
| 108 |
+
a_rows = rows
|
| 109 |
+
a = tl.load(a_ptr + a_rows[:, None] * sa_m + kk[None, :] * sa_k,
|
| 110 |
+
mask=rmask[:, None] & kmask[None, :], other=0.0)
|
| 111 |
+
dy = tl.load(dy_ptr + rows[:, None] * sd_m + nn[None, :] * sd_n,
|
| 112 |
+
mask=rmask[:, None] & nmask[None, :], other=0.0)
|
| 113 |
+
acc += tl.dot(tl.trans(a), dy)
|
| 114 |
+
tl.store(dw_ptr + e * sw_e + kk[:, None] * sw_k + nn[None, :] * sw_n,
|
| 115 |
+
acc.to(dw_ptr.dtype.element_ty),
|
| 116 |
+
mask=kmask[:, None] & nmask[None, :])
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# ---------------------------------------------------------------------------
|
| 120 |
+
# fused weighted scatter: out[gidx[r]] += s[r] * yp[r] (atomics, fp32 out)
|
| 121 |
+
# backward: dyp[r] = s[r] * dout[gidx[r]]
|
| 122 |
+
# ds[r] = <dout[gidx[r]], yp[r]>
|
| 123 |
+
# ---------------------------------------------------------------------------
|
| 124 |
+
|
| 125 |
+
@triton.jit
|
| 126 |
+
def _wscatter_fwd(yp_ptr, gidx_ptr, s_ptr, out_ptr,
|
| 127 |
+
D, sm, sd_m,
|
| 128 |
+
BR: tl.constexpr, BD: tl.constexpr):
|
| 129 |
+
r = tl.program_id(0)
|
| 130 |
+
dn = tl.program_id(1)
|
| 131 |
+
t = tl.load(gidx_ptr + r)
|
| 132 |
+
s = tl.load(s_ptr + r).to(tl.float32)
|
| 133 |
+
cols = dn * BD + tl.arange(0, BD)
|
| 134 |
+
m = cols < D
|
| 135 |
+
v = tl.load(yp_ptr + r * sm + cols, mask=m, other=0.).to(tl.float32)
|
| 136 |
+
tl.atomic_add(out_ptr + t * sd_m + cols, s * v, mask=m)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
@triton.jit
|
| 140 |
+
def _wscatter_bwd(dout_ptr, gidx_ptr, s_ptr, yp_ptr, dyp_ptr, ds_ptr,
|
| 141 |
+
D, sd_m, sm,
|
| 142 |
+
BR: tl.constexpr, BD: tl.constexpr):
|
| 143 |
+
r = tl.program_id(0)
|
| 144 |
+
dn = tl.program_id(1)
|
| 145 |
+
t = tl.load(gidx_ptr + r)
|
| 146 |
+
s = tl.load(s_ptr + r).to(tl.float32)
|
| 147 |
+
cols = dn * BD + tl.arange(0, BD)
|
| 148 |
+
m = cols < D
|
| 149 |
+
do = tl.load(dout_ptr + t * sd_m + cols, mask=m, other=0.).to(tl.float32)
|
| 150 |
+
yp = tl.load(yp_ptr + r * sm + cols, mask=m, other=0.).to(tl.float32)
|
| 151 |
+
tl.store(dyp_ptr + r * sm + cols, (s * do).to(dyp_ptr.dtype.element_ty),
|
| 152 |
+
mask=m)
|
| 153 |
+
tl.atomic_add(ds_ptr + r, tl.sum(do * yp, 0))
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _next_pow2(x):
|
| 157 |
+
return 1 << (x - 1).bit_length()
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
class SegMM(torch.autograd.Function):
|
| 161 |
+
"""c[offs[e]:offs[e+1]] = a[gidx[offs[e]:offs[e+1]]] @ w[e] for each e.
|
| 162 |
+
|
| 163 |
+
a: [M,K] rows sorted by segment (or an arbitrarily-ordered dense
|
| 164 |
+
matrix when gidx is given — the kernel gathers rows itself);
|
| 165 |
+
w: [E,K,N]; offs: [E+1] int32; gidx (optional): [M] int32 row
|
| 166 |
+
indices into a (gather fused into the GEMM read).
|
| 167 |
+
Grid is a static upper bound — no host<->device sync anywhere.
|
| 168 |
+
"""
|
| 169 |
+
|
| 170 |
+
BM, BK, BN, BR = 64, 64, 64, 32
|
| 171 |
+
|
| 172 |
+
@staticmethod
|
| 173 |
+
def _tile_meta(offs, E, M, dev):
|
| 174 |
+
counts = (offs[1:] - offs[:-1])
|
| 175 |
+
tiles = (counts + SegMM.BM - 1) // SegMM.BM
|
| 176 |
+
mt_starts = torch.cumsum(tiles, 0) - tiles
|
| 177 |
+
total = tiles.sum()
|
| 178 |
+
return mt_starts.to(torch.int32), total[None].to(torch.int32)
|
| 179 |
+
|
| 180 |
+
@staticmethod
|
| 181 |
+
def forward(ctx, a, w, offs, gidx=None):
|
| 182 |
+
if gidx is not None:
|
| 183 |
+
M = gidx.shape[0]
|
| 184 |
+
else:
|
| 185 |
+
M = a.shape[0]
|
| 186 |
+
_, K = a.shape
|
| 187 |
+
E, _, N = w.shape
|
| 188 |
+
dev = a.device
|
| 189 |
+
offs = offs.to(torch.int32)
|
| 190 |
+
has_idx = gidx is not None
|
| 191 |
+
if has_idx:
|
| 192 |
+
gidx = gidx.to(torch.int32).contiguous()
|
| 193 |
+
else:
|
| 194 |
+
gidx = offs # dummy ptr — kernel ignores when HAS_IDX=False
|
| 195 |
+
mt_starts, total = SegMM._tile_meta(offs, E, M, dev)
|
| 196 |
+
c = torch.empty(M, N, device=dev, dtype=a.dtype)
|
| 197 |
+
max_tiles = (M + SegMM.BM - 1) // SegMM.BM + E
|
| 198 |
+
grid_n = (N + SegMM.BN - 1) // SegMM.BN
|
| 199 |
+
_seg_mm_fwd[(max_tiles, grid_n)](
|
| 200 |
+
a, w, c, offs, mt_starts, total, gidx, K, N,
|
| 201 |
+
a.stride(0), a.stride(1), w.stride(0), w.stride(1),
|
| 202 |
+
w.stride(2), c.stride(0), c.stride(1),
|
| 203 |
+
E=E, BE=_next_pow2(E + 1), HAS_IDX=has_idx)
|
| 204 |
+
ctx.save_for_backward(a, w, offs, gidx if has_idx else None)
|
| 205 |
+
ctx.has_idx = has_idx
|
| 206 |
+
return c
|
| 207 |
+
|
| 208 |
+
@staticmethod
|
| 209 |
+
def backward(ctx, dc):
|
| 210 |
+
a, w, offs, gidx = ctx.saved_tensors
|
| 211 |
+
has_idx = ctx.has_idx
|
| 212 |
+
M = dc.shape[0]
|
| 213 |
+
_, K = a.shape
|
| 214 |
+
E, _, N = w.shape
|
| 215 |
+
dev = a.device
|
| 216 |
+
dc = dc.contiguous()
|
| 217 |
+
# da = seg_mm(dc, w^T) — packed-space grad (M rows either way)
|
| 218 |
+
mt_starts, total = SegMM._tile_meta(offs, E, M, dev)
|
| 219 |
+
da_packed = torch.empty(M, K, device=dev, dtype=a.dtype)
|
| 220 |
+
max_tiles = (M + SegMM.BM - 1) // SegMM.BM + E
|
| 221 |
+
grid_k = (K + SegMM.BN - 1) // SegMM.BN
|
| 222 |
+
dummy = gidx if has_idx else offs
|
| 223 |
+
_seg_mm_fwd[(max_tiles, grid_k)](
|
| 224 |
+
dc, w, da_packed, offs, mt_starts, total, dummy, N, K,
|
| 225 |
+
dc.stride(0), dc.stride(1), w.stride(0), w.stride(2),
|
| 226 |
+
w.stride(1), da_packed.stride(0), da_packed.stride(1),
|
| 227 |
+
E=E, BE=_next_pow2(E + 1), HAS_IDX=False)
|
| 228 |
+
if has_idx:
|
| 229 |
+
# scatter packed grads back to the dense a-rows
|
| 230 |
+
da = torch.zeros_like(a)
|
| 231 |
+
da.index_add_(0, gidx.long(), da_packed)
|
| 232 |
+
else:
|
| 233 |
+
da = da_packed
|
| 234 |
+
# dw[e] = a_e^T @ dc_e — reduction kernel (gathers via gidx)
|
| 235 |
+
dw = torch.empty_like(w)
|
| 236 |
+
_seg_mm_dw[(E, (K + SegMM.BK - 1) // SegMM.BK,
|
| 237 |
+
(N + SegMM.BN - 1) // SegMM.BN)](
|
| 238 |
+
a, dc, dw, offs, dummy, K, N,
|
| 239 |
+
a.stride(0), a.stride(1), dc.stride(0), dc.stride(1),
|
| 240 |
+
dw.stride(0), dw.stride(1), dw.stride(2),
|
| 241 |
+
HAS_IDX=has_idx,
|
| 242 |
+
BR=SegMM.BR, BK=SegMM.BK, BN=SegMM.BN)
|
| 243 |
+
return da, dw, None, None
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def seg_mm(a, w, offs, gidx=None):
|
| 247 |
+
"""Grouped matmul: a [M,K] sorted rows, w [E,K,N], offs [E+1].
|
| 248 |
+
With gidx [M] int32: a stays dense, packed row r reads a[gidx[r]]."""
|
| 249 |
+
return SegMM.apply(a, w, offs, gidx)
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
class WScatter(torch.autograd.Function):
|
| 253 |
+
"""out[t[r]] += s[r] * yp[r] — fused weighted scatter-add.
|
| 254 |
+
|
| 255 |
+
out: [N,D] fp32 (must be pre-zeroed); yp: [M,D]; s: [M];
|
| 256 |
+
t: [M] int32 destination rows (duplicates allowed — atomics).
|
| 257 |
+
Backward: dyp = s * dout[t]; ds = <dout[t], yp>.
|
| 258 |
+
"""
|
| 259 |
+
|
| 260 |
+
BD = 256
|
| 261 |
+
|
| 262 |
+
@staticmethod
|
| 263 |
+
def forward(ctx, out, yp, s, t):
|
| 264 |
+
M, D = yp.shape
|
| 265 |
+
t32 = t.to(torch.int32).contiguous()
|
| 266 |
+
grid_d = (D + WScatter.BD - 1) // WScatter.BD
|
| 267 |
+
_wscatter_fwd[(M, grid_d)](
|
| 268 |
+
yp, t32, s, out, D, yp.stride(0), out.stride(0),
|
| 269 |
+
BR=1, BD=WScatter.BD)
|
| 270 |
+
ctx.mark_dirty(out)
|
| 271 |
+
ctx.save_for_backward(yp, s, t32)
|
| 272 |
+
ctx.D, ctx.sm = D, yp.stride(0)
|
| 273 |
+
return out
|
| 274 |
+
|
| 275 |
+
@staticmethod
|
| 276 |
+
def backward(ctx, dout):
|
| 277 |
+
yp, s, t32 = ctx.saved_tensors
|
| 278 |
+
M, D = yp.shape
|
| 279 |
+
dout = dout.contiguous()
|
| 280 |
+
dyp = torch.empty_like(yp)
|
| 281 |
+
ds = torch.zeros(M, device=yp.device, dtype=torch.float32)
|
| 282 |
+
grid_d = (D + WScatter.BD - 1) // WScatter.BD
|
| 283 |
+
_wscatter_bwd[(M, grid_d)](
|
| 284 |
+
dout, t32, s, yp, dyp, ds, D, dout.stride(0),
|
| 285 |
+
yp.stride(0), BR=1, BD=WScatter.BD)
|
| 286 |
+
return dout, dyp, ds.to(s.dtype), None
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def wscatter(out, yp, s, t):
|
| 290 |
+
"""out [N,D] fp32 pre-zeroed; yp [M,D]; s [M]; t [M] int rows."""
|
| 291 |
+
return WScatter.apply(out, yp, s, t)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
# ---------------------------------------------------------------------------
|
| 295 |
+
if __name__ == "__main__":
|
| 296 |
+
torch.manual_seed(0)
|
| 297 |
+
dev = "cuda"
|
| 298 |
+
E, M, K, N = 32, 9000, 256, 192
|
| 299 |
+
# ragged segment sizes
|
| 300 |
+
counts = torch.multinomial(torch.rand(E), E, replacement=True) * 7
|
| 301 |
+
counts = (counts.float() / counts.sum() * M).long()
|
| 302 |
+
counts[0] += M - counts.sum()
|
| 303 |
+
offs = torch.zeros(E + 1, dtype=torch.int64)
|
| 304 |
+
offs[1:] = torch.cumsum(counts, 0)
|
| 305 |
+
offs = offs.to(dev)
|
| 306 |
+
a = torch.randn(M, K, device=dev, dtype=torch.bfloat16,
|
| 307 |
+
requires_grad=True)
|
| 308 |
+
w = (torch.randn(E, K, N, device=dev, dtype=torch.bfloat16)
|
| 309 |
+
/ (K ** 0.5)).requires_grad_(True)
|
| 310 |
+
|
| 311 |
+
a_ref = a.detach().clone().requires_grad_(True)
|
| 312 |
+
w_ref = w.detach().clone().requires_grad_(True)
|
| 313 |
+
c2 = torch.zeros(M, N, device=dev, dtype=torch.bfloat16)
|
| 314 |
+
for e in range(E):
|
| 315 |
+
s, t = int(offs[e]), int(offs[e + 1])
|
| 316 |
+
if t > s:
|
| 317 |
+
c2[s:t] = a_ref[s:t] @ w_ref[e]
|
| 318 |
+
|
| 319 |
+
c = seg_mm(a, w, offs)
|
| 320 |
+
err = (c.float() - c2.float()).abs().max().item()
|
| 321 |
+
rel = err / (c2.float().abs().max().item() + 1e-9)
|
| 322 |
+
print(f"fwd rel err {rel:.2e}")
|
| 323 |
+
|
| 324 |
+
g = torch.randn_like(c)
|
| 325 |
+
c.backward(g); c2.backward(g)
|
| 326 |
+
da_err = ((a.grad - a_ref.grad).abs().max()
|
| 327 |
+
/ a_ref.grad.abs().max()).item()
|
| 328 |
+
dw_err = ((w.grad - w_ref.grad).abs().max()
|
| 329 |
+
/ w_ref.grad.abs().max()).item()
|
| 330 |
+
print(f"bwd da rel err {da_err:.2e} dw rel err {dw_err:.2e}")
|
| 331 |
+
|
| 332 |
+
# --- gidx gather variant: dense a_src[N,K], gidx[M] picks rows ---
|
| 333 |
+
Nsrc = 4000
|
| 334 |
+
gidx = torch.randint(0, Nsrc, (M,), device=dev)
|
| 335 |
+
a_src = torch.randn(Nsrc, K, device=dev, dtype=torch.bfloat16,
|
| 336 |
+
requires_grad=True)
|
| 337 |
+
w3 = (torch.randn(E, K, N, device=dev, dtype=torch.bfloat16)
|
| 338 |
+
/ (K ** 0.5)).requires_grad_(True)
|
| 339 |
+
a_src_r = a_src.detach().clone().requires_grad_(True)
|
| 340 |
+
w3_r = w3.detach().clone().requires_grad_(True)
|
| 341 |
+
c3 = torch.zeros(M, N, device=dev, dtype=torch.bfloat16)
|
| 342 |
+
for e in range(E):
|
| 343 |
+
s, t = int(offs[e]), int(offs[e + 1])
|
| 344 |
+
if t > s:
|
| 345 |
+
c3[s:t] = a_src_r[gidx[s:t]] @ w3_r[e]
|
| 346 |
+
c4 = seg_mm(a_src, w3, offs, gidx=gidx)
|
| 347 |
+
rel = ((c4.float() - c3.float()).abs().max()
|
| 348 |
+
/ (c3.float().abs().max() + 1e-9)).item()
|
| 349 |
+
print(f"gidx fwd rel err {rel:.2e}")
|
| 350 |
+
g4 = torch.randn_like(c4)
|
| 351 |
+
c4.backward(g4); c3.backward(g4)
|
| 352 |
+
da_err = ((a_src.grad - a_src_r.grad).abs().max()
|
| 353 |
+
/ a_src_r.grad.abs().max()).item()
|
| 354 |
+
dw_err = ((w3.grad - w3_r.grad).abs().max()
|
| 355 |
+
/ w3_r.grad.abs().max()).item()
|
| 356 |
+
print(f"gidx bwd da rel {da_err:.2e} dw rel {dw_err:.2e}")
|
| 357 |
+
|
| 358 |
+
# --- wscatter: out[t[r]] += s[r]*yp[r] vs index_add ---
|
| 359 |
+
Nout = 3000
|
| 360 |
+
yp = torch.randn(M, N, device=dev, dtype=torch.bfloat16,
|
| 361 |
+
requires_grad=True)
|
| 362 |
+
tt = torch.randint(0, Nout, (M,), device=dev)
|
| 363 |
+
sw = torch.rand(M, device=dev, dtype=torch.float32,
|
| 364 |
+
requires_grad=True)
|
| 365 |
+
yp_r = yp.detach().clone().requires_grad_(True)
|
| 366 |
+
sw_r = sw.detach().clone().requires_grad_(True)
|
| 367 |
+
ref = torch.zeros(Nout, N, device=dev, dtype=torch.float32)
|
| 368 |
+
ref.index_add_(0, tt, (yp_r * sw_r[:, None]).float())
|
| 369 |
+
out5 = wscatter(torch.zeros(Nout, N, device=dev, dtype=torch.float32),
|
| 370 |
+
yp, sw, tt)
|
| 371 |
+
rel = ((out5 - ref).abs().max()
|
| 372 |
+
/ (ref.abs().max() + 1e-9)).item()
|
| 373 |
+
print(f"wscatter fwd rel err {rel:.2e}")
|
| 374 |
+
g5 = torch.randn(Nout, N, device=dev, dtype=torch.float32)
|
| 375 |
+
out5.backward(g5); ref.backward(g5)
|
| 376 |
+
dyp_err = ((yp.grad.float() - yp_r.grad.float()).abs().max()
|
| 377 |
+
/ yp_r.grad.float().abs().max()).item()
|
| 378 |
+
ds_err = ((sw.grad - sw_r.grad).abs().max()
|
| 379 |
+
/ (sw_r.grad.abs().max() + 1e-9)).item()
|
| 380 |
+
print(f"wscatter bwd dyp rel {dyp_err:.2e} ds rel {ds_err:.2e}")
|
| 381 |
+
|
| 382 |
+
# speed vs padded bmm
|
| 383 |
+
import time
|
| 384 |
+
def bench(fn, n=50):
|
| 385 |
+
for _ in range(5): fn()
|
| 386 |
+
torch.cuda.synchronize(); t0 = time.time()
|
| 387 |
+
for _ in range(n): fn()
|
| 388 |
+
torch.cuda.synchronize(); return (time.time() - t0) / n * 1e3
|
| 389 |
+
print(f"seg_mm fwd {bench(lambda: seg_mm(a, w, offs)):.3f}ms")
|
| 390 |
+
cap = int(counts.max())
|
| 391 |
+
xp = torch.zeros(E, cap, K, device=dev, dtype=torch.bfloat16)
|
| 392 |
+
print(f"padded bmm {bench(lambda: torch.bmm(xp, w)):.3f}ms")
|
| 393 |
+
print("segmm self-test done")
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|