| """Inference-only runtime for the trace transformer (tiers 3-6).
|
|
|
| Ships in the submission. Contains ONLY: token constants, base-256 limb
|
| conversion (base conversion is explicitly allowed representation work),
|
| prompt construction, and a batched KV-cache greedy decode loop that collects
|
| the model-emitted answer digits after the ANS marker.
|
|
|
| No arithmetic on input values happens here: Python only selects and collects
|
| model-emitted token ids. A fixed, value-independent selection (slice after
|
| ANS) maps the model's emission to the returned digit list; garbage emissions
|
| produce garbage (or sentinel) answers.
|
| """
|
|
|
| from __future__ import annotations
|
|
|
| import torch
|
|
|
|
|
| PAD, SEP, EQ, ANS, EOS = 256, 257, 258, 259, 260
|
|
|
|
|
| TRACE_TIERS = {
|
| 3: (9, 16, 2),
|
| 4: (17, 32, 4),
|
| 5: (33, 64, 8),
|
| 6: (65, 128, 16),
|
| }
|
|
|
|
|
| def tier_of_bits(bits: int) -> int | None:
|
| for t, (lo, hi, _nl) in TRACE_TIERS.items():
|
| if lo <= bits <= hi:
|
| return t
|
| return None
|
|
|
|
|
| def expected_trace_tokens(n_limbs: int) -> int:
|
|
|
|
|
|
|
| return 12 * n_limbs * n_limbs + 38 * n_limbs + 23
|
|
|
|
|
| def int_to_limbs(x: int, n_limbs: int) -> list[int]:
|
| """Base-256 digits, MSB-first, zero-padded to n_limbs."""
|
| out = [0] * n_limbs
|
| i = n_limbs - 1
|
| while x > 0 and i >= 0:
|
| out[i] = x & 0xFF
|
| x >>= 8
|
| i -= 1
|
| return out
|
|
|
|
|
| def prompt_tokens(a_red: int, b_red: int, p: int, n_limbs: int) -> list[int]:
|
| return (
|
| int_to_limbs(p, n_limbs) + [SEP]
|
| + int_to_limbs(a_red, n_limbs) + [SEP]
|
| + int_to_limbs(b_red, n_limbs) + [EQ]
|
| )
|
|
|
|
|
| @torch.no_grad()
|
| def decode_batch(model, problems, device, sentinel, max_new_factor: float = 1.3):
|
| """Batched greedy decode with KV cache.
|
|
|
| problems: list of (a_red, b_red, p, n_limbs) — all with the SAME n_limbs.
|
| Returns list of digit lists (n_limbs ints in [0,255], MSB-first) or
|
| `sentinel` (copied) where decoding failed to produce a full answer.
|
| """
|
| if not problems:
|
| return []
|
| model.eval()
|
| model.clear_cache()
|
| B = len(problems)
|
| n_limbs = problems[0][3]
|
| max_new = int(expected_trace_tokens(n_limbs) * max_new_factor) + 8
|
|
|
| prompts = [prompt_tokens(a, b, p, nl) for (a, b, p, nl) in problems]
|
|
|
| ctx = torch.tensor(prompts, dtype=torch.long, device=device)
|
|
|
| logits = model(ctx, use_cache=True)
|
| next_toks = logits[:, -1, :].argmax(-1)
|
|
|
| ans_seen = [False] * B
|
| ans_digits: list[list[int]] = [[] for _ in range(B)]
|
| done = [False] * B
|
|
|
| for _ in range(max_new):
|
| all_done = True
|
| toks = next_toks.tolist()
|
| for i in range(B):
|
| if done[i]:
|
| continue
|
| tok = toks[i]
|
| if tok == EOS:
|
| done[i] = True
|
| elif tok == ANS:
|
| ans_seen[i] = True
|
| elif ans_seen[i] and tok < 256:
|
| ans_digits[i].append(tok)
|
| if len(ans_digits[i]) == n_limbs:
|
| done[i] = True
|
| if not done[i]:
|
| all_done = False
|
| if all_done:
|
| break
|
| inp = next_toks.clone()
|
| step_logits = model(inp.unsqueeze(1), use_cache=True)
|
| next_toks = step_logits[:, -1, :].argmax(-1)
|
|
|
| model.clear_cache()
|
| return [
|
| d if len(d) == n_limbs else list(sentinel)
|
| for d in ans_digits
|
| ]
|
|
|