Spaces:
Running on Zero
Running on Zero
Download pathrepl/thinker.py from JohanDL/sudoku-by-drawing: direct link, hf CLI and curl.
- Browser
- Download file 6.87 kB
-
https://huggingface.co/spaces/JohanDL/sudoku-by-drawing/resolve/main/pathrepl/thinker.py
- Command line
-
hf download hf://spaces/JohanDL/sudoku-by-drawing/pathrepl/thinker.py
-
curl -L -o thinker.py https://huggingface.co/spaces/JohanDL/sudoku-by-drawing/resolve/main/pathrepl/thinker.py
6.87 kB
| """The Thinker: condition encoder -> TRM recursion over tokens -> decoder -> ControlNet adapters into a frozen Painter. | |
| TRM recursion (Jolicoeur-Martineau, 2025): | |
| deep_recursion: H cycles, the first H-1 without grad; each cycle does | |
| L times z <- net(z, y + c) (latent update, input c supplied) | |
| once y <- net(y, z) (answer update) | |
| deep supervision: N_sup calls of deep_recursion, (y, z) detached in between. | |
| """ | |
| import math | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from .unet import UNet, timestep_embedding | |
| def rms_norm(x, eps=1e-5): | |
| return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + eps) | |
| def rope(x, cos, sin): | |
| x1, x2 = x.chunk(2, -1) | |
| return torch.cat([x1 * cos - x2 * sin, x2 * cos + x1 * sin], -1) | |
| class Block(nn.Module): | |
| """Post-norm transformer block (attention + SwiGLU), as in TRM.""" | |
| def __init__(self, dim, heads, expansion): | |
| super().__init__() | |
| self.heads = heads | |
| self.qkv, self.o = nn.Linear(dim, 3 * dim, bias=False), nn.Linear(dim, dim, bias=False) | |
| inter = 256 * math.ceil(round(expansion * dim * 2 / 3) / 256) | |
| self.gate_up, self.down = nn.Linear(dim, 2 * inter, bias=False), nn.Linear(inter, dim, bias=False) | |
| def forward(self, x, cos, sin): | |
| B, N, D = x.shape | |
| q, k, v = self.qkv(x).view(B, N, 3, self.heads, D // self.heads).permute(2, 0, 3, 1, 4) | |
| q, k = rope(q, cos, sin), rope(k, cos, sin) | |
| a = F.scaled_dot_product_attention(q, k, v).transpose(1, 2).reshape(B, N, D) | |
| x = rms_norm(x + self.o(a)) | |
| g, u = self.gate_up(x).chunk(2, -1) | |
| return rms_norm(x + self.down(F.silu(g) * u)) | |
| class Net(nn.Module): | |
| """The single weight-tied network used for both the z- and y-updates: net(h, injection).""" | |
| def __init__(self, dim=512, heads=8, layers=2, expansion=4, seq_len=81): | |
| super().__init__() | |
| self.blocks = nn.ModuleList([Block(dim, heads, expansion) for _ in range(layers)]) | |
| hd = dim // heads | |
| inv = 1.0 / (10000 ** (torch.arange(0, hd, 2).float() / hd)) | |
| ang = torch.outer(torch.arange(seq_len).float(), inv) | |
| self.register_buffer("cos", ang.cos(), persistent=False) | |
| self.register_buffer("sin", ang.sin(), persistent=False) | |
| def forward(self, h, injection): | |
| h = h + injection | |
| for b in self.blocks: | |
| h = b(h, self.cos, self.sin) | |
| return h | |
| class Encoder(nn.Module): | |
| """concat(x_t, cond, mask) -> conv stack -> avg-pool to grid x grid tokens -> dim, plus timestep embedding.""" | |
| def __init__(self, dim=512, hidden=(128, 256, 256), grid=9, in_ch=3): | |
| super().__init__() | |
| layers, c = [], in_ch | |
| for i, h in enumerate(hidden): | |
| layers += [nn.Conv2d(c, h, 3, stride=1 if i == 0 else 2, padding=1), nn.SiLU()] | |
| c = h | |
| self.conv, self.grid = nn.Sequential(*layers), grid | |
| self.proj = nn.Linear(c, dim) | |
| self.t = nn.Sequential(nn.Linear(dim, dim), nn.SiLU(), nn.Linear(dim, dim)) | |
| def forward(self, xt, t, cond, mask): | |
| h = F.adaptive_avg_pool2d(self.conv(torch.cat([xt, cond, mask], 1)), self.grid) | |
| tok = self.proj(h.flatten(2).transpose(1, 2)) | |
| return tok + self.t(timestep_embedding(t, tok.shape[-1]))[:, None] | |
| class Decoder(nn.Module): | |
| """y tokens -> image-resolution feature map -> one zero-initialised 1x1 adapter per Painter skip (+ mid).""" | |
| def __init__(self, painter: UNet, dim=512, grid=9, img=144, ch=32): | |
| super().__init__() | |
| self.grid, self.img = grid, img | |
| self.proj = nn.Conv2d(dim, 2 * ch, 1) | |
| self.up = nn.Sequential(nn.ConvTranspose2d(2 * ch, 2 * ch, 4, stride=4), nn.SiLU(), | |
| nn.ConvTranspose2d(2 * ch, ch, 2, stride=2), nn.SiLU(), | |
| nn.ConvTranspose2d(ch, ch, 2, stride=2), nn.SiLU(), | |
| nn.Conv2d(ch, ch, 3, padding=1), nn.SiLU()) | |
| def zero(c): | |
| conv = nn.Conv2d(ch, c, 1) | |
| nn.init.zeros_(conv.weight), nn.init.zeros_(conv.bias) | |
| return conv | |
| self.specs = painter.skip_specs | |
| self.adapters = nn.ModuleList([zero(c) for c, _ in self.specs]) | |
| self.mid_factor = painter.mid_spec[1] | |
| self.mid_adapter = zero(painter.mid_spec[0]) | |
| def forward(self, y): | |
| B, N, D = y.shape | |
| h = self.proj(y.transpose(1, 2).reshape(B, D, self.grid, self.grid)) | |
| h = self.up(h) # 16x upsample | |
| if h.shape[-1] != self.img: # token grids that do not divide the image (paper App. E) | |
| h = F.interpolate(h, size=(self.img, self.img), mode="bilinear", align_corners=False) | |
| pool = lambda f: F.avg_pool2d(h, f) if f > 1 else h | |
| skips = [a(pool(f)) for a, (_, f) in zip(self.adapters, self.specs)] | |
| return skips, self.mid_adapter(pool(self.mid_factor)) | |
| class Thinker(nn.Module): | |
| """Encoder + TRM net + decoder. The Painter is passed in at call time and never owned (stays frozen).""" | |
| def __init__(self, painter, dim=512, heads=8, layers=2, expansion=4, grid=9, | |
| enc_hidden=(128, 256, 256), H=3, L=6, img=144): | |
| super().__init__() | |
| self.H, self.L = H, L | |
| self.encoder = Encoder(dim, enc_hidden, grid) | |
| self.net = Net(dim, heads, layers, expansion, grid * grid) | |
| self.decoder = Decoder(painter, dim, grid, img) | |
| self.register_buffer("y_init", nn.init.trunc_normal_(torch.empty(dim), std=1.0)) | |
| self.register_buffer("z_init", nn.init.trunc_normal_(torch.empty(dim), std=1.0)) | |
| def init_carry(self, c): | |
| return self.y_init.expand_as(c).clone(), self.z_init.expand_as(c).clone() | |
| def deep_recursion(self, c, y, z): | |
| with torch.no_grad(): | |
| for _ in range(self.H - 1): | |
| for _ in range(self.L): | |
| z = self.net(z, y + c) | |
| y = self.net(y, z) | |
| for _ in range(self.L): | |
| z = self.net(z, y + c) | |
| y = self.net(y, z) | |
| return (y.detach(), z.detach()), y | |
| def residuals(self, y): | |
| return self.decoder(y) | |
| def think(thinker, xt, t, cond, mask, carry=None, n_sup=1): | |
| """Run n_sup supervision steps (no grad except the last). Returns (Painter residuals, carry).""" | |
| c = thinker.encoder(xt, t, cond, mask) | |
| y, z = carry if carry is not None else thinker.init_carry(c) | |
| with torch.no_grad(): | |
| for _ in range(n_sup - 1): | |
| (y, z), _ = thinker.deep_recursion(c, y, z) | |
| carry, y_out = thinker.deep_recursion(c, y, z) | |
| return thinker.residuals(y_out), carry | |
| def path_forward(thinker, painter, xt, t, cond, mask, carry=None, n_sup=1): | |
| """Thinker then Painter. Returns (x0_hat, carry).""" | |
| residuals, carry = think(thinker, xt, t, cond, mask, carry, n_sup) | |
| return painter(xt, t, residuals), carry | |