|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Training driver for v3.46-trained. |
| 3 | +
|
| 4 | +Starts from v346-revertE-topk-nonexclusive-7e97 SUT (attention-pool ctx encoder, |
| 5 | +cluster-crowding retrieval, refresh-on-write, additive tail residual, |
| 6 | +top1-exclusive OFF, cond-buffer mirror). Runs N Trainer.step iterations |
| 7 | +over a rotating corpus; saves non-backbone state_dict to ckpt/v346_trained.pt. |
| 8 | +
|
| 9 | +Per SPRINT_CLOSEOUT_v3.46.md §5.3 / §5.4. |
| 10 | +""" |
| 11 | +import argparse, os, time, json, math, sys |
| 12 | +import torch |
| 13 | +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
| 14 | +import scheme_b_v344 as sb |
| 15 | + |
| 16 | +MUSIC = [ |
| 17 | + "He practiced piano for hours perfecting a difficult Chopin nocturne.", |
| 18 | + "She studied music theory and harmonic progression at the conservatory.", |
| 19 | + "The orchestra performed Beethoven symphony with remarkable precision.", |
| 20 | +] |
| 21 | +SPACE = [ |
| 22 | + "The telescope revealed distant galaxies beyond the Milky Way.", |
| 23 | + "Astronauts trained for the Mars mission in simulated zero gravity.", |
| 24 | + "The nebula emitted radiation across the electromagnetic spectrum.", |
| 25 | +] |
| 26 | +GENERIC = [ |
| 27 | + "The pianist practiced arpeggios and Chopin nocturnes until midnight.", |
| 28 | + "A musician refined finger technique, phrasing, and pedal control.", |
| 29 | + "Classical interpretation often depends on dynamics, tempo rubato, and touch.", |
| 30 | + "A conservatory student studied etudes, scales, and expressive keyboard skills.", |
| 31 | + "Distant astronomers observed galaxies quasars and stellar evolution.", |
| 32 | + "Space orbital mechanics explains satellites and planetary motion.", |
| 33 | +] |
| 34 | +ALL = MUSIC + SPACE + GENERIC |
| 35 | + |
| 36 | + |
| 37 | +def main(): |
| 38 | + ap = argparse.ArgumentParser() |
| 39 | + ap.add_argument("--steps", type=int, default=60) |
| 40 | + ap.add_argument("--batch", type=int, default=3) |
| 41 | + ap.add_argument("--out", type=str, default="ckpt/v346_trained.pt") |
| 42 | + ap.add_argument("--seed", type=int, default=42) |
| 43 | + ap.add_argument("--log", type=str, default="ckpt/v346_train_log.jsonl") |
| 44 | + args = ap.parse_args() |
| 45 | + |
| 46 | + os.makedirs(os.path.dirname(args.out) or ".", exist_ok=True) |
| 47 | + log_dir = os.path.dirname(args.log) or "." |
| 48 | + os.makedirs(log_dir, exist_ok=True) |
| 49 | + torch.manual_seed(args.seed) |
| 50 | + |
| 51 | + c = sb.Cfg() |
| 52 | + # Sanity: confirm v3.46 Cfg (same assert as §8 step 3, catches env corruption) |
| 53 | + assert c.use_top1_exclusive_content_bias is False, \ |
| 54 | + "Cfg.use_top1_exclusive_content_bias must be False on v3.46" |
| 55 | + assert c.tail_slot_residual_dominant is False, \ |
| 56 | + "Cfg.tail_slot_residual_dominant must be False on v3.46 (revert [B])" |
| 57 | + |
| 58 | + m = sb.MemLLM(c) |
| 59 | + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| 60 | + if device.type != "cuda": |
| 61 | + if os.environ.get("AMS_ALLOW_CPU_TRAIN", "0") != "1": |
| 62 | + raise AssertionError( |
| 63 | + "train_v346 expects CUDA; CPU fallback is ~10x slower and not the intent. " |
| 64 | + "Set AMS_ALLOW_CPU_TRAIN=1 to override explicitly.") |
| 65 | + print("[build] WARNING: running on CPU (AMS_ALLOW_CPU_TRAIN=1)") |
| 66 | + m.to(device); m.load(); m.to(device) |
| 67 | + trainable = sum(p.numel() for p in m.parameters() if p.requires_grad) |
| 68 | + total = sum(p.numel() for p in m.parameters()) |
| 69 | + print(f"[build] device={device} params total={total:,} trainable={trainable:,}") |
| 70 | + |
| 71 | + for t in ALL: |
| 72 | + m.write(t, training_mode=True) |
| 73 | + try: |
| 74 | + m.amm.maybe_recluster(force=True) |
| 75 | + except Exception as e: |
| 76 | + print(f"[build] amm.maybe_recluster skipped: {type(e).__name__}: {e}") |
| 77 | + m._refresh_rare_keyword_indices() |
| 78 | + m.eval() |
| 79 | + print(f"[build] initial memory count = {len(m.amm.tree.store)}") |
| 80 | + |
| 81 | + # Pre-training mechanism snapshot (per §5.6): tail_head[1] + vocab_proj last weights |
| 82 | + def _probe_weights(model): |
| 83 | + out = {} |
| 84 | + try: |
| 85 | + w = model.bridge.tail_head.slot_heads[1][0].weight |
| 86 | + out["tail_head_slot1_abs_mean"] = float(w.detach().abs().mean()) |
| 87 | + except Exception as e: |
| 88 | + out["tail_head_slot1_abs_mean"] = f"ERR {type(e).__name__}" |
| 89 | + try: |
| 90 | + w = model.vocab_proj.proj[-1].weight |
| 91 | + out["vocab_proj_last_abs_mean"] = float(w.detach().abs().mean()) |
| 92 | + except Exception as e: |
| 93 | + out["vocab_proj_last_abs_mean"] = f"ERR {type(e).__name__}" |
| 94 | + return out |
| 95 | + pre_probe = _probe_weights(m) |
| 96 | + print(f"[probe pre-train] {pre_probe}") |
| 97 | + |
| 98 | + trainer = sb.Trainer(m, c) |
| 99 | + print(f"[train] Trainer built batch={args.batch} steps={args.steps}") |
| 100 | + |
| 101 | + t_start = time.time() |
| 102 | + with open(args.log, "w") as flog: |
| 103 | + for step in range(args.steps): |
| 104 | + start = (step * args.batch) % len(ALL) |
| 105 | + batch = [ALL[(start + i) % len(ALL)] for i in range(args.batch)] |
| 106 | + t0 = time.time() |
| 107 | + try: |
| 108 | + stats = trainer.step(batch) |
| 109 | + except Exception as e: |
| 110 | + print(f"[step {step}] EXCEPTION: {type(e).__name__}: {e}") |
| 111 | + raise |
| 112 | + dt = time.time() - t0 |
| 113 | + tot = stats.get("total") |
| 114 | + print( |
| 115 | + f"step {step:3d} total={tot:.4f} " |
| 116 | + f"recon={stats.get('recon', 0):.3f} " |
| 117 | + f"sa={stats.get('semantic_alignment', 0):.3f} " |
| 118 | + f"tsa={stats.get('tail_semantic_anchor', 0):.3f} " |
| 119 | + f"va={stats.get('vocab_anchor', 0):.3f} " |
| 120 | + f"fs={stats.get('functional_suppression', 0):.3f} " |
| 121 | + f"cs={stats.get('context_separation', 0):.3f} " |
| 122 | + f"dt={dt:.1f}s" |
| 123 | + ) |
| 124 | + rec = {"step": step, "dt_s": dt, |
| 125 | + **{k: v for k, v in stats.items() |
| 126 | + if k not in ("grad_norms", "loss_weights")}} |
| 127 | + flog.write(json.dumps(rec, ensure_ascii=False) + "\n") |
| 128 | + flog.flush() |
| 129 | + elapsed = time.time() - t_start |
| 130 | + post_probe = _probe_weights(m) |
| 131 | + print(f"[probe post-train] {post_probe}") |
| 132 | + print(f"[train] elapsed {elapsed:.1f}s avg/step={elapsed/max(1,args.steps):.2f}s") |
| 133 | + |
| 134 | + sd = {n: p.detach().cpu() for n, p in m.named_parameters() if "backbone" not in n} |
| 135 | + for n, b in m.named_buffers(): |
| 136 | + if "backbone" not in n: |
| 137 | + sd[n] = b.detach().cpu() |
| 138 | + torch.save({ |
| 139 | + "state_dict": sd, |
| 140 | + "cfg_snapshot": {k: getattr(c, k) for k in ( |
| 141 | + "L_mem", "d_ctx", "d_M", "d_F", "cfg_scale", |
| 142 | + "use_top1_exclusive_content_bias", |
| 143 | + "tail_slot_residual_dominant", |
| 144 | + "use_inter_domain_margin", |
| 145 | + "context_encoder_use_attention_pool", |
| 146 | + )}, |
| 147 | + "provenance": "AgentMemory/v346-revertE-topk-nonexclusive-7e97", |
| 148 | + "steps": args.steps, |
| 149 | + "elapsed_s": elapsed, |
| 150 | + "pre_probe": pre_probe, |
| 151 | + "post_probe": post_probe, |
| 152 | + }, args.out) |
| 153 | + print(f"[save] wrote {args.out} tensors={len(sd)}") |
| 154 | + |
| 155 | + |
| 156 | +if __name__ == "__main__": |
| 157 | + main() |
0 commit comments