Download dit.py from Cyronius/sprite-diffusion: direct link, hf CLI and curl.
- Browser
- Download file 5.25 kB
-
https://huggingface.co/Cyronius/sprite-diffusion/resolve/main/dit.py
- Command line
-
hf download hf://Cyronius/sprite-diffusion/dit.py
-
curl -L -o dit.py https://huggingface.co/Cyronius/sprite-diffusion/resolve/main/dit.py
5.25 kB
| # Klein-style single-stream DiT for 32x32 RGBA sprites, shared by train.py and prof_train.py; 2026-10-17. | |
| import math | |
| import torch, torch.nn as nn, torch.nn.functional as F | |
| HEAD = 128 | |
| NCAT, NFP = 8, 3 | |
| # prof_train.py flips these to measure them; 'off' settings exist only for timing. | |
| FAST = {'norm': True, 'rope': 'stack', 'qknorm': True, 'joint_qk': False, 'addcmul': False} | |
| class RMSNorm(nn.Module): | |
| def __init__(s, d): | |
| super().__init__(); s.w = nn.Parameter(torch.ones(d)) | |
| def forward(s, x): | |
| if FAST['norm']: | |
| return F.rms_norm(x, (x.shape[-1],), s.w, 1e-6) | |
| return x * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + 1e-6).to(x.dtype) * s.w | |
| def rope_table(side): | |
| # Half of each head encodes the row, half the column (2D RoPE, as in klein). | |
| f = 1.0 / (10000 ** (torch.arange(0, HEAD // 4) / (HEAD // 4))) | |
| ys, xs = torch.meshgrid(torch.arange(side), torch.arange(side), indexing='ij') | |
| ang = torch.cat([ys.reshape(-1, 1) * f, xs.reshape(-1, 1) * f], 1) | |
| return torch.cos(ang), torch.sin(ang) | |
| def rope(x, cos, sin): | |
| if FAST['rope'] == 'off': | |
| return x | |
| if FAST['rope'] == 'half': | |
| # Rotate-half pairing (dim i with i+64): fewer passes over q/k than the interleaved form. | |
| c, s_ = torch.cat([cos, cos], -1).to(x.dtype), torch.cat([sin, sin], -1).to(x.dtype) | |
| x1, x2 = x.chunk(2, -1) | |
| return x * c + torch.cat([-x2, x1], -1) * s_ | |
| if FAST['rope'] == 'stack': | |
| a, b = x.unflatten(-1, (-1, 2)).unbind(-1) | |
| cos, sin = cos.to(x.dtype), sin.to(x.dtype) | |
| return torch.stack([a * cos - b * sin, a * sin + b * cos], -1).flatten(-2) | |
| a, b = x.float().unflatten(-1, (-1, 2)).unbind(-1) | |
| return torch.stack([a * cos - b * sin, a * sin + b * cos], -1).flatten(-2).to(x.dtype) | |
| class Block(nn.Module): | |
| def __init__(s, h): | |
| super().__init__() | |
| s.h, s.nh, s.mlp = h, h // HEAD, 3 * h | |
| s.norm = nn.LayerNorm(h, elementwise_affine=False, eps=1e-6) | |
| s.mod = nn.Linear(h, 3 * h) | |
| s.lin1 = nn.Linear(h, 3 * h + 2 * s.mlp) | |
| s.qn, s.kn = RMSNorm(HEAD), RMSNorm(HEAD) | |
| s.lin2 = nn.Linear(h + s.mlp, h) | |
| nn.init.zeros_(s.mod.weight); nn.init.zeros_(s.mod.bias) | |
| def forward(s, x, c, cos, sin): | |
| B, T, _ = x.shape | |
| sh, sc, g = s.mod(F.silu(c)).unsqueeze(1).chunk(3, -1) | |
| xm = torch.addcmul(sh, s.norm(x), 1 + sc) if FAST['addcmul'] else s.norm(x) * (1 + sc) + sh | |
| qkv, a, b = s.lin1(xm).split([3 * s.h, s.mlp, s.mlp], -1) | |
| qkv = qkv.view(B, T, 3, s.nh, HEAD).permute(2, 0, 3, 1, 4) | |
| v = qkv[2] | |
| if FAST['joint_qk']: | |
| qk = qkv[:2] | |
| if FAST['qknorm']: | |
| qk = F.rms_norm(qk, (HEAD,), eps=1e-6) * torch.stack([s.qn.w, s.kn.w]).view(2, 1, 1, 1, HEAD).to(qk.dtype) | |
| q, k = rope(qk, cos, sin).unbind(0) | |
| else: | |
| q, k = qkv[0], qkv[1] | |
| if FAST['qknorm']: | |
| q, k = s.qn(q), s.kn(k) | |
| q, k = rope(q, cos, sin), rope(k, cos, sin) | |
| o = F.scaled_dot_product_attention(q, k, v).transpose(1, 2).reshape(B, T, s.h) | |
| out = s.lin2(torch.cat([o, F.silu(a) * b], -1)) | |
| return torch.addcmul(x, g, out) if FAST['addcmul'] else x + g * out | |
| class DiT(nn.Module): | |
| def __init__(s, h, depth, patch, vocab=0): | |
| super().__init__() | |
| s.p, s.side = patch, 32 // patch | |
| s.inp = nn.Linear(4 * patch * patch, h) | |
| s.t1, s.t2 = nn.Linear(256, h), nn.Linear(h, h) | |
| s.cat = nn.Embedding(NCAT + 1, h) # last index = no label | |
| s.fp = nn.Embedding(NFP + 1, h) | |
| if vocab: # caption words: learned embeddings, mean-pooled, 0 = padding/none | |
| s.wemb = nn.Embedding(vocab, h, padding_idx=0) | |
| s.wproj = nn.Sequential(nn.Linear(h, h), nn.SiLU(), nn.Linear(h, h)) | |
| s.blocks = nn.ModuleList([Block(h) for _ in range(depth)]) | |
| s.fnorm = nn.LayerNorm(h, elementwise_affine=False, eps=1e-6) | |
| s.fmod = nn.Linear(h, 2 * h) | |
| s.outp = nn.Linear(h, 4 * patch * patch) | |
| for m in (s.fmod, s.outp): | |
| nn.init.zeros_(m.weight); nn.init.zeros_(m.bias) | |
| cos, sin = rope_table(s.side) | |
| s.register_buffer('cos', cos, persistent=False); s.register_buffer('sin', sin, persistent=False) | |
| def forward(s, x, t, cat, fp, txt=None): | |
| B, p, n = x.shape[0], s.p, s.side | |
| x = x.unfold(2, p, p).unfold(3, p, p).permute(0, 2, 3, 1, 4, 5).reshape(B, n * n, 4 * p * p) | |
| half = torch.exp(-math.log(10000) * torch.arange(128, device=x.device) / 128) | |
| te = torch.cat([torch.cos(t[:, None] * 1000 * half), torch.sin(t[:, None] * 1000 * half)], -1) | |
| c = s.t2(F.silu(s.t1(te))) + s.cat(cat) + s.fp(fp) | |
| if txt is not None: | |
| m = (txt > 0).to(c.dtype) | |
| e = (s.wemb(txt) * m[..., None]).sum(1) / m.sum(1, keepdim=True).clamp(min=1) | |
| c = c + s.wproj(e) * (m.sum(1, keepdim=True) > 0) | |
| x = s.inp(x) | |
| for blk in s.blocks: | |
| x = blk(x, c, s.cos, s.sin) | |
| sh, sc = s.fmod(F.silu(c)).unsqueeze(1).chunk(2, -1) | |
| x = s.outp(s.fnorm(x) * (1 + sc) + sh) | |
| return x.view(B, n, n, 4, p, p).permute(0, 3, 1, 4, 2, 5).reshape(B, 4, 32, 32) | |