Upload fused CE helper
Browse files- fused_ce.py +57 -0
fused_ce.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 8 |
+
class FusedCE(torch.autograd.Function):
|
| 9 |
+
@staticmethod
|
| 10 |
+
def forward(ctx, h, W, tgt, vchunk=16384):
|
| 11 |
+
with torch.cuda.amp.autocast(enabled=False):
|
| 12 |
+
hf = h.float()
|
| 13 |
+
Wf = W.float()
|
| 14 |
+
N, d = h.shape
|
| 15 |
+
V = W.shape[0]
|
| 16 |
+
m = torch.full((N,), -1e30, device=h.device, dtype=torch.float32)
|
| 17 |
+
s = torch.zeros(N, device=h.device, dtype=torch.float32)
|
| 18 |
+
zt = torch.zeros(N, device=h.device, dtype=torch.float32)
|
| 19 |
+
for c in range(0, V, vchunk):
|
| 20 |
+
lg = hf @ Wf[c:c+vchunk].T # [N,vchunk] transient only
|
| 21 |
+
cm = lg.max(1).values
|
| 22 |
+
nm = torch.maximum(m, cm)
|
| 23 |
+
s = s * torch.exp(m - nm) + torch.exp(lg - nm[:, None]).sum(1)
|
| 24 |
+
m = nm
|
| 25 |
+
ic = (tgt >= c) & (tgt < c+vchunk)
|
| 26 |
+
if ic.any():
|
| 27 |
+
zt[ic] = lg[ic, tgt[ic] - c].float()
|
| 28 |
+
lse = m + torch.log(s)
|
| 29 |
+
ctx.save_for_backward(h, W, tgt, lse)
|
| 30 |
+
ctx.vchunk = vchunk
|
| 31 |
+
return (lse - zt).mean()
|
| 32 |
+
|
| 33 |
+
@staticmethod
|
| 34 |
+
def backward(ctx, go):
|
| 35 |
+
h, W, tgt, lse = ctx.saved_tensors
|
| 36 |
+
vc = ctx.vchunk
|
| 37 |
+
N, d = h.shape
|
| 38 |
+
V = W.shape[0]
|
| 39 |
+
with torch.cuda.amp.autocast(enabled=False):
|
| 40 |
+
hf = h.float()
|
| 41 |
+
Wc_all = W.float()
|
| 42 |
+
gh = torch.zeros_like(hf)
|
| 43 |
+
gW = torch.zeros(W.shape, device=W.device, dtype=torch.float32)
|
| 44 |
+
sc = float(go) / N
|
| 45 |
+
for c in range(0, V, vc):
|
| 46 |
+
Wc = Wc_all[c:c+vc]
|
| 47 |
+
p = torch.exp(hf @ Wc.T - lse[:, None]) # softmax chunk [N,vchunk]
|
| 48 |
+
ic = (tgt >= c) & (tgt < c+vc)
|
| 49 |
+
if ic.any():
|
| 50 |
+
p[ic, tgt[ic] - c] -= 1.0
|
| 51 |
+
p *= sc
|
| 52 |
+
gh += p @ Wc
|
| 53 |
+
gW[c:c+vc] += p.T @ hf
|
| 54 |
+
return gh.to(h.dtype), gW.to(W.dtype), None, None
|
| 55 |
+
|
| 56 |
+
def fused_ce(h, W, tgt, vchunk=16384):
|
| 57 |
+
return FusedCE.apply(h.reshape(-1, h.size(-1)), W, tgt.reshape(-1), vchunk)
|