slkML commited on
Commit
df42b8c
·
verified ·
1 Parent(s): 42e4ba6

Upload 3 files

Browse files
Files changed (3) hide show
  1. manifest.json +7 -0
  2. model.py +125 -0
  3. weights.pt +3 -0
manifest.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "entry_class": "model.VanillaModel",
3
+ "output_base": 10,
4
+ "framework": "pytorch",
5
+ "model_description": "3-layer feedforward MLP (input -> 512 hidden ReLU -> output). Input: zero-padded decimal digits of a mod p, b mod p, and p concatenated into a 30-float vector. Output: 10 digit positions x 10 classes (base-10 digits of the answer, MSB first). ~16K parameters.",
6
+ "training_description": "Trained from random init on 200K synthetic (a, b, p) -> (a*b mod p) examples spanning tiers 1-4 (p up to 32 bits). Adam optimizer, lr=1e-3 with cosine annealing, 30 epochs, batch size 512. Best checkpoint saved by validation accuracy."
7
+ }
model.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Vanilla 3-layer feedforward ANN for modular multiplication.
2
+
3
+ Input: digit-encoded (a_red, b_red, p) — each zero-padded to MAX_DIGITS
4
+ Hidden: one fully-connected layer with ReLU
5
+ Output: MAX_OUT_DIGITS * 10 logits — one 10-way class per output digit position
6
+
7
+ At inference, a and b are reduced mod p inside predict_digits (allowed by rules)
8
+ before being encoded and fed to the network.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+
15
+ import torch
16
+ import torch.nn as nn
17
+
18
+ from modchallenge.interface.base_model import ModularMultiplicationModel
19
+
20
+ MAX_DIGITS = 10 # decimal digits per input slot (covers p up to ~4 billion = tier 4)
21
+ MAX_OUT_DIGITS = 10 # decimal digits in the answer (answer < p <= tier-4 max)
22
+ INPUT_SIZE = 3 * MAX_DIGITS # 30
23
+ HIDDEN_SIZE = 512
24
+ OUTPUT_SIZE = MAX_OUT_DIGITS * 10 # 100
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Architecture
29
+ # ---------------------------------------------------------------------------
30
+
31
+ class VanillaMLP(nn.Module):
32
+ def __init__(
33
+ self,
34
+ input_size: int = INPUT_SIZE,
35
+ hidden_size: int = HIDDEN_SIZE,
36
+ output_size: int = OUTPUT_SIZE,
37
+ ):
38
+ super().__init__()
39
+ self.net = nn.Sequential(
40
+ nn.Linear(input_size, hidden_size),
41
+ nn.ReLU(),
42
+ nn.Linear(hidden_size, output_size),
43
+ )
44
+
45
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
46
+ """x: (B, INPUT_SIZE) floats in [0, 1]. returns (B, MAX_OUT_DIGITS, 10) logits."""
47
+ out = self.net(x) # (B, OUTPUT_SIZE)
48
+ return out.view(-1, MAX_OUT_DIGITS, 10) # (B, D, 10)
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # Encoding helpers (shared by train.py and predict_digits)
53
+ # ---------------------------------------------------------------------------
54
+
55
+ def encode_int(n: int, length: int = MAX_DIGITS) -> list[int]:
56
+ """Integer -> zero-padded decimal digit list of fixed length, MSB first."""
57
+ s = str(int(n)).zfill(length)
58
+ if len(s) > length:
59
+ s = s[-length:] # truncate if somehow too long
60
+ return [int(c) for c in s]
61
+
62
+
63
+ def digits_to_tensor(n: int, length: int = MAX_DIGITS) -> list[float]:
64
+ """Encode integer n as normalized floats in [0, 1]."""
65
+ return [d / 9.0 for d in encode_int(n, length)]
66
+
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # Submission entry point
70
+ # ---------------------------------------------------------------------------
71
+
72
+ class VanillaModel(ModularMultiplicationModel):
73
+ def __init__(self):
74
+ self.model: VanillaMLP | None = None
75
+ self.device: torch.device | None = None
76
+
77
+ def load(self, model_dir: str) -> None:
78
+ if torch.cuda.is_available():
79
+ self.device = torch.device("cuda")
80
+ else:
81
+ self.device = torch.device("cpu")
82
+
83
+ ckpt = torch.load(
84
+ Path(model_dir) / "weights.pt",
85
+ map_location=self.device,
86
+ weights_only=True,
87
+ )
88
+ cfg = ckpt.get("config", {})
89
+ self.model = VanillaMLP(
90
+ input_size=cfg.get("input_size", INPUT_SIZE),
91
+ hidden_size=cfg.get("hidden_size", HIDDEN_SIZE),
92
+ output_size=cfg.get("output_size", OUTPUT_SIZE),
93
+ )
94
+ self.model.load_state_dict(ckpt["state_dict"])
95
+ self.model.to(self.device)
96
+ self.model.eval()
97
+
98
+ def preprocess_a(self, a: str):
99
+ return a
100
+
101
+ def preprocess_b(self, b: str):
102
+ return b
103
+
104
+ def preprocess_p(self, p: str):
105
+ return p
106
+
107
+ @torch.no_grad()
108
+ def predict_digits(self, a_enc, b_enc, p_enc) -> list[int]:
109
+ assert self.model is not None
110
+
111
+ p = int(p_enc)
112
+ a_red = int(a_enc) % p
113
+ b_red = int(b_enc) % p
114
+
115
+ x = digits_to_tensor(a_red) + digits_to_tensor(b_red) + digits_to_tensor(p)
116
+ inp = torch.tensor([x], dtype=torch.float32, device=self.device) # (1, 30)
117
+
118
+ logits = self.model(inp) # (1, MAX_OUT_DIGITS, 10)
119
+ preds = logits[0].argmax(-1).tolist() # [d0, d1, ..., d9]
120
+
121
+ # Strip leading zeros, return at least [0]
122
+ result = preds
123
+ while len(result) > 1 and result[0] == 0:
124
+ result = result[1:]
125
+ return result
weights.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:47fd133c738e17e2288accbcccc6ebe65b3987772823adfcff362ca0576e10bd
3
+ size 271133