Marxist-Leninist commited on
Commit ·
82d098e
0
Parent(s):
AGILLM4-DiffusionBlocks: block-wise AR+SAT+NAT denoising, fused CE, tied heads
Browse files- README.md +42 -0
- dblocks_agillm4.py +82 -0
- dblocks_agillm4_lm.py +110 -0
- fused_ce.py +31 -0
README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AGILLM4-DiffusionBlocks
|
| 2 |
+
|
| 3 |
+
Block-wise (DiffusionBlocks-style) training adapted to the **AGILLM-4** decoder-only
|
| 4 |
+
LLM, with all three AGILLM-4 heads (AR + SAT fixed/variable + NAT) and a set of
|
| 5 |
+
long-context memory upgrades. Adapts & improves on
|
| 6 |
+
[SakanaAI/DiffusionBlocks](https://github.com/SakanaAI/DiffusionBlocks) (ICLR 2026),
|
| 7 |
+
whose released code is ViT/classification only.
|
| 8 |
+
|
| 9 |
+
## What's here
|
| 10 |
+
- `dblocks_agillm4.py` — self-contained DiffusionBlocks prototype (proves the
|
| 11 |
+
per-step 1/B gradient locality on a small transformer).
|
| 12 |
+
- `dblocks_agillm4_lm.py` — real-architecture trainer: reuses AGILLM-4's `Encoder`
|
| 13 |
+
blocks (ALiBi, causal `Block`), partitions 28 layers into B EDM-noise blocks,
|
| 14 |
+
trains one block per step as an EDM denoiser, supervised by:
|
| 15 |
+
* **AR** — causal next-token CE
|
| 16 |
+
* **SAT** — fixed (proj CE over SAT_BLOCK, block-causal mask) **and** variable (gate CE)
|
| 17 |
+
* **NAT** — bidirectional mask-predict CE
|
| 18 |
+
Memory upgrades: EDM-weight clamp, AMP bf16, **tied shared vocab projection**
|
| 19 |
+
(1.21B -> 0.72B params), grad-checkpointed blocks, and `fused_ce`.
|
| 20 |
+
- `fused_ce.py` — fused cross-entropy that streams over the 129k vocab via
|
| 21 |
+
online-softmax with a custom backward, never materializing the `[T x 129280]`
|
| 22 |
+
logit matrix (the DiffusionBlocks "process in chunks" idea applied to the head).
|
| 23 |
+
|
| 24 |
+
## Measured (real AGILLM-4 floor, 28L, 0.72B tied)
|
| 25 |
+
| ctx | full (28L) | one block (7L) |
|
| 26 |
+
|----:|-----------:|---------------:|
|
| 27 |
+
| 1280 | 7.48 GB | 5.53 GB |
|
| 28 |
+
| 4096 | 11.08 GB | 8.68 GB |
|
| 29 |
+
| 8192 | 22.11 GB | 19.37 GB |
|
| 30 |
+
|
| 31 |
+
## Honest findings
|
| 32 |
+
- DiffusionBlocks and gradient-checkpointing are **substitutes** for activation
|
| 33 |
+
memory; with checkpointing on, the 28->7 layer saving is only ~1.1-1.3x.
|
| 34 |
+
- The big fixed-cost win was **tied heads + fused CE** (ctx-1280 ~24 GB -> ~5.5 GB).
|
| 35 |
+
- The long-context ceiling (16k+) is **attention-bound**, so the next lever is the
|
| 36 |
+
sublinear attention backend, not more block/CE work.
|
| 37 |
+
|
| 38 |
+
Status: validated research prototype. The official AGILLM-4 training line remains the
|
| 39 |
+
proven AR/SAT/NAT end-to-end trainer; these wins (tied heads, fused-CE, sublinear
|
| 40 |
+
attention) are intended to be folded into it for long context.
|
| 41 |
+
|
| 42 |
+
License: Apache-2.0 (matching the upstream method).
|
dblocks_agillm4.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""DiffusionBlocks prototype for AGILLM-4 (block-wise denoising training).
|
| 2 |
+
|
| 3 |
+
Faithful to SakanaAI/DiffusionBlocks: EDM (Karras) diffusion, equi-probability
|
| 4 |
+
block sigma partitioning, and the key property -- each step trains ONE block as a
|
| 5 |
+
denoiser for its noise range, so backprop touches only that block => training
|
| 6 |
+
memory ~ 1/B of end-to-end. Here we reuse a transformer-block stack (stand-in for
|
| 7 |
+
AGILLM-4's Encoder.blocks: same pre-norm residual Block API x = blk(x, mask)).
|
| 8 |
+
"""
|
| 9 |
+
import math, numpy as np, torch, torch.nn as nn, torch.nn.functional as F
|
| 10 |
+
def _cdf(x): return 0.5*(1+math.erf(x/math.sqrt(2)))
|
| 11 |
+
def _ppf(p): return float(torch.erfinv(torch.tensor(2*p-1.0))*math.sqrt(2))
|
| 12 |
+
|
| 13 |
+
# ---- EDM block partitioning (verbatim mechanism from dblock_modules.py) ----
|
| 14 |
+
def get_block_sigmas(num_blocks, sigma_min=0.002, sigma_max=80.0, p_mean=-1.2, p_std=1.2):
|
| 15 |
+
cdf_min = _cdf((np.log(sigma_min)-p_mean)/p_std)
|
| 16 |
+
cdf_max = _cdf((np.log(sigma_max)-p_mean)/p_std)
|
| 17 |
+
out=[]
|
| 18 |
+
for i in range(num_blocks+1):
|
| 19 |
+
p = cdf_min + (cdf_max-cdf_min)*(i/num_blocks)
|
| 20 |
+
out.append(float(np.exp(p_mean+p_std*_ppf(p))))
|
| 21 |
+
return out # descending->ascending sigma boundaries, equal CDF mass per block
|
| 22 |
+
|
| 23 |
+
class DBlockLM(nn.Module):
|
| 24 |
+
"""Shared token emb + output proj; n_layers split into B independent blocks."""
|
| 25 |
+
def __init__(self, vocab=2048, d=256, n_layers=8, heads=4, num_blocks=4):
|
| 26 |
+
super().__init__()
|
| 27 |
+
self.emb = nn.Embedding(vocab, d)
|
| 28 |
+
self.blocks = nn.ModuleList([nn.TransformerEncoderLayer(d, heads, 4*d,
|
| 29 |
+
batch_first=True, norm_first=True) for _ in range(n_layers)])
|
| 30 |
+
self.ln = nn.LayerNorm(d); self.out = nn.Linear(d, vocab)
|
| 31 |
+
self.num_blocks = num_blocks
|
| 32 |
+
split = n_layers // num_blocks
|
| 33 |
+
self.assign = [list(range(i*split,(i+1)*split)) for i in range(num_blocks)]
|
| 34 |
+
self.block_sigmas = get_block_sigmas(num_blocks)
|
| 35 |
+
self.sigma_data = 0.5
|
| 36 |
+
|
| 37 |
+
def run_block(self, b, zt, sigma):
|
| 38 |
+
# EDM preconditioning (Karras 2022), exactly as DiffusionBlocks.denoise()
|
| 39 |
+
s = sigma[:,None,None]
|
| 40 |
+
c_skip = self.sigma_data**2/(s**2+self.sigma_data**2)
|
| 41 |
+
c_out = s*self.sigma_data/(s**2+self.sigma_data**2)**0.5
|
| 42 |
+
c_in = 1/(s**2+self.sigma_data**2)**0.5
|
| 43 |
+
h = zt*c_in
|
| 44 |
+
for li in self.assign[b]: # <-- ONLY this block's layers run
|
| 45 |
+
h = self.blocks[li](h)
|
| 46 |
+
return c_skip*zt + c_out*h # denoiser D_theta(zt,sigma) -> predicts z0
|
| 47 |
+
|
| 48 |
+
def edm_weight(sigma, sigma_data=0.5):
|
| 49 |
+
return (sigma**2+sigma_data**2)/(sigma*sigma_data)**2
|
| 50 |
+
|
| 51 |
+
def train_step(model, opt, ids):
|
| 52 |
+
z0 = F.normalize(model.emb(ids), p=2, dim=-1).detach() # clean target embeds
|
| 53 |
+
b = np.random.randint(model.num_blocks) # pick one block
|
| 54 |
+
lo, hi = model.block_sigmas[b], model.block_sigmas[b+1]
|
| 55 |
+
lo, hi = min(lo,hi), max(lo,hi)
|
| 56 |
+
sigma = torch.from_numpy(np.exp(np.random.uniform(np.log(lo),np.log(hi),
|
| 57 |
+
size=ids.shape[0])).astype('float32'))
|
| 58 |
+
zt = z0 + sigma[:,None,None]*torch.randn_like(z0)
|
| 59 |
+
D = model.run_block(b, zt, sigma) # backprop only block b
|
| 60 |
+
loss = (edm_weight(sigma)[:,None,None]*(D-z0)**2).mean()
|
| 61 |
+
opt.zero_grad(set_to_none=True); loss.backward(); opt.step()
|
| 62 |
+
return b, loss.item()
|
| 63 |
+
|
| 64 |
+
if __name__ == "__main__":
|
| 65 |
+
torch.manual_seed(0)
|
| 66 |
+
NB=4; m=DBlockLM(num_blocks=NB); opt=torch.optim.AdamW(m.parameters(),1e-3)
|
| 67 |
+
ids=torch.randint(0,2048,(8,64))
|
| 68 |
+
print("layers:",len(m.blocks),"| blocks:",NB,"| layer assignment:",m.assign)
|
| 69 |
+
print("block sigma boundaries:",[round(s,3) for s in m.block_sigmas])
|
| 70 |
+
# one step, then verify ONLY the chosen block's layer params got gradients
|
| 71 |
+
b,loss=train_step(m,opt,ids)
|
| 72 |
+
def has_grad(mod): return any(p.grad is not None and p.grad.abs().sum()>0 for p in mod.parameters())
|
| 73 |
+
grad_blocks=[i for i in range(NB) if any(has_grad(m.blocks[li]) for li in m.assign[i])]
|
| 74 |
+
tot=sum(p.numel() for p in m.parameters())
|
| 75 |
+
blk_params=sum(p.numel() for li in m.assign[b] for p in m.blocks[li].parameters())
|
| 76 |
+
print(f"\\nstep trained block {b} loss={loss:.4f}")
|
| 77 |
+
print("blocks whose layers received gradients:",grad_blocks,"(expect just",[b],")")
|
| 78 |
+
print(f"layer-params with grad this step: {blk_params:,} / {sum(p.numel() for blk in m.blocks for p in blk.parameters()):,}"
|
| 79 |
+
f" ({100*blk_params/sum(p.numel() for blk in m.blocks for p in blk.parameters()):.0f}% = ~1/{NB})")
|
| 80 |
+
print("\\n--- a few more steps (each trains one block independently) ---")
|
| 81 |
+
for _ in range(6):
|
| 82 |
+
b,loss=train_step(m,opt,ids); print(f" block {b}: loss={loss:.4f}")
|
dblocks_agillm4_lm.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""AR + SAT(fixed+variable) + NAT DiffusionBlocks for AGILLM-4 (v3, long-context).
|
| 2 |
+
|
| 3 |
+
Upgrades over v2:
|
| 4 |
+
* SAT fixed (proj CE over SAT_BLOCK, block-causal sat_mask) AND variable (gate CE)
|
| 5 |
+
* tied shared vocab projection (emb.weight) across AR/SAT/NAT -> drops ~0.5B params
|
| 6 |
+
* chunked cut-CE: logits computed per token-chunk on a detached hidden, backward
|
| 7 |
+
incrementally, then ONE block backward -> [T x 129280] never fully materialized
|
| 8 |
+
* sequential per-objective backward (one block-forward graph live at a time)
|
| 9 |
+
* AMP bf16 + clamped EDM weight (stable)
|
| 10 |
+
"""
|
| 11 |
+
import sys, math, argparse, numpy as np, torch, torch.nn as nn, torch.nn.functional as F
|
| 12 |
+
import torch.utils.checkpoint as _ck
|
| 13 |
+
from fused_ce import fused_ce
|
| 14 |
+
sys.path.insert(0,"/workspace/agillm-4"); import nB300_agillm4 as M
|
| 15 |
+
V=M.VOCAB; SATB=M.SAT_BLOCK; EMIT=getattr(M,"EMIT_LAMBDA",0.1); SD=0.5
|
| 16 |
+
def _cdf(x): return 0.5*(1+math.erf(x/math.sqrt(2)))
|
| 17 |
+
def _ppf(p): return float(torch.erfinv(torch.tensor(2*p-1.0))*math.sqrt(2))
|
| 18 |
+
def bsig(B,smin=0.002,smax=80.0,pm=-1.2,ps=1.2):
|
| 19 |
+
a,b=_cdf((math.log(smin)-pm)/ps),_cdf((math.log(smax)-pm)/ps)
|
| 20 |
+
return [float(np.exp(pm+ps*_ppf(a+(b-a)*(i/B)))) for i in range(B+1)]
|
| 21 |
+
def edm_pre(s): s=s[:,None,None]; return SD**2/(s**2+SD**2), s*SD/(s**2+SD**2)**0.5, 1/(s**2+SD**2)**0.5
|
| 22 |
+
def edm_w(s,wmax=5.0): return float(((s**2+SD**2)/(s*SD)**2).clamp(max=wmax).mean())
|
| 23 |
+
|
| 24 |
+
def make(B):
|
| 25 |
+
cfg=M.PRESETS["agillm4_floor"].copy()
|
| 26 |
+
core=M.Encoder(cfg, attn_backend="sdpa", grad_checkpoint=False).to(M.DEV)
|
| 27 |
+
sat_gate=nn.Linear(cfg["d"],2).to(M.DEV) # SAT *variable* gate (tiny)
|
| 28 |
+
L=cfg["layers"]; sp=L//B
|
| 29 |
+
asg=[list(range(i*sp,(i+1)*sp)) for i in range(B)]; asg[-1]=list(range((B-1)*sp,L))
|
| 30 |
+
return core,sat_gate,asg,bsig(B)
|
| 31 |
+
|
| 32 |
+
def runblk(core,h,layers,mask):
|
| 33 |
+
for li in layers: h=_ck.checkpoint(core.blocks[li], h, mask, use_reentrant=False) # recompute in backward
|
| 34 |
+
return h
|
| 35 |
+
|
| 36 |
+
def cut_ce_backward(core, hidden, targets, scale, chunk=1024, mask_pos=None):
|
| 37 |
+
"""chunked CE on detached hidden -> grad; caller backprops block once."""
|
| 38 |
+
hd=hidden.detach().requires_grad_(True); T=hd.size(1)
|
| 39 |
+
if mask_pos is None: N=targets.numel()
|
| 40 |
+
else: N=int(mask_pos.sum().item()) or 1
|
| 41 |
+
tot=0.0
|
| 42 |
+
for s in range(0,T,chunk):
|
| 43 |
+
lg=F.linear(hd[:,s:s+chunk], core.emb.weight).float() # tied proj, transient
|
| 44 |
+
tg=targets[:,s:s+chunk]
|
| 45 |
+
if mask_pos is None:
|
| 46 |
+
l=F.cross_entropy(lg.reshape(-1,V),tg.reshape(-1),reduction="sum")*scale/N
|
| 47 |
+
else:
|
| 48 |
+
mm=mask_pos[:,s:s+chunk]
|
| 49 |
+
if not bool(mm.any()): continue
|
| 50 |
+
l=F.cross_entropy(lg[mm],tg[mm],reduction="sum")*scale/N
|
| 51 |
+
l.backward(); tot+=float(l.detach())
|
| 52 |
+
return tot, hd.grad
|
| 53 |
+
|
| 54 |
+
def step(core,sat_gate,ids,layers,lo,hi,nat_ratio=0.5,bw=True):
|
| 55 |
+
T=ids.size(1); ar=sat=nat=0.0
|
| 56 |
+
sig=torch.from_numpy(np.exp(np.random.uniform(math.log(lo),math.log(hi),ids.size(0))).astype("float32")).to(M.DEV)
|
| 57 |
+
cs,co,ci=edm_pre(sig); w=edm_w(sig)
|
| 58 |
+
# ---- AR: causal diffusion denoise ----
|
| 59 |
+
with torch.autocast("cuda",dtype=torch.bfloat16):
|
| 60 |
+
emb=core.emb(ids); zt=emb+sig[:,None,None]*torch.randn_like(emb)
|
| 61 |
+
Dn=core.ln(cs*zt+co*runblk(core,ci*zt,layers,M.causal_mask(T)))
|
| 62 |
+
ar_loss=w*fused_ce(Dn[:,:-1].contiguous(), core.emb.weight, ids[:,1:].contiguous())
|
| 63 |
+
if bw: ar_loss.backward()
|
| 64 |
+
ar=float(ar_loss.detach())
|
| 65 |
+
# ---- SAT: block-causal diffusion; fixed proj + variable gate ----
|
| 66 |
+
with torch.autocast("cuda",dtype=torch.bfloat16):
|
| 67 |
+
zt2=core.emb(ids)+sig[:,None,None]*torch.randn_like(emb)
|
| 68 |
+
Ds=core.ln(cs*zt2+co*runblk(core,ci*zt2,layers,M.sat_mask(T)))
|
| 69 |
+
last=Ds[:,-SATB:]
|
| 70 |
+
satf=F.cross_entropy(F.linear(last,core.emb.weight).float().reshape(-1,V), ids[:,1:SATB+1].reshape(-1))
|
| 71 |
+
satv=EMIT*F.cross_entropy(sat_gate(Ds[:,0].float()), torch.ones(ids.size(0),dtype=torch.long,device=M.DEV))
|
| 72 |
+
sat_loss=w*(satf+satv)
|
| 73 |
+
if bw: sat_loss.backward()
|
| 74 |
+
sat=float(sat_loss)
|
| 75 |
+
# ---- NAT: bidirectional mask-predict ----
|
| 76 |
+
with torch.autocast("cuda",dtype=torch.bfloat16):
|
| 77 |
+
nat_ids=ids.clone(); m=torch.rand(ids.shape,device=M.DEV)<nat_ratio
|
| 78 |
+
if not bool(m.any()): m[...,-1]=True
|
| 79 |
+
nat_ids[m]=M.BLANK
|
| 80 |
+
Dnat=core.ln(runblk(core,core.emb(nat_ids),layers,None))
|
| 81 |
+
nat_loss=fused_ce(Dnat[m], core.emb.weight, ids[m])
|
| 82 |
+
if bw: nat_loss.backward()
|
| 83 |
+
nat=float(nat_loss.detach())
|
| 84 |
+
return ar,sat,nat
|
| 85 |
+
|
| 86 |
+
def peak(fn):
|
| 87 |
+
torch.cuda.empty_cache(); torch.cuda.reset_peak_memory_stats(); fn(); torch.cuda.synchronize()
|
| 88 |
+
return torch.cuda.max_memory_allocated()/1e9
|
| 89 |
+
|
| 90 |
+
if __name__=="__main__":
|
| 91 |
+
ap=argparse.ArgumentParser(); ap.add_argument("--blocks",type=int,default=4); a=ap.parse_args()
|
| 92 |
+
core,sg,asg,bs=make(a.blocks); nL=len(core.blocks); bi=a.blocks//2
|
| 93 |
+
P=sum(p.numel() for p in core.parameters())+sum(p.numel() for p in sg.parameters())
|
| 94 |
+
print(f"AGILLM-4 floor {nL}L, {P/1e9:.2f}B params (tied heads) | blocks={a.blocks} assign={asg}")
|
| 95 |
+
def full(ctx): step(core,sg,torch.randint(0,V,(1,ctx),device=M.DEV),list(range(nL)),bs[0],bs[-1]); core.zero_grad(set_to_none=True); sg.zero_grad(set_to_none=True)
|
| 96 |
+
def blk(ctx): step(core,sg,torch.randint(0,V,(1,ctx),device=M.DEV),asg[bi],bs[bi],bs[bi+1]); core.zero_grad(set_to_none=True); sg.zero_grad(set_to_none=True)
|
| 97 |
+
print("\\n=== v3: tied-heads + cut-CE + AMP + SAT(fixed+var)+NAT ===")
|
| 98 |
+
for ctx in (1280,4096,8192,16384):
|
| 99 |
+
try: mf=peak(lambda:full(ctx)); sf=f"{mf:.2f} GB"
|
| 100 |
+
except RuntimeError as e: torch.cuda.empty_cache(); sf=("OOM" if "out of memory" in str(e).lower() else "ERR")
|
| 101 |
+
try: mb=peak(lambda:blk(ctx)); sb=f"{mb:.2f} GB"
|
| 102 |
+
except RuntimeError as e: torch.cuda.empty_cache(); sb=("OOM" if "out of memory" in str(e).lower() else "ERR")
|
| 103 |
+
print(f" ctx={ctx:>6}: full(28L)={sf:>9} one block({len(asg[bi])}L)={sb:>9}")
|
| 104 |
+
print("\\n--- smoke train (block-wise AR+SAT(fixed+var)+NAT) ---")
|
| 105 |
+
for st in range(4):
|
| 106 |
+
b=np.random.randint(a.blocks)
|
| 107 |
+
ar,sat,nat=step(core,sg,torch.randint(0,V,(1,1280),device=M.DEV),asg[b],bs[b],bs[b+1])
|
| 108 |
+
opt=torch.optim.SGD([p for li in asg[b] for p in core.blocks[li].parameters()]+list(sg.parameters()),1e-3)
|
| 109 |
+
opt.step(); opt.zero_grad(); core.zero_grad(set_to_none=True)
|
| 110 |
+
print(f" step {st} block {b}: AR={ar:.2f} SAT={sat:.2f} NAT={nat:.2f}")
|
fused_ce.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Fused cross-entropy: streams over the VOCAB dimension (online-softmax) so the
|
| 2 |
+
[N x V] logit matrix is NEVER materialized -- only [N x vchunk]. Custom backward
|
| 3 |
+
recomputes softmax per vocab-chunk (grad = softmax - onehot). This is the
|
| 4 |
+
DiffusionBlocks 'process in chunks, don't hold the whole thing' idea applied to
|
| 5 |
+
the output head instead of network depth."""
|
| 6 |
+
import torch
|
| 7 |
+
class FusedCE(torch.autograd.Function):
|
| 8 |
+
@staticmethod
|
| 9 |
+
def forward(ctx, h, W, tgt, vchunk=16384):
|
| 10 |
+
N,d=h.shape; V=W.shape[0]; hf=h.float()
|
| 11 |
+
m=torch.full((N,),-1e30,device=h.device); s=torch.zeros(N,device=h.device); zt=torch.zeros(N,device=h.device)
|
| 12 |
+
for c in range(0,V,vchunk):
|
| 13 |
+
lg=hf@W[c:c+vchunk].float().T # [N,vchunk] transient only
|
| 14 |
+
cm=lg.max(1).values; nm=torch.maximum(m,cm)
|
| 15 |
+
s=s*torch.exp(m-nm)+torch.exp(lg-nm[:,None]).sum(1); m=nm
|
| 16 |
+
ic=(tgt>=c)&(tgt<c+vchunk)
|
| 17 |
+
if ic.any(): zt[ic]=lg[ic,tgt[ic]-c]
|
| 18 |
+
lse=m+torch.log(s); ctx.save_for_backward(h,W,tgt,lse); ctx.vchunk=vchunk
|
| 19 |
+
return (lse-zt).mean()
|
| 20 |
+
@staticmethod
|
| 21 |
+
def backward(ctx, go):
|
| 22 |
+
h,W,tgt,lse=ctx.saved_tensors; vc=ctx.vchunk; N,d=h.shape; V=W.shape[0]; hf=h.float()
|
| 23 |
+
gh=torch.zeros_like(hf); gW=torch.zeros(W.shape,device=W.device,dtype=torch.float32); sc=float(go)/N
|
| 24 |
+
for c in range(0,V,vc):
|
| 25 |
+
Wc=W[c:c+vc].float(); p=torch.exp(hf@Wc.T-lse[:,None]) # softmax chunk [N,vchunk]
|
| 26 |
+
ic=(tgt>=c)&(tgt<c+vc)
|
| 27 |
+
if ic.any(): p[ic,tgt[ic]-c]-=1.0
|
| 28 |
+
p*=sc; gh+=p@Wc; gW[c:c+vc]+=p.T@hf
|
| 29 |
+
return gh.to(h.dtype), gW.to(W.dtype), None, None
|
| 30 |
+
def fused_ce(h, W, tgt, vchunk=16384):
|
| 31 |
+
return FusedCE.apply(h.reshape(-1,h.size(-1)), W, tgt.reshape(-1), vchunk)
|