Buckets:
| #!/usr/bin/env python3 | |
| """Full data scaling sweep for Claim 6. | |
| Trains toy DiT with N=20,50,100,200,400,800 samples. | |
| Paper: 2512.24165 | |
| """ | |
| import modal, json, os, time, sys | |
| import numpy as np | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| app = modal.App("diffthinker-data-scaling") | |
| image = ( | |
| modal.Image.debian_slim(python_version="3.12") | |
| .pip_install("torch>=2.1.0", "torchvision", "numpy", "Pillow") | |
| ) | |
| def make_maze_data(grid_size=8, num=100): | |
| import torch | |
| data = [] | |
| for _ in range(num): | |
| g = np.zeros((grid_size, grid_size), dtype=int) | |
| start = (0, np.random.randint(0, grid_size)) | |
| goal = (grid_size-1, np.random.randint(0, grid_size)) | |
| for _ in range(int(grid_size*grid_size*0.15)): | |
| wx, wy = np.random.randint(1, grid_size-1, 2) | |
| g[wx, wy] = 1 | |
| from collections import deque | |
| q = deque([start]) | |
| parent = {start: None} | |
| solved = False | |
| while q: | |
| r, c = q.popleft() | |
| if (r, c) == goal: | |
| solved = True; break | |
| for dr, dc in [(0,1),(0,-1),(1,0),(-1,0)]: | |
| nr, nc = r+dr, c+dc | |
| if 0 <= nr < grid_size and 0 <= nc < grid_size and g[nr,nc] != 1 and (nr,nc) not in parent: | |
| parent[(nr,nc)] = (r,c) | |
| # Build solution path | |
| path_grid = np.zeros((grid_size, grid_size), dtype=np.float32) | |
| if solved: | |
| cur = goal | |
| while cur != start: | |
| path_grid[cur] = 1.0 | |
| cur = parent[cur] | |
| path_grid[start] = 1.0 | |
| # Input: walls+start+goal | |
| inp = np.stack([g==1, np.zeros_like(g), np.zeros_like(g)], axis=0).astype(np.float32) | |
| inp[1, start[0], start[1]] = 1.0 | |
| inp[2, goal[0], goal[1]] = 1.0 | |
| data.append({"input": inp, "target": path_grid, "solved": solved}) | |
| return data | |
| class TinyDiT(nn.Module): | |
| def __init__(self, grid_size=8, hidden=64): | |
| super().__init__() | |
| self.grid_size = grid_size | |
| self.hidden = hidden | |
| self.time_embed = nn.Sequential( | |
| nn.Linear(1, hidden), nn.SiLU(), nn.Linear(hidden, hidden)) | |
| self.input_proj = nn.Conv2d(3, hidden, 3, padding=1) | |
| self.block = nn.Sequential( | |
| nn.Conv2d(hidden+hidden, hidden, 3, padding=1), nn.SiLU(), | |
| nn.Conv2d(hidden, hidden, 3, padding=1), nn.SiLU()) | |
| self.out = nn.Conv2d(hidden, 1, 1) | |
| def forward(self, x, t): | |
| B = x.shape[0] | |
| te = self.time_embed(t.view(B,1)) | |
| h = self.input_proj(x) | |
| te = te.view(B, self.hidden, 1, 1).expand(-1, -1, self.grid_size, self.grid_size) | |
| h = torch.cat([h, te], dim=1) | |
| h = self.block(h) | |
| return self.out(h) | |
| class DataScaling: | |
| def train_and_eval(self, num_train): | |
| device = "cuda" | |
| data = make_maze_data(num=num_train) | |
| model = TinyDiT().to(device) | |
| opt = torch.optim.AdamW(model.parameters(), lr=1e-3) | |
| N = len(data) | |
| losses = [] | |
| for epoch in range(30): | |
| epoch_loss = 0.0 | |
| idxs = np.random.permutation(N) | |
| for i in idxs: | |
| inp = torch.tensor(data[i]["input"]).unsqueeze(0).to(device) | |
| target = torch.tensor(data[i]["target"]).unsqueeze(0).unsqueeze(0).to(device) | |
| t = torch.rand(1,1).to(device) | |
| noise = torch.randn_like(target) | |
| v_pred = model(inp, t) | |
| v_target = noise - target | |
| loss = F.mse_loss(v_pred, v_target) | |
| opt.zero_grad() | |
| loss.backward() | |
| opt.step() | |
| epoch_loss += loss.item() | |
| avg_loss = epoch_loss / N | |
| losses.append(avg_loss) | |
| if (epoch+1) % 5 == 0: | |
| print(f" Ep {epoch+1}/30 loss={avg_loss:.4f}") | |
| eval_data = make_maze_data(num=50) | |
| correct = 0 | |
| for d in eval_data: | |
| inp = torch.tensor(d["input"]).unsqueeze(0).to(device) | |
| x = torch.randn(1,1,8,8).to(device) | |
| steps = 20 | |
| for s in range(steps): | |
| t = torch.tensor([[(s+1)/steps]]).to(device) | |
| v = model(inp, t) | |
| x = x + v * (1.0/steps) | |
| pred_path = (x.squeeze().detach().cpu().numpy() > 0.5).astype(int) | |
| gt = d["target"] | |
| acc = (pred_path == gt).mean() | |
| correct += 1 if acc > 0.9 else 0 | |
| acc = correct / len(eval_data) * 100 | |
| print(f" Acc: {acc:.1f}% ({correct}/{len(eval_data)})") | |
| return {"num_train": num_train, "final_loss": round(losses[-1], 4), "accuracy": round(acc, 1), "epochs": 30} | |
| def sweep(self, data_sizes=[20, 50, 100, 200, 400, 800]): | |
| results = [] | |
| for n in data_sizes: | |
| print(f"\n=== Training with N={n} samples ===") | |
| t0 = time.time() | |
| r = self.train_and_eval(n) | |
| r["time_sec"] = round(time.time()-t0, 1) | |
| results.append(r) | |
| print(f" Done in {r['time_sec']:.1f}s") | |
| print(json.dumps(results, indent=2)) | |
| return results | |
| def main(): | |
| ds = DataScaling() | |
| res = ds.sweep.remote([20, 50, 100, 200, 400, 800]) | |
| with open("/tmp/data_scaling_results.json", "w") as f: | |
| json.dump(res, f, indent=2) | |
| print("Results saved to /tmp/data_scaling_results.json") | |
Xet Storage Details
- Size:
- 5.5 kB
- Xet hash:
- 749dc60d686e46af1983a17638ebaaa89212b0fdbfdce65b14b1e7847ef93069
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.