tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.cd /app && ls -la && echo "---- nvidia ----" && nvidia-smi 2>/dev/null || echo "no gpu"; echo "---- python ----"; python3 --version; python3 -c "import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())"total 15 drwxrws--- 3 h2tagent h2tagent 138 Jun 19 18:00 . drwxr-xr-x 1 root root 4096 Jun 20 07:11 .. -rw-rw---- 1 h2tagent h2tagent 2710 Jun 19 17:59 candidate_model.py drwxrws--- 5 h2tagent h2tagent 58 Jun 19 17:59 data -rw-rw---- 1 h2tagent h2tagent 1195 Jun 19 17:59 dataset.py -rw-rw---- 1 h2tagent h2tagent 238 Jun 19 17:59 feature_schema.json -rw-rw---- 1 h2tagent h2tagent 633 Jun 19 17:59 task_config.json -rw-rw---- 1 h2tagent h2tagent 3184 Jun 19 17:59 train.py ---- nvidia ---- Sat Jun 20 07:11:30 2026 +-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 580.82.07 Driver Version: 580.82.07 CUDA Version: 13.0 | +-----------------------------------------+------------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+========================+======================| | 0 NVIDIA H100 80GB HBM3 Off | N/A Off | 0 | | N/A 33C P0 124W / 700W | 5685MiB / 81559MiB | 1% Default | | | | Disabled | +-----------------------------------------+------------------------+----------------------+ +-----------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=========================================================================================| | No running processes found | +-----------------------------------------------------------------------------------------+ ---- python ---- Python 3.11.10 torch 2.5.1+cu124 cuda True
cd /app && echo "==== task_config.json ====" && cat task_config.json && echo && echo "==== feature_schema.json ====" && cat feature_schema.json && echo && echo "==== candidate_model.py ====" && cat candidate_model.py
==== task_config.json ====
{
"task_id": "ks-equation-1d-forecast",
"dataset_name": "ks-equation-1d-chaotic",
"feature_format": "trajectory-window-k-frames",
"label_format": "next-frame",
"grid_size": 128,
"in_channels": 4,
"out_channels": 1,
"rollout_horizon": 20,
"seed": 20260517,
"model": {
"width": 32,
"modes": 16,
"num_layers": 4
},
"training": {
"epochs": 10,
"batch_size": 64,
"lr": 0.001,
"weight_decay": 0.0001,
"loss": "MSE"
},
"evaluation": {
"primary_metric": "rollout_rel_l2_score",
"secondary_metrics": [
"rollout_rmse_score",
"final_step_rel_l2_score"
]
}
}
==== feature_schema.json ====
{
"domain": "1d-periodic",
"grid_size": 128,
"domain_length": 22.0,
"dt_effective": 0.25,
"input": {"channels": 4, "description": "last k=4 frames of u(x,t)"},
"output": {"channels": 1, "description": "next frame u(x,t+1)"}
}
==== candidate_model.py ====
from __future__ import annotations
from typing import Mapping
import torch
import torch.nn as nn
class SpectralConv1d(nn.Module):
def __init__(self, in_channels: int, out_channels: int, modes: int) -> None:
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.modes = modes
scale = 1.0 / (in_channels * out_channels)
self.weight = nn.Parameter(
scale * torch.randn(in_channels, out_channels, modes, dtype=torch.cfloat)
)
@staticmethod
def _mul1d(inp: torch.Tensor, weight: torch.Tensor) -> torch.Tensor:
return torch.einsum("bix,iox->box", inp, weight)
def forward(self, x: torch.Tensor) -> torch.Tensor:
b, _, n = x.shape
x_ft = torch.fft.rfft(x, norm="ortho")
out_ft = torch.zeros(b, self.out_channels, n // 2 + 1, dtype=torch.cfloat, device=x.device)
m = min(self.modes, x_ft.shape[-1])
out_ft[:, :, :m] = self._mul1d(x_ft[:, :, :m], self.weight[:, :, :m])
return torch.fft.irfft(out_ft, n=n, norm="ortho")
class FNO1dBlock(nn.Module):
def __init__(self, width: int, modes: int) -> None:
super().__init__()
self.spectral = SpectralConv1d(width, width, modes)
self.bypass = nn.Conv1d(width, width, 1)
self.act = nn.GELU()
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.act(self.spectral(x) + self.bypass(x))
class FNO1d(nn.Module):
def __init__(self, in_channels: int, out_channels: int, width: int, modes: int, num_layers: int, grid_size: int) -> None:
super().__init__()
self.lift = nn.Conv1d(in_channels + 1, width, 1)
self.blocks = nn.ModuleList(FNO1dBlock(width, modes) for _ in range(num_layers))
self.proj = nn.Sequential(
nn.Conv1d(width, width, 1),
nn.GELU(),
nn.Conv1d(width, out_channels, 1),
)
self.register_buffer("_grid", torch.linspace(0.0, 1.0, grid_size + 1)[:grid_size].view(1, 1, grid_size), persistent=False)
def forward(self, x: torch.Tensor) -> torch.Tensor:
b = x.shape[0]
grid = self._grid.expand(b, -1, -1).to(x.device, dtype=x.dtype)
x = torch.cat([x, grid], dim=1)
x = self.lift(x)
for block in self.blocks:
x = block(x)
return self.proj(x)
def build_model(config: Mapping) -> FNO1d:
m = config["model"]
return FNO1d(
in_channels=int(config["in_channels"]),
out_channels=int(config["out_channels"]),
width=int(m["width"]),
modes=int(m["modes"]),
num_layers=int(m["num_layers"]),
grid_size=int(config["grid_size"]),
)cd /app && echo "==== dataset.py ====" && cat dataset.py && echo && echo "==== train.py ====" && cat train.py
==== dataset.py ====
from __future__ import annotations
from pathlib import Path
import torch
from torch.utils.data import Dataset
class KSForecast(Dataset):
def __init__(self, split_dir: str | Path, k: int = 4) -> None:
self.dir = Path(split_dir)
self.k = int(k)
payload = torch.load(self.dir / "trajectories.pt", map_location="cpu", weights_only=True)
self.trajectories: torch.Tensor = payload["trajectories"].float()
n_traj, t_stored, _ = self.trajectories.shape
if t_stored <= self.k:
raise ValueError(f"trajectories too short for k={self.k}: t_stored={t_stored}")
self._index = [(i, t) for i in range(n_traj) for t in range(self.k, t_stored)]
def __len__(self) -> int:
return len(self._index)
def __getitem__(self, idx: int) -> dict:
i, t = self._index[idx]
return {
"window": self.trajectories[i, t - self.k : t],
"next_frame": self.trajectories[i, t : t + 1],
}
def compute_standardization(trajectories: torch.Tensor) -> tuple[float, float]:
mean = float(trajectories.mean().item())
std = float(trajectories.std().clamp_min(1e-6).item())
return mean, std
==== train.py ====
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
import torch
import torch.nn.functional as F
APP_DIR = Path(os.environ.get("APP_DIR", "/app"))
sys.path.insert(0, str(Path(__file__).resolve().parent))
from candidate_model import build_model
from dataset import KSForecast, compute_standardization
def main() -> None:
with (APP_DIR / "task_config.json").open() as fh:
cfg = json.load(fh)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
torch.manual_seed(int(cfg["seed"]))
k = int(cfg["in_channels"])
train_ds = KSForecast(APP_DIR / "data" / "train", k=k)
val_ds = KSForecast(APP_DIR / "data" / "val", k=k)
feat_mean, feat_std = compute_standardization(train_ds.trajectories)
model = build_model(cfg).to(device)
epochs = int(os.environ.get("TASK_EPOCHS_OVERRIDE", cfg["training"]["epochs"]))
batch_size = int(cfg["training"]["batch_size"])
lr = float(cfg["training"]["lr"])
optim = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=float(cfg["training"]["weight_decay"]))
sched = torch.optim.lr_scheduler.CosineAnnealingLR(optim, T_max=max(1, epochs))
def normalize(t): return (t - feat_mean) / feat_std
idx = torch.tensor(train_ds._index, dtype=torch.long)
traj = train_ds.trajectories
t_idx = idx[:, 1]
offsets = torch.arange(-k, 0)
train_x = traj[idx[:, 0].unsqueeze(1).expand(-1, k), t_idx.unsqueeze(1) + offsets]
train_y = traj[idx[:, 0], t_idx].unsqueeze(1)
idx_v = torch.tensor(val_ds._index, dtype=torch.long)
traj_v = val_ds.trajectories
t_idx_v = idx_v[:, 1]
val_x = traj_v[idx_v[:, 0].unsqueeze(1).expand(-1, k), t_idx_v.unsqueeze(1) + offsets]
val_y = traj_v[idx_v[:, 0], t_idx_v].unsqueeze(1)
print(f"[train] device={device} epochs={epochs} pairs={len(train_ds)}", flush=True)
for epoch in range(epochs):
model.train()
perm = torch.randperm(train_x.shape[0])
running, n_batches = 0.0, 0
for start in range(0, train_x.shape[0], batch_size):
idx_b = perm[start:start + batch_size]
xb = normalize(train_x[idx_b]).to(device)
yb = normalize(train_y[idx_b]).to(device)
optim.zero_grad(set_to_none=True)
loss = F.mse_loss(model(xb), yb)
loss.backward()
optim.step()
running += loss.item()
n_batches += 1
sched.step()
with torch.no_grad():
model.eval()
pred = model(normalize(val_x).to(device)).cpu() * feat_std + feat_mean
val_rl2 = ((pred - val_y).pow(2).sum(-1).sqrt() / val_y.pow(2).sum(-1).sqrt().clamp_min(1e-8)).mean().item()
…[truncated]cd /app && python3 -c "
import torch
for split in ['train','val','public_test']:
p = f'data/{split}/trajectories.pt'
payload = torch.load(p, map_location='cpu', weights_only=True)
print('====', split, '====')
print('keys:', list(payload.keys()))
tr = payload['trajectories']
print('shape', tr.shape, 'dtype', tr.dtype)
print('mean %.5f std %.5f min %.3f max %.3f' % (tr.mean(), tr.std(), tr.min(), tr.max()))
"==== train ==== keys: ['trajectories'] shape torch.Size([200, 100, 128]) dtype torch.float32 mean -0.00000 std 1.15997 min -3.245 max 3.248 ==== val ==== keys: ['trajectories'] shape torch.Size([32, 100, 128]) dtype torch.float32 mean -0.00000 std 1.13391 min -3.197 max 3.210 ==== public_test ==== keys: ['trajectories'] shape torch.Size([16, 100, 128]) dtype torch.float32 mean 0.00000 std 1.22519 min -3.172 max 3.183
cd /app && python3 -c "
import torch
tr = torch.load('data/val/trajectories.pt', map_location='cpu', weights_only=True)['trajectories']
# relative L2 between frame t and t+lag, averaged
def rel_l2(a,b):
return ((a-b).pow(2).sum(-1).sqrt() / b.pow(2).sum(-1).sqrt().clamp_min(1e-8)).mean().item()
print('rel_l2 frame t vs t+1 :', rel_l2(tr[:,:-1], tr[:,1:]))
print('rel_l2 frame t vs t+5 :', rel_l2(tr[:,:-5], tr[:,5:]))
print('rel_l2 frame t vs t+20:', rel_l2(tr[:,:-20], tr[:,20:]))
# persistence rollout: predict next = last frame, 20 steps from t0=4
# window last frame is frame index 3, predict frames 4..23 all equal frame 3
N,T,X = tr.shape
import numpy as np
scores=[]
for start in range(4, T-20):
last = tr[:, start-1:start] # frame start-1
gt = tr[:, start:start+20]
pred = last.expand(-1,20,-1)
r = ((pred-gt).pow(2).sum(-1).sqrt()/gt.pow(2).sum(-1).sqrt().clamp_min(1e-8)).mean().item()
scores.append(r)
print('persistence 20-step rollout mean rel_l2:', np.mean(scores))
"rel_l2 frame t vs t+1 : 0.03409876674413681 rel_l2 frame t vs t+5 : 0.16701358556747437 rel_l2 frame t vs t+20: 0.5774152874946594 persistence 20-step rollout mean rel_l2: 0.3212855344540195
cd /app && mkdir -p outputs && TASK_EPOCHS_OVERRIDE=3 python3 train.py 2>&1 | tail -8
[train] device=cuda epochs=3 pairs=19200 [train] epoch=1/3 mse=6.0238e-02 val_rel_l2=0.0136 [train] epoch=2/3 mse=1.4463e-04 val_rel_l2=0.0079 [train] epoch=3/3 mse=8.2739e-05 val_rel_l2=0.0069 [train] wrote /app/outputs/model.pt
cd /app && cat > rollout_eval.py << 'EOF'
from __future__ import annotations
import json, os, sys
from pathlib import Path
import torch
sys.path.insert(0, '/app')
from candidate_model import build_model
def load_model(ckpt_path, cfg, device):
ck = torch.load(ckpt_path, map_location='cpu', weights_only=True)
model = build_model(cfg).to(device)
model.load_state_dict(ck['state_dict'])
model.eval()
return model, float(ck['feat_mean']), float(ck['feat_std'])
@torch.no_grad()
def rollout_eval(model, feat_mean, feat_std, traj, device, k=4, horizon=20, starts=None):
# traj: [N, T, X]. For each start s: window = frames [s-k:s], predict s..s+horizon-1
N, T, X = traj.shape
if starts is None:
starts = list(range(k, T - horizon + 1))
all_step_rl2 = [] # per-step rel l2 averaged over traj and starts
final_rl2 = []
rmse_all = []
for s in starts:
window = traj[:, s-k:s].to(device) # [N,k,X]
w = (window - feat_mean) / feat_std
preds = []
for h in range(horizon):
p = model(w) # [N,1,X] normalized
preds.append(p)
w = torch.cat([w[:, 1:], p], dim=1)
pred = torch.cat(preds, dim=1) * feat_std + feat_mean # [N,horizon,X]
gt = traj[:, s:s+horizon].to(device)
num = (pred - gt).pow(2).sum(-1).sqrt()
den = gt.pow(2).sum(-1).sqrt().clamp_min(1e-8)
rl2 = (num / den) # [N, horizon]
all_step_rl2.append(rl2.mean(0).cpu()) # [horizon]
final_rl2.append(rl2[:, -1].mean().cpu())
rmse = (pred - gt).pow(2).mean(-1).sqrt().mean(0).cpu()
rmse_all.append(rmse)
step_rl2 = torch.stack(all_step_rl2).mean(0) # [horizon]
rmse_step = torch.stack(rmse_all).mean(0)
return {
'mean_rollout_rel_l2': step_rl2.mean().item(),
'final_step_rel_l2': step_rl2[-1].item(),
'mean_rollout_rmse': rmse_step.mean().item(),
'step_rl2': step_rl2.tolist(),
'n_starts': len(starts),
}
if __name__ == '__main__':
cfg = json.load(open('/app/task_config.json'))
device = 'cuda' if torch.cuda.is_available() else 'cpu'
ckpt = sys.argv[1] if len(sys.argv) > 1 else '/app/outputs/model.pt'
split = sys.argv[2] if len(sys.argv) > 2 else 'val'
traj = torch.load(f'/app/data/{split}/trajectories.pt', map_location='cpu', weights_only=True)['trajectories'].float()
model, fm, fs = load_model(ckpt, cfg, device)
# Evaluation mimicking grader: use early start (window=first 4 frames)
res_single = rollout_eval(model, fm, fs, traj, device, starts=[4])
res_multi = rollout_eval(model, fm, fs, traj, device)
print(f'== {split} ckpt={ckpt} ==')
print('start=4 : mean_rl2=%.4f final=%.4f rmse=%.4f' % (res_single['mean_rollout_rel_l2'], res_single['final_step_rel_l2'], res_single['mean_rollout_rmse']))
print('all starts(%d): mean_rl2=%.4f final=%.4f rmse=%.4f' % (res_multi['n_starts'], res_multi['mean_rollout_rel_l2'], res_multi['final_step_rel_l2'], res_multi['mean_rollout_rmse']))
sr = res_multi['step_rl2']
print('per-step rl2 [1,2,3,5,10,15,20]:', [round(sr[i],4) for i in [0,1,2,4,9,14,19]])
EOF
python3 rollout_eval.py /app/outputs/model.pt val== val ckpt=/app/outputs/model.pt == start=4 : mean_rl2=0.0255 final=0.0443 rmse=0.0311 all starts(77): mean_rl2=0.0240 final=0.0444 rmse=0.0278 per-step rl2 [1,2,3,5,10,15,20]: [0.0067, 0.008, 0.0091, 0.0131, 0.0222, 0.0326, 0.0444]
cd /app && python3 train.py 2>&1 | tail -12 && cp outputs/model.pt outputs/baseline_10ep.pt && python3 rollout_eval.py outputs/baseline_10ep.pt val
[train] device=cuda epochs=10 pairs=19200 [train] epoch=1/10 mse=6.0238e-02 val_rel_l2=0.0136 [train] epoch=2/10 mse=1.3373e-04 val_rel_l2=0.0076 [train] epoch=3/10 mse=6.1280e-05 val_rel_l2=0.0058 [train] epoch=4/10 mse=3.6445e-05 val_rel_l2=0.0046 [train] epoch=5/10 mse=2.4894e-05 val_rel_l2=0.0038 [train] epoch=6/10 mse=1.8479e-05 val_rel_l2=0.0034 [train] epoch=7/10 mse=1.4761e-05 val_rel_l2=0.0032 [train] epoch=8/10 mse=1.2601e-05 val_rel_l2=0.0030 [train] epoch=9/10 mse=1.1432e-05 val_rel_l2=0.0029 [train] epoch=10/10 mse=1.0889e-05 val_rel_l2=0.0028 [train] wrote /app/outputs/model.pt == val ckpt=outputs/baseline_10ep.pt == start=4 : mean_rl2=0.0099 final=0.0192 rmse=0.0122 all starts(77): mean_rl2=0.0093 final=0.0176 rmse=0.0108 per-step rl2 [1,2,3,5,10,15,20]: [0.0026, 0.0031, 0.0035, 0.005, 0.0085, 0.0127, 0.0176]
cd /app && python3 -c "
import torch
tr = torch.load('data/train/trajectories.pt', map_location='cpu', weights_only=True)['trajectories'].float()
# energy per fourier mode (rfft along x)
ft = torch.fft.rfft(tr, dim=-1, norm='ortho') # [N,T,65]
energy = ft.abs().pow(2).mean(dim=(0,1)) # [65]
cum = torch.cumsum(energy, 0) / energy.sum()
print('num rfft modes:', energy.shape[0])
print('cumulative energy fraction at modes [4,8,12,16,20,24,32]:')
for m in [4,8,12,16,20,24,32,64]:
print(' modes<=%2d : %.5f' % (m, cum[m].item() if m < len(cum) else 1.0))
print('energy beyond mode 16 fraction: %.5e' % (1 - cum[16].item()))
# mean frame norm
print('mean ||frame||_2:', tr.pow(2).sum(-1).sqrt().mean().item())
"num rfft modes: 65 cumulative energy fraction at modes [4,8,12,16,20,24,32]: modes<= 4 : 0.97440 modes<= 8 : 1.00000 modes<=12 : 1.00000 modes<=16 : 1.00000 modes<=20 : 1.00000 modes<=24 : 1.00000 modes<=32 : 1.00000 modes<=64 : 1.00000 energy beyond mode 16 fraction: 0.00000e+00 mean ||frame||_2: 12.913924217224121
cd /app && python3 -c "
import torch
# Verify reflect+negate symmetry: if u(x,t) solves KS, so does -u(-x,t).
# Test via spectral time-derivative residual on real data consistency:
# Instead, check that transformed trajectory is dynamically consistent by
# comparing one-step finite-difference operators' statistics (sanity proxy).
tr = torch.load('data/train/trajectories.pt', map_location='cpu', weights_only=True)['trajectories'].float()
L=22.0; N=128; dx=L/N
u = tr[0,50] # a frame
# build spatial derivatives via spectral
k = 2*torch.pi*torch.fft.rfftfreq(N, d=dx)
def deriv(u, order):
uf = torch.fft.rfft(u)
return torch.fft.irfft((1j*k)**order * uf, n=N)
# KS RHS: -u*u_x - u_xx - u_xxxx
def rhs(u):
return -(u*deriv(u,1) + deriv(u,2) + deriv(u,4))
r1 = rhs(u)
# transform w(x) = -u(-x): grid reflection about origin = -roll(flip(u),1)
w = -torch.roll(torch.flip(u, dims=[-1]), shifts=1, dims=-1)
rw = rhs(w)
# w's rhs should equal the transform of r1: (R r1)(x) = -r1(-x) = -roll(flip(r1),1)
r1_transformed = -torch.roll(torch.flip(r1, dims=[-1]), shifts=1, dims=-1)
print('reflect-negate symmetry residual (should be ~0):', (rw - r1_transformed).abs().max().item())
# translation: shift by s cells -> rhs shifts by s cells
s=7
us = torch.roll(u, shifts=s, dims=-1)
print('translation symmetry residual (should be ~0):', (rhs(us) - torch.roll(r1, shifts=s, dims=-1)).abs().max().item())
"reflect-negate symmetry residual (should be ~0): 0.015765637159347534 translation symmetry residual (should be ~0): 0.010350704193115234
cd /app && python3 -c "
import torch
tr = torch.load('data/train/trajectories.pt', map_location='cpu', weights_only=True)['trajectories'].float()
L=22.0; N=128; dx=L/N
u = tr[0,50]
k = 2*torch.pi*torch.fft.rfftfreq(N, d=dx)
def deriv(u, order):
return torch.fft.irfft((1j*k)**order * torch.fft.rfft(u), n=N)
rhs = -(u*deriv(u,1)+deriv(u,2)+deriv(u,4))
print('RHS magnitude (max abs): %.3f, symmetry residual ~0.016 => relative %.2e' % (rhs.abs().max().item(), 0.016/rhs.abs().max().item()))
"RHS magnitude (max abs): 0.755, symmetry residual ~0.016 => relative 2.12e-02
command: create
path: /app/train_improved.py
file_text: from __future__ import annotations
import json, os, sys, math, copy, time
from pathlib import Path
import torch
import torch.nn.functional as F
APP_DIR = Path(os.environ.get("APP_DIR", "/app"))
sys.path.insert(0, str(APP_DIR))
from candidate_model import build_model
from dataset import compute_standardization
from rollout_eval import rollout_eval
# ----------------------------- config -----------------------------
def getenv_f(name, default):
return float(os.environ.get(name, default))
def getenv_i(name, default):
return int(os.environ.get(name, default))
CFG = json.load(open(APP_DIR / "task_config.json"))
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
K = int(CFG["in_channels"]) # 4 input frames
HORIZON = int(CFG["rollout_horizon"]) # 20 (eval)
EPOCHS = getenv_i("EPOCHS", 80)
BATCH = getenv_i("BATCH", 128)
LR = getenv_f("LR", 1.5e-3)
WD = getenv_f("WD", 1e-4)
WARMUP_FRAC = getenv_f("WARMUP_FRAC", 0.05)
EMA_DECAY = getenv_f("EMA_DECAY", 0.9995)
GRAD_CLIP = getenv_f("GRAD_CLIP", 1.0)
AUG = getenv_i("AUG", 1) # symmetry augmentation on/off
NOISE_STD = getenv_f("NOISE_STD", 0.0) # input noise (in normalized units)
MAX_M = getenv_i("MAX_M", 10) # max rollout length in curriculum
SEED = getenv_i("SEED", int(CFG["seed"]))
TAG = os.environ.get("TAG", "run")
LOSS = os.environ.get("LOSS", "rel_l2") # rel_l2 | mse
PUSHFWD = getenv_i("PUSHFWD", 0) # pushforward: unroll no-grad then grad on tail
SAVE_PATH = os.environ.get("SAVE_PATH", str(APP_DIR / "outputs" / f"model_{TAG}.pt"))
torch.manual_seed(SEED)
# ----------------------------- data -----------------------------
def load_split(name):
return torch.load(APP_DIR / "data" / name / "trajectories.pt",
map_location="cpu", weights_only=True)["trajectories"].float()
train_traj = load_split("train").to(DEVICE) # [200,100,128]
val_traj = load_split("val") # keep on cpu; eval moves as needed
feat_mean, feat_std = compute_standardization(train_traj.cpu())
print(f"[{TAG}] feat_mean={feat_mean:.5f} feat_std={feat_std:.5f} device={DEVICE}", flush=True)
N, T, X = train_traj.shape
train_norm = (train_traj - feat_mean) / feat_std # normalized, on GPU
# ----------------------------- augmentation -----------------------------
def augment(seq):
"""seq: [B, L, X] normalized frames. Apply KS symmetries.
- reflect+negate: u(x) -> -u(-x) (valid: -u(-x,t) solves KS)
- random circular shift (translation on periodic domain)
Same transform applied to all L frames of a sample."""
B, L, Xl = seq.shape
# reflect+negate with prob 0.5 (normalized mean~0 so negation is well-defined)
do_ref = (torch.rand(B, device=seq.device) < 0.5)
reflected = -torch.roll(torch.flip(seq, dims=[-1]), shifts=1, dims=-1)
seq = torch.where(do_ref.view(B, 1, 1), reflected, seq)
# random circular shift
shifts = torch.randint(0, Xl, (B,), device=seq.device)
ar = torch.arange(Xl, device=seq.device)
idx = (ar.view(1, Xl) - shifts.view(B, 1)) % Xl # [B,X]
idx = idx.view(B, 1, Xl).expand(B, L, Xl)
seq = torch.gather(seq, 2, idx)
return seq
# ----------------------------- curriculum -----------------------------
def rollout_len_for_epoch(ep):
# gradually grow the training rollout horizon
frac = ep / max(1, EPOCHS - 1)
if frac < 0.12: return 1
if frac < 0.25: return 2
if frac < 0.40: return 4
if frac < 0.55: return 6
if frac < 0.72: return 8
return MAX_M
# ----------------------------- model / optim -----------------------------
model = build_model(CFG).to(DEVICE)
ema = copy.deepcopy(model)
for p in ema.parameters():
p.requires_grad_(False)
opt = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=WD, betas=(0.9, 0.99))
steps_per_epoch = math.ceil((N * (T - K - 1)) / BATC
…[truncated]New file created successfully at /app/train_improved.py
cd /app && EPOCHS=6 TAG=smoke SAVE_PATH=/app/outputs/model_smoke.pt python3 train_improved.py 2>&1 | tail -15
[smoke] feat_mean=-0.00000 feat_std=1.15997 device=cuda [smoke] ep=2/6 M=2 loss=2.3291e-02 best_rl2=0.02331(final 0.04262 ema@2) lr=1.19e-03 t=9s [smoke] ep=3/6 M=6 loss=2.0080e-02 best_rl2=0.01657(final 0.02921 ema@3) lr=8.25e-04 t=18s [smoke] ep=4/6 M=8 loss=2.3564e-02 best_rl2=0.01411(final 0.02445 ema@4) lr=4.46e-04 t=29s [smoke] ep=5/6 M=10 loss=1.5229e-02 best_rl2=0.01210(final 0.02079 ema@5) lr=1.53e-04 t=41s [smoke] ep=6/6 M=10 loss=7.9869e-03 best_rl2=0.01052(final 0.01794 raw@6) lr=8.18e-06 t=54s [smoke] SAVED /app/outputs/model_smoke.pt best_val_rollout_rel_l2=0.01052 final=0.01794 (raw@ep6)
cd /app && nohup env EPOCHS=80 TAG=main SAVE_PATH=/app/outputs/model_main.pt python3 train_improved.py > /app/outputs/log_main.txt 2>&1 & echo "launched PID $!"; sleep 60; echo "---- progress ----"; tail -5 /app/outputs/log_main.txt
launched PID 206 ---- progress ---- [main] feat_mean=-0.00000 feat_std=1.15997 device=cuda
cd /app && sleep 150; tail -8 /app/outputs/log_main.txt
[main] feat_mean=-0.00000 feat_std=1.15997 device=cuda [main] ep=25/80 M=4 loss=1.0755e-02 best_rl2=0.01093(final 0.01806 ema@25) lr=1.24e-03 t=87s [main] ep=28/80 M=4 loss=1.7491e-02 best_rl2=0.01093(final 0.01806 ema@25) lr=1.16e-03 t=107s [main] ep=31/80 M=4 loss=9.3020e-03 best_rl2=0.01064(final 0.01691 ema@31) lr=1.08e-03 t=124s [main] ep=34/80 M=6 loss=1.5237e-02 best_rl2=0.00922(final 0.01454 ema@34) lr=1.00e-03 t=145s [main] ep=37/80 M=6 loss=1.1355e-02 best_rl2=0.00814(final 0.01275 ema@37) lr=9.15e-04 t=167s [main] ep=40/80 M=6 loss=7.1439e-03 best_rl2=0.00730(final 0.01141 ema@40) lr=8.27e-04 t=190s [main] ep=43/80 M=6 loss=9.8400e-03 best_rl2=0.00612(final 0.00958 ema@43) lr=7.38e-04 t=213s
cd /app && sleep 180; tail -10 /app/outputs/log_main.txt
[main] ep=37/80 M=6 loss=1.1355e-02 best_rl2=0.00814(final 0.01275 ema@37) lr=9.15e-04 t=167s [main] ep=40/80 M=6 loss=7.1439e-03 best_rl2=0.00730(final 0.01141 ema@40) lr=8.27e-04 t=190s [main] ep=43/80 M=6 loss=9.8400e-03 best_rl2=0.00612(final 0.00958 ema@43) lr=7.38e-04 t=213s [main] ep=46/80 M=8 loss=1.2072e-02 best_rl2=0.00512(final 0.00805 ema@46) lr=6.50e-04 t=237s [main] ep=49/80 M=8 loss=7.0779e-03 best_rl2=0.00500(final 0.00786 ema@49) lr=5.65e-04 t=262s [main] ep=52/80 M=8 loss=8.4958e-03 best_rl2=0.00432(final 0.00675 ema@52) lr=4.81e-04 t=286s [main] ep=55/80 M=8 loss=3.9573e-03 best_rl2=0.00367(final 0.00574 ema@55) lr=4.02e-04 t=309s [main] ep=58/80 M=10 loss=4.6450e-03 best_rl2=0.00314(final 0.00493 ema@58) lr=3.28e-04 t=331s [main] ep=61/80 M=10 loss=5.3483e-03 best_rl2=0.00274(final 0.00429 ema@61) lr=2.60e-04 t=356s [main] ep=64/80 M=10 loss=4.2694e-03 best_rl2=0.00251(final 0.00391 ema@64) lr=1.99e-04 t=382s
cd /app && sleep 130; tail -6 /app/outputs/log_main.txt; echo "--- still running? ---"; pgrep -f train_improved.py || echo "DONE"
[main] ep=73/80 M=10 loss=1.6914e-03 best_rl2=0.00198(final 0.00310 ema@73) lr=6.12e-05 t=461s [main] ep=75/80 M=10 loss=1.4519e-03 best_rl2=0.00189(final 0.00295 raw@75) lr=4.08e-05 t=482s [main] ep=76/80 M=10 loss=1.2293e-03 best_rl2=0.00174(final 0.00277 raw@76) lr=3.21e-05 t=494s [main] ep=77/80 M=10 loss=1.1946e-03 best_rl2=0.00174(final 0.00277 raw@76) lr=2.44e-05 t=506s [main] ep=78/80 M=10 loss=1.1336e-03 best_rl2=0.00167(final 0.00263 raw@78) lr=1.78e-05 t=519s [main] ep=79/80 M=10 loss=1.1123e-03 best_rl2=0.00167(final 0.00263 raw@78) lr=1.22e-05 t=532s --- still running? --- 208
cd /app && sleep 20; tail -2 /app/outputs/log_main.txt; pgrep -f train_improved.py >/dev/null && echo RUNNING || echo DONE
[main] ep=80/80 M=10 loss=1.0947e-03 best_rl2=0.00162(final 0.00254 raw@80) lr=7.61e-06 t=544s [main] SAVED /app/outputs/model_main.pt best_val_rollout_rel_l2=0.00162 final=0.00254 (raw@ep80) DONE
cd /app && for split in val public_test; do echo "######## $split ########" echo "-- baseline --"; python3 rollout_eval.py outputs/baseline_10ep.pt $split | sed -n '2,4p' echo "-- main --"; python3 rollout_eval.py outputs/model_main.pt $split | sed -n '2,4p' done
######## val ######## -- baseline -- start=4 : mean_rl2=0.0099 final=0.0192 rmse=0.0122 all starts(77): mean_rl2=0.0093 final=0.0176 rmse=0.0108 per-step rl2 [1,2,3,5,10,15,20]: [0.0026, 0.0031, 0.0035, 0.005, 0.0085, 0.0127, 0.0176] -- main -- start=4 : mean_rl2=0.0018 final=0.0026 rmse=0.0023 all starts(77): mean_rl2=0.0016 final=0.0025 rmse=0.0019 per-step rl2 [1,2,3,5,10,15,20]: [0.0009, 0.0007, 0.001, 0.0012, 0.0014, 0.0021, 0.0025] ######## public_test ######## -- baseline -- start=4 : mean_rl2=0.0082 final=0.0149 rmse=0.0105 all starts(77): mean_rl2=0.0076 final=0.0145 rmse=0.0092 per-step rl2 [1,2,3,5,10,15,20]: [0.0023, 0.0027, 0.003, 0.0042, 0.0069, 0.0103, 0.0145] -- main -- start=4 : mean_rl2=0.0016 final=0.0023 rmse=0.0021 all starts(77): mean_rl2=0.0013 final=0.0019 rmse=0.0016 per-step rl2 [1,2,3,5,10,15,20]: [0.0007, 0.0005, 0.0008, 0.0009, 0.0011, 0.0016, 0.0019]
command: str_replace
path: /app/train_improved.py
old_str: if frac < 0.55: return 6
if frac < 0.72: return 8
return MAX_M
new_str: if frac < 0.55: return 6
if frac < 0.72: return 8
if frac < 0.85: return min(MAX_M, 10)
return MAX_MThe file /app/train_improved.py has been edited successfully.
cd /app && nohup env EPOCHS=90 MAX_M=16 TAG=longM SAVE_PATH=/app/outputs/model_longM.pt python3 train_improved.py > /app/outputs/log_longM.txt 2>&1 & echo "longM PID $!" nohup env EPOCHS=80 MAX_M=10 AUG=0 TAG=noaug SAVE_PATH=/app/outputs/model_noaug.pt python3 train_improved.py > /app/outputs/log_noaug.txt 2>&1 & echo "noaug PID $!" sleep 5; echo "both launched"; nvidia-smi --query-gpu=memory.used,utilization.gpu --format=csv,noheader
longM PID 308 noaug PID 309 both launched 15470 MiB, 99 %
cd /app && python3 -c "
import torch, json
from candidate_model import build_model
cfg = json.load(open('task_config.json'))
ck = torch.load('outputs/model_main.pt', weights_only=True)
print('top-level keys:', list(ck.keys()))
print('feat_mean type/val:', type(ck['feat_mean']).__name__, ck['feat_mean'])
print('feat_std type/val:', type(ck['feat_std']).__name__, ck['feat_std'])
devs = set(str(v.device) for v in ck['state_dict'].values())
print('state_dict tensor devices:', devs)
m = build_model(cfg)
ret = m.load_state_dict(ck['state_dict'], strict=True)
print('load_state_dict strict=True OK:', ret)
# confirm a forward pass on cpu
import torch
x = torch.randn(2,4,128)
print('forward output shape:', tuple(m(x).shape))
"top-level keys: ['state_dict', 'feat_mean', 'feat_std']
feat_mean type/val: float -3.819819571759808e-09
feat_std type/val: float 1.1599736213684082
state_dict tensor devices: {'cuda:0'}
load_state_dict strict=True OK: <All keys matched successfully>
forward output shape: (2, 1, 128)command: str_replace
path: /app/train_improved.py
old_str: if sc < best["score"]:
best = {"score": sc, "final": r["final_step_rel_l2"], "which": name, "epoch": ep,
"state": copy.deepcopy(mdl.state_dict())}
new_str: if sc < best["score"]:
cpu_state = {k: v.detach().cpu().clone() for k, v in mdl.state_dict().items()}
best = {"score": sc, "final": r["final_step_rel_l2"], "which": name, "epoch": ep,
"state": cpu_state}The file /app/train_improved.py has been edited successfully.
cd /app && sleep 210; echo "=== longM ==="; tail -3 /app/outputs/log_longM.txt; echo "=== noaug ==="; tail -3 /app/outputs/log_noaug.txt
=== longM === [longM] ep=37/90 M=6 loss=9.4912e-03 best_rl2=0.00789(final 0.01227 ema@37) lr=1.03e-03 t=153s [longM] ep=40/90 M=6 loss=1.6992e-02 best_rl2=0.00789(final 0.01227 ema@37) lr=9.57e-04 t=181s [longM] ep=43/90 M=6 loss=7.4957e-03 best_rl2=0.00789(final 0.01227 ema@37) lr=8.79e-04 t=209s === noaug === [noaug] ep=37/80 M=6 loss=8.0489e-03 best_rl2=0.00949(final 0.01496 ema@37) lr=9.15e-04 t=169s [noaug] ep=40/80 M=6 loss=1.0435e-02 best_rl2=0.00749(final 0.01170 ema@40) lr=8.27e-04 t=196s [noaug] ep=43/80 M=6 loss=6.4300e-03 best_rl2=0.00612(final 0.00951 ema@43) lr=7.38e-04 t=224s
cd /app && sleep 220; echo "=== longM ==="; tail -3 /app/outputs/log_longM.txt; echo "=== noaug ==="; tail -3 /app/outputs/log_noaug.txt; echo "--- running ---"; pgrep -f train_improved.py || echo NONE
=== longM === [longM] ep=61/90 M=8 loss=6.5234e-03 best_rl2=0.00348(final 0.00528 ema@61) lr=4.22e-04 t=376s [longM] ep=64/90 M=8 loss=3.2117e-03 best_rl2=0.00317(final 0.00484 ema@64) lr=3.54e-04 t=406s [longM] ep=67/90 M=10 loss=3.3459e-03 best_rl2=0.00281(final 0.00432 ema@67) lr=2.92e-04 t=441s === noaug === [noaug] ep=58/80 M=10 loss=8.1025e-03 best_rl2=0.00321(final 0.00494 ema@58) lr=3.28e-04 t=365s [noaug] ep=61/80 M=10 loss=5.4871e-03 best_rl2=0.00284(final 0.00438 ema@61) lr=2.60e-04 t=395s [noaug] ep=64/80 M=10 loss=3.1952e-03 best_rl2=0.00256(final 0.00396 ema@64) lr=1.99e-04 t=431s --- running --- 309 311
cd /app && sleep 150; echo "=== noaug tail ==="; tail -2 /app/outputs/log_noaug.txt; echo "=== longM tail ==="; tail -2 /app/outputs/log_longM.txt; pgrep -f train_improved.py || echo NONE
=== noaug tail === [noaug] ep=77/80 M=10 loss=1.1710e-03 best_rl2=0.00193(final 0.00289 raw@77) lr=2.44e-05 t=587s [noaug] ep=78/80 M=10 loss=1.1366e-03 best_rl2=0.00178(final 0.00276 raw@78) lr=1.78e-05 t=602s === longM tail === [longM] ep=76/90 M=10 loss=2.4086e-03 best_rl2=0.00216(final 0.00333 ema@76) lr=1.36e-04 t=543s [longM] ep=79/90 M=16 loss=3.6883e-03 best_rl2=0.00203(final 0.00311 ema@79) lr=9.82e-05 t=583s 309 311
cd /app && sleep 120; echo "=== noaug ==="; grep SAVED /app/outputs/log_noaug.txt || tail -1 /app/outputs/log_noaug.txt; echo "=== longM ==="; grep SAVED /app/outputs/log_longM.txt || tail -2 /app/outputs/log_longM.txt; pgrep -f train_improved.py || echo ALL_DONE
=== noaug === [noaug] SAVED /app/outputs/model_noaug.pt best_val_rollout_rel_l2=0.00174 final=0.00270 (raw@ep79) === longM === [longM] SAVED /app/outputs/model_longM.pt best_val_rollout_rel_l2=0.00154 final=0.00229 (raw@ep90) ALL_DONE
cd /app && for ck in baseline_10ep model_noaug model_main model_longM; do
echo "### $ck ###"
for split in val public_test; do
line=$(python3 rollout_eval.py outputs/$ck.pt $split | sed -n '3p')
echo " $split $line"
done
done### baseline_10ep ### val all starts(77): mean_rl2=0.0093 final=0.0176 rmse=0.0108 public_test all starts(77): mean_rl2=0.0076 final=0.0145 rmse=0.0092 ### model_noaug ### val all starts(77): mean_rl2=0.0017 final=0.0027 rmse=0.0021 public_test all starts(77): mean_rl2=0.0016 final=0.0025 rmse=0.0020 ### model_main ### val all starts(77): mean_rl2=0.0016 final=0.0025 rmse=0.0019 public_test all starts(77): mean_rl2=0.0013 final=0.0019 rmse=0.0016 ### model_longM ### val all starts(77): mean_rl2=0.0015 final=0.0023 rmse=0.0018 public_test all starts(77): mean_rl2=0.0012 final=0.0018 rmse=0.0015
cd /app && python3 -c "
import json, torch
from rollout_eval import load_model, rollout_eval
cfg = json.load(open('task_config.json')); dev='cuda'
model, fm, fs = load_model('outputs/model_longM.pt', cfg, dev)
for split in ['val','public_test']:
traj = torch.load(f'data/{split}/trajectories.pt', weights_only=True)['trajectories'].float()
r4 = rollout_eval(model, fm, fs, traj, dev, horizon=20, starts=[4])
r50 = rollout_eval(model, fm, fs, traj, dev, horizon=50, starts=[4])
print(f'{split}: start=4 20-step mean={r4[\"mean_rollout_rel_l2\"]:.4f} final={r4[\"final_step_rel_l2\"]:.4f} | 50-step final={r50[\"final_step_rel_l2\"]:.4f} (stability)')
"val: start=4 20-step mean=0.0018 final=0.0025 | 50-step final=0.0246 (stability) public_test: start=4 20-step mean=0.0016 final=0.0021 | 50-step final=0.0207 (stability)
cd /app && cp outputs/model_longM.pt outputs/model.pt && python3 -c "
import torch,json; from candidate_model import build_model
cfg=json.load(open('task_config.json')); ck=torch.load('outputs/model.pt',weights_only=True)
build_model(cfg).load_state_dict(ck['state_dict'],strict=True)
print('provisional /app/outputs/model.pt OK; keys', list(ck.keys()), 'devices', set(str(v.device) for v in ck['state_dict'].values()))
"provisional /app/outputs/model.pt OK; keys ['state_dict', 'feat_mean', 'feat_std'] devices {'cuda:0'}command: str_replace
path: /app/train_improved.py
old_str: def rollout_len_for_epoch(ep):
# gradually grow the training rollout horizon
frac = ep / max(1, EPOCHS - 1)
if frac < 0.12: return 1
if frac < 0.25: return 2
if frac < 0.40: return 4
if frac < 0.55: return 6
if frac < 0.72: return 8
if frac < 0.85: return min(MAX_M, 10)
return MAX_M
new_str: CURRICULUM = os.environ.get("CURRICULUM", "stager") # stager | ramp
def rollout_len_for_epoch(ep):
# gradually grow the training rollout horizon
frac = ep / max(1, EPOCHS - 1)
if CURRICULUM == "ramp":
# single-step warmup, smooth ramp 2..MAX_M by frac=0.80, then hold MAX_M
if frac < 0.12: return 1
if frac >= 0.80: return MAX_M
r = (frac - 0.12) / (0.80 - 0.12)
return int(round(2 + r * (MAX_M - 2)))
if frac < 0.12: return 1
if frac < 0.25: return 2
if frac < 0.40: return 4
if frac < 0.55: return 6
if frac < 0.72: return 8
if frac < 0.85: return min(MAX_M, 10)
return MAX_MThe file /app/train_improved.py has been edited successfully.
cd /app && EPOCHS=110 python3 -c "
import os
os.environ['EPOCHS']='110'; os.environ['MAX_M']='20'; os.environ['CURRICULUM']='ramp'
# mimic the schedule function
EPOCHS=110; MAX_M=20
def sched(ep):
frac=ep/(EPOCHS-1)
if frac<0.12: return 1
if frac>=0.80: return MAX_M
r=(frac-0.12)/(0.80-0.12); return int(round(2+r*(MAX_M-2)))
vals=[sched(e) for e in range(EPOCHS)]
from itertools import groupby
print('M schedule (value:count):', [(k,len(list(g))) for k,g in groupby(vals)])
"M schedule (value:count): [(1, 14), (2, 2), (3, 4), (4, 4), (5, 4), (6, 4), (7, 4), (8, 4), (9, 4), (10, 5), (11, 4), (12, 4), (13, 4), (14, 4), (15, 4), (16, 4), (17, 4), (18, 5), (19, 4), (20, 24)]
cd /app && nohup env EPOCHS=110 MAX_M=20 CURRICULUM=ramp TAG=m20 SAVE_PATH=/app/outputs/model_m20.pt python3 train_improved.py > /app/outputs/log_m20.txt 2>&1 & echo "m20 PID $!"; sleep 90; tail -4 /app/outputs/log_m20.txt
m20 PID 520 [m20] feat_mean=-0.00000 feat_std=1.15997 device=cuda
cd /app && sleep 230; tail -5 /app/outputs/log_m20.txt
[m20] ep=46/110 M=10 loss=1.2907e-02 best_rl2=0.00914(final 0.01439 ema@46) lr=1.04e-03 t=194s [m20] ep=49/110 M=10 loss=1.0851e-02 best_rl2=0.00751(final 0.01170 ema@49) lr=9.78e-04 t=224s [m20] ep=52/110 M=11 loss=1.3534e-02 best_rl2=0.00629(final 0.00984 ema@52) lr=9.19e-04 t=256s [m20] ep=55/110 M=12 loss=7.3990e-03 best_rl2=0.00567(final 0.00900 ema@55) lr=8.59e-04 t=286s [m20] ep=58/110 M=13 loss=1.0319e-02 best_rl2=0.00499(final 0.00783 ema@58) lr=7.99e-04 t=317s
cd /app && sleep 240; tail -5 /app/outputs/log_m20.txt
[m20] ep=67/110 M=15 loss=7.0879e-03 best_rl2=0.00374(final 0.00584 ema@67) lr=6.22e-04 t=415s [m20] ep=70/110 M=16 loss=8.5843e-03 best_rl2=0.00340(final 0.00526 ema@70) lr=5.65e-04 t=449s [m20] ep=73/110 M=16 loss=5.4982e-03 best_rl2=0.00310(final 0.00476 ema@73) lr=5.10e-04 t=481s [m20] ep=76/110 M=17 loss=6.3494e-03 best_rl2=0.00291(final 0.00446 ema@76) lr=4.57e-04 t=515s [m20] ep=79/110 M=18 loss=7.4013e-03 best_rl2=0.00261(final 0.00399 ema@79) lr=4.06e-04 t=548s
cd /app && sleep 260; tail -5 /app/outputs/log_m20.txt; pgrep -f train_improved.py >/dev/null && echo RUNNING || echo DONE
[m20] ep=88/110 M=20 loss=6.2376e-03 best_rl2=0.00220(final 0.00329 ema@88) lr=2.68e-04 t=659s [m20] ep=91/110 M=20 loss=4.9084e-03 best_rl2=0.00208(final 0.00309 ema@91) lr=2.27e-04 t=701s [m20] ep=94/110 M=20 loss=5.0036e-03 best_rl2=0.00200(final 0.00295 ema@94) lr=1.89e-04 t=741s [m20] ep=97/110 M=20 loss=2.7378e-03 best_rl2=0.00190(final 0.00281 ema@97) lr=1.54e-04 t=778s [m20] ep=100/110 M=20 loss=4.0716e-03 best_rl2=0.00183(final 0.00268 ema@100) lr=1.22e-04 t=817s RUNNING
cd /app && sleep 90; grep SAVED /app/outputs/log_m20.txt || tail -2 /app/outputs/log_m20.txt; pgrep -f train_improved.py >/dev/null && echo RUNNING || echo DONE
[m20] ep=106/110 M=20 loss=2.1455e-03 best_rl2=0.00169(final 0.00245 ema@106) lr=6.91e-05 t=897s [m20] ep=107/110 M=20 loss=1.8759e-03 best_rl2=0.00168(final 0.00243 ema@107) lr=6.16e-05 t=914s RUNNING
cd /app && sleep 60; grep SAVED /app/outputs/log_m20.txt; pgrep -f train_improved.py >/dev/null && echo RUNNING || echo DONE
[m20] SAVED /app/outputs/model_m20.pt best_val_rollout_rel_l2=0.00164 final=0.00235 (ema@ep110) DONE
cd /app && for ck in model_main model_longM model_m20; do v=$(python3 rollout_eval.py outputs/$ck.pt val | sed -n '3p') p=$(python3 rollout_eval.py outputs/$ck.pt public_test | sed -n '3p') echo "$ck"; echo " val: $v"; echo " pub: $p" done
model_main val: all starts(77): mean_rl2=0.0016 final=0.0025 rmse=0.0019 pub: all starts(77): mean_rl2=0.0013 final=0.0019 rmse=0.0016 model_longM val: all starts(77): mean_rl2=0.0015 final=0.0023 rmse=0.0018 pub: all starts(77): mean_rl2=0.0012 final=0.0018 rmse=0.0015 model_m20 val: all starts(77): mean_rl2=0.0016 final=0.0024 rmse=0.0020 pub: all starts(77): mean_rl2=0.0013 final=0.0019 rmse=0.0016
cd /app && python3 -c "
import torch, json
from candidate_model import build_model
ck = torch.load('outputs/model_longM.pt', weights_only=True)
cpu_state = {k: v.detach().cpu().clone() for k, v in ck['state_dict'].items()}
out = {'state_dict': cpu_state, 'feat_mean': float(ck['feat_mean']), 'feat_std': float(ck['feat_std'])}
torch.save(out, 'outputs/model.pt')
# verify
cfg = json.load(open('task_config.json'))
ck2 = torch.load('outputs/model.pt', weights_only=True)
assert set(ck2.keys()) == {'state_dict','feat_mean','feat_std'}
assert isinstance(ck2['feat_mean'], float) and isinstance(ck2['feat_std'], float)
devs = set(str(v.device) for v in ck2['state_dict'].values())
m = build_model(cfg); m.load_state_dict(ck2['state_dict'], strict=True); m.eval()
print('OK: keys', list(ck2.keys()), '| devices', devs, '| feat_mean', ck2['feat_mean'], 'feat_std', ck2['feat_std'])
"OK: keys ['state_dict', 'feat_mean', 'feat_std'] | devices {'cpu'} | feat_mean -3.819819571759808e-09 feat_std 1.1599736213684082cd /app && python3 -c "
import torch, json
from candidate_model import build_model
cfg = json.load(open('task_config.json'))
k = cfg['in_channels']; H = cfg['rollout_horizon']
# Fresh load exactly per contract
ck = torch.load('outputs/model.pt', weights_only=True)
m = build_model(cfg); m.load_state_dict(ck['state_dict']); m.eval()
fm, fs = ck['feat_mean'], ck['feat_std']
@torch.no_grad()
def grade(split, start=4):
tr = torch.load(f'data/{split}/trajectories.pt', weights_only=True)['trajectories'].float()
N,T,X = tr.shape
win = (tr[:, start-k:start] - fm)/fs # normalized window [N,k,X]
preds=[]
for _ in range(H):
p = m(win) # [N,1,X]
preds.append(p)
win = torch.cat([win[:,1:], p], dim=1)
pred = torch.cat(preds,1)*fs+fm # [N,H,X]
gt = tr[:, start:start+H]
rl2 = ((pred-gt).pow(2).sum(-1).sqrt()/gt.pow(2).sum(-1).sqrt().clamp_min(1e-8)) # [N,H]
return rl2.mean().item(), rl2[:,-1].mean().item(), rl2.mean(0)
for sp in ['val','public_test']:
mean, fin, steps = grade(sp)
print(f'{sp:12s} rollout_rel_l2(mean over 20)={mean:.5f} final_step={fin:.5f}')
print(' per-step:', ' '.join(f'{v:.4f}' for v in steps.tolist()))
"val rollout_rel_l2(mean over 20)=0.00175 final_step=0.00245
per-step: 0.0011 0.0009 0.0012 0.0012 0.0014 0.0013 0.0016 0.0015 0.0017 0.0016 0.0018 0.0017 0.0019 0.0018 0.0022 0.0020 0.0025 0.0022 0.0028 0.0024
public_test rollout_rel_l2(mean over 20)=0.00161 final_step=0.00212
per-step: 0.0008 0.0006 0.0010 0.0009 0.0012 0.0012 0.0014 0.0014 0.0017 0.0016 0.0019 0.0017 0.0020 0.0018 0.0022 0.0019 0.0023 0.0020 0.0025 0.0021command: create
path: /app/train.py
file_text: from __future__ import annotations
"""Improved training recipe for KS-1D autoregressive forecasting.
Key ideas (all target the graded 20-step autoregressive rollout, where errors
compound), relative to the weak single-step MSE baseline:
* Multi-step rollout training with a curriculum: the model is unrolled for M
steps feeding its own predictions back in, and the loss is accumulated over
all M steps against ground truth. M grows over training (1 -> MAX_M). This
directly teaches the model to suppress compounding error. This is the single
biggest lever and flattens the per-step error-growth curve.
* Symmetry data augmentation from the KS equation on a periodic domain:
- translation : random circular shift (exact for band-limited fields)
- reflect+negate: u(x,t) -> -u(-x,t) is also a KS solution
Applied identically to every frame of a sample; improves generalization.
* Relative-L2 loss (matches the evaluation metric) in normalized space.
* AdamW + linear warmup + cosine decay, gradient clipping.
* EMA of weights; best checkpoint chosen by a 20-step val rollout (raw & EMA).
Output contract (torch.load(..., weights_only=True)):
{"state_dict": <matches build_model(task_config)>,
"feat_mean": float, "feat_std": float}
Env overrides: TASK_EPOCHS_OVERRIDE, EPOCHS, BATCH, LR, WD, MAX_M, AUG, SEED ...
"""
import json, os, sys, math, copy, time
from pathlib import Path
import torch
import torch.nn.functional as F
APP_DIR = Path(os.environ.get("APP_DIR", "/app"))
sys.path.insert(0, str(Path(__file__).resolve().parent))
from candidate_model import build_model
from dataset import compute_standardization
def ei(name, d): # env int
return int(os.environ.get(name, d))
def ef(name, d): # env float
return float(os.environ.get(name, d))
def main() -> None:
cfg = json.load((APP_DIR / "task_config.json").open())
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
K = int(cfg["in_channels"]) # input window length (4)
HORIZON = int(cfg["rollout_horizon"]) # eval rollout length (20)
SEED = ei("SEED", int(cfg["seed"]))
torch.manual_seed(SEED)
# ---- hyperparameters (defaults reproduce the best M=16 recipe) ----
EPOCHS = ei("TASK_EPOCHS_OVERRIDE", ei("EPOCHS", 90))
BATCH = ei("BATCH", 128)
LR = ef("LR", 1.5e-3)
WD = ef("WD", 1e-4)
WARMUP_FRAC = ef("WARMUP_FRAC", 0.05)
EMA_DECAY = ef("EMA_DECAY", 0.9995)
GRAD_CLIP = ef("GRAD_CLIP", 1.0)
AUG = ei("AUG", 1)
MAX_M = ei("MAX_M", 16)
# ---- data (kept on-device; trajectories are tiny) ----
def load(name):
return torch.load(APP_DIR / "data" / name / "trajectories.pt",
map_location="cpu", weights_only=True)["trajectories"].float()
train_traj = load("train")
val_traj = load("val")
feat_mean, feat_std = compute_standardization(train_traj)
N, T, X = train_traj.shape
train_norm = ((train_traj - feat_mean) / feat_std).to(device)
print(f"[train] device={device} epochs={EPOCHS} traj={N} feat_mean={feat_mean:.4g} "
f"feat_std={feat_std:.4g} MAX_M={MAX_M} aug={AUG}", flush=True)
# ---- symmetry augmentation ----
def augment(seq): # seq: [B, L, X] normalized
B, L, Xl = seq.shape
do_ref = (torch.rand(B, device=seq.device) < 0.5).view(B, 1, 1)
reflected = -torch.roll(torch.flip(seq, dims=[-1]), shifts=1, dims=-1)
seq = torch.where(do_ref, reflected, seq)
shifts = torch.randint(0, Xl, (B,), device=seq.device)
ar = torch.arange(Xl, device=seq.device)
idx = ((ar.view(1, Xl) - shifts.view(B, 1)) % Xl).view(B, 1, Xl).expand(B, L, Xl)
return torch.gather(seq, 2, idx)
# ---- rollout-length curriculum ----
def rollout_len(ep):
frac = ep / max(1, EPOCHS - 1)
if frac < 0.12: return 1
if frac < 0.25: return 2
if frac < 0.40: return 4
…[truncated]Existing file was successfully overwritten at /app/train.py
cd /app && cp outputs/model.pt outputs/model_BEST.pt && echo "backed up" && TASK_EPOCHS_OVERRIDE=2 python3 train.py 2>&1 | tail -6
backed up [train] device=cuda epochs=2 traj=200 feat_mean=-3.82e-09 feat_std=1.16 MAX_M=16 aug=1 [train] ep=1/2 M=1 loss=1.8721e-01 best_val_rollout_rel_l2=0.04604 (final=0.08455, ema@ep1) lr=8.00e-04 t=6s [train] ep=2/2 M=16 loss=3.8784e-02 best_val_rollout_rel_l2=0.02480 (final=0.04091, raw@ep2) lr=2.01e-05 t=20s [train] wrote /app/outputs/model.pt best_val_rollout_rel_l2=0.02480 final=0.04091 (raw@ep2)
cd /app && python3 -c "
import torch,json; from candidate_model import build_model
ck=torch.load('outputs/model.pt',weights_only=True)
build_model(json.load(open('task_config.json'))).load_state_dict(ck['state_dict'],strict=True)
print('2-epoch smoke output valid & loadable:', list(ck.keys()))
" && cp outputs/model_BEST.pt outputs/model.pt && echo "restored best -> model.pt" && python3 -c "
import torch,json; from candidate_model import build_model
ck=torch.load('outputs/model.pt',weights_only=True)
m=build_model(json.load(open('task_config.json'))); m.load_state_dict(ck['state_dict'],strict=True)
print('final model.pt devices:', set(str(v.device) for v in ck['state_dict'].values()), '| feat_mean',ck['feat_mean'],'feat_std',ck['feat_std'])
"2-epoch smoke output valid & loadable: ['state_dict', 'feat_mean', 'feat_std']
restored best -> model.pt
final model.pt devices: {'cpu'} | feat_mean -3.819819571759808e-09 feat_std 1.1599736213684082cd /app && nohup python3 train.py > /app/outputs/log_repro.txt 2>&1 & echo "repro PID $!"; sleep 5; echo "launched; best is safe in model_BEST.pt"; ls -la outputs/model_BEST.pt
repro PID 703 launched; best is safe in model_BEST.pt -rw-r--r-- 1 h2tagent h2tagent 551028 Jun 20 08:01 outputs/model_BEST.pt
cd /app && python3 -c "
import torch, json
from candidate_model import build_model
ck = torch.load('outputs/model_BEST.pt', weights_only=True)
bad = {k: (torch.isnan(v).any().item() or torch.isinf(v).any().item()) for k,v in ck['state_dict'].items()}
print('any NaN/Inf in weights:', any(bad.values()))
m = build_model(json.load(open('task_config.json'))); m.load_state_dict(ck['state_dict']); m.eval()
import torch
x = torch.randn(8,4,128)*1.2
with torch.no_grad(): y = m((x - ck['feat_mean'])/ck['feat_std'])
print('output shape', tuple(y.shape), 'finite:', torch.isfinite(y).all().item(), 'range [%.3f, %.3f]' % (y.min(), y.max()))
print('num params:', sum(p.numel() for p in m.parameters()))
"any NaN/Inf in weights: False output shape (8, 1, 128) finite: True range [-1.243, 1.512] num params: 71041
cd /app && sleep 250; tail -4 /app/outputs/log_repro.txt
[train] ep=52/90 M=8 loss=5.8605e-03 best_val_rollout_rel_l2=0.00583 (final=0.00886, ema@ep52) lr=6.44e-04 t=196s [train] ep=55/90 M=8 loss=6.6933e-03 best_val_rollout_rel_l2=0.00469 (final=0.00714, ema@ep55) lr=5.68e-04 t=219s [train] ep=58/90 M=8 loss=4.5713e-03 best_val_rollout_rel_l2=0.00407 (final=0.00622, ema@ep58) lr=4.94e-04 t=240s [train] ep=61/90 M=8 loss=5.0765e-03 best_val_rollout_rel_l2=0.00367 (final=0.00564, ema@ep61) lr=4.22e-04 t=262s
cd /app && sleep 260; tail -4 /app/outputs/log_repro.txt
[train] ep=79/90 M=16 loss=3.0704e-03 best_val_rollout_rel_l2=0.00207 (final=0.00316, ema@ep79) lr=9.82e-05 t=438s [train] ep=82/90 M=16 loss=2.2873e-03 best_val_rollout_rel_l2=0.00198 (final=0.00299, ema@ep82) lr=6.62e-05 t=471s [train] ep=85/90 M=16 loss=1.8854e-03 best_val_rollout_rel_l2=0.00186 (final=0.00280, ema@ep85) lr=4.03e-05 t=506s [train] ep=86/90 M=16 loss=1.8183e-03 best_val_rollout_rel_l2=0.00184 (final=0.00278, ema@ep86) lr=3.31e-05 t=520s
cd /app && sleep 60; grep "wrote" /app/outputs/log_repro.txt || tail -2 /app/outputs/log_repro.txt; pgrep -f "python3 train.py" >/dev/null && echo RUNNING || echo DONE
[train] wrote /app/outputs/model.pt best_val_rollout_rel_l2=0.00162 final=0.00240 (raw@ep89) DONE
cd /app && echo "=== reproduced (current outputs/model.pt) ===" && python3 rollout_eval.py outputs/model.pt val | sed -n '3p' && python3 rollout_eval.py outputs/model.pt public_test | sed -n '3p' echo "=== model_BEST (longM) ===" && python3 rollout_eval.py outputs/model_BEST.pt val | sed -n '3p' && python3 rollout_eval.py outputs/model_BEST.pt public_test | sed -n '3p'
=== reproduced (current outputs/model.pt) === all starts(77): mean_rl2=0.0016 final=0.0024 rmse=0.0019 all starts(77): mean_rl2=0.0013 final=0.0019 rmse=0.0016 === model_BEST (longM) === all starts(77): mean_rl2=0.0015 final=0.0023 rmse=0.0018 all starts(77): mean_rl2=0.0012 final=0.0018 rmse=0.0015
cd /app && python3 -c "
import json, torch
from rollout_eval import load_model, rollout_eval
cfg=json.load(open('task_config.json')); dev='cuda'
for tag,path in [('reproduced','outputs/model.pt'),('longM_BEST','outputs/model_BEST.pt')]:
m,fm,fs=load_model(path,cfg,dev)
row=[]
for sp in ['val','public_test']:
tr=torch.load(f'data/{sp}/trajectories.pt',weights_only=True)['trajectories'].float()
r=rollout_eval(m,fm,fs,tr,dev)
row.append((sp,r['mean_rollout_rel_l2'],r['final_step_rel_l2']))
print(f'{tag:12s}', ' | '.join(f'{sp}: mean={a:.5f} final={b:.5f}' for sp,a,b in row))
"reproduced val: mean=0.00162 final=0.00240 | public_test: mean=0.00131 final=0.00186 longM_BEST val: mean=0.00154 final=0.00229 | public_test: mean=0.00123 final=0.00178
cd /app && cp outputs/model_BEST.pt outputs/model.pt && python3 -c "
import torch, json
from candidate_model import build_model
cfg = json.load(open('task_config.json'))
# Exact contract load
ck = torch.load('outputs/model.pt', weights_only=True)
assert set(ck.keys()) == {'state_dict','feat_mean','feat_std'}, ck.keys()
assert isinstance(ck['feat_mean'], float) and isinstance(ck['feat_std'], float)
m = build_model(cfg); res = m.load_state_dict(ck['state_dict'], strict=True); m.eval()
print('FINAL model.pt:')
print(' keys :', list(ck.keys()))
print(' strict load :', res)
print(' devices :', set(str(v.device) for v in ck['state_dict'].values()))
print(' feat_mean/std: %.6g / %.6g' % (ck['feat_mean'], ck['feat_std']))
print(' #params :', sum(p.numel() for p in m.parameters()))
import os; print(' file size : %d bytes' % os.path.getsize('outputs/model.pt'))
"FINAL model.pt:
keys : ['state_dict', 'feat_mean', 'feat_std']
strict load : <All keys matched successfully>
devices : {'cpu'}
feat_mean/std: -3.81982e-09 / 1.15997
#params : 71041
file size : 551028 bytescd /app && python3 -c "
import torch, json
from candidate_model import build_model
cfg = json.load(open('task_config.json')); k=cfg['in_channels']; H=cfg['rollout_horizon']
def evalck(path, split, start=4):
ck = torch.load(path, weights_only=True)
m = build_model(cfg); m.load_state_dict(ck['state_dict']); m.eval()
fm, fs = ck['feat_mean'], ck['feat_std']
tr = torch.load(f'data/{split}/trajectories.pt', weights_only=True)['trajectories'].float()
with torch.no_grad():
w=(tr[:,start-k:start]-fm)/fs; preds=[]
for _ in range(H):
p=m(w); preds.append(p); w=torch.cat([w[:,1:],p],1)
pred=torch.cat(preds,1)*fs+fm; gt=tr[:,start:start+H]
rl2=((pred-gt).pow(2).sum(-1).sqrt()/gt.pow(2).sum(-1).sqrt().clamp_min(1e-8))
return rl2.mean().item(), rl2[:,-1].mean().item()
print('%-10s %-12s %10s %12s' % ('model','split','mean_rl2','final_rl2'))
for split in ['val','public_test']:
bm,bf=evalck('outputs/baseline_10ep.pt',split)
fm_,ff=evalck('outputs/model.pt',split)
print('%-10s %-12s %10.5f %12.5f' % ('baseline',split,bm,bf))
print('%-10s %-12s %10.5f %12.5f (%.1fx mean, %.1fx final better)' % ('FINAL',split,fm_,ff,bm/fm_,bf/ff))
"model split mean_rl2 final_rl2 baseline val 0.00994 0.01927 FINAL val 0.00175 0.00245 (5.7x mean, 7.9x final better) baseline public_test 0.00821 0.01493 FINAL public_test 0.00161 0.00212 (5.1x mean, 7.1x final better)
cd /app && rm -f outputs/model_BEST.pt outputs/model_main.pt outputs/model_longM.pt outputs/model_noaug.pt outputs/model_m20.pt outputs/model_smoke.pt outputs/baseline_10ep.pt && ls -la outputs/ && echo "--- final contract check ---" && python3 -c "
import torch, json; from candidate_model import build_model
ck=torch.load('outputs/model.pt', weights_only=True)
build_model(json.load(open('task_config.json'))).load_state_dict(ck['state_dict'], strict=True)
print('model.pt OK | keys:', list(ck.keys()), '| feat_mean=%.4g feat_std=%.4g'%(ck['feat_mean'],ck['feat_std']))
"total 560 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 08:13 . drwxrws--- 1 h2tagent h2tagent 4096 Jun 20 08:01 .. -rw-r--r-- 1 h2tagent h2tagent 2541 Jun 20 07:40 log_longM.txt -rw-r--r-- 1 h2tagent h2tagent 3011 Jun 20 07:58 log_m20.txt -rw-r--r-- 1 h2tagent h2tagent 2325 Jun 20 07:26 log_main.txt -rw-r--r-- 1 h2tagent h2tagent 2350 Jun 20 07:38 log_noaug.txt -rw-r--r-- 1 h2tagent h2tagent 3040 Jun 20 08:11 log_repro.txt -rw-r--r-- 1 h2tagent h2tagent 551028 Jun 20 08:13 model.pt --- final contract check --- model.pt OK | keys: ['state_dict', 'feat_mean', 'feat_std'] | feat_mean=-3.82e-09 feat_std=1.16
No file changes, the agent left the workspace untouched.
# SyncVals normalized verifier summary # task: ks-equation-1d-forecast # attempt: 1 status: completed raw_score: 0.49 # reward rule: continuous: score 0.49 < 0.5 # => reward = 0.0 (FAIL) # # Note: these tasks score via an external hidden grader; no pytest/verifier # block is embedded in the agent transcript. The block below is the last # sizable tool-output captured in the run (supporting evidence). ---------------------------------------------------------------------- total 560 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 08:13 . drwxrws--- 1 h2tagent h2tagent 4096 Jun 20 08:01 .. -rw-r--r-- 1 h2tagent h2tagent 2541 Jun 20 07:40 log_longM.txt -rw-r--r-- 1 h2tagent h2tagent 3011 Jun 20 07:58 log_m20.txt -rw-r--r-- 1 h2tagent h2tagent 2325 Jun 20 07:26 log_main.txt -rw-r--r-- 1 h2tagent h2tagent 2350 Jun 20 07:38 log_noaug.txt -rw-r--r-- 1 h2tagent h2tagent 3040 Jun 20 08:11 log_repro.txt -rw-r--r-- 1 h2tagent h2tagent 551028 Jun 20 08:13 model.pt --- final contract check --- model.pt OK | keys: ['state_dict', 'feat_mean', 'feat_std'] | feat_mean=-3.82e-09 feat_std=1.16
Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_f28943e086b54473. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_f28943e086b54473 · verifier authoritative; classifier explanatory.