cire77 commited on
Commit
08e3255
·
verified ·
1 Parent(s): f6b7989

tier-4: update model.py (htop90=4)

Browse files
Files changed (1) hide show
  1. model.py +56 -8
model.py CHANGED
@@ -9,9 +9,11 @@ Compliance contract (see rules/evaluation.md):
9
  ``[0, p)``) materially determines the answer.
10
  - We emit the residue as base-10 digits (``output_base = 10``); the harness decodes.
11
 
12
- Out of regime (``p >= 10**WIDTH``, i.e. tiers >= 4) the network's fixed-width
13
- residue encoding cannot represent the operands, so we emit ``[0]`` an honest
14
- fallback, not a guess. This model targets the low tiers (1-3).
 
 
15
 
16
  The architecture (encoder + classification/angular head) is loaded from the
17
  checkpoint's ``arch`` field, so the same wrapper serves either trained head.
@@ -281,6 +283,7 @@ def _modmul_decode(model, cfg, xyp, device, chunk=128):
281
  seg = torch.zeros(g, dtype=torch.long, device=device)
282
  done = torch.zeros(g, dtype=torch.bool, device=device)
283
  gen = [[] for _ in range(g)]
 
284
  while toks.shape[1] < max_len and not bool(done.all()):
285
  nxt = model(toks, abac)[:, -1].argmax(-1)
286
  nxt = torch.where(done, torch.full_like(nxt, MM_PAD), nxt)
@@ -295,6 +298,15 @@ def _modmul_decode(model, cfg, xyp, device, chunk=128):
295
  toks = torch.cat([toks, nxt.unsqueeze(1)], dim=1)
296
  abac = torch.cat([abac, new_abac.unsqueeze(1)], dim=1)
297
  done = done | (nxt == MM_EOS)
 
 
 
 
 
 
 
 
 
298
  for j, i in enumerate(sub):
299
  gj = gen[j]
300
  if MM_COLON in gj:
@@ -303,6 +315,14 @@ def _modmul_decode(model, cfg, xyp, device, chunk=128):
303
  out[i] = ans if ans else [0]
304
  else:
305
  out[i] = [0]
 
 
 
 
 
 
 
 
306
  return [o if o is not None else [0] for o in out]
307
 
308
 
@@ -317,6 +337,8 @@ class EBMModMul(ModularMultiplicationModel):
317
  self.arch = None
318
  self.mm = None # tier-3 modmul scratchpad
319
  self.mm_cfg = None
 
 
320
 
321
  def load(self, model_dir: str) -> None:
322
  if torch.cuda.is_available():
@@ -343,6 +365,16 @@ class EBMModMul(ModularMultiplicationModel):
343
  ).to(self.device)
344
  self.mm.load_state_dict(ckpt["tier3"]["state_dict"])
345
  self.mm.eval()
 
 
 
 
 
 
 
 
 
 
346
 
347
  # Per-argument identity preprocessing (each hook sees only its own argument).
348
  def preprocess_a(self, a): return a
@@ -354,26 +386,34 @@ class EBMModMul(ModularMultiplicationModel):
354
  return self.predict_digits_batch([(a_enc, b_enc, p_enc)])[0]
355
 
356
  # Prime routing: tiers 1-2 (p < 512) use the classification head; tier 3
357
- # (512 <= p < 65536) uses the modmul scratchpad; p >= 65536 (tiers 4+) is out
358
- # of regime. 512 = 2**9 is exactly the tier-3 floor (see config TIERS).
 
359
  TIER3_LO = 512
360
  TIER3_HI = 65536
 
361
 
362
  @torch.no_grad()
363
  def predict_digits_batch(self, inputs):
364
  out: list[list[int] | None] = [None] * len(inputs)
365
  x_rows, y_rows, p_rows, p_ints, idx = [], [], [], [], [] # tiers 1-2
366
  mm_items, mm_idx = [], [] # tier 3
 
367
 
368
  for i, (a_enc, b_enc, p_enc) in enumerate(inputs):
369
  p = int(p_enc)
370
- # Out of regime (residues don't fit the fixed-width / trained range): honest 0.
371
- if p >= self.TIER3_HI:
372
  out[i] = [0]
373
  continue
374
  a_red = int(a_enc) % p # per-operand reduction (allowed)
375
  b_red = int(b_enc) % p
376
- if p >= self.TIER3_LO and self.mm is not None:
 
 
 
 
 
377
  mm_items.append((a_red, b_red, p)); mm_idx.append(i)
378
  else:
379
  x_rows.append(digits_fixed(a_red))
@@ -397,6 +437,14 @@ class EBMModMul(ModularMultiplicationModel):
397
  for j, i in enumerate(mm_idx):
398
  out[i] = res[j]
399
 
 
 
 
 
 
 
 
 
400
  return [o if o is not None else [0] for o in out]
401
 
402
  def max_batch_size(self) -> int:
 
9
  ``[0, p)``) materially determines the answer.
10
  - We emit the residue as base-10 digits (``output_base = 10``); the harness decodes.
11
 
12
+ Routing by prime size: tiers 1-2 (p < 512) use the classification head; tier 3
13
+ (512 <= p < 65536) and tier 4 (65536 <= p < 2**32) use the interleaved
14
+ modular-multiply scratchpad decoder (same architecture, separately trained
15
+ weights). p >= 2**32 (tiers 5+) is out of regime, so we emit ``[0]`` — an honest
16
+ fallback, not a guess.
17
 
18
  The architecture (encoder + classification/angular head) is loaded from the
19
  checkpoint's ``arch`` field, so the same wrapper serves either trained head.
 
283
  seg = torch.zeros(g, dtype=torch.long, device=device)
284
  done = torch.zeros(g, dtype=torch.bool, device=device)
285
  gen = [[] for _ in range(g)]
286
+ steps = 0
287
  while toks.shape[1] < max_len and not bool(done.all()):
288
  nxt = model(toks, abac)[:, -1].argmax(-1)
289
  nxt = torch.where(done, torch.full_like(nxt, MM_PAD), nxt)
 
298
  toks = torch.cat([toks, nxt.unsqueeze(1)], dim=1)
299
  abac = torch.cat([abac, new_abac.unsqueeze(1)], dim=1)
300
  done = done | (nxt == MM_EOS)
301
+ # The sequence grows one token per step, so the caching allocator
302
+ # holds a distinct buffer for every length (~800 on tier-4 chains)
303
+ # and OOMs mid-decode. Periodically release them.
304
+ steps += 1
305
+ if steps % 32 == 0:
306
+ if device.type == "mps":
307
+ torch.mps.empty_cache()
308
+ elif device.type == "cuda":
309
+ torch.cuda.empty_cache()
310
  for j, i in enumerate(sub):
311
  gj = gen[j]
312
  if MM_COLON in gj:
 
315
  out[i] = ans if ans else [0]
316
  else:
317
  out[i] = [0]
318
+ # Release the chunk's activations: the caching allocator otherwise
319
+ # accumulates across length-groups/chunks (MPS in particular never
320
+ # frees mid-run) and OOMs on long tier-4 chains.
321
+ del toks, abac, seg, done, gen
322
+ if device.type == "mps":
323
+ torch.mps.empty_cache()
324
+ elif device.type == "cuda":
325
+ torch.cuda.empty_cache()
326
  return [o if o is not None else [0] for o in out]
327
 
328
 
 
337
  self.arch = None
338
  self.mm = None # tier-3 modmul scratchpad
339
  self.mm_cfg = None
340
+ self.mm4 = None # tier-4 modmul scratchpad
341
+ self.mm4_cfg = None
342
 
343
  def load(self, model_dir: str) -> None:
344
  if torch.cuda.is_available():
 
365
  ).to(self.device)
366
  self.mm.load_state_dict(ckpt["tier3"]["state_dict"])
367
  self.mm.eval()
368
+ # Tier 4: same scratchpad architecture, trained on [2**17, 2**32).
369
+ if "tier4" in ckpt:
370
+ c4 = ckpt["tier4"]["config"]
371
+ self.mm4_cfg = c4
372
+ self.mm4 = AbacusDecoder(
373
+ max_len=c4["max_len"], abacus_max=c4["abacus_max"], d_model=c4["d_model"],
374
+ nhead=c4["nhead"], num_layers=c4["layers"], dim_ff=c4["dim_ff"],
375
+ ).to(self.device)
376
+ self.mm4.load_state_dict(ckpt["tier4"]["state_dict"])
377
+ self.mm4.eval()
378
 
379
  # Per-argument identity preprocessing (each hook sees only its own argument).
380
  def preprocess_a(self, a): return a
 
386
  return self.predict_digits_batch([(a_enc, b_enc, p_enc)])[0]
387
 
388
  # Prime routing: tiers 1-2 (p < 512) use the classification head; tier 3
389
+ # (512 <= p < 65536) and tier 4 (65536 <= p < 2**32) use the modmul scratchpad
390
+ # (separate trained weights); p >= 2**32 (tiers 5+) is out of regime.
391
+ # 512 = 2**9 is the tier-3 floor, 65536 = 2**16 the tier-3/4 boundary (TIERS).
392
  TIER3_LO = 512
393
  TIER3_HI = 65536
394
+ TIER4_HI = 2 ** 32
395
 
396
  @torch.no_grad()
397
  def predict_digits_batch(self, inputs):
398
  out: list[list[int] | None] = [None] * len(inputs)
399
  x_rows, y_rows, p_rows, p_ints, idx = [], [], [], [], [] # tiers 1-2
400
  mm_items, mm_idx = [], [] # tier 3
401
+ mm4_items, mm4_idx = [], [] # tier 4
402
 
403
  for i, (a_enc, b_enc, p_enc) in enumerate(inputs):
404
  p = int(p_enc)
405
+ # Out of regime (residues don't fit the trained range): honest 0.
406
+ if p >= self.TIER4_HI:
407
  out[i] = [0]
408
  continue
409
  a_red = int(a_enc) % p # per-operand reduction (allowed)
410
  b_red = int(b_enc) % p
411
+ if p >= self.TIER3_HI:
412
+ if self.mm4 is not None:
413
+ mm4_items.append((a_red, b_red, p)); mm4_idx.append(i)
414
+ else:
415
+ out[i] = [0]
416
+ elif p >= self.TIER3_LO and self.mm is not None:
417
  mm_items.append((a_red, b_red, p)); mm_idx.append(i)
418
  else:
419
  x_rows.append(digits_fixed(a_red))
 
437
  for j, i in enumerate(mm_idx):
438
  out[i] = res[j]
439
 
440
+ if mm4_items:
441
+ # Tier-4 chains are ~800 tokens; without a KV-cache the per-step
442
+ # forward is O(L^2), so decode in small sub-batches to bound peak
443
+ # memory (a single batch of 100 OOMs on a 20 GB device).
444
+ res = _modmul_decode(self.mm4, self.mm4_cfg, mm4_items, self.device, chunk=16)
445
+ for j, i in enumerate(mm4_idx):
446
+ out[i] = res[j]
447
+
448
  return [o if o is not None else [0] for o in out]
449
 
450
  def max_batch_size(self) -> int: