amer224 commited on
Commit
07dc3fe
·
verified ·
1 Parent(s): 39cb2ee

Add brains/sovereign-v3/model.py

Browse files
Files changed (1) hide show
  1. brains/sovereign-v3/model.py +108 -4
brains/sovereign-v3/model.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
- SOVEREIGN v3 — ~272M-param return-conditioned spatial policy for
3
- territorial.io (last-survivor elimination).
4
 
5
  = DESIGN SUMMARY (full reasoning: ARCHITECTURE.md, training: train_notes.md)
6
  Size mandate: >=200M, <400M (user-specified). Four modules:
@@ -115,11 +115,21 @@ class SovereignConfig:
115
  aux_seg_classes: int = 4 # water / neutral / me / enemy
116
  dropout_mlp: float = 0.1
117
  grad_ckpt: bool = False # checkpoint the 4 biggest stages
 
 
 
 
 
 
118
 
119
  @property
120
  def ctx_full_dim(self) -> int:
121
  return self.ctx_dim + self.rtg_dim
122
 
 
 
 
 
123
 
124
  # --------------------------------------------------------------------------
125
  # Blocks
@@ -431,6 +441,32 @@ class Sovereign(nn.Module):
431
  self.mem = nn.GRU(pool_dim, c.mem_hidden, 1, batch_first=True)
432
  self.mem_proj = nn.Linear(c.mem_hidden, c.cortex_d)
433
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  self.apply(_init_weights)
435
 
436
  # -- helpers ------------------------------------------------------------
@@ -468,6 +504,64 @@ class Sovereign(nn.Module):
468
  align_corners=False)
469
  return logg
470
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  # -- forward ------------------------------------------------------------
472
 
473
  def forward(self, rgb: torch.Tensor, nums: Optional[torch.Tensor] = None,
@@ -783,8 +877,9 @@ def param_breakdown(net: Sovereign) -> dict:
783
  if net.cfg.use_cell_value:
784
  heads += n(net.cell_value)
785
  mem = (n(net.mem) + n(net.mem_proj)) if net.mem is not None else 0
 
786
  return dict(spine=spine, cortex=cortex, decoder=decoder, heads=heads,
787
- mem=mem)
788
 
789
 
790
  def make_model(cfg: Optional[SovereignConfig] = None,
@@ -803,7 +898,8 @@ if __name__ == "__main__":
803
  bd = param_breakdown(m)
804
  print(f"params: {total/1e6:.2f}M (spine {bd['spine']/1e6:.1f}M | "
805
  f"cortex {bd['cortex']/1e6:.1f}M | decoder {bd['decoder']/1e6:.1f}M "
806
- f"| heads {bd['heads']/1e6:.1f}M | mem {bd['mem']/1e6:.1f}M)")
 
807
  H = cfg.map_size
808
  rgb = torch.rand(2, 3, H, H)
809
  nums = torch.rand(2, 8)
@@ -821,6 +917,14 @@ if __name__ == "__main__":
821
  for k, v in a.items() if k not in ("kind_probs",)})
822
  a2 = m.act(rgb, nums, rtg=rtg, state=a["state"])
823
  print("mem carry ok:", a2["state"].shape)
 
 
 
 
 
 
 
 
824
  # no-rtg path (contract default)
825
  _, _, _, _ = m(rgb, nums)
826
  print("no-rtg contract path OK")
 
1
  """
2
+ SOVEREIGN v3 — ~290M-param return-conditioned spatial policy with MCTS
3
+ planning for territorial.io (last-survivor elimination).
4
 
5
  = DESIGN SUMMARY (full reasoning: ARCHITECTURE.md, training: train_notes.md)
6
  Size mandate: >=200M, <400M (user-specified). Four modules:
 
115
  aux_seg_classes: int = 4 # water / neutral / me / enemy
116
  dropout_mlp: float = 0.1
117
  grad_ckpt: bool = False # checkpoint the 4 biggest stages
118
+ # -- planning / search block (MuZero-lite) ------------------------------
119
+ use_search: bool = True # latent encode/predict/dynamics for MCTS
120
+ lat_c: int = 32 # compressed spatial channels in the latent
121
+ lat_spatial: int = 8 # latent map size (8x8)
122
+ dyn_hidden: int = 1024 # dynamics MLP hidden width
123
+ act_embed: int = 512 # action embedding width
124
 
125
  @property
126
  def ctx_full_dim(self) -> int:
127
  return self.ctx_dim + self.rtg_dim
128
 
129
+ @property
130
+ def latent_dim(self) -> int:
131
+ return self.cortex_d + self.lat_c * self.lat_spatial * self.lat_spatial
132
+
133
 
134
  # --------------------------------------------------------------------------
135
  # Blocks
 
441
  self.mem = nn.GRU(pool_dim, c.mem_hidden, 1, batch_first=True)
442
  self.mem_proj = nn.Linear(c.mem_hidden, c.cortex_d)
443
 
444
+ # -- planning block (MuZero-lite): latent encode/predict/dynamics ---
445
+ # Trained on CONSECUTIVE shard frames (rgb_t, a_t -> rgb_{t+1}):
446
+ # encode -> z (B, latent_dim)
447
+ # prediction(z) -> prior logits over (kind,cell), value, win
448
+ # dynamics(z, a) -> z_next, reward_hat
449
+ # Drives MCTS at decision time (see search.py). ~17M params.
450
+ self.planning = None
451
+ if c.use_search:
452
+ m = nn.Module()
453
+ m.lat_proj = nn.Sequential(
454
+ nn.Conv2d(c.enc_ch[-1], c.lat_c, 1, bias=False),
455
+ nn.BatchNorm2d(c.lat_c), nn.SiLU())
456
+ m.a_emb_kind = nn.Embedding(c.num_kinds, 64)
457
+ m.a_emb_cell = nn.Embedding(c.grid_default * c.grid_default, 256)
458
+ m.a_emb_pct = nn.Linear(1, 128)
459
+ m.a_emb_out = nn.Linear(64 + 256 + 128, c.act_embed)
460
+ m.dyn_mlp = nn.Sequential(
461
+ nn.Linear(c.latent_dim + c.act_embed, c.dyn_hidden),
462
+ nn.SiLU(), nn.Linear(c.dyn_hidden, c.latent_dim))
463
+ m.reward_head = nn.Linear(c.latent_dim, 1)
464
+ m.pred_prior = nn.Linear(
465
+ c.latent_dim, c.num_kinds * c.grid_default * c.grid_default)
466
+ m.pred_value = nn.Linear(c.latent_dim, 1)
467
+ m.pred_win = nn.Linear(c.latent_dim, 2)
468
+ self.planning = m
469
+
470
  self.apply(_init_weights)
471
 
472
  # -- helpers ------------------------------------------------------------
 
504
  align_corners=False)
505
  return logg
506
 
507
+ # -- planning interface (used by search.py) -----------------------------
508
+
509
+ def encode(self, rgb: torch.Tensor, nums: Optional[torch.Tensor] = None,
510
+ rtg: Optional[torch.Tensor] = None) -> torch.Tensor:
511
+ """Compressed latent state z (B, latent_dim) for MCTS + dynamics
512
+ training. Runs the vision spine + cortex (no decoder)."""
513
+ assert self.planning is not None, "planning block disabled"
514
+ cfg = self.cfg
515
+ B, C, H, W = rgb.shape
516
+ assert H == W == cfg.map_size, \
517
+ f"expected {cfg.map_size}x{cfg.map_size}, got {H}x{W}"
518
+ if nums is None:
519
+ nums = torch.zeros(B, cfg.ctx_dim, device=rgb.device)
520
+ ctx_full = self._ctx_full(nums, rtg)
521
+ x = rgb
522
+ if cfg.use_coord:
523
+ x = torch.cat([x, self.coords.expand(B, -1, -1, -1)], dim=1)
524
+ x = self.stem(x)
525
+ prev = None
526
+ for i, stage in enumerate(self.enc):
527
+ prev = x
528
+ x = self._staged(stage, x) if i >= len(self.enc) - 2 else stage(x)
529
+ # x = e5 output, prev = e4 output (cortex fine tokens)
530
+ bot = self.film(x, ctx_full)
531
+ _, g = self.cortex(bot, prev)
532
+ lat_map = F.adaptive_avg_pool2d(
533
+ self.planning.lat_proj(bot),
534
+ (cfg.lat_spatial, cfg.lat_spatial)).flatten(1)
535
+ return torch.cat([g, lat_map], dim=1) # (B, latent_dim)
536
+
537
+ def prediction(self, z: torch.Tensor
538
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
539
+ """MuZero-lite prediction net: prior logits over (kind,cell) joint
540
+ actions (B, 3*g*g), value (B,), win logits (B,2)."""
541
+ assert self.planning is not None, "planning block disabled"
542
+ prior = self.planning.pred_prior(z)
543
+ value = self.planning.pred_value(z).squeeze(-1)
544
+ win = self.planning.pred_win(z)
545
+ return prior, value, win
546
+
547
+ def dynamics(self, z: torch.Tensor, kind: torch.Tensor,
548
+ cell: torch.Tensor,
549
+ pct: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
550
+ """One-step latent transition: (z, a) -> (z_next, reward_hat)."""
551
+ assert self.planning is not None, "planning block disabled"
552
+ cfg = self.cfg
553
+ B = z.shape[0]
554
+ ka = self.planning.a_emb_kind(kind) # (B,64)
555
+ ca = self.planning.a_emb_cell(
556
+ cell.clamp(0, cfg.grid_default * cfg.grid_default - 1)) # (B,256)
557
+ pa = self.planning.a_emb_pct(
558
+ pct.clamp(0.0, 1.0).to(z.dtype).view(B, 1)) # (B,128)
559
+ a = self.planning.a_emb_out(torch.cat([ka, ca, pa], dim=1)) # (B,512)
560
+ dz = self.planning.dyn_mlp(torch.cat([z, a], dim=1))
561
+ z_next = z + dz # residual
562
+ r_hat = self.planning.reward_head(z_next).squeeze(-1)
563
+ return z_next, r_hat
564
+
565
  # -- forward ------------------------------------------------------------
566
 
567
  def forward(self, rgb: torch.Tensor, nums: Optional[torch.Tensor] = None,
 
877
  if net.cfg.use_cell_value:
878
  heads += n(net.cell_value)
879
  mem = (n(net.mem) + n(net.mem_proj)) if net.mem is not None else 0
880
+ planning = n(net.planning) if net.planning is not None else 0
881
  return dict(spine=spine, cortex=cortex, decoder=decoder, heads=heads,
882
+ mem=mem, planning=planning)
883
 
884
 
885
  def make_model(cfg: Optional[SovereignConfig] = None,
 
898
  bd = param_breakdown(m)
899
  print(f"params: {total/1e6:.2f}M (spine {bd['spine']/1e6:.1f}M | "
900
  f"cortex {bd['cortex']/1e6:.1f}M | decoder {bd['decoder']/1e6:.1f}M "
901
+ f"| heads {bd['heads']/1e6:.1f}M | mem {bd['mem']/1e6:.1f}M | "
902
+ f"planning {bd['planning']/1e6:.1f}M)")
903
  H = cfg.map_size
904
  rgb = torch.rand(2, 3, H, H)
905
  nums = torch.rand(2, 8)
 
917
  for k, v in a.items() if k not in ("kind_probs",)})
918
  a2 = m.act(rgb, nums, rtg=rtg, state=a["state"])
919
  print("mem carry ok:", a2["state"].shape)
920
+ # planning block smoke (encode -> predict -> dynamics round trip)
921
+ z = m.encode(rgb, nums, rtg=rtg)
922
+ prior_z, v_z, win_z = m.prediction(z)
923
+ z2, r_hat = m.dynamics(z, torch.zeros(2, dtype=torch.long),
924
+ torch.zeros(2, dtype=torch.long),
925
+ torch.full((2,), 0.25))
926
+ print("planning:", prior_z.shape, v_z.shape, win_z.shape, z2.shape,
927
+ r_hat.shape)
928
  # no-rtg path (contract default)
929
  _, _, _, _ = m(rgb, nums)
930
  print("no-rtg contract path OK")