#!/usr/bin/env python3 """V18 long-training runs of the V14 C1 recipe; no writer or test cohort access.""" from __future__ import annotations import argparse import hashlib import json import time from pathlib import Path import numpy as np import torch from mcss.dynamic.types import hash_value from mcss.geometry import transform_cameras from mcss.mechanism_pilot.geometry_carrier_experiment import ( select_checkpoint, verify_data, verify_lock, ) from mcss.mechanism_pilot.rgbd_long_data_carrier import ( EXTRA_SEED_OFFSET, VARIANTS, build_state, extra_count, make_carrier, parameter_hash, save_checkpoint, scene_data, train_records, training_loss, with_extra_views, ) from mcss.mechanism_pilot.rgbd_stream_write import StreamRGBDLoader from mcss.mechanism_pilot.small_training import AccessLog, sha, write_json def train(root, variant, seed, device): root = Path(root).resolve() manifest, config = verify_lock(root) verify_data(root) torch.set_num_threads(config["num_threads"]) if variant not in config["variants"] or seed not in config["seeds"]: raise PermissionError("Unregistered model/seed") destination = root / "checkpoints" / f"{variant}_{seed}" destination.mkdir(exist_ok=False) access = AccessLog(destination / "access.jsonl") records = train_records(manifest, config, variant) scenes = [scene_data(variant)(r, manifest, root, device, access) for r in records] holdout = set(manifest.get("protected_scene_ids", [])) | { r["scene_id"] for r in manifest["scenes"] if r["split"] != "TRAIN" } streams = [ StreamRGBDLoader( r, manifest["image_size"], root, device, access, allowed_scene_ids=[x["scene_id"] for x in records], holdout_scene_ids=holdout, ) for r in records ] # Extra-view counts have their own generator: scene order and rays stay matched. extra_generator = torch.Generator(device="cpu").manual_seed(seed + EXTRA_SEED_OFFSET) generator = torch.Generator(device="cpu").manual_seed(seed) order = torch.randperm(len(scenes), generator=generator).tolist() prior = json.loads((root / "train_depth_prior.json").read_text()) carrier = make_carrier(variant, seed, device, prior["near_m"], prior["far_m"]) carrier.train() initial_hash = hash_value(carrier.state_dict()) initial_parameters = parameter_hash(carrier) optimizer = torch.optim.Adam(carrier.parameters(), lr=config["learning_rate"], weight_decay=0) stream = hashlib.sha256() candidates = [] if str(device).startswith("cuda"): torch.cuda.reset_peak_memory_stats() start = time.perf_counter() with (destination / "training.jsonl").open("x", buffering=1) as log: for step in range(1, config["steps"] + 1): tick = time.perf_counter() visit = (step - 1) // len(scenes) index = order[(step - 1) % len(scenes)] scene = scenes[index] slot = visit % len(scene.record["roles"]["primary_query"]) optimizer.zero_grad(set_to_none=True) # Independently built states share only the trainable slow carrier parameters. count = extra_count(config["base_extra"], extra_generator) states = { role: build_state( carrier, with_extra_views(scene.context(role), streams[index].stream(role), count) if count else scene.context(role), scene.bounds[role], f"{seed}:{step}:{role}", ) for role in ("A", "B") } target = scene.target(slot) # offline targets only after state construction ray_count = int(np.prod(manifest["image_size"])) indices = torch.randint(ray_count, (config["batch_rays"],), generator=generator) stream.update(json.dumps([scene.record["scene_id"], slot]).encode()) stream.update(indices.numpy().astype("