| |
| """Cost of d(loss)/d(action) -- the inner loop of gradient-based planning. |
| |
| Three regimes: |
| dino-wm full encode context frames, predict, backprop to the action chunk |
| dino-wm cached context features precomputed once (what a planner actually does across |
| CEM/GD iterations, since the context is fixed), predict + backprop only |
| mlp 3x256 a 3-layer 256-wide state-space dynamics model, (state, action) -> next state |
| |
| policy_learning/lerobot/.venv/bin/python dynamics_model/bench_action_grad.py |
| """ |
|
|
| import time |
|
|
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| from transformers import AutoModel |
|
|
| from dino_dynamics import DINO_ID, DinoDynamics |
| from sd3_dynamics import HORIZON |
|
|
| V, ACT, ST, HIST, SZ = 2, 7, 9, 2, 224 |
| BATCHES = (1, 32, 256, 1024) |
| REPS = 10 |
|
|
|
|
| def timeit(fn, reps=REPS, warmup=3): |
| for _ in range(warmup): |
| fn() |
| torch.cuda.synchronize() |
| t0 = time.time() |
| for _ in range(reps): |
| fn() |
| torch.cuda.synchronize() |
| return (time.time() - t0) / reps * 1000 |
|
|
|
|
| def main(): |
| dev = "cuda" |
| torch.manual_seed(0) |
| dino = AutoModel.from_pretrained(DINO_ID, dtype=torch.float32) |
| model = DinoDynamics(dino, None, V, ACT, ST, history=HIST).to(dev).eval() |
| for p in model.parameters(): |
| p.requires_grad_(False) |
|
|
| mlp = nn.Sequential( |
| nn.Linear(ST + HORIZON * ACT, 256), nn.ReLU(), |
| nn.Linear(256, 256), nn.ReLU(), |
| nn.Linear(256, 256), nn.ReLU(), |
| nn.Linear(256, ST), |
| ).to(dev).eval() |
| for p in mlp.parameters(): |
| p.requires_grad_(False) |
| print(f"dino-wm predictor params: {sum(p.numel() for p in model.parameters()) / 1e6:.1f}M " |
| f"(incl. frozen DINOv2) mlp params: {sum(p.numel() for p in mlp.parameters()) / 1e3:.0f}k") |
|
|
| print(f"\n{'batch':>6} {'dino full':>12} {'dino cached':>13} {'mlp 3x256':>11} " |
| f"{'cached/mlp':>11} {'full/cached':>12}") |
| for b in BATCHES: |
| ctx = torch.rand(b, HIST, V, 3, SZ, SZ, device=dev) |
| state = torch.randn(b, ST, device=dev) |
| goal = torch.randn(b, V, model.n_patches, model.dim, device=dev) |
| goal_s = torch.randn(b, ST, device=dev) |
|
|
| def dino_full(): |
| act = torch.randn(b, HORIZON, ACT, device=dev, requires_grad=True) |
| loss = F.mse_loss(model({"context": ctx, "action": act, "state": state}), goal) |
| return torch.autograd.grad(loss, act)[0] |
|
|
| |
| with torch.no_grad(): |
| z = model.encode_views(ctx) |
|
|
| def dino_cached(): |
| act = torch.randn(b, HORIZON, ACT, device=dev, requires_grad=True) |
| loss = F.mse_loss(model.predict_from_features(z, act, state), goal) |
| return torch.autograd.grad(loss, act)[0] |
|
|
| def mlp_grad(): |
| act = torch.randn(b, HORIZON, ACT, device=dev, requires_grad=True) |
| loss = F.mse_loss(mlp(torch.cat([state, act.flatten(1)], 1)), goal_s) |
| return torch.autograd.grad(loss, act)[0] |
|
|
| def safe(fn): |
| try: |
| return timeit(fn) |
| except torch.OutOfMemoryError: |
| torch.cuda.empty_cache() |
| return float("nan") |
|
|
| t_full, t_cached, t_mlp = safe(dino_full), safe(dino_cached), safe(mlp_grad) |
| fmt = lambda x, w, p: ("OOM" if x != x else f"{x:.{p}f}ms").rjust(w) |
| ratio = lambda x, y: "-" if (x != x or y != y) else f"{x / y:.0f}x" |
| print(f"{b:>6} {fmt(t_full, 12, 2)} {fmt(t_cached, 13, 2)} {fmt(t_mlp, 11, 3)} " |
| f"{ratio(t_cached, t_mlp):>11} {ratio(t_full, t_cached):>12}") |
| del ctx, z |
| torch.cuda.empty_cache() |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|