Add --log_every arg to control epoch print frequency (default 100)
Browse files
trm_solver/trm_nano/train_task.py
CHANGED
|
@@ -8,10 +8,11 @@ via manual builder (weight reuse), logs everything.
|
|
| 8 |
Usage (Kaggle):
|
| 9 |
python train_task.py --data_dir /kaggle/input/competitions/neurogolf-2026/ \
|
| 10 |
--output_dir /kaggle/working/trm_onnx/ \
|
| 11 |
-
--skip_solved /kaggle/working/solved_by_analytical.json
|
|
|
|
| 12 |
|
| 13 |
Single task mode:
|
| 14 |
-
python train_task.py --task_file /path/to/task001.json --output_dir onnx_out/
|
| 15 |
"""
|
| 16 |
|
| 17 |
import os
|
|
@@ -140,7 +141,7 @@ class ARCTaskDataset(Dataset):
|
|
| 140 |
# βββ Training βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 141 |
|
| 142 |
def train_on_task(task_data, config, epochs=500, lr=1e-3, batch_size=64,
|
| 143 |
-
num_aug=1000, device="cuda"):
|
| 144 |
model = TRMNano(config).to(device)
|
| 145 |
dataset = ARCTaskDataset(task_data, num_augmentations=num_aug)
|
| 146 |
loader = DataLoader(dataset, batch_size=batch_size, shuffle=True,
|
|
@@ -182,7 +183,7 @@ def train_on_task(task_data, config, epochs=500, lr=1e-3, batch_size=64,
|
|
| 182 |
best_loss = avg_loss
|
| 183 |
best_state = {n: p.clone() for n, p in ema_params.items()}
|
| 184 |
|
| 185 |
-
if (epoch + 1) %
|
| 186 |
logging.info(f" epoch {epoch+1}/{epochs} loss={avg_loss:.4f} best={best_loss:.4f}")
|
| 187 |
|
| 188 |
if best_state:
|
|
@@ -212,7 +213,6 @@ def verify_with_onnx(onnx_path, task_data):
|
|
| 212 |
inp_grid = np.array(example["input"], dtype=np.int64)
|
| 213 |
out_grid = np.array(example["output"], dtype=np.int64)
|
| 214 |
|
| 215 |
-
# Convert input to one-hot [1, 10, 30, 30]
|
| 216 |
h, w = inp_grid.shape
|
| 217 |
if h > 30 or w > 30:
|
| 218 |
fails += 1
|
|
@@ -222,11 +222,9 @@ def verify_with_onnx(onnx_path, task_data):
|
|
| 222 |
for c in range(w):
|
| 223 |
inp_onehot[0, inp_grid[r, c], r, c] = 1.0
|
| 224 |
|
| 225 |
-
# Run inference
|
| 226 |
result = sess.run(None, {"input": inp_onehot})
|
| 227 |
pred_onehot = (result[0] > 0.0).astype(np.float32)
|
| 228 |
|
| 229 |
-
# Convert expected output to one-hot
|
| 230 |
oh, ow = out_grid.shape
|
| 231 |
exp_onehot = np.zeros((1, 10, 30, 30), dtype=np.float32)
|
| 232 |
for r in range(oh):
|
|
@@ -271,6 +269,8 @@ def main():
|
|
| 271 |
parser.add_argument("--hidden_size", type=int, default=96)
|
| 272 |
parser.add_argument("--H_cycles", type=int, default=3)
|
| 273 |
parser.add_argument("--L_cycles", type=int, default=4)
|
|
|
|
|
|
|
| 274 |
# Limits
|
| 275 |
parser.add_argument("--max_tasks", type=int, default=None)
|
| 276 |
parser.add_argument("--start_task", type=int, default=1)
|
|
@@ -293,6 +293,7 @@ def main():
|
|
| 293 |
logging.info(f"Params: {count_params(TRMNano(config)):,}")
|
| 294 |
logging.info(f"Device: {args.device}")
|
| 295 |
logging.info(f"Epochs: {args.epochs}, LR: {args.lr}, Batch: {args.batch_size}, Aug: {args.num_aug}")
|
|
|
|
| 296 |
logging.info(f"Output: {args.output_dir}")
|
| 297 |
|
| 298 |
# βββ Single task mode βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -305,7 +306,7 @@ def main():
|
|
| 305 |
t0 = time.time()
|
| 306 |
model, best_loss = train_on_task(task_data, config, epochs=args.epochs, lr=args.lr,
|
| 307 |
batch_size=args.batch_size, num_aug=args.num_aug,
|
| 308 |
-
device=args.device)
|
| 309 |
elapsed = time.time() - t0
|
| 310 |
logging.info(f" Trained in {elapsed:.1f}s, best_loss={best_loss:.4f}")
|
| 311 |
|
|
@@ -322,7 +323,6 @@ def main():
|
|
| 322 |
return
|
| 323 |
|
| 324 |
# βββ Batch mode βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 325 |
-
# Load skip list
|
| 326 |
skip_tasks = set()
|
| 327 |
if args.skip_solved and os.path.exists(args.skip_solved):
|
| 328 |
with open(args.skip_solved) as f:
|
|
@@ -333,26 +333,23 @@ def main():
|
|
| 333 |
skip_tasks = set(skip_data.keys())
|
| 334 |
logging.info(f"Skipping {len(skip_tasks)} already-solved tasks")
|
| 335 |
|
| 336 |
-
# Find task files
|
| 337 |
task_files = sorted(glob.glob(os.path.join(args.data_dir, "task*.json")))
|
| 338 |
if not task_files:
|
| 339 |
task_files = sorted(glob.glob(os.path.join(args.data_dir, "*.json")))
|
| 340 |
logging.info(f"Found {len(task_files)} task files in {args.data_dir}")
|
| 341 |
|
| 342 |
-
# Filter
|
| 343 |
tasks_to_run = []
|
| 344 |
for f in task_files:
|
| 345 |
task_id = os.path.basename(f).replace(".json", "")
|
| 346 |
-
# Extract task number if possible
|
| 347 |
task_num = None
|
| 348 |
try:
|
| 349 |
task_num = int(task_id.replace("task", ""))
|
| 350 |
except ValueError:
|
| 351 |
pass
|
| 352 |
-
|
| 353 |
-
# Skip if in solved list (check both task_id and task_num)
|
| 354 |
if task_id in skip_tasks or str(task_num) in skip_tasks:
|
| 355 |
continue
|
|
|
|
|
|
|
| 356 |
if task_num is not None and task_num < args.start_task:
|
| 357 |
continue
|
| 358 |
tasks_to_run.append((task_id, task_num, f))
|
|
@@ -384,14 +381,12 @@ def main():
|
|
| 384 |
model, best_loss = train_on_task(
|
| 385 |
task_data, config, epochs=args.epochs, lr=args.lr,
|
| 386 |
batch_size=args.batch_size, num_aug=args.num_aug,
|
| 387 |
-
device=args.device)
|
| 388 |
train_time = time.time() - t0
|
| 389 |
|
| 390 |
-
# Export ONNX
|
| 391 |
onnx_path = os.path.join(args.output_dir, f"{task_id}.onnx")
|
| 392 |
size_bytes = export_onnx_manual(model, config, onnx_path)
|
| 393 |
|
| 394 |
-
# Verify
|
| 395 |
passes, fails = verify_with_onnx(onnx_path, task_data)
|
| 396 |
solved = (fails == 0 and passes > 0)
|
| 397 |
if solved:
|
|
@@ -411,7 +406,6 @@ def main():
|
|
| 411 |
"train_time_s": train_time,
|
| 412 |
})
|
| 413 |
|
| 414 |
-
# Remove ONNX if not solved (save disk)
|
| 415 |
if not solved and os.path.exists(onnx_path):
|
| 416 |
os.remove(onnx_path)
|
| 417 |
|
|
@@ -424,7 +418,6 @@ def main():
|
|
| 424 |
"error": str(e),
|
| 425 |
})
|
| 426 |
|
| 427 |
-
# Save progress after each task
|
| 428 |
progress_path = os.path.join(args.output_dir, "progress.json")
|
| 429 |
with open(progress_path, "w") as f:
|
| 430 |
json.dump({
|
|
@@ -445,7 +438,6 @@ def main():
|
|
| 445 |
logging.info(f" Output: {args.output_dir}")
|
| 446 |
logging.info(f" Log: {log_path}")
|
| 447 |
|
| 448 |
-
# List solved ONNX files
|
| 449 |
onnx_files = sorted(glob.glob(os.path.join(args.output_dir, "*.onnx")))
|
| 450 |
total_kb = sum(os.path.getsize(f) for f in onnx_files) / 1024
|
| 451 |
logging.info(f" ONNX files: {len(onnx_files)} ({total_kb:.1f} KB total)")
|
|
|
|
| 8 |
Usage (Kaggle):
|
| 9 |
python train_task.py --data_dir /kaggle/input/competitions/neurogolf-2026/ \
|
| 10 |
--output_dir /kaggle/working/trm_onnx/ \
|
| 11 |
+
--skip_solved /kaggle/working/solved_by_analytical.json \
|
| 12 |
+
--log_every 50
|
| 13 |
|
| 14 |
Single task mode:
|
| 15 |
+
python train_task.py --task_file /path/to/task001.json --output_dir onnx_out/ --log_every 25
|
| 16 |
"""
|
| 17 |
|
| 18 |
import os
|
|
|
|
| 141 |
# βββ Training βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 142 |
|
| 143 |
def train_on_task(task_data, config, epochs=500, lr=1e-3, batch_size=64,
|
| 144 |
+
num_aug=1000, device="cuda", log_every=100):
|
| 145 |
model = TRMNano(config).to(device)
|
| 146 |
dataset = ARCTaskDataset(task_data, num_augmentations=num_aug)
|
| 147 |
loader = DataLoader(dataset, batch_size=batch_size, shuffle=True,
|
|
|
|
| 183 |
best_loss = avg_loss
|
| 184 |
best_state = {n: p.clone() for n, p in ema_params.items()}
|
| 185 |
|
| 186 |
+
if (epoch + 1) % log_every == 0:
|
| 187 |
logging.info(f" epoch {epoch+1}/{epochs} loss={avg_loss:.4f} best={best_loss:.4f}")
|
| 188 |
|
| 189 |
if best_state:
|
|
|
|
| 213 |
inp_grid = np.array(example["input"], dtype=np.int64)
|
| 214 |
out_grid = np.array(example["output"], dtype=np.int64)
|
| 215 |
|
|
|
|
| 216 |
h, w = inp_grid.shape
|
| 217 |
if h > 30 or w > 30:
|
| 218 |
fails += 1
|
|
|
|
| 222 |
for c in range(w):
|
| 223 |
inp_onehot[0, inp_grid[r, c], r, c] = 1.0
|
| 224 |
|
|
|
|
| 225 |
result = sess.run(None, {"input": inp_onehot})
|
| 226 |
pred_onehot = (result[0] > 0.0).astype(np.float32)
|
| 227 |
|
|
|
|
| 228 |
oh, ow = out_grid.shape
|
| 229 |
exp_onehot = np.zeros((1, 10, 30, 30), dtype=np.float32)
|
| 230 |
for r in range(oh):
|
|
|
|
| 269 |
parser.add_argument("--hidden_size", type=int, default=96)
|
| 270 |
parser.add_argument("--H_cycles", type=int, default=3)
|
| 271 |
parser.add_argument("--L_cycles", type=int, default=4)
|
| 272 |
+
parser.add_argument("--log_every", type=int, default=100,
|
| 273 |
+
help="Print training loss every N epochs (default: 100)")
|
| 274 |
# Limits
|
| 275 |
parser.add_argument("--max_tasks", type=int, default=None)
|
| 276 |
parser.add_argument("--start_task", type=int, default=1)
|
|
|
|
| 293 |
logging.info(f"Params: {count_params(TRMNano(config)):,}")
|
| 294 |
logging.info(f"Device: {args.device}")
|
| 295 |
logging.info(f"Epochs: {args.epochs}, LR: {args.lr}, Batch: {args.batch_size}, Aug: {args.num_aug}")
|
| 296 |
+
logging.info(f"Log every: {args.log_every} epochs")
|
| 297 |
logging.info(f"Output: {args.output_dir}")
|
| 298 |
|
| 299 |
# βββ Single task mode βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 306 |
t0 = time.time()
|
| 307 |
model, best_loss = train_on_task(task_data, config, epochs=args.epochs, lr=args.lr,
|
| 308 |
batch_size=args.batch_size, num_aug=args.num_aug,
|
| 309 |
+
device=args.device, log_every=args.log_every)
|
| 310 |
elapsed = time.time() - t0
|
| 311 |
logging.info(f" Trained in {elapsed:.1f}s, best_loss={best_loss:.4f}")
|
| 312 |
|
|
|
|
| 323 |
return
|
| 324 |
|
| 325 |
# βββ Batch mode βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 326 |
skip_tasks = set()
|
| 327 |
if args.skip_solved and os.path.exists(args.skip_solved):
|
| 328 |
with open(args.skip_solved) as f:
|
|
|
|
| 333 |
skip_tasks = set(skip_data.keys())
|
| 334 |
logging.info(f"Skipping {len(skip_tasks)} already-solved tasks")
|
| 335 |
|
|
|
|
| 336 |
task_files = sorted(glob.glob(os.path.join(args.data_dir, "task*.json")))
|
| 337 |
if not task_files:
|
| 338 |
task_files = sorted(glob.glob(os.path.join(args.data_dir, "*.json")))
|
| 339 |
logging.info(f"Found {len(task_files)} task files in {args.data_dir}")
|
| 340 |
|
|
|
|
| 341 |
tasks_to_run = []
|
| 342 |
for f in task_files:
|
| 343 |
task_id = os.path.basename(f).replace(".json", "")
|
|
|
|
| 344 |
task_num = None
|
| 345 |
try:
|
| 346 |
task_num = int(task_id.replace("task", ""))
|
| 347 |
except ValueError:
|
| 348 |
pass
|
|
|
|
|
|
|
| 349 |
if task_id in skip_tasks or str(task_num) in skip_tasks:
|
| 350 |
continue
|
| 351 |
+
if task_num is not None and task_num in skip_tasks:
|
| 352 |
+
continue
|
| 353 |
if task_num is not None and task_num < args.start_task:
|
| 354 |
continue
|
| 355 |
tasks_to_run.append((task_id, task_num, f))
|
|
|
|
| 381 |
model, best_loss = train_on_task(
|
| 382 |
task_data, config, epochs=args.epochs, lr=args.lr,
|
| 383 |
batch_size=args.batch_size, num_aug=args.num_aug,
|
| 384 |
+
device=args.device, log_every=args.log_every)
|
| 385 |
train_time = time.time() - t0
|
| 386 |
|
|
|
|
| 387 |
onnx_path = os.path.join(args.output_dir, f"{task_id}.onnx")
|
| 388 |
size_bytes = export_onnx_manual(model, config, onnx_path)
|
| 389 |
|
|
|
|
| 390 |
passes, fails = verify_with_onnx(onnx_path, task_data)
|
| 391 |
solved = (fails == 0 and passes > 0)
|
| 392 |
if solved:
|
|
|
|
| 406 |
"train_time_s": train_time,
|
| 407 |
})
|
| 408 |
|
|
|
|
| 409 |
if not solved and os.path.exists(onnx_path):
|
| 410 |
os.remove(onnx_path)
|
| 411 |
|
|
|
|
| 418 |
"error": str(e),
|
| 419 |
})
|
| 420 |
|
|
|
|
| 421 |
progress_path = os.path.join(args.output_dir, "progress.json")
|
| 422 |
with open(progress_path, "w") as f:
|
| 423 |
json.dump({
|
|
|
|
| 438 |
logging.info(f" Output: {args.output_dir}")
|
| 439 |
logging.info(f" Log: {log_path}")
|
| 440 |
|
|
|
|
| 441 |
onnx_files = sorted(glob.glob(os.path.join(args.output_dir, "*.onnx")))
|
| 442 |
total_kb = sum(os.path.getsize(f) for f in onnx_files) / 1024
|
| 443 |
logging.info(f" ONNX files: {len(onnx_files)} ({total_kb:.1f} KB total)")
|