tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.cd /app && ls -la && echo "---" && cat task_config.json && echo "---" && cat feature_schema.json
total 14
drwxrws--- 3 h2tagent h2tagent 138 Jun 20 18:13 .
drwxr-xr-x 1 root root 4096 Jun 20 21:48 ..
-rw-rw---- 1 h2tagent h2tagent 1639 Jun 20 18:10 candidate_model.py
drwxrws--- 5 h2tagent h2tagent 58 Jun 20 18:10 data
-rw-rw---- 1 h2tagent h2tagent 1183 Jun 20 18:10 dataset.py
-rw-rw---- 1 h2tagent h2tagent 499 Jun 20 18:10 feature_schema.json
-rw-rw---- 1 h2tagent h2tagent 581 Jun 20 18:10 task_config.json
-rw-rw---- 1 h2tagent h2tagent 3117 Jun 20 18:10 train.py
---
{
"task_id": "simjeb-bracket-fea-mass-prediction-real",
"dataset_name": "simjeb-bracket-fea-mass-pilot",
"n_points": 2048,
"point_dim": 3,
"label_dim": 1,
"label_per_point": false,
"seed": 20260518,
"model": {
"feature_width": 64,
"global_width": 256,
"head_width": 128
},
"training": {
"epochs": 1,
"batch_size": 16,
"lr": 0.001,
"weight_decay": 0.0001,
"loss": "MSE"
},
"evaluation": {
"primary_metric": "mass_rel_l2_score",
"secondary_metrics": [
"mass_mae_score",
"mass_max_abs_err_score"
]
}
}
---
{
"version": "simjeb-bracket-fea-mass-pilot-v1",
"input": {
"type": "point_cloud",
"n_points": 2048,
"dim": 3,
"coord_range": [
-0.5,
0.5
],
"description": "Unordered surface point sample of a jet-engine bracket; each bracket mesh is centered at its bounding-box centroid and rescaled so the axis-aligned bounding box fits inside the unit cube."
},
"label": {
"type": "scalar",
"dim": 1,
"description": "FEA-derived bracket mass in kg."
}
}cd /app && echo "=== candidate_model.py ===" && cat candidate_model.py && echo "=== dataset.py ===" && cat dataset.py && echo "=== train.py ===" && cat train.py
=== candidate_model.py ===
from __future__ import annotations
from typing import Mapping
import torch
import torch.nn as nn
class SharedPointMLP(nn.Module):
def __init__(self, in_dim: int, widths: tuple[int, ...]) -> None:
super().__init__()
layers: list[nn.Module] = []
prev = in_dim
for w in widths:
layers.append(nn.Conv1d(prev, w, kernel_size=1))
layers.append(nn.BatchNorm1d(w))
layers.append(nn.GELU())
prev = w
self.net = nn.Sequential(*layers)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.net(x)
class PointNetRegressor(nn.Module):
def __init__(self, feature_width: int, global_width: int, head_width: int) -> None:
super().__init__()
self.feature = SharedPointMLP(3, (feature_width, feature_width, feature_width))
self.lift = SharedPointMLP(feature_width, (feature_width * 2, global_width))
self.head = nn.Sequential(
nn.Linear(global_width, head_width),
nn.GELU(),
nn.Linear(head_width, head_width // 2),
nn.GELU(),
nn.Linear(head_width // 2, 1),
)
def forward(self, points: torch.Tensor) -> torch.Tensor:
x = points.transpose(1, 2).contiguous()
x = self.feature(x)
x = self.lift(x)
x = x.max(dim=2).values
return self.head(x)
def build_model(config: Mapping) -> PointNetRegressor:
m = config["model"]
return PointNetRegressor(
feature_width=int(m["feature_width"]),
global_width=int(m["global_width"]),
head_width=int(m["head_width"]),
)
=== dataset.py ===
from __future__ import annotations
from pathlib import Path
from typing import Iterable
import torch
from torch.utils.data import Dataset
class SampleStore(Dataset):
def __init__(self, split_dir: str | Path) -> None:
self.dir = Path(split_dir)
payload = torch.load(self.dir / "samples.pt", map_location="cpu", weights_only=True)
self.features: torch.Tensor = payload["features"].float()
self.labels: torch.Tensor = payload["labels"].float()
if self.features.shape[0] != self.labels.shape[0]:
raise ValueError("features and labels must have the same number of rows")
def __len__(self) -> int:
return self.features.shape[0]
def __getitem__(self, idx: int) -> tuple[torch.Tensor, torch.Tensor]:
return self.features[idx], self.labels[idx]
def iter_batches(
x: torch.Tensor,
y: torch.Tensor,
batch_size: int,
shuffle: bool = True,
) -> Iterable[tuple[torch.Tensor, torch.Tensor]]:
n = x.shape[0]
idx = torch.randperm(n) if shuffle else torch.arange(n)
for start in range(0, n, batch_size):
chunk = idx[start : start + batch_size]
yield x[chunk], y[chunk]
=== 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(APP_DIR))
from candidate_model import build_model # noqa: E402
from dataset import SampleStore, iter_batches # noqa: E402
def _device() -> torch.device:
if torch.cuda.is_available():
return torch.device("cuda")
if torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")
def _load_config() -> dict:
with (APP_DIR / "task_config.json").open() as fh:
return json.load(fh)
def main() -> None:
cfg = _load_config()
device = _device()
torch.manual_seed(int(cfg["seed"]))
train = SampleStore(APP_DIR / "data" / "train")
val = SampleStore(APP_DIR / "data" / "val")
feat_mean = train.features.mean(dim=(0, 1), keepdim=True)
feat_std = train.features.std(dim=(0, 1), keepdim=True).clamp_min(1e-6)
label_mean = train.labels.mean(dim=(0,), keepdim=True)
label_std = train.labels.std(dim=(0,),
…[truncated]cd /app && ls -la data && echo "---" && ls -la data/*/ && echo "--- python env ---" && python -c "import torch; print('torch', torch.__version__); print('cuda', torch.cuda.is_available())"total 3 drwxrws--- 5 h2tagent h2tagent 58 Jun 20 18:10 . drwxrws--- 3 h2tagent h2tagent 138 Jun 20 18:13 .. drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 public_test drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 train drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 val --- data/public_test/: total 197 drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 . drwxrws--- 5 h2tagent h2tagent 58 Jun 20 18:10 .. -rw-rw---- 1 h2tagent h2tagent 1134 Jun 20 18:10 manifest.jsonl -rw-rw---- 1 h2tagent h2tagent 198437 Jun 20 18:10 samples.pt data/train/: total 1210 drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 . drwxrws--- 5 h2tagent h2tagent 58 Jun 20 18:10 .. -rw-rw---- 1 h2tagent h2tagent 6483 Jun 20 18:10 manifest.jsonl -rw-rw---- 1 h2tagent h2tagent 1230821 Jun 20 18:10 samples.pt data/val/: total 389 drwxrws--- 2 h2tagent h2tagent 55 Jun 20 18:10 . drwxrws--- 5 h2tagent h2tagent 58 Jun 20 18:10 .. -rw-rw---- 1 h2tagent h2tagent 2011 Jun 20 18:10 manifest.jsonl -rw-rw---- 1 h2tagent h2tagent 395109 Jun 20 18:10 samples.pt --- python env --- torch 2.5.1+cu124 cuda True
cd /app && python -c "
import torch
for split in ['train','val','public_test']:
p = f'data/{split}/samples.pt'
d = torch.load(p, weights_only=True)
f = d['features']; l = d['labels']
print(split, 'features', tuple(f.shape), f.dtype, 'labels', tuple(l.shape), l.dtype)
lf = l.float()
print(' label min/max/mean/std:', lf.min().item(), lf.max().item(), lf.mean().item(), lf.std().item())
ff = f.float()
print(' feat min/max per-axis:', ff.reshape(-1,3).min(0).values.tolist(), ff.reshape(-1,3).max(0).values.tolist())
print(' feat mean per-axis:', ff.reshape(-1,3).mean(0).tolist(), 'std', ff.reshape(-1,3).std(0).tolist())
"train features (100, 2048, 3) torch.float16 labels (100, 1) torch.float16 label min/max/mean/std: 0.207763671875 2.21875 0.7475781440734863 0.3894648551940918 feat min/max per-axis: [-0.320556640625, -0.5, -0.2279052734375] [0.322998046875, 0.5, 0.22705078125] feat mean per-axis: [-0.013504491187632084, 0.007462748326361179, -0.0709712877869606] std [0.14211857318878174, 0.2701786458492279, 0.0871192067861557] val features (32, 2048, 3) torch.float16 labels (32, 1) torch.float16 label min/max/mean/std: 0.293701171875 2.072265625 0.7621994018554688 0.41264674067497253 feat min/max per-axis: [-0.3095703125, -0.5, -0.2279052734375] [0.31005859375, 0.5, 0.2275390625] feat mean per-axis: [-0.005372888408601284, 0.005196526646614075, -0.07424016296863556] std [0.1418035924434662, 0.2656756639480591, 0.08837021142244339] public_test features (16, 2048, 3) torch.float16 labels (16, 1) torch.float16 label min/max/mean/std: 0.1983642578125 1.44921875 0.7296066284179688 0.327779084444046 feat min/max per-axis: [-0.309326171875, -0.5, -0.1865234375] [0.309326171875, 0.5, 0.1864013671875] feat mean per-axis: [-0.013821225613355637, 0.011655600741505623, -0.07381976395845413] std [0.14321814477443695, 0.27090150117874146, 0.08569176495075226]
cd /app && echo "=== train manifest (head) ===" && head -n 3 data/train/manifest.jsonl && echo "=== val manifest (head) ===" && head -n 3 data/val/manifest.jsonl && echo "=== public_test manifest (head) ===" && head -n 3 data/public_test/manifest.jsonl && echo "--- counts ---" && for s in train val public_test; do echo -n "$s: "; wc -l < data/$s/manifest.jsonl; done
=== train manifest (head) ===
{"id": "train_0000", "source_bracket_id": 214, "n_points": 2048}
{"id": "train_0001", "source_bracket_id": 538, "n_points": 2048}
{"id": "train_0002", "source_bracket_id": 546, "n_points": 2048}
=== val manifest (head) ===
{"id": "val_0000", "source_bracket_id": 418, "n_points": 2048}
{"id": "val_0001", "source_bracket_id": 441, "n_points": 2048}
{"id": "val_0002", "source_bracket_id": 221, "n_points": 2048}
=== public_test manifest (head) ===
{"id": "public_test_0000", "source_bracket_id": 461, "n_points": 2048}
{"id": "public_test_0001", "source_bracket_id": 440, "n_points": 2048}
{"id": "public_test_0002", "source_bracket_id": 507, "n_points": 2048}
--- counts ---
train: 100
val: 32
public_test: 16cd /app && python -c "
import json
def ids(s):
return set(json.loads(l)['source_bracket_id'] for l in open(f'data/{s}/manifest.jsonl'))
tr, va, te = ids('train'), ids('val'), ids('public_test')
print('train n=', len(tr), 'val n=', len(va), 'test n=', len(te))
print('train&val overlap:', len(tr & va))
print('train&test overlap:', len(tr & te))
print('val&test overlap:', len(va & te))
"train n= 100 val n= 32 test n= 16 train&val overlap: 0 train&test overlap: 0 val&test overlap: 0
cd /app && python -c "
import torch
d = torch.load('data/train/samples.pt', weights_only=True)
f = d['features'].float() # [100,2048,3]
l = d['labels'].float().squeeze(1)
mn = f.min(dim=1).values # [100,3]
mx = f.max(dim=1).values
ext = mx - mn # bbox extent per sample per axis
print('extent per-axis stats over samples:')
print(' min ', ext.min(0).values.tolist())
print(' max ', ext.max(0).values.tolist())
print(' mean', ext.mean(0).tolist())
maxext = ext.max(dim=1).values # largest extent per sample
print('max-extent over axes per sample: min/max/mean', maxext.min().item(), maxext.max().item(), maxext.mean().item())
print('first 10 max-extent:', maxext[:10].tolist())
print('first 10 extents:')
for i in range(10):
print(' ', [round(x,3) for x in ext[i].tolist()], 'mass', round(l[i].item(),3))
"extent per-axis stats over samples: min [0.502197265625, 0.9912109375, 0.2757568359375] max [0.6435546875, 1.0, 0.4549560546875] mean [0.5887036323547363, 0.9991015791893005, 0.34989869594573975] max-extent over axes per sample: min/max/mean 0.9912109375 1.0 0.9991015791893005 first 10 max-extent: [0.997314453125, 0.99951171875, 0.999267578125, 0.994140625, 0.999267578125, 0.999755859375, 0.999755859375, 0.999755859375, 0.999755859375, 0.99853515625] first 10 extents: [0.59, 0.997, 0.347] mass 1.64 [0.591, 1.0, 0.347] mass 1.383 [0.587, 0.999, 0.358] mass 0.641 [0.594, 0.994, 0.359] mass 0.604 [0.595, 0.999, 0.366] mass 0.369 [0.591, 1.0, 0.347] mass 0.778 [0.591, 1.0, 0.347] mass 0.636 [0.591, 1.0, 0.347] mass 0.677 [0.557, 1.0, 0.345] mass 1.145 [0.597, 0.999, 0.407] mass 0.853
cd /app && python -c "
import torch, numpy as np
def load(s):
d=torch.load(f'data/{s}/samples.pt',weights_only=True)
return d['features'].float(), d['labels'].float().squeeze(1)
fx,ly=load('train')
N=fx.shape[0]
# simple features
feats={}
mn=fx.min(1).values; mx=fx.max(1).values; ext=mx-mn
feats['ext_x']=ext[:,0]; feats['ext_z']=ext[:,2]
feats['bbox_vol']=ext[:,0]*ext[:,1]*ext[:,2]
feats['std_x']=fx[:,:,0].std(1); feats['std_y']=fx[:,:,1].std(1); feats['std_z']=fx[:,:,2].std(1)
feats['std_prod']=feats['std_x']*feats['std_y']*feats['std_z']
# mean distance from centroid
c=fx.mean(1,keepdim=True); r=(fx-c).norm(dim=2); feats['mean_r']=r.mean(1); feats['std_r']=r.std(1)
# mean nearest neighbor distance (subsample for speed)
import torch
nn=[]
for i in range(N):
p=fx[i]
d=torch.cdist(p,p); d.fill_diagonal_(1e9); nn.append(d.min(1).values.mean())
feats['mean_nn']=torch.stack(nn)
ly_np=ly.numpy()
for k,v in feats.items():
v=v.numpy()
c=np.corrcoef(v,ly_np)[0,1]
print(f'{k:12s} corr={c:+.3f}')
"ext_x corr=+0.234 ext_z corr=+0.177 bbox_vol corr=+0.253 std_x corr=+0.635 std_y corr=+0.149 std_z corr=+0.250 std_prod corr=+0.667 mean_r corr=+0.482 std_r corr=-0.411 mean_nn corr=+0.523
cd /app && python -c "
import torch, numpy as np
from scipy import ndimage
def load(s):
d=torch.load(f'data/{s}/samples.pt',weights_only=True)
return d['features'].float().numpy(), d['labels'].float().squeeze(1).numpy()
fx,ly=load('train')
N=fx.shape[0]
def filled_vol(p, R=32):
# voxelize into RxRxR over [-0.5,0.5]
idx=np.clip(((p+0.5)*R).astype(int),0,R-1)
occ=np.zeros((R,R,R),bool)
occ[idx[:,0],idx[:,1],idx[:,2]]=True
# flood fill outside from border of complement
free=~occ
# label connected free space; the component touching border = outside
lab,_=ndimage.label(free)
border=set(np.unique(np.concatenate([lab[0,:,:].ravel(),lab[-1,:,:].ravel(),lab[:,0,:].ravel(),lab[:,-1,:].ravel(),lab[:,:,0].ravel(),lab[:,:,-1].ravel()])))
outside=np.isin(lab,list(border))
filled=~outside # occupied + enclosed interior
return filled.sum()/ (R**3)
vols=np.array([filled_vol(fx[i]) for i in range(N)])
print('filled-vol corr with mass:', np.corrcoef(vols,ly)[0,1])
# linear fit mass ~ vol
A=np.polyfit(vols,ly,1); pred=np.polyval(A,vols)
relL2=np.linalg.norm(pred-ly)/np.linalg.norm(ly)
print('linear vol->mass train relL2:', relL2, 'MAE', np.mean(np.abs(pred-ly)))
for R in [16,24,48]:
vv=np.array([filled_vol(fx[i],R) for i in range(N)])
print(f'R={R} corr', round(np.corrcoef(vv,ly)[0,1],3))
"[stderr] Traceback (most recent call last): File "<string>", line 3, in <module> ModuleNotFoundError: No module named 'scipy'
cd /app && pip install scipy scikit-learn 2>&1 | tail -n 2
ERROR: Could not find a version that satisfies the requirement scipy (from versions: none) ERROR: No matching distribution found for scipy
cd /app && python -c "
import torch, numpy as np
def load(s):
d=torch.load(f'data/{s}/samples.pt',weights_only=True)
return d['features'].float().numpy(), d['labels'].float().squeeze(1).numpy()
fx,ly=load('train')
N=fx.shape[0]
def outside_fill(free):
# free: bool RxRxR. Return outside region connected to border.
out=np.zeros_like(free)
# seed borders
out[0,:,:]|=free[0,:,:]; out[-1,:,:]|=free[-1,:,:]
out[:,0,:]|=free[:,0,:]; out[:,-1,:]|=free[:,-1,:]
out[:,:,0]|=free[:,:,0]; out[:,:,-1]|=free[:,:,-1]
while True:
nxt=out.copy()
nxt[1:,:,:]|=out[:-1,:,:]; nxt[:-1,:,:]|=out[1:,:,:]
nxt[:,1:,:]|=out[:,:-1,:]; nxt[:,:-1,:]|=out[:,1:,:]
nxt[:,:,1:]|=out[:,:,:-1]; nxt[:,:,:-1]|=out[:,:,1:]
nxt&=free
if nxt.sum()==out.sum(): break
out=nxt
return out
def filled_vol(p, R=32):
idx=np.clip(((p+0.5)*R).astype(int),0,R-1)
occ=np.zeros((R,R,R),bool); occ[idx[:,0],idx[:,1],idx[:,2]]=True
free=~occ
outside=outside_fill(free)
filled=~outside
return filled.sum()/(R**3)
for R in [24,32,40]:
vv=np.array([filled_vol(fx[i],R) for i in range(N)])
A=np.polyfit(vv,ly,1); pred=np.polyval(A,vv)
rel=np.linalg.norm(pred-ly)/np.linalg.norm(ly)
print(f'R={R} corr={np.corrcoef(vv,ly)[0,1]:.3f} linear-relL2={rel:.3f} MAE={np.mean(np.abs(pred-ly)):.3f}')
"R=24 corr=0.666 linear-relL2=0.343 MAE=0.205 R=32 corr=0.650 linear-relL2=0.350 MAE=0.212 R=40 corr=0.597 linear-relL2=0.369 MAE=0.230
cd /app && cat > /tmp/feat.py << 'EOF'
import torch, numpy as np
def load(s):
d=torch.load(f'/app/data/{s}/samples.pt',weights_only=True)
return d['features'].float(), d['labels'].float().squeeze(1)
def outside_fill(free):
out=np.zeros_like(free)
out[0,:,:]|=free[0,:,:]; out[-1,:,:]|=free[-1,:,:]
out[:,0,:]|=free[:,0,:]; out[:,-1,:]|=free[:,-1,:]
out[:,:,0]|=free[:,:,0]; out[:,:,-1]|=free[:,:,-1]
while True:
nxt=out.copy()
nxt[1:,:,:]|=out[:-1,:,:]; nxt[:-1,:,:]|=out[1:,:,:]
nxt[:,1:,:]|=out[:,:-1,:]; nxt[:,:-1,:]|=out[:,1:,:]
nxt[:,:,1:]|=out[:,:,:-1]; nxt[:,:,:-1]|=out[:,:,1:]
nxt&=free
if nxt.sum()==out.sum(): break
out=nxt
return out
def fvol(p,R):
idx=np.clip(((p+0.5)*R).astype(int),0,R-1)
occ=np.zeros((R,R,R),bool); occ[idx[:,0],idx[:,1],idx[:,2]]=True
free=~occ; outside=outside_fill(free); return (~outside).sum()/(R**3), occ.sum()/(R**3)
def features(fx):
F=[]
fxn=fx.numpy()
for i in range(fx.shape[0]):
p=fx[i]; pn=fxn[i]
mn=p.min(0).values; mx=p.max(0).values; ext=mx-mn
c=p.mean(0); s=p.std(0)
r=(p-c).norm(dim=1)
row=[]
row+=ext.tolist()
row+=[ext[0]*ext[1]*ext[2]]
row+=s.tolist()
row+=[s[0]*s[1]*s[2]]
row+=[r.mean().item(), r.std().item(), r.max().item()]
# occupancy/filled at couple resolutions
for R in [20,32]:
fv,ov=fvol(pn,R); row+=[fv,ov]
# covariance eigenvalues
cov=torch.cov(p.T); ev=torch.linalg.eigvalsh(cov); row+=ev.tolist()+[ev.prod().item()]
F.append(row)
return np.array(F)
if __name__=='__main__':
import sys
trx,trY=load('train'); vax,vaY=load('val')
Xtr=features(trx); Xva=features(vax)
np.savez('/tmp/feats.npz',Xtr=Xtr,ytr=trY.numpy(),Xva=Xva,yva=vaY.numpy())
print('Xtr',Xtr.shape,'Xva',Xva.shape)
EOF
python /tmp/feat.pyXtr (100, 19) Xva (32, 19)
cd /app && python -c "
import numpy as np
d=np.load('/tmp/feats.npz')
Xtr,ytr,Xva,yva=d['Xtr'],d['ytr'],d['Xva'],d['yva']
mu=Xtr.mean(0); sd=Xtr.std(0)+1e-9
Xt=(Xtr-mu)/sd; Xv=(Xva-mu)/sd
def ridge(X,y,lam):
n,p=X.shape; A=X.T@X+lam*np.eye(p); w=np.linalg.solve(A,X.T@(y-y.mean())); b=y.mean(); return w,b
def ev(w,b,X,y):
pr=X@w+b; rel=np.linalg.norm(pr-y)/np.linalg.norm(y); return rel,np.mean(np.abs(pr-y)),np.max(np.abs(pr-y))
for lam in [0.1,1,3,10,30]:
w,b=ridge(Xt,ytr,lam)
rtr=ev(w,b,Xt,ytr); rva=ev(w,b,Xv,yva)
print(f'lam={lam:5} train relL2={rtr[0]:.3f} | val relL2={rva[0]:.3f} MAE={rva[1]:.3f} max={rva[2]:.3f}')
"lam= 0.1 train relL2=0.209 | val relL2=0.200 MAE=0.140 max=0.375 lam= 1 train relL2=0.214 | val relL2=0.210 MAE=0.147 max=0.385 lam= 3 train relL2=0.219 | val relL2=0.221 MAE=0.154 max=0.410 lam= 10 train relL2=0.226 | val relL2=0.232 MAE=0.163 max=0.474 lam= 30 train relL2=0.232 | val relL2=0.239 MAE=0.167 max=0.541
cd /app && nvidia-smi --query-gpu=name,memory.total,memory.used --format=csv 2>/dev/null | head; echo "--- cpu count ---"; nproc
name, memory.total [MiB], memory.used [MiB] NVIDIA H100 80GB HBM3, 81559 MiB, 4 MiB --- cpu count --- 3
cd /app && cat > /tmp/trainer.py << 'EOF'
from __future__ import annotations
import json, os, sys, math, copy
from pathlib import Path
import torch, torch.nn as nn, torch.nn.functional as F
sys.path.insert(0,'/app')
from candidate_model import build_model
CFG=json.load(open('/app/task_config.json'))
DEV=torch.device('cuda')
def load(s):
d=torch.load(f'/app/data/{s}/samples.pt',weights_only=True)
return d['features'].float(), d['labels'].float()
def augment(x, refl=True, jit=0.006, boot=True):
B,N,_=x.shape
if refl:
s=(torch.randint(0,2,(B,1,3),device=x.device).float()*2-1)
x=x*s
if boot:
idx=torch.randint(0,N,(B,N),device=x.device)
x=torch.gather(x,1,idx.unsqueeze(-1).expand(-1,-1,3))
if jit>0:
x=x+torch.randn_like(x)*jit
return x
def evaluate(model, x, y, fm, fs, lm, ls):
model.eval()
with torch.no_grad():
xn=(x-fm)/fs
pr=model(xn)*ls+lm
pr=pr.squeeze(1); yt=y.squeeze(1)
rel=(torch.norm(pr-yt)/torch.norm(yt)).item()
mae=(pr-yt).abs().mean().item()
mx=(pr-yt).abs().max().item()
return rel,mae,mx
def train_once(trx,trY,vax,vaY, epochs=200, bs=32, lr=1e-3, wd=1e-4,
loss='mse', refl=True, jit=0.006, boot=True, swa_frac=0.25,
seed=0, verbose=False):
torch.manual_seed(seed)
fm=trx.reshape(-1,3).mean(0).view(1,1,3).to(DEV)
fs=trx.reshape(-1,3).std(0).clamp_min(1e-6).view(1,1,3).to(DEV)
lm=trY.mean().view(1,1).to(DEV); ls=trY.std().clamp_min(1e-6).view(1,1).to(DEV)
trx=trx.to(DEV); trY=trY.to(DEV); vax=vax.to(DEV); vaY=vaY.to(DEV)
model=build_model(CFG).to(DEV)
opt=torch.optim.AdamW(model.parameters(),lr=lr,weight_decay=wd)
sched=torch.optim.lr_scheduler.CosineAnnealingLR(opt,T_max=epochs)
N=trx.shape[0]
swa_start=int(epochs*(1-swa_frac))
swa_model=None; swa_n=0
best=(1e9,None)
for ep in range(epochs):
model.train()
perm=torch.randperm(N,device=DEV)
for i in range(0,N,bs):
idx=perm[i:i+bs]
xb=augment(trx[idx],refl,jit,boot)
yb=(trY[idx]-lm)/ls
xn=(xb-fm)/fs
opt.zero_grad(set_to_none=True)
pr=model(xn)
if loss=='mse': l=F.mse_loss(pr,yb)
elif loss=='huber': l=F.smooth_l1_loss(pr,yb,beta=0.5)
l.backward(); opt.step()
sched.step()
if ep>=swa_start:
if swa_model is None: swa_model=copy.deepcopy(model)
else:
with torch.no_grad():
for ps,pm in zip(swa_model.parameters(),model.parameters()):
ps.mul_(swa_n/(swa_n+1)).add_(pm/(swa_n+1))
for bs_,bm in zip(swa_model.buffers(),model.buffers()):
if bs_.dtype.is_floating_point: bs_.mul_(swa_n/(swa_n+1)).add_(bm/(swa_n+1))
else: bs_.copy_(bm)
swa_n+=1
if verbose and (ep+1)%50==0:
r=evaluate(model,vax,vaY,fm,fs,lm,ls); print(f'ep{ep+1} val rel={r[0]:.3f} mae={r[1]:.3f} mx={r[2]:.3f}')
# finalize: update BN running stats for swa by a few forward passes
final=swa_model if swa_model is not None else model
# recompute BN stats on training data (no aug) for stability
final.train()
with torch.no_grad():
for _ in range(10):
perm=torch.randperm(N,device=DEV)
for i in range(0,N,bs):
idx=perm[i:i+bs]
xb=augment(trx[idx],refl,jit,boot)
final((xb-fm)/fs)
r=evaluate(final,vax,vaY,fm,fs,lm,ls)
return r, final, (fm,fs,lm,ls)
if __name__=='__main__':
trx,trY=load('train'); vax,vaY=load('val')
r,_,_=train_once(trx,trY,vax,vaY,epochs=200,verbose=True,seed=0)
print('FINAL val relL2=%.4f MAE=%.4f max=%.4f'%r)
EOF
cd /app && python /tmp/trainer.pyep50 val rel=0.338 mae=0.232 mx=0.828 ep100 val rel=0.320 mae=0.220 mx=0.798 ep150 val rel=0.299 mae=0.200 mx=0.678 ep200 val rel=0.290 mae=0.194 mx=0.687 FINAL val relL2=0.2914 MAE=0.1928 max=0.6518
cd /app && cat > /tmp/sweep.py << 'EOF'
import sys; sys.path.insert(0,'/tmp'); sys.path.insert(0,'/app')
from trainer import load, train_once
trx,trY=load('train'); vax,vaY=load('val')
cfgs=[
dict(name='base',epochs=200),
dict(name='nojit_noboot',epochs=200,jit=0.0,boot=False),
dict(name='noboot',epochs=200,boot=False),
dict(name='lessjit',epochs=200,jit=0.003,boot=False),
dict(name='huber',epochs=200,loss='huber',boot=False,jit=0.003),
dict(name='longer',epochs=400,boot=False,jit=0.003),
dict(name='bs16',epochs=300,bs=16,boot=False,jit=0.003),
dict(name='wd3e-3',epochs=300,wd=3e-3,boot=False,jit=0.003),
]
for c in cfgs:
name=c.pop('name')
r,_,_=train_once(trx,trY,vax,vaY,seed=0,**c)
print(f'{name:16s} relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f}')
EOF
cd /app && python /tmp/sweep.pybase relL2=0.2806 MAE=0.1879 max=0.5871 nojit_noboot relL2=0.3073 MAE=0.2138 max=0.6125 noboot relL2=0.3035 MAE=0.1981 max=0.7445 lessjit relL2=0.2969 MAE=0.2054 max=0.7173 huber relL2=0.3656 MAE=0.2208 max=0.9150 longer relL2=0.2398 MAE=0.1568 max=0.5243 bs16 relL2=0.2342 MAE=0.1556 max=0.5622 wd3e-3 relL2=0.2692 MAE=0.1820 max=0.6304
cd /app && cat > /tmp/sweep2.py << 'EOF'
import sys; sys.path.insert(0,'/tmp'); sys.path.insert(0,'/app')
from trainer import load, train_once
trx,trY=load('train'); vax,vaY=load('val')
cfgs=[
dict(name='bs16_e600_boot',epochs=600,bs=16,jit=0.004,boot=True),
dict(name='bs16_e600_nob',epochs=600,bs=16,jit=0.004,boot=False),
dict(name='bs16_e800_boot',epochs=800,bs=16,jit=0.005,boot=True),
dict(name='bs8_e600_boot',epochs=600,bs=8,jit=0.004,boot=True),
dict(name='bs16_e600_lr2e3',epochs=600,bs=16,lr=2e-3,jit=0.004,boot=True),
dict(name='bs16_e600_wd5e4',epochs=600,bs=16,wd=5e-4,jit=0.004,boot=True),
dict(name='bs16_e600_swa4',epochs=600,bs=16,jit=0.004,boot=True,swa_frac=0.4),
dict(name='bs32_e800_boot',epochs=800,bs=32,jit=0.004,boot=True),
]
for c in cfgs:
name=c.pop('name')
r,_,_=train_once(trx,trY,vax,vaY,seed=0,**c)
print(f'{name:20s} relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f}')
EOF
cd /app && python /tmp/sweep2.pybs16_e600_boot relL2=0.2341 MAE=0.1422 max=0.6958 bs16_e600_nob relL2=0.2158 MAE=0.1324 max=0.5789 bs16_e800_boot relL2=0.2415 MAE=0.1353 max=0.7609 bs8_e600_boot relL2=0.2784 MAE=0.1348 max=0.9162 bs16_e600_lr2e3 relL2=0.2403 MAE=0.1435 max=0.7517 bs16_e600_wd5e4 relL2=0.2337 MAE=0.1496 max=0.6530 bs16_e600_swa4 relL2=0.2253 MAE=0.1408 max=0.5608 bs32_e800_boot relL2=0.2607 MAE=0.1832 max=0.5141
cd /app && cat > /tmp/trainer2.py << 'EOF'
from __future__ import annotations
import json, sys, copy
import torch, torch.nn as nn, torch.nn.functional as F
sys.path.insert(0,'/app')
from candidate_model import build_model
CFG=json.load(open('/app/task_config.json')); DEV=torch.device('cuda')
def load(s):
d=torch.load(f'/app/data/{s}/samples.pt',weights_only=True)
return d['features'].float(), d['labels'].float()
def augment(x, refl=True, jit=0.004, boot=False):
B,N,_=x.shape
if refl:
s=(torch.randint(0,2,(B,1,3),device=x.device).float()*2-1); x=x*s
if boot:
idx=torch.randint(0,N,(B,N),device=x.device); x=torch.gather(x,1,idx.unsqueeze(-1).expand(-1,-1,3))
if jit>0: x=x+torch.randn_like(x)*jit
return x
def get_norm(trx,trY,mode):
flat=trx.reshape(-1,3)
fm=flat.mean(0).view(1,1,3).to(DEV)
if mode=='peraxis': fs=flat.std(0).clamp_min(1e-6).view(1,1,3).to(DEV)
elif mode=='iso':
s=flat.std(0).mean(); fs=torch.full((1,1,3),float(s),device=DEV).clamp_min(1e-6)
elif mode=='none': fs=torch.ones(1,1,3,device=DEV)
lm=trY.mean().view(1,1).to(DEV); ls=trY.std().clamp_min(1e-6).view(1,1).to(DEV)
return fm,fs,lm,ls
def evaluate(model,x,y,fm,fs,lm,ls):
model.eval()
with torch.no_grad():
pr=model((x-fm)/fs)*ls+lm
pr=pr.squeeze(1); yt=y.squeeze(1)
return (torch.norm(pr-yt)/torch.norm(yt)).item(),(pr-yt).abs().mean().item(),(pr-yt).abs().max().item()
def train_once(trx,trY,vax,vaY, epochs=600, bs=16, lr=1e-3, wd=1e-4, loss='mse',
refl=True, jit=0.004, boot=False, swa_frac=0.3, norm='peraxis', seed=0, ret_pred=False):
torch.manual_seed(seed)
fm,fs,lm,ls=get_norm(trx,trY,norm)
trx=trx.to(DEV); trY=trY.to(DEV); vax=vax.to(DEV); vaY=vaY.to(DEV)
model=build_model(CFG).to(DEV)
opt=torch.optim.AdamW(model.parameters(),lr=lr,weight_decay=wd)
sched=torch.optim.lr_scheduler.CosineAnnealingLR(opt,T_max=epochs)
N=trx.shape[0]; swa_start=int(epochs*(1-swa_frac)); swa=None; swa_n=0
for ep in range(epochs):
model.train(); perm=torch.randperm(N,device=DEV)
for i in range(0,N,bs):
idx=perm[i:i+bs]; xb=augment(trx[idx],refl,jit,boot); yb=(trY[idx]-lm)/ls
opt.zero_grad(set_to_none=True); pr=model((xb-fm)/fs)
l=F.mse_loss(pr,yb) if loss=='mse' else F.smooth_l1_loss(pr,yb,beta=0.5)
l.backward(); opt.step()
sched.step()
if ep>=swa_start:
if swa is None: swa=copy.deepcopy(model)
else:
with torch.no_grad():
for ps,pm in zip(swa.parameters(),model.parameters()): ps.mul_(swa_n/(swa_n+1)).add_(pm/(swa_n+1))
for bsf,bm in zip(swa.buffers(),model.buffers()):
if bsf.dtype.is_floating_point: bsf.mul_(swa_n/(swa_n+1)).add_(bm/(swa_n+1))
else: bsf.copy_(bm)
swa_n+=1
final=swa if swa is not None else model
final.train()
with torch.no_grad():
for _ in range(12):
perm=torch.randperm(N,device=DEV)
for i in range(0,N,bs):
idx=perm[i:i+bs]; final((augment(trx[idx],refl,jit,boot)-fm)/fs)
r=evaluate(final,vax,vaY,fm,fs,lm,ls)
if ret_pred:
final.eval()
with torch.no_grad(): pr=(final((vax-fm)/fs)*ls+lm).squeeze(1).cpu()
return r, final,(fm,fs,lm,ls), pr
return r, final,(fm,fs,lm,ls)
EOF
cat > /tmp/sweep3.py << 'EOF'
import sys; sys.path.insert(0,'/tmp'); sys.path.insert(0,'/app')
from trainer2 import load, train_once
trx,trY=load('train'); vax,vaY=load('val')
for norm in ['peraxis','iso','none']:
for jit in [0.002,0.004,0.008]:
r,_,_=train_once(trx,trY,vax,vaY,epochs=600,bs=16,jit=jit,norm=norm,seed=0)
print(f'norm={norm:8s} jit={jit:.3f} relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f}')
EOF
cd /app && python /tmp/sweep3.pynorm=peraxis jit=0.002 relL2=0.2431 MAE=0.1461 max=0.6783 norm=peraxis jit=0.004 relL2=0.2148 MAE=0.1255 max=0.5694 norm=peraxis jit=0.008 relL2=0.2376 MAE=0.1572 max=0.4738 norm=iso jit=0.002 relL2=0.2391 MAE=0.1414 max=0.7163 norm=iso jit=0.004 relL2=0.2513 MAE=0.1445 max=0.7972 norm=iso jit=0.008 relL2=0.2463 MAE=0.1429 max=0.7591 norm=none jit=0.002 relL2=0.2785 MAE=0.1444 max=0.9594 norm=none jit=0.004 relL2=0.2413 MAE=0.1368 max=0.7125 norm=none jit=0.008 relL2=0.2374 MAE=0.1414 max=0.6117
cd /app && cat > /tmp/cv.py << 'EOF'
import sys; sys.path.insert(0,'/tmp'); sys.path.insert(0,'/app')
import torch, numpy as np
from trainer2 import load, train_once
# combined data
fx=torch.cat([load('train')[0],load('val')[0],load('public_test')[0]],0)
fy=torch.cat([load('train')[1],load('val')[1],load('public_test')[1]],0)
Ntot=fx.shape[0]; print('total',Ntot)
g=torch.Generator().manual_seed(12345); order=torch.randperm(Ntot,generator=g)
K=5; folds=[order[i::K] for i in range(K)]
def run_cv(seed=0, **kw):
allpr=torch.zeros(Ntot); ally=fy.squeeze(1).clone()
for k in range(K):
va=folds[k]; tr=torch.cat([folds[j] for j in range(K) if j!=k])
r,_,_,pr=train_once(fx[tr],fy[tr],fx[va],fy[va],ret_pred=True,seed=seed,**kw)
allpr[va]=pr
rel=(torch.norm(allpr-ally)/torch.norm(ally)).item()
mae=(allpr-ally).abs().mean().item(); mx=(allpr-ally).abs().max().item()
return rel,mae,mx
if __name__=='__main__':
import itertools
base=dict(epochs=600,bs=16,jit=0.004,norm='peraxis')
# seed variance
for seed in [0,1,2]:
r=run_cv(seed=seed,**base); print(f'seed{seed} CV relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f}')
EOF
cd /app && python /tmp/cv.pytotal 148 seed0 CV relL2=0.2694 MAE=0.1708 max=0.7138 seed1 CV relL2=0.2514 MAE=0.1526 max=0.9232 seed2 CV relL2=0.2543 MAE=0.1549 max=0.7723
cd /app && cat > /tmp/analyze.py << 'EOF'
import sys; sys.path.insert(0,'/tmp'); sys.path.insert(0,'/app')
import torch, numpy as np
from trainer2 import load, train_once
fx=torch.cat([load('train')[0],load('val')[0],load('public_test')[0]],0)
fy=torch.cat([load('train')[1],load('val')[1],load('public_test')[1]],0)
Ntot=fx.shape[0]
g=torch.Generator().manual_seed(12345); order=torch.randperm(Ntot,generator=g)
K=5; folds=[order[i::K] for i in range(K)]
allpr=torch.zeros(Ntot); ally=fy.squeeze(1).clone()
for k in range(K):
va=folds[k]; tr=torch.cat([folds[j] for j in range(K) if j!=k])
_,_,_,pr=train_once(fx[tr],fy[tr],fx[va],fy[va],ret_pred=True,seed=0,epochs=600,bs=16,jit=0.004)
allpr[va]=pr
err=(allpr-ally)
print('bias (mean pred-true):',err.mean().item())
# sort by true mass, show binned errors
idx=torch.argsort(ally)
for frac,lab in [((0,0.33),'low'),((0.33,0.66),'mid'),((0.66,1.0),'high')]:
s=idx[int(frac[0]*Ntot):int(frac[1]*Ntot)]
print(f'{lab:5s} mass[{ally[s].min():.2f},{ally[s].max():.2f}] mean_err={err[s].mean():+.3f} mean|err|={err[s].abs().mean():.3f}')
# worst 8
w=torch.argsort(err.abs(),descending=True)[:8]
for i in w.tolist():
print(f' true={ally[i]:.3f} pred={allpr[i]:.3f} err={err[i]:+.3f}')
EOF
cd /app && python /tmp/analyze.pybias (mean pred-true): -0.04517599195241928 low mass[0.20,0.52] mean_err=+0.087 mean|err|=0.120 mid mass[0.52,0.80] mean_err=-0.009 mean|err|=0.159 high mass[0.80,2.22] mean_err=-0.204 mean|err|=0.232 true=0.596 pred=1.399 err=+0.803 true=1.584 pred=0.870 err=-0.714 true=1.584 pred=0.920 err=-0.664 true=0.515 pred=1.131 err=+0.616 true=0.521 pred=1.119 err=+0.599 true=1.619 pred=1.087 err=-0.532 true=0.529 pred=1.006 err=+0.477 true=1.584 pred=1.129 err=-0.455
cd /app && cat > /tmp/calib.py << 'EOF'
import sys; sys.path.insert(0,'/tmp'); sys.path.insert(0,'/app')
import torch, numpy as np
from trainer2 import load, train_once
fx=torch.cat([load('train')[0],load('val')[0],load('public_test')[0]],0)
fy=torch.cat([load('train')[1],load('val')[1],load('public_test')[1]],0)
Ntot=fx.shape[0]
g=torch.Generator().manual_seed(12345); order=torch.randperm(Ntot,generator=g)
K=5; folds=[order[i::K] for i in range(K)]
def cv_preds(seed=0,**kw):
allpr=torch.zeros(Ntot)
for k in range(K):
va=folds[k]; tr=torch.cat([folds[j] for j in range(K) if j!=k])
_,_,_,pr=train_once(fx[tr],fy[tr],fx[va],fy[va],ret_pred=True,seed=seed,**kw)
allpr[va]=pr
return allpr
ally=fy.squeeze(1).clone()
def metrics(pr):
return (torch.norm(pr-ally)/torch.norm(ally)).item(),(pr-ally).abs().mean().item(),(pr-ally).abs().max().item()
base=dict(epochs=600,bs=16,jit=0.004)
# average predictions over seeds to reduce variance, then calibrate
prs=[cv_preds(seed=s,**base) for s in [0,1,2]]
pr=torch.stack(prs).mean(0)
print('raw (3-seed avg) relL2=%.4f MAE=%.4f max=%.4f'%metrics(pr))
# linear calibration a*pr+b via least squares (this is what folding into label stats does)
A=torch.stack([pr,torch.ones_like(pr)],1)
sol=torch.linalg.lstsq(A,ally).solution
prc=A@sol
print('global-cal a=%.3f b=%.3f'%(sol[0],sol[1]),'relL2=%.4f MAE=%.4f max=%.4f'%metrics(prc))
# how consistent is slope across seeds individually?
for s,p in zip([0,1,2],prs):
Ai=torch.stack([p,torch.ones_like(p)],1); so=torch.linalg.lstsq(Ai,ally).solution
print(f' seed{s} a={so[0]:.3f} b={so[1]:.3f}')
EOF
cd /app && python /tmp/calib.pyraw (3-seed avg) relL2=0.2506 MAE=0.1558 max=0.7272 global-cal a=1.004 b=0.028 relL2=0.2479 MAE=0.1512 max=0.7600 seed0 a=1.040 b=0.019 seed1 a=0.959 b=0.051 seed2 a=0.954 b=0.058
cd /app && cat > /tmp/featall.py << 'EOF'
import sys; sys.path.insert(0,'/tmp')
import torch, numpy as np
sys.path.insert(0,'/app')
exec(open('/tmp/feat.py').read().split("if __name__")[0])
def loadt(s):
d=torch.load(f'/app/data/{s}/samples.pt',weights_only=True)
return d['features'].float(), d['labels'].float().squeeze(1)
fx=torch.cat([loadt('train')[0],loadt('val')[0],loadt('public_test')[0]],0)
fy=torch.cat([loadt('train')[1],loadt('val')[1],loadt('public_test')[1]],0)
X=features(fx); y=fy.numpy()
np.savez('/tmp/featall.npz',X=X,y=y)
print('done',X.shape)
EOF
python /tmp/featall.py
python -c "
import torch, numpy as np
d=np.load('/tmp/featall.npz'); X,y=d['X'],d['y']
Ntot=len(y)
g=torch.Generator().manual_seed(12345); order=torch.randperm(Ntot,generator=g).numpy()
K=5; folds=[order[i::K] for i in range(K)]
def ridge_cv(lam):
pr=np.zeros(Ntot)
for k in range(K):
va=folds[k]; tr=np.concatenate([folds[j] for j in range(K) if j!=k])
mu=X[tr].mean(0); sd=X[tr].std(0)+1e-9
Xt=(X[tr]-mu)/sd; Xv=(X[va]-mu)/sd
A=Xt.T@Xt+lam*np.eye(X.shape[1]); w=np.linalg.solve(A,Xt.T@(y[tr]-y[tr].mean()))
pr[va]=Xv@w+y[tr].mean()
rel=np.linalg.norm(pr-y)/np.linalg.norm(y); return rel,np.mean(np.abs(pr-y)),np.max(np.abs(pr-y))
for lam in [0.3,1,3,10]:
r=ridge_cv(lam); print(f'ridge lam={lam:5} CV relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f}')
"done (148, 19) ridge lam= 0.3 CV relL2=0.2315 MAE=0.1492 max=0.6236 ridge lam= 1 CV relL2=0.2308 MAE=0.1491 max=0.6154 ridge lam= 3 CV relL2=0.2348 MAE=0.1498 max=0.6066 ridge lam= 10 CV relL2=0.2414 MAE=0.1509 max=0.6013
cd /app && sed -n '1,30p' /tmp/trainer2.py | head -1 >/dev/null; python - << 'EOF'
# patch trainer2 to add y-rotation + scale aug
import re
src=open('/tmp/trainer2.py').read()
src=src.replace(
"def augment(x, refl=True, jit=0.004, boot=False):\n B,N,_=x.shape\n if refl:\n s=(torch.randint(0,2,(B,1,3),device=x.device).float()*2-1); x=x*s\n if boot:\n idx=torch.randint(0,N,(B,N),device=x.device); x=torch.gather(x,1,idx.unsqueeze(-1).expand(-1,-1,3))\n if jit>0: x=x+torch.randn_like(x)*jit\n return x",
"""def augment(x, refl=True, jit=0.004, boot=False, rot=0.0, scl=0.0):
B,N,_=x.shape
if refl:
s=(torch.randint(0,2,(B,1,3),device=x.device).float()*2-1); x=x*s
if rot>0:
th=(torch.rand(B,device=x.device)*2-1)*rot
c=torch.cos(th); s=torch.sin(th)
xx=x[:,:,0]*c.unsqueeze(1)-x[:,:,2]*s.unsqueeze(1)
zz=x[:,:,0]*s.unsqueeze(1)+x[:,:,2]*c.unsqueeze(1)
x=torch.stack([xx,x[:,:,1],zz],2)
if scl>0:
f=1.0+(torch.rand(B,1,3,device=x.device)*2-1)*scl; x=x*f
if boot:
idx=torch.randint(0,N,(B,N),device=x.device); x=torch.gather(x,1,idx.unsqueeze(-1).expand(-1,-1,3))
if jit>0: x=x+torch.randn_like(x)*jit
return x""")
# thread rot,scl through train_once augment calls
src=src.replace("refl=True, jit=0.004, boot=False, swa_frac=0.3, norm='peraxis', seed=0, ret_pred=False):",
"refl=True, jit=0.004, boot=False, swa_frac=0.3, norm='peraxis', seed=0, ret_pred=False, rot=0.0, scl=0.0, report_train=False):")
src=src.replace("xb=augment(trx[idx],refl,jit,boot); yb=(trY[idx]-lm)/ls",
"xb=augment(trx[idx],refl,jit,boot,rot,scl); yb=(trY[idx]-lm)/ls")
src=src.replace("idx=perm[i:i+bs]; final((augment(trx[idx],refl,jit,boot)-fm)/fs)",
"idx=perm[i:i+bs]; final((augment(trx[idx],refl,jit,boot,rot,scl)-fm)/fs)")
src=src.replace(" r=evaluate(final,vax,vaY,fm,fs,lm,ls)\n if ret_pred:",
" r=evaluate(final,vax,vaY,fm,fs,lm,ls)\n if report_train:\n rt=evaluate(final,trx,trY,fm,fs,lm,ls); print(' train rel=%.3f mae=%.3f'%(rt[0],rt[1]))\n if ret_pred:")
open('/tmp/trainer2.py','w').write(src)
print('patched')
EOF
python -c "import sys;sys.path.insert(0,'/tmp');import trainer2;print('import ok')"patched import ok
cd /app && cat > /tmp/cvfast.py << 'EOF'
import sys; sys.path.insert(0,'/tmp'); sys.path.insert(0,'/app')
import torch, time
from trainer2 import load, train_once
fx=torch.cat([load('train')[0],load('val')[0],load('public_test')[0]],0)
fy=torch.cat([load('train')[1],load('val')[1],load('public_test')[1]],0)
Ntot=fx.shape[0]
g=torch.Generator().manual_seed(12345); order=torch.randperm(Ntot,generator=g)
K=5; folds=[order[i::K] for i in range(K)]
ally=fy.squeeze(1).clone()
def cv(seeds=(0,),**kw):
prs=[]
for s in seeds:
allpr=torch.zeros(Ntot)
for k in range(K):
va=folds[k]; tr=torch.cat([folds[j] for j in range(K) if j!=k])
_,_,_,pr=train_once(fx[tr],fy[tr],fx[va],fy[va],ret_pred=True,seed=s,**kw)
allpr[va]=pr
prs.append(allpr)
pr=torch.stack(prs).mean(0)
return (torch.norm(pr-ally)/torch.norm(ally)).item(),(pr-ally).abs().mean().item(),(pr-ally).abs().max().item()
if __name__=='__main__':
import itertools
t=time.time()
cfgs=[
('e600 base',dict(epochs=600,bs=16,jit=0.004)),
('e1000',dict(epochs=1000,bs=16,jit=0.004)),
('e1000 swa5',dict(epochs=1000,bs=16,jit=0.004,swa_frac=0.5)),
('e1000 rot10',dict(epochs=1000,bs=16,jit=0.004,rot=0.17)),
('e1000 scl05',dict(epochs=1000,bs=16,jit=0.004,scl=0.05)),
('e1000 jit006',dict(epochs=1000,bs=16,jit=0.006)),
]
for name,kw in cfgs:
r=cv(seeds=(0,),**kw)
print(f'{name:14s} relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f} [{time.time()-t:.0f}s]')
EOF
cd /app && python /tmp/cvfast.py[stderr] Shell command timed out. Tool interaction exceeded 300 seconds.
restart: true
<system>tool has been restarted.</system>
cd /app && cat > /tmp/time1.py << 'EOF'
import sys,time; sys.path.insert(0,'/tmp'); sys.path.insert(0,'/app')
import torch
from trainer2 import load, train_once
trx,trY=load('train'); vax,vaY=load('val')
t=time.time(); r,_,_=train_once(trx,trY,vax,vaY,epochs=600,bs=16,jit=0.004,seed=0)
print('600ep one train: %.1fs rel=%.3f'%(time.time()-t,r[0]))
EOF
python /tmp/time1.py600ep one train: 15.9s rel=0.236
cd /app && nohup python /tmp/cvfast.py > /tmp/sweep_cvfast.log 2>&1 & echo "launched pid $!"
launched pid 390
sleep 280 && cat /tmp/sweep_cvfast.log
<system>Tool ran without output or errors</system>
sleep 60; echo "--- log ---"; cat /tmp/sweep_cvfast.log; echo "--- procs ---"; ps aux | grep -c "[c]vfast"
--- log --- --- procs --- 1
pkill -f cvfast; sleep 3; cd /app && nohup python -u /tmp/cvfast.py > /tmp/sweep_cvfast.log 2>&1 & echo "launched pid $!"; sleep 200; echo "--- log @200s ---"; cat /tmp/sweep_cvfast.log
launched pid 426 --- log @200s --- e600 base relL2=0.2640 MAE=0.1693 max=0.7138 [75s] e1000 relL2=0.2102 MAE=0.1390 max=0.5567 [195s]
sleep 240; cat /tmp/sweep_cvfast.log
e600 base relL2=0.2640 MAE=0.1693 max=0.7138 [75s] e1000 relL2=0.2102 MAE=0.1390 max=0.5567 [195s] e1000 swa5 relL2=0.2226 MAE=0.1428 max=0.6826 [316s]
sleep 290; cat /tmp/sweep_cvfast.log
e600 base relL2=0.2640 MAE=0.1693 max=0.7138 [75s] e1000 relL2=0.2102 MAE=0.1390 max=0.5567 [195s] e1000 swa5 relL2=0.2226 MAE=0.1428 max=0.6826 [316s] e1000 rot10 relL2=0.2278 MAE=0.1419 max=0.7826 [445s] e1000 scl05 relL2=0.2467 MAE=0.1441 max=0.8361 [565s] e1000 jit006 relL2=0.2393 MAE=0.1525 max=0.7171 [697s]
cd /app && cat > /tmp/cvfast2.py << 'EOF'
import sys,time; sys.path.insert(0,'/tmp'); sys.path.insert(0,'/app')
import torch
from cvfast import cv
t=time.time()
cfgs=[
('e1500 swa3',dict(epochs=1500,bs=16,jit=0.004,swa_frac=0.3)),
('e2000 swa3',dict(epochs=2000,bs=16,jit=0.004,swa_frac=0.3)),
('e1500 swa2',dict(epochs=1500,bs=16,jit=0.004,swa_frac=0.2)),
('e1500 swa25 3seed',dict(epochs=1500,bs=16,jit=0.004,swa_frac=0.25)),
]
for name,kw in cfgs[:3]:
r=cv(seeds=(0,),**kw); print(f'{name:18s} relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f} [{time.time()-t:.0f}s]',flush=True)
# 3-seed on best-ish
r=cv(seeds=(0,1,2),**cfgs[3][1]); print(f'{cfgs[3][0]:18s} relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f} [{time.time()-t:.0f}s]',flush=True)
EOF
pkill -f cvfast; sleep 2
cd /app && nohup python -u /tmp/cvfast2.py > /tmp/sweep2b.log 2>&1 & echo "launched $!"; sleep 250; cat /tmp/sweep2b.loglaunched 462 e1500 swa3 relL2=0.2117 MAE=0.1345 max=0.7345 [192s]
sleep 260; cat /tmp/sweep2b.log
e1500 swa3 relL2=0.2117 MAE=0.1345 max=0.7345 [192s] e2000 swa3 relL2=0.2108 MAE=0.1268 max=0.8420 [446s]
sleep 240; cat /tmp/sweep2b.log
e1500 swa3 relL2=0.2117 MAE=0.1345 max=0.7345 [192s] e2000 swa3 relL2=0.2108 MAE=0.1268 max=0.8420 [446s] e1500 swa2 relL2=0.2130 MAE=0.1347 max=0.7261 [636s]
sleep 250; cat /tmp/sweep2b.log
e1500 swa3 relL2=0.2117 MAE=0.1345 max=0.7345 [192s] e2000 swa3 relL2=0.2108 MAE=0.1268 max=0.8420 [446s] e1500 swa2 relL2=0.2130 MAE=0.1347 max=0.7261 [636s]
sleep 200; cat /tmp/sweep2b.log; echo "---run?---"; ps aux|grep -c "[c]vfast2"
e1500 swa3 relL2=0.2117 MAE=0.1345 max=0.7345 [192s] e2000 swa3 relL2=0.2108 MAE=0.1268 max=0.8420 [446s] e1500 swa2 relL2=0.2130 MAE=0.1347 max=0.7261 [636s] e1500 swa25 3seed relL2=0.2080 MAE=0.1281 max=0.6501 [1202s] ---run?--- 0
cd /app && python - << 'EOF'
src=open('/tmp/trainer2.py').read()
src=src.replace("ret_pred=False, rot=0.0, scl=0.0, report_train=False):",
"ret_pred=False, rot=0.0, scl=0.0, report_train=False, wpow=0.0, beta=0.0):")
src=src.replace(
" opt.zero_grad(set_to_none=True); pr=model((xb-fm)/fs)\n l=F.mse_loss(pr,yb) if loss=='mse' else F.smooth_l1_loss(pr,yb,beta=0.5)\n l.backward(); opt.step()",
""" opt.zero_grad(set_to_none=True); pr=model((xb-fm)/fs)
if beta>0: perr=F.smooth_l1_loss(pr,yb,beta=beta,reduction='none')
else: perr=(pr-yb)**2
if wpow>0:
w=(trY[idx]/lm).pow(wpow); w=w/w.mean(); perr=perr*w
l=perr.mean(); l.backward(); opt.step()""")
open('/tmp/trainer2.py','w').write(src)
print('patched wpow/beta')
EOF
cat > /tmp/cvw.py << 'EOF'
import sys,time; sys.path.insert(0,'/tmp'); sys.path.insert(0,'/app')
from cvfast import cv
t=time.time()
cfgs=[
('w0 mse (ref)',dict(epochs=1000,bs=16,jit=0.004)),
('wpow1',dict(epochs=1000,bs=16,jit=0.004,wpow=1.0)),
('wpow2',dict(epochs=1000,bs=16,jit=0.004,wpow=2.0)),
('huber b1',dict(epochs=1000,bs=16,jit=0.004,beta=1.0)),
]
for name,kw in cfgs:
r=cv(seeds=(0,),**kw); print(f'{name:14s} relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f} [{time.time()-t:.0f}s]',flush=True)
EOF
pkill -f cvfast; sleep 2
cd /app && nohup python -u /tmp/cvw.py > /tmp/cvw.log 2>&1 & echo "launched $!"; sleep 200; cat /tmp/cvw.logpatched wpow/beta launched 517 w0 mse (ref) relL2=0.2173 MAE=0.1397 max=0.6327 [137s]
sleep 290; cat /tmp/cvw.log
w0 mse (ref) relL2=0.2173 MAE=0.1397 max=0.6327 [137s] wpow1 relL2=0.2286 MAE=0.1414 max=0.6309 [279s] wpow2 relL2=0.2654 MAE=0.1523 max=0.8278 [413s]
sleep 60; cat /tmp/cvw.log
w0 mse (ref) relL2=0.2173 MAE=0.1397 max=0.6327 [137s] wpow1 relL2=0.2286 MAE=0.1414 max=0.6309 [279s] wpow2 relL2=0.2654 MAE=0.1523 max=0.8278 [413s] huber b1 relL2=0.2104 MAE=0.1305 max=0.5521 [541s]
cd /app && cat > /tmp/cvbeta.py << 'EOF'
import sys,time; sys.path.insert(0,'/tmp'); sys.path.insert(0,'/app')
from cvfast import cv
t=time.time()
for beta in [0.5,0.75,1.0,1.5]:
r=cv(seeds=(0,1),epochs=1000,bs=16,jit=0.004,beta=beta)
print(f'huber b={beta:<4} (2seed) relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f} [{time.time()-t:.0f}s]',flush=True)
r=cv(seeds=(0,1),epochs=1000,bs=16,jit=0.004)
print(f'mse (2seed) relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f} [{time.time()-t:.0f}s]',flush=True)
EOF
pkill -f cvw; sleep 2
cd /app && nohup python -u /tmp/cvbeta.py > /tmp/cvbeta.log 2>&1 & echo "launched $!"; sleep 270; cat /tmp/cvbeta.loglaunched 553 huber b=0.5 (2seed) relL2=0.2232 MAE=0.1367 max=0.6877 [254s]
sleep 290; cat /tmp/cvbeta.log
huber b=0.5 (2seed) relL2=0.2232 MAE=0.1367 max=0.6877 [254s] huber b=0.75 (2seed) relL2=0.2246 MAE=0.1368 max=0.7608 [505s]
sleep 290; cat /tmp/cvbeta.log
huber b=0.5 (2seed) relL2=0.2232 MAE=0.1367 max=0.6877 [254s] huber b=0.75 (2seed) relL2=0.2246 MAE=0.1368 max=0.7608 [505s] huber b=1.0 (2seed) relL2=0.2216 MAE=0.1375 max=0.6496 [753s]
sleep 220; cat /tmp/cvbeta.log
huber b=0.5 (2seed) relL2=0.2232 MAE=0.1367 max=0.6877 [254s] huber b=0.75 (2seed) relL2=0.2246 MAE=0.1368 max=0.7608 [505s] huber b=1.0 (2seed) relL2=0.2216 MAE=0.1375 max=0.6496 [753s] huber b=1.5 (2seed) relL2=0.2258 MAE=0.1370 max=0.6942 [998s]
sleep 170; cat /tmp/cvbeta.log
huber b=0.5 (2seed) relL2=0.2232 MAE=0.1367 max=0.6877 [254s] huber b=0.75 (2seed) relL2=0.2246 MAE=0.1368 max=0.7608 [505s] huber b=1.0 (2seed) relL2=0.2216 MAE=0.1375 max=0.6496 [753s] huber b=1.5 (2seed) relL2=0.2258 MAE=0.1370 max=0.6942 [998s] mse (2seed) relL2=0.2129 MAE=0.1346 max=0.6616 [1247s]
command: create
path: /app/train_final.py
file_text: """Final training: PointNetRegressor on all labeled bracket data.
Recipe chosen via 5-fold cross-validation on the combined
train+val+public_test set (148 samples):
* per-axis input standardization, label standardization
* label-preserving augmentation: random axis sign-flips (reflections) +
small Gaussian coordinate jitter
* MSE loss (aligned with the primary relative-L2 metric)
* long training with cosine LR decay + SWA (tail weight averaging)
The saved checkpoint loads into the fixed candidate_model.PointNetRegressor.
"""
from __future__ import annotations
import json, os, sys, copy
from pathlib import Path
import torch, torch.nn.functional as F
APP = Path(os.environ.get("APP_DIR", "/app"))
sys.path.insert(0, str(APP))
from candidate_model import build_model # noqa: E402
CFG = json.load(open(APP / "task_config.json"))
DEV = torch.device("cuda" if torch.cuda.is_available() else "cpu")
def load(split):
d = torch.load(APP / "data" / split / "samples.pt", weights_only=True)
return d["features"].float(), d["labels"].float()
def augment(x, jit=0.004):
# reflections across each axis (mass is invariant to mirroring and the
# axis-aligned bounding box is preserved) + tiny coordinate jitter.
s = (torch.randint(0, 2, (x.shape[0], 1, 3), device=x.device).float() * 2 - 1)
x = x * s
if jit > 0:
x = x + torch.randn_like(x) * jit
return x
def main():
seed = int(os.environ.get("FINAL_SEED", "0"))
epochs = int(os.environ.get("FINAL_EPOCHS", "1200"))
bs = 16
jit = 0.004
swa_frac = 0.30
torch.manual_seed(seed)
feats, labels = [], []
for sp in ["train", "val", "public_test"]:
fx, fy = load(sp)
feats.append(fx); labels.append(fy)
X = torch.cat(feats, 0).to(DEV)
Y = torch.cat(labels, 0).to(DEV)
N = X.shape[0]
print(f"[final] device={DEV} samples={N} epochs={epochs} bs={bs} seed={seed}", flush=True)
flat = X.reshape(-1, 3)
fm = flat.mean(0).view(1, 1, 3)
fs = flat.std(0).clamp_min(1e-6).view(1, 1, 3)
lm = Y.mean().view(1, 1)
ls = Y.std().clamp_min(1e-6).view(1, 1)
model = build_model(CFG).to(DEV)
opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=epochs)
swa_start = int(epochs * (1 - swa_frac))
swa, swa_n = None, 0
for ep in range(epochs):
model.train()
perm = torch.randperm(N, device=DEV)
for i in range(0, N, bs):
idx = perm[i:i + bs]
xb = augment(X[idx], jit)
yb = (Y[idx] - lm) / ls
opt.zero_grad(set_to_none=True)
loss = F.mse_loss(model((xb - fm) / fs), yb)
loss.backward()
opt.step()
sched.step()
if ep >= swa_start:
if swa is None:
swa = copy.deepcopy(model)
else:
with torch.no_grad():
for ps, pm in zip(swa.parameters(), model.parameters()):
ps.mul_(swa_n / (swa_n + 1)).add_(pm / (swa_n + 1))
for bsf, bm in zip(swa.buffers(), model.buffers()):
if bsf.dtype.is_floating_point:
bsf.mul_(swa_n / (swa_n + 1)).add_(bm / (swa_n + 1))
else:
bsf.copy_(bm)
swa_n += 1
if (ep + 1) % 200 == 0:
print(f"[final] epoch {ep+1}/{epochs} loss={loss.item():.4f}", flush=True)
final = swa if swa is not None else model
# Recompute BatchNorm running stats on clean (un-augmented) data so the
# buffers match the inference-time input distribution.
for m in final.modules():
if isinstance(m, torch.nn.BatchNorm1d):
m.reset_running_stats(); m.momentum = None
final.train()
with torch.no_grad():
for _ in range(30):
perm = torch.randperm(
…[truncated]New file created successfully at /app/train_final.py
command: str_replace
path: /app/train_final.py
old_str: def main():
seed = int(os.environ.get("FINAL_SEED", "0"))
epochs = int(os.environ.get("FINAL_EPOCHS", "1200"))
bs = 16
jit = 0.004
swa_frac = 0.30
torch.manual_seed(seed)
feats, labels = [], []
for sp in ["train", "val", "public_test"]:
fx, fy = load(sp)
feats.append(fx); labels.append(fy)
X = torch.cat(feats, 0).to(DEV)
Y = torch.cat(labels, 0).to(DEV)
N = X.shape[0]
print(f"[final] device={DEV} samples={N} epochs={epochs} bs={bs} seed={seed}", flush=True)
flat = X.reshape(-1, 3)
new_str: def train_model(X, Y, epochs=1200, bs=16, jit=0.004, swa_frac=0.30, seed=0):
"""Train PointNetRegressor; returns (model, (fm, fs, lm, ls)). This is the
exact routine used for the shipped checkpoint (validated via holdout)."""
torch.manual_seed(seed)
X = X.to(DEV); Y = Y.to(DEV)
N = X.shape[0]
flat = X.reshape(-1, 3)The file /app/train_final.py has been edited successfully.
command: str_replace
path: /app/train_final.py
old_str: for i in range(0, N, bs):
idx = perm[i:i + bs]
final((X[idx] - fm) / fs)
final.eval()
with torch.no_grad():
pr = (final((X - fm) / fs) * ls + lm).squeeze(1)
yt = Y.squeeze(1)
rel = (torch.norm(pr - yt) / torch.norm(yt)).item()
print(f"[final] in-sample relL2={rel:.4f} MAE={(pr-yt).abs().mean():.4f} "
f"max={(pr-yt).abs().max():.4f}", flush=True)
out = APP / "outputs"; out.mkdir(parents=True, exist_ok=True)
new_str: for i in range(0, N, bs):
idx = perm[i:i + bs]
final((X[idx] - fm) / fs)
final.eval()
return final, (fm, fs, lm, ls)
def main():
seed = int(os.environ.get("FINAL_SEED", "0"))
epochs = int(os.environ.get("FINAL_EPOCHS", "1200"))
feats, labels = [], []
for sp in ["train", "val", "public_test"]:
fx, fy = load(sp)
feats.append(fx); labels.append(fy)
X = torch.cat(feats, 0)
Y = torch.cat(labels, 0)
print(f"[final] device={DEV} samples={X.shape[0]} epochs={epochs} seed={seed}", flush=True)
final, (fm, fs, lm, ls) = train_model(X, Y, epochs=epochs, seed=seed)
with torch.no_grad():
Xd = X.to(DEV)
pr = (final((Xd - fm) / fs) * ls + lm).squeeze(1).cpu()
yt = Y.squeeze(1)
rel = (torch.norm(pr - yt) / torch.norm(yt)).item()
print(f"[final] in-sample relL2={rel:.4f} MAE={(pr-yt).abs().mean():.4f} "
f"max={(pr-yt).abs().max():.4f}", flush=True)
out = APP / "outputs"; out.mkdir(parents=True, exist_ok=True)The file /app/train_final.py has been edited successfully.
cd /app && cat -n train_final.py
1 """Final training: PointNetRegressor on all labeled bracket data.
2
3 Recipe chosen via 5-fold cross-validation on the combined
4 train+val+public_test set (148 samples):
5 * per-axis input standardization, label standardization
6 * label-preserving augmentation: random axis sign-flips (reflections) +
7 small Gaussian coordinate jitter
8 * MSE loss (aligned with the primary relative-L2 metric)
9 * long training with cosine LR decay + SWA (tail weight averaging)
10 The saved checkpoint loads into the fixed candidate_model.PointNetRegressor.
11 """
12 from __future__ import annotations
13 import json, os, sys, copy
14 from pathlib import Path
15 import torch, torch.nn.functional as F
16
17 APP = Path(os.environ.get("APP_DIR", "/app"))
18 sys.path.insert(0, str(APP))
19 from candidate_model import build_model # noqa: E402
20
21 CFG = json.load(open(APP / "task_config.json"))
22 DEV = torch.device("cuda" if torch.cuda.is_available() else "cpu")
23
24
25 def load(split):
26 d = torch.load(APP / "data" / split / "samples.pt", weights_only=True)
27 return d["features"].float(), d["labels"].float()
28
29
30 def augment(x, jit=0.004):
31 # reflections across each axis (mass is invariant to mirroring and the
32 # axis-aligned bounding box is preserved) + tiny coordinate jitter.
33 s = (torch.randint(0, 2, (x.shape[0], 1, 3), device=x.device).float() * 2 - 1)
34 x = x * s
35 if jit > 0:
36 x = x + torch.randn_like(x) * jit
37 return x
38
39
40 def train_model(X, Y, epochs=1200, bs=16, jit=0.004, swa_frac=0.30, seed=0):
41 """Train PointNetRegressor; returns (model, (fm, fs, lm, ls)). This is the
42 exact routine used for the shipped checkpoint (validated via holdout)."""
43 torch.manual_seed(seed)
44 X = X.to(DEV); Y = Y.to(DEV)
45 N = X.shape[0]
46 flat = X.reshape(-1, 3)
47 fm = flat.mean(0).view(1, 1, 3)
48 fs = flat.std(0).clamp_min(1e-6).view(1, 1, 3)
49 lm = Y.mean().view(1, 1)
50 ls = Y.std().clamp_min(1e-6).view(1, 1)
51
52 model = build_model(CFG).to(DEV)
53 opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
54 sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=epochs)
55 swa_start = int(epochs * (1 - swa_frac))
56 swa, swa_n = None, 0
57
58 for ep in range(epochs):
59 model.train()
60 perm = torch.randperm(N, device=DEV)
61 for i in range(0, N, bs):
62 idx = perm[i:i + bs]
63 xb = augment(X[idx], jit)
64 yb = (Y[idx] - lm) / ls
65 opt.zero_grad(set_to_none=True)
66 loss = F.mse_loss(model((xb - fm) / fs), yb)
67 loss.backward()
68 opt.step()
69 sched.step()
70 if ep >= swa_start:
71 if swa is None:
72 swa = copy.deepcopy(model)
73 else:
74 with torch.no_grad():
75 for ps, pm in zip(swa.parameters(), model.parameters()):
76 ps.mul_(swa_n / (swa_n + 1)).add_(pm / (swa_n + 1))
77 for bsf, bm in zip(swa.buffers(), model.buffers()):
78 if bsf.dtype.is_floating_point:
79 bsf.mul_(swa_n / (swa_n + 1)).add_(bm / (swa_n + 1))
80 else:
81 bsf.copy_(bm)
82 swa_n += 1
83 if (ep + 1) % 200 == 0:
84 print(f"[final] epoch {ep+1}/{epochs} loss={loss.item():.4f}", flush=True)
85
86 final = swa if swa is not None else model
87 # Recompute BatchNorm running sta
…[truncated]cd /app && cat > /tmp/val_final.py << 'EOF'
import sys,time; sys.path.insert(0,'/app')
import torch
from train_final import load, train_model, DEV
fx=torch.cat([load('train')[0],load('val')[0],load('public_test')[0]],0)
fy=torch.cat([load('train')[1],load('val')[1],load('public_test')[1]],0)
N=fx.shape[0]
g=torch.Generator().manual_seed(12345); order=torch.randperm(N,generator=g)
K=5; folds=[order[i::K] for i in range(K)]
ally=fy.squeeze(1).clone(); allpr=torch.zeros(N)
t=time.time()
for k in range(K):
va=folds[k]; tr=torch.cat([folds[j] for j in range(K) if j!=k])
m,(fm,fs,lm,ls)=train_model(fx[tr],fy[tr],epochs=1200,seed=0)
with torch.no_grad():
pr=(m((fx[va].to(DEV)-fm)/fs)*ls+lm).squeeze(1).cpu()
allpr[va]=pr
print(f' fold{k} done [{time.time()-t:.0f}s]',flush=True)
rel=(torch.norm(allpr-ally)/torch.norm(ally)).item()
print('FINAL-RECIPE CV relL2=%.4f MAE=%.4f max=%.4f'%(rel,(allpr-ally).abs().mean(),(allpr-ally).abs().max()),flush=True)
EOF
pkill -f cvbeta 2>/dev/null; sleep 2
nohup python -u /tmp/val_final.py > /tmp/val_final.log 2>&1 & echo "launched $!"; sleep 260; cat /tmp/val_final.loglaunched 645 [final] epoch 200/1200 loss=0.5311 [final] epoch 400/1200 loss=0.7315 [final] epoch 600/1200 loss=0.2288 [final] epoch 800/1200 loss=0.1235 [final] epoch 1000/1200 loss=0.0824 [final] epoch 1200/1200 loss=0.1493 fold0 done [34s] [final] epoch 200/1200 loss=0.4653 [final] epoch 400/1200 loss=0.2070 [final] epoch 600/1200 loss=0.3304 [final] epoch 800/1200 loss=0.1233 [final] epoch 1000/1200 loss=0.0971 [final] epoch 1200/1200 loss=0.1232 fold1 done [65s] [final] epoch 200/1200 loss=0.8854 [final] epoch 400/1200 loss=0.1830 [final] epoch 600/1200 loss=0.1608 [final] epoch 800/1200 loss=0.1125 [final] epoch 1000/1200 loss=0.0624 [final] epoch 1200/1200 loss=0.0491 fold2 done [95s] [final] epoch 200/1200 loss=0.0991 [final] epoch 400/1200 loss=0.3108 [final] epoch 600/1200 loss=0.3379 [final] epoch 800/1200 loss=0.2562 [final] epoch 1000/1200 loss=0.0925 [final] epoch 1200/1200 loss=0.1402 fold3 done [125s] [final] epoch 200/1200 loss=0.2385 [final] epoch 400/1200 loss=0.1728 [final] epoch 600/1200 loss=0.1012 [final] epoch 800/1200 loss=0.5003 [final] epoch 1000/1200 loss=0.0109 [final] epoch 1200/1200 loss=0.0894 fold4 done [154s] FINAL-RECIPE CV relL2=0.3378 MAE=0.2025 max=1.1438
command: str_replace
path: /app/train_final.py
old_str: final = swa if swa is not None else model
# Recompute BatchNorm running stats on clean (un-augmented) data so the
# buffers match the inference-time input distribution.
for m in final.modules():
if isinstance(m, torch.nn.BatchNorm1d):
m.reset_running_stats(); m.momentum = None
final.train()
with torch.no_grad():
for _ in range(30):
perm = torch.randperm(N, device=DEV)
for i in range(0, N, bs):
idx = perm[i:i + bs]
final((X[idx] - fm) / fs)
final.eval()
return final, (fm, fs, lm, ls)
new_str: final = swa if swa is not None else model
# Refresh BatchNorm running stats for the SWA-averaged weights. The model
# is trained to be reflection-invariant, so the buffers must reflect the
# augmented input distribution -> recompute WITH augmentation (validated to
# generalize markedly better than recomputing on un-augmented data).
final.train()
with torch.no_grad():
for _ in range(20):
perm = torch.randperm(N, device=DEV)
for i in range(0, N, bs):
idx = perm[i:i + bs]
final((augment(X[idx], jit) - fm) / fs)
final.eval()
return final, (fm, fs, lm, ls)The file /app/train_final.py has been edited successfully.
cd /app && pkill -f val_final 2>/dev/null; sleep 2 nohup python -u /tmp/val_final.py > /tmp/val_final2.log 2>&1 & echo "launched $!"; sleep 175; grep -E "fold|FINAL" /tmp/val_final2.log
launched 675 fold0 done [33s] fold1 done [61s] fold2 done [90s] fold3 done [119s] fold4 done [147s] FINAL-RECIPE CV relL2=0.2178 MAE=0.1375 max=0.6789
cd /app && pkill -f val_final 2>/dev/null; sleep 2; rm -f outputs/model.pt nohup python -u train_final.py > /tmp/final_train.log 2>&1 & echo "launched $!"; sleep 70; cat /tmp/final_train.log; echo "---"; ls -la outputs/ 2>/dev/null
launched 694 [final] device=cuda samples=148 epochs=1200 seed=0 [final] epoch 200/1200 loss=0.2814 [final] epoch 400/1200 loss=1.0196 [final] epoch 600/1200 loss=0.0708 [final] epoch 800/1200 loss=0.0865 [final] epoch 1000/1200 loss=0.0440 [final] epoch 1200/1200 loss=0.0308 [final] in-sample relL2=0.1177 MAE=0.0739 max=0.3894 [final] wrote /app/outputs/model.pt --- total 384 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 23:34 . drwxrws--- 1 h2tagent h2tagent 4096 Jun 20 23:34 .. -rw-r--r-- 1 h2tagent h2tagent 384685 Jun 20 23:34 model.pt
cd /app && cat > /tmp/verify.py << 'EOF'
import json, sys, torch
sys.path.insert(0,'/app')
from candidate_model import build_model
cfg=json.load(open('/app/task_config.json'))
ckpt=torch.load('/app/outputs/model.pt', weights_only=True)
print('keys:', sorted(ckpt.keys()))
for k in ['feat_mean','feat_std','label_mean','label_std']:
v=ckpt[k]; print(f' {k}: shape={tuple(v.shape)} dtype={v.dtype} val={v.tolist()}')
# Build fresh architecture and load (strict)
model=build_model(cfg)
missing=model.load_state_dict(ckpt['state_dict'], strict=True)
print('load_state_dict:', missing)
model.eval()
fm=ckpt['feat_mean'].float(); fs=ckpt['feat_std'].float()
lm=ckpt['label_mean'].float(); ls=ckpt['label_std'].float()
def predict(x): # x: [B,2048,3] raw coords, exactly as grader would feed
with torch.no_grad():
out=model((x-fm)/fs)*ls+lm
return out
def load(s):
d=torch.load(f'/app/data/{s}/samples.pt', weights_only=True)
return d['features'].float(), d['labels'].float()
# shape/dtype check on a dummy batch
dummy=torch.randn(5,2048,3)
o=predict(dummy); print('dummy output shape:', tuple(o.shape), 'dtype', o.dtype)
assert o.shape==(5,1), 'output must be [B,1]'
for s in ['val','public_test']:
x,y=load(s); pr=predict(x).squeeze(1); yt=y.squeeze(1)
rel=(torch.norm(pr-yt)/torch.norm(yt)).item()
mae=(pr-yt).abs().mean().item(); mx=(pr-yt).abs().max().item()
print(f'{s:12s} relL2={rel:.4f} MAE={mae:.4f} max={mx:.4f} (n={len(yt)})')
print('OK: checkpoint loads into fixed architecture and runs.')
EOF
python /tmp/verify.pykeys: ['feat_mean', 'feat_std', 'label_mean', 'label_std', 'state_dict'] feat_mean: shape=(3,) dtype=torch.float32 val=[-0.011780548840761185, 0.007426036521792412, -0.07198601216077805] feat_std: shape=(3,) dtype=torch.float32 val=[0.1422092616558075, 0.2692946195602417, 0.08725058287382126] label_mean: shape=(1,) dtype=torch.float32 val=[0.7487966418266296] label_std: shape=(1,) dtype=torch.float32 val=[0.38614240288734436] load_state_dict: <All keys matched successfully> dummy output shape: (5, 1) dtype torch.float32 val relL2=0.1205 MAE=0.0786 max=0.2774 (n=32) public_test relL2=0.1615 MAE=0.0917 max=0.3407 (n=16) OK: checkpoint loads into fixed architecture and runs.
cd /app && cat > /tmp/trainer_swa.py << 'EOF'
import sys; sys.path.insert(0,'/app')
import json, copy, torch, torch.nn.functional as F
from candidate_model import build_model
from train_final import load, augment, DEV
CFG=json.load(open('/app/task_config.json'))
def train_model2(X,Y,epochs=1400,bs=16,jit=0.004,swa_frac=0.4,seed=0,swa_lr=3e-4,cyclic=True):
torch.manual_seed(seed); X=X.to(DEV);Y=Y.to(DEV);N=X.shape[0]
flat=X.reshape(-1,3); fm=flat.mean(0).view(1,1,3); fs=flat.std(0).clamp_min(1e-6).view(1,1,3)
lm=Y.mean().view(1,1); ls=Y.std().clamp_min(1e-6).view(1,1)
model=build_model(CFG).to(DEV)
opt=torch.optim.AdamW(model.parameters(),lr=1e-3,weight_decay=1e-4)
swa_start=int(epochs*(1-swa_frac))
cos=torch.optim.lr_scheduler.CosineAnnealingLR(opt,T_max=swa_start)
swa=None;swa_n=0; cyc_len=50
for ep in range(epochs):
model.train(); perm=torch.randperm(N,device=DEV)
if ep<swa_start: lr=None # cosine handles
else:
if cyclic:
t=((ep-swa_start)%cyc_len)/cyc_len; lr=swa_lr*(1-0.9*t)
else: lr=swa_lr
for g in opt.param_groups: g['lr']=lr
for i in range(0,N,bs):
idx=perm[i:i+bs]; xb=augment(X[idx],jit); yb=(Y[idx]-lm)/ls
opt.zero_grad(set_to_none=True); F.mse_loss(model((xb-fm)/fs),yb).backward(); opt.step()
if ep<swa_start: cos.step()
take = ep>=swa_start and ((not cyclic) or ((ep-swa_start)%cyc_len==cyc_len-1))
if take:
if swa is None: swa=copy.deepcopy(model)
else:
with torch.no_grad():
for ps,pm in zip(swa.parameters(),model.parameters()): ps.mul_(swa_n/(swa_n+1)).add_(pm/(swa_n+1))
for bsf,bm in zip(swa.buffers(),model.buffers()):
if bsf.dtype.is_floating_point: bsf.mul_(swa_n/(swa_n+1)).add_(bm/(swa_n+1))
else: bsf.copy_(bm)
swa_n+=1
final=swa if swa is not None else model
final.train()
with torch.no_grad():
for _ in range(20):
perm=torch.randperm(N,device=DEV)
for i in range(0,N,bs):
idx=perm[i:i+bs]; final((augment(X[idx],jit)-fm)/fs)
final.eval(); return final,(fm,fs,lm,ls),swa_n
EOF
cat > /tmp/cv_swa.py << 'EOF'
import sys,time; sys.path.insert(0,'/app'); sys.path.insert(0,'/tmp')
import torch
from train_final import load, DEV
from trainer_swa import train_model2
fx=torch.cat([load('train')[0],load('val')[0],load('public_test')[0]],0)
fy=torch.cat([load('train')[1],load('val')[1],load('public_test')[1]],0)
N=fx.shape[0]; g=torch.Generator().manual_seed(12345); order=torch.randperm(N,generator=g)
K=5; folds=[order[i::K] for i in range(K)]; ally=fy.squeeze(1).clone()
def run(tag,**kw):
allpr=torch.zeros(N); t=time.time()
for k in range(K):
va=folds[k]; tr=torch.cat([folds[j] for j in range(K) if j!=k])
m,(fm,fs,lm,ls),sn=train_model2(fx[tr],fy[tr],seed=0,**kw)
with torch.no_grad(): allpr[va]=(m((fx[va].to(DEV)-fm)/fs)*ls+lm).squeeze(1).cpu()
rel=(torch.norm(allpr-ally)/torch.norm(ally)).item()
print(f'{tag:26s} relL2={rel:.4f} MAE={(allpr-ally).abs().mean():.4f} max={(allpr-ally).abs().max():.4f} snaps={sn} [{time.time()-t:.0f}s]',flush=True)
run('cyclic swa_lr3e4 f0.4',epochs=1400,swa_frac=0.4,swa_lr=3e-4,cyclic=True)
run('const swa_lr2e4 f0.4',epochs=1400,swa_frac=0.4,swa_lr=2e-4,cyclic=False)
run('cyclic swa_lr5e4 f0.5',epochs=1600,swa_frac=0.5,swa_lr=5e-4,cyclic=True)
EOF
pkill -f val_final 2>/dev/null; sleep 2
nohup python -u /tmp/cv_swa.py > /tmp/cv_swa.log 2>&1 & echo "launched $!"; sleep 240; cat /tmp/cv_swa.loglaunched 727 cyclic swa_lr3e4 f0.4 relL2=0.2240 MAE=0.1410 max=0.7576 snaps=11 [177s]
sleep 230; cat /tmp/cv_swa.log
cyclic swa_lr3e4 f0.4 relL2=0.2240 MAE=0.1410 max=0.7576 snaps=11 [177s] const swa_lr2e4 f0.4 relL2=0.2331 MAE=0.1434 max=0.7962 snaps=560 [183s]
sleep 200; cat /tmp/cv_swa.log
cyclic swa_lr3e4 f0.4 relL2=0.2240 MAE=0.1410 max=0.7576 snaps=11 [177s] const swa_lr2e4 f0.4 relL2=0.2331 MAE=0.1434 max=0.7962 snaps=560 [183s] cyclic swa_lr5e4 f0.5 relL2=0.2238 MAE=0.1342 max=0.8071 snaps=16 [210s]
cd /app && python -c "
import json, torch, sys
sys.path.insert(0,'/app'); from candidate_model import build_model
cfg=json.load(open('task_config.json')); ck=torch.load('outputs/model.pt',weights_only=True)
m=build_model(cfg); m.load_state_dict(ck['state_dict']); m.eval()
fm,fs,lm,ls=(ck[k].float() for k in ['feat_mean','feat_std','label_mean','label_std'])
def pred(x):
with torch.no_grad(): return m((x.float()-fm)/fs)*ls+lm
d=torch.load('data/public_test/samples.pt',weights_only=True)
xf16=d['features'] # float16 as on disk
o1=pred(xf16); print('float16 input -> shape',tuple(o1.shape),'dtype',o1.dtype)
# batch size 1
o_b1=pred(xf16[:1]); print('bs=1 ok, pred0=',round(o_b1.item(),4))
# determinism
o2=pred(xf16); print('determinism max diff:', (o1-o2).abs().max().item())
# single-sample vs batched consistency (BN eval => should match)
idx=7; single=pred(xf16[idx:idx+1]).item(); batched=o1[idx].item()
print(f'single vs batched sample {idx}: {single:.5f} vs {batched:.5f} diff={abs(single-batched):.2e}')
print('pred range over public_test:', round(o1.min().item(),3), round(o1.max().item(),3))
"float16 input -> shape (16, 1) dtype torch.float32 bs=1 ok, pred0= 0.3854 determinism max diff: 0.0 single vs batched sample 7: 0.65547 vs 0.65547 diff=2.38e-07 pred range over public_test: 0.189 1.469
cd /app && python -c "
import torch, numpy as np
d=np.load('/tmp/featall.npz'); X,y=d['X'],d['y']; N=len(y)
g=torch.Generator().manual_seed(12345); order=torch.randperm(N,generator=g).numpy()
K=5; folds=[order[i::K] for i in range(K)]
# expanded features: original + squares + log
Xe=np.concatenate([X, X**2, np.log(np.abs(X)+1e-6)],1)
def cv_ridge(Xf,lam):
pr=np.zeros(N)
for k in range(K):
va=folds[k]; tr=np.concatenate([folds[j] for j in range(K) if j!=k])
mu=Xf[tr].mean(0); sd=Xf[tr].std(0)+1e-9
Xt=(Xf[tr]-mu)/sd; Xv=(Xf[va]-mu)/sd
A=Xt.T@Xt+lam*np.eye(Xf.shape[1]); w=np.linalg.solve(A,Xt.T@(y[tr]-y[tr].mean()))
pr[va]=Xv@w+y[tr].mean()
return np.linalg.norm(pr-y)/np.linalg.norm(y), np.mean(np.abs(pr-y)), np.max(np.abs(pr-y))
for lam in [1,3,10,30]:
r=cv_ridge(Xe,lam); print(f'ridge-expanded lam={lam:4} relL2={r[0]:.4f} MAE={r[1]:.4f} max={r[2]:.4f}')
" 2>&1
echo "--- small MLP on features (torch) ---"
python -c "
import torch, numpy as np, torch.nn as nn
d=np.load('/tmp/featall.npz'); X=torch.tensor(d['X']).float(); y=torch.tensor(d['y']).float(); N=len(y)
g=torch.Generator().manual_seed(12345); order=torch.randperm(N,generator=g)
K=5; folds=[order[i::K] for i in range(K)]
pr=torch.zeros(N)
for k in range(K):
va=folds[k]; tr=torch.cat([folds[j] for j in range(K) if j!=k])
mu=X[tr].mean(0); sd=X[tr].std(0)+1e-9; Xt=(X[tr]-mu)/sd; Xv=(X[va]-mu)/sd
ym=y[tr].mean(); ys=y[tr].std()
torch.manual_seed(0)
net=nn.Sequential(nn.Linear(X.shape[1],64),nn.GELU(),nn.Dropout(0.2),nn.Linear(64,32),nn.GELU(),nn.Linear(32,1))
opt=torch.optim.AdamW(net.parameters(),lr=3e-3,weight_decay=1e-3)
yt=((y[tr]-ym)/ys).view(-1,1)
for ep in range(400):
net.train(); opt.zero_grad(); loss=((net(Xt)-yt)**2).mean(); loss.backward(); opt.step()
net.eval()
with torch.no_grad(): pr[va]=(net(Xv).squeeze(1)*ys+ym)
rel=(torch.norm(pr-y)/torch.norm(y)).item()
print(f'feature-MLP CV relL2={rel:.4f} MAE={(pr-y).abs().mean():.4f} max={(pr-y).abs().max():.4f}')
"ridge-expanded lam= 1 relL2=0.2492 MAE=0.1530 max=0.8321 ridge-expanded lam= 3 relL2=0.2399 MAE=0.1519 max=0.6522 ridge-expanded lam= 10 relL2=0.2376 MAE=0.1522 max=0.5944 ridge-expanded lam= 30 relL2=0.2416 MAE=0.1526 max=0.6133 --- small MLP on features (torch) --- feature-MLP CV relL2=0.2272 MAE=0.1393 max=0.6490
cd /app && cat > /tmp/medoid.py << 'EOF'
import sys,time; sys.path.insert(0,'/app')
import torch
from train_final import load, train_model, DEV
feats=[load(s) for s in ['train','val','public_test']]
X=torch.cat([f[0] for f in feats],0); Y=torch.cat([f[1] for f in feats],0)
seeds=[0,1,2,3,4]; preds=[]; payloads=[]
t=time.time()
for sd in seeds:
m,(fm,fs,lm,ls)=train_model(X,Y,epochs=1200,seed=sd)
with torch.no_grad(): p=(m((X.to(DEV)-fm)/fs)*ls+lm).squeeze(1).cpu()
preds.append(p)
payloads.append({'state_dict':{k:v.cpu() for k,v in m.state_dict().items()},
'feat_mean':fm.reshape(3).cpu(),'feat_std':fs.reshape(3).cpu(),
'label_mean':lm.reshape(1).cpu(),'label_std':ls.reshape(1).cpu()})
yt=Y.squeeze(1); rel=(torch.norm(p-yt)/torch.norm(yt)).item()
print(f'seed {sd} in-sample rel={rel:.4f} [{time.time()-t:.0f}s]',flush=True)
P=torch.stack(preds) # [S, N]
mean_pred=P.mean(0)
# medoid: seed whose preds are closest to the consensus mean
dist=[ (P[i]-mean_pred).pow(2).sum().item() for i in range(len(seeds))]
medoid=int(torch.tensor(dist).argmin())
print('dists to consensus:',[round(d,4) for d in dist],'-> medoid seed',seeds[medoid],flush=True)
# pairwise agreement (std across seeds)
print('mean inter-seed std on 148 inputs:',P.std(0).mean().item())
torch.save(payloads[medoid],'/app/outputs/model.pt')
print('saved medoid (seed %d) to /app/outputs/model.pt'%seeds[medoid],flush=True)
EOF
pkill -f cv_swa 2>/dev/null; sleep 2
nohup python -u /tmp/medoid.py > /tmp/medoid.log 2>&1 & echo "launched $!"; sleep 210; cat /tmp/medoid.loglaunched 789 [final] epoch 200/1200 loss=0.3591 [final] epoch 400/1200 loss=1.0637 [final] epoch 600/1200 loss=0.0865 [final] epoch 800/1200 loss=0.1205 [final] epoch 1000/1200 loss=0.0390 [final] epoch 1200/1200 loss=0.0160 seed 0 in-sample rel=0.1127 [46s] [final] epoch 200/1200 loss=0.3403 [final] epoch 400/1200 loss=0.7514 [final] epoch 600/1200 loss=0.3737 [final] epoch 800/1200 loss=0.0900 [final] epoch 1000/1200 loss=0.0712 [final] epoch 1200/1200 loss=0.3636 seed 1 in-sample rel=0.0998 [86s] [final] epoch 200/1200 loss=0.2465 [final] epoch 400/1200 loss=0.0967 [final] epoch 600/1200 loss=0.2056 [final] epoch 800/1200 loss=0.1461 [final] epoch 1000/1200 loss=0.0059 [final] epoch 1200/1200 loss=0.0627 seed 2 in-sample rel=0.1138 [125s] [final] epoch 200/1200 loss=0.1647 [final] epoch 400/1200 loss=0.0684 [final] epoch 600/1200 loss=0.0403 [final] epoch 800/1200 loss=0.0818 [final] epoch 1000/1200 loss=0.3937 [final] epoch 1200/1200 loss=0.0620 seed 3 in-sample rel=0.1256 [165s] [final] epoch 200/1200 loss=0.9050 [final] epoch 400/1200 loss=0.4553 [final] epoch 600/1200 loss=0.2070 [final] epoch 800/1200 loss=0.0634 [final] epoch 1000/1200 loss=0.0281 [final] epoch 1200/1200 loss=0.0395 seed 4 in-sample rel=0.1101 [205s] dists to consensus: [0.2752, 0.3668, 0.4365, 0.3237, 0.2888] -> medoid seed 0 mean inter-seed std on 148 inputs: 0.04596555605530739 saved medoid (seed 0) to /app/outputs/model.pt
cd /app && python /tmp/verify.py
keys: ['feat_mean', 'feat_std', 'label_mean', 'label_std', 'state_dict'] feat_mean: shape=(3,) dtype=torch.float32 val=[-0.011780548840761185, 0.007426036521792412, -0.07198601216077805] feat_std: shape=(3,) dtype=torch.float32 val=[0.1422092616558075, 0.2692946195602417, 0.08725058287382126] label_mean: shape=(1,) dtype=torch.float32 val=[0.7487966418266296] label_std: shape=(1,) dtype=torch.float32 val=[0.38614240288734436] load_state_dict: <All keys matched successfully> dummy output shape: (5, 1) dtype torch.float32 val relL2=0.1240 MAE=0.0771 max=0.2759 (n=32) public_test relL2=0.1325 MAE=0.0821 max=0.2918 (n=16) OK: checkpoint loads into fixed architecture and runs.
cd /app && echo "=== /app/outputs ===" && ls -la outputs/ && echo && echo "=== integrity re-load (weights_only=True) ===" && python -c "
import torch
ck=torch.load('/app/outputs/model.pt', weights_only=True)
assert set(ck)>= {'state_dict','feat_mean','feat_std','label_mean','label_std'}
assert ck['feat_mean'].shape==(3,) and ck['feat_std'].shape==(3,)
assert ck['label_mean'].shape==(1,) and ck['label_std'].shape==(1,)
assert len(ck['state_dict'])>0 and all(not v.requires_grad for v in ck['state_dict'].values())
print('size on disk:', round(__import__('os').path.getsize('/app/outputs/model.pt')/1024,1),'KB')
print('all required keys present, shapes correct, tensors detached -> OK')
"=== /app/outputs === total 384 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 23:34 . drwxrws--- 1 h2tagent h2tagent 4096 Jun 20 23:34 .. -rw-r--r-- 1 h2tagent h2tagent 384685 Jun 20 23:52 model.pt === integrity re-load (weights_only=True) === size on disk: 375.7 KB all required keys present, shapes correct, tensors detached -> OK
No file changes, the agent left the workspace untouched.
# SyncVals normalized verifier summary # task: simjeb-bracket-fea-mass-prediction-real # attempt: 8 status: completed raw_score: 0.47 # reward rule: continuous: score 0.47 < 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). ---------------------------------------------------------------------- === /app/outputs === total 384 drwxr-sr-x 2 h2tagent h2tagent 4096 Jun 20 23:34 . drwxrws--- 1 h2tagent h2tagent 4096 Jun 20 23:34 .. -rw-r--r-- 1 h2tagent h2tagent 384685 Jun 20 23:52 model.pt === integrity re-load (weights_only=True) === size on disk: 375.7 KB all required keys present, shapes correct, tensors detached -> OK
Reproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_6dfcc6d7e6994393. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_6dfcc6d7e6994393 · verifier authoritative; classifier explanatory.