| """Minimal codec training on STREAMED CS2-10k — proves the streamed data -> MIRA model -> loss |
| pipeline end to end, in the existing venv (no pixi, no hydra). Pure L1 reconstruction so it needs |
| NO external weights; `require_dino_weights=False` builds the DINOv3 backbone with random weights |
| (swap in real gated DINOv3 later for a real RAE codec). Small config to fit 16GB and run fast. |
| """ |
| import os, sys, time, traceback |
| sys.path.insert(0, "src") |
| import torch |
|
|
| from mira.codec import VideoCodec, CodecLoss |
| from mira.codec.config import ( |
| VideoCodecConfig, RAEEncoderConfig, ViTDecoderConfig, |
| StridedConvBottleneckConfig, ImageConfig, |
| ) |
| from mira.codec.loss import CodecLossWeights |
| from mira.data.cs2_stream import create_cs2_loader |
|
|
| dev = "cuda" if torch.cuda.is_available() else "cpu" |
| |
| |
| |
| T = int(os.environ.get("CS2_T", "40")) |
| FPS = 24 |
| H, W = 288, 512 |
| img = ImageConfig(height=H, width=W, channels=3, timesteps=T, fps=FPS) |
| enc = RAEEncoderConfig( |
| latent_dim=32, rae_model="dinov3_vitl16", |
| aggregation_layers=[11, 13, 15, 17, 19, 21, 23], |
| bottleneck=StridedConvBottleneckConfig(stride=2, temporal_stride=2, noise_tau=0.0), |
| compile_dino=False, video=img, |
| ) |
| dec = ViTDecoderConfig( |
| latent_dim=32, bottleneck=StridedConvBottleneckConfig(stride=2), |
| |
| vit_width=int(os.environ.get("CODEC_VIT_WIDTH", "512")), |
| vit_depth=int(os.environ.get("CODEC_VIT_DEPTH", "6")), |
| vit_num_heads=int(os.environ.get("CODEC_VIT_HEADS", "8")), |
| mlp_dim_multiplier=4, |
| qk_norm="layernorm", patch_size=16, patch_size_t=2, video=img, |
| activation_checkpointing=True, |
| ) |
| cfg = VideoCodecConfig(encoder=enc, decoder=dec) |
|
|
| |
| |
| want_dino = bool(os.environ.get("RS_DINO_WEIGHTS_DIR")) |
| print(f"[cs2-codec] device={dev} T={T} {H}x{W}@{FPS}fps real_dino_weights={want_dino}", flush=True) |
| try: |
| model = VideoCodec(cfg, require_dino_weights=want_dino).to(dev).train() |
| except FileNotFoundError: |
| print("[cs2-codec] RS_DINO_WEIGHTS_DIR set but weights not found; using RANDOM backbone. " |
| "For a real codec, put dinov3_vitl16_pretrain_lvd1689m-*.pth in that dir.", flush=True) |
| model = VideoCodec(cfg, require_dino_weights=False).to(dev).train() |
| except Exception: |
| traceback.print_exc(); sys.exit(3) |
|
|
| print(f"[cs2-codec] params: {sum(p.numel() for p in model.parameters())/1e6:.1f}M", flush=True) |
| loss_fn = CodecLoss(CodecLossWeights( |
| loss_mae=1.0, loss_lpips_perceptual=0.0, loss_dino_latent_consistency=0.0, auto_weight=False, |
| )).to(dev) |
| opt = torch.optim.AdamW(model.parameters(), lr=1e-4) |
|
|
| loader = create_cs2_loader( |
| subset=os.environ.get("CS2_SUBSET", "sample"), n_players=1, clip_len=T, target_fps=FPS, |
| frame_size=(H, W), batch_size=int(os.environ.get("CS2_BS", "1")), |
| num_workers=int(os.environ.get("CS2_WORKERS", "0")), |
| infinite=True, |
| ) |
| print("[cs2-codec] streaming CS2 sample split; first batch downloads a few clips...", flush=True) |
|
|
| from pathlib import Path |
| STEPS = int(os.environ.get("CS2_STEPS", "20000")) |
| CKPT_EVERY = int(os.environ.get("CS2_CKPT_EVERY", "500")) |
| ckpt_dir = Path(os.environ.get("CS2_OUT", "runs/cs2_codec")) |
| ckpt_dir.mkdir(parents=True, exist_ok=True) |
| print(f"[cs2-codec] REAL RUN: {STEPS} steps, checkpoint every {CKPT_EVERY} -> {ckpt_dir}", flush=True) |
|
|
| |
| import glob |
| _start_step, _ema0 = 0, None |
| _resume = os.environ.get("CS2_RESUME") or (sorted(glob.glob(str(ckpt_dir / "codec_0*.pt"))) or [None])[-1] |
| def _trainable_sd(): |
| |
| return {k: v for k, v in model.state_dict().items() if "rae_dino.dino_model" not in k} |
|
|
| if _resume and os.path.exists(_resume): |
| _sd = torch.load(_resume, map_location=dev, weights_only=False) |
| model.load_state_dict(_sd["model"], strict=False) |
| _start_step = int(_sd.get("step", 0)); _ema0 = _sd.get("ema_loss") |
| print(f"[cs2-codec] resumed from {_resume} at step {_start_step} (ema_loss={_ema0})", flush=True) |
|
|
| |
| PAUSE_EVERY = int(os.environ.get("CS2_PAUSE_EVERY", "1000")) |
| PAUSE_SECS = int(os.environ.get("CS2_PAUSE_SECS", "900")) |
|
|
| step, ema = _start_step, _ema0 |
| _pace_t0, _pace_s0 = time.time(), _start_step |
| for batch, meta in loader: |
| batch = batch.to(dev) |
| out = model(batch) |
| losses = loss_fn(out, global_step=step) |
| total = losses["loss"] if "loss" in losses else sum(v for v in losses.values() if v.ndim == 0) |
| opt.zero_grad(); total.backward(); opt.step() |
| ema = float(total) if ema is None else 0.98 * ema + 0.02 * float(total) |
| if step % 20 == 0: |
| print(f"[cs2-codec] step {step:6d} loss={float(total):.4f} ema={ema:.4f} " |
| f"({(time.time()-_pace_t0)/(step-_pace_s0+1):.2f}s/step)", flush=True) |
| step += 1 |
| if step % CKPT_EVERY == 0: |
| torch.save({"step": step, "model": _trainable_sd(), "config": cfg.model_dump(), |
| "ema_loss": ema}, ckpt_dir / f"codec_{step:06d}.pt") |
| print(f"[cs2-codec] saved {ckpt_dir / f'codec_{step:06d}.pt'} (ema_loss={ema:.4f})", flush=True) |
| keep = int(os.environ.get("CS2_KEEP", "2")) |
| for old in sorted(ckpt_dir.glob("codec_0*.pt"))[:-keep]: |
| old.unlink(missing_ok=True) |
| if step >= STEPS: |
| break |
| if PAUSE_EVERY and step % PAUSE_EVERY == 0: |
| import datetime as _dt |
| print(f"[cs2-codec] PAUSE {PAUSE_SECS}s at step {step} for M.2 cooldown " |
| f"(resume ~{(_dt.datetime.now()+_dt.timedelta(seconds=PAUSE_SECS)).strftime('%H:%M:%S')})", |
| flush=True) |
| time.sleep(PAUSE_SECS) |
| _pace_t0, _pace_s0 = time.time(), step |
| print(f"[cs2-codec] RESUME at step {step}", flush=True) |
| torch.save({"step": step, "model": _trainable_sd(), "config": cfg.model_dump(), "ema_loss": ema}, |
| ckpt_dir / "codec_final.pt") |
| print(f"[cs2-codec] DONE {step} steps, final ema_loss={ema:.4f} -> {ckpt_dir/'codec_final.pt'}", flush=True) |
|
|