summaryrefslogtreecommitdiff
path: root/src/model_api.py
blob: 31ffe31374e7f825171836d57308b0812a351919 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
"""Model interface contract + build dispatch + shared greedy decode."""
import torch
import torch.nn as nn

from src.config import Config
from src.data import pad_inputs


class PrimeModel(nn.Module):
    """Contract: forward(x, y_in) -> {"logits": (B,T_out,vocab), "halt_steps": (B,) or None}."""

    def forward(self, x, y_in):
        raise NotImplementedError

    def param_count(self) -> int:
        return sum(p.numel() for p in self.parameters())


def build_model(cfg: Config) -> PrimeModel:
    if cfg.model == "rnn":
        from src.models.rnn import TiedRNN
        return TiedRNN(cfg)
    if cfg.model == "transformer":
        from src.models.transformer import TransformerBaseline
        return TransformerBaseline(cfg)
    raise ValueError(f"unknown model: {cfg.model}")


@torch.no_grad()
def greedy_decode(model: PrimeModel, x: torch.Tensor, cfg: Config, max_len: int | None = None) -> torch.Tensor:
    """Autoregressive greedy decode of output digits. Returns (B, max_len) tokens (BOS stripped).

    Inputs are LEFT-padded to the global layout so positions match training exactly
    (batch/singleton invariance — codex BLOCKER fix)."""
    x = pad_inputs(x, cfg)
    max_len = max_len or cfg.max_out_len
    B = x.shape[0]
    y_in = torch.full((B, 1), cfg.eos_id, dtype=torch.long, device=x.device)
    for _ in range(max_len):
        out = model(x, y_in)
        nxt = out["logits"][:, -1].argmax(-1)
        y_in = torch.cat([y_in, nxt[:, None]], dim=1)
    return y_in[:, 1:]