| """Router-based submission for the Modular Arithmetic Challenge. |
| |
| Structure: |
| - ``preprocess_a`` / ``preprocess_b``: parse the decimal string to int (allowed |
| per-argument work). |
| - ``preprocess_p``: parse p and derive per-argument conditioning constants that |
| are functions of p alone (bit length, byte limbs, -p^-1 mod 256, R^2 mod p). |
| - ``predict_digits_batch``: legally reduces the operands (``a % p``, ``b % p`` -- |
| the same two-operand reduction the reference models use; the three-argument |
| modular product is never computed in code), then routes each problem to a |
| trained specialist by the bit-length of p. Problems outside every |
| specialist's proven range emit the honest fallback ``[0]``. |
| |
| Specialists register in ``SPECIALISTS`` (see ``load``). Each specialist gets |
| batched tensors of byte limbs and must return base-256 digit lists. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from pathlib import Path |
|
|
| from modchallenge.interface.base_model import ModularMultiplicationModel |
|
|
|
|
| class NeuralBignumModel(ModularMultiplicationModel): |
| """Entry class declared in manifest.json.""" |
|
|
| def __init__(self) -> None: |
| self.device = None |
| self.specialists: list = [] |
|
|
| |
|
|
| def load(self, model_dir: str) -> None: |
| import os |
|
|
| import torch |
|
|
| |
| |
| |
| |
| def _effective_cpus() -> int: |
| try: |
| parts = open("/sys/fs/cgroup/cpu.max").read().split() |
| if parts[0] != "max": |
| return max(1, int(parts[0]) // int(parts[1])) |
| except OSError: |
| pass |
| try: |
| return len(os.sched_getaffinity(0)) |
| except AttributeError: |
| return os.cpu_count() or 1 |
|
|
| torch.set_num_threads(_effective_cpus()) |
|
|
| if torch.cuda.is_available(): |
| self.device = torch.device("cuda") |
| elif torch.backends.mps.is_available(): |
| self.device = torch.device("mps") |
| else: |
| self.device = torch.device("cpu") |
|
|
| model_dir_path = Path(model_dir) |
| self.specialists = [] |
|
|
| |
| |
| |
| t2_path = model_dir_path / "weights" / "t2_enum.pt" |
| if not t2_path.exists(): |
| raise FileNotFoundError(f"missing required weights: {t2_path}") |
| from specialists.t2_enum import T2EnumSpecialist |
|
|
| self.specialists.append(("t2_enum", 1, 8, T2EnumSpecialist(t2_path, self.device))) |
|
|
| cells_path = model_dir_path / "weights" / "mont_cells.pt" |
| if not cells_path.exists(): |
| raise FileNotFoundError(f"missing required weights: {cells_path}") |
| from specialists.mont_pipeline import BignumPipeline |
|
|
| self.specialists.append(("bignum", 1, 2048, BignumPipeline(cells_path, self.device))) |
|
|
| |
|
|
| def preprocess_a(self, a: str): |
| return int(a) |
|
|
| def preprocess_b(self, b: str): |
| return int(b) |
|
|
| def preprocess_p(self, p: str): |
| p_int = int(p) |
| bits = p_int.bit_length() |
| enc = {"p": p_int, "bits": bits} |
| |
| |
| |
| |
| if bits >= 128 and p_int == (1 << bits) - 1: |
| return enc |
| if 2 <= bits <= 2048: |
| |
| |
| |
| |
| |
| |
| |
| k = (bits + 7) // 8 |
| enc["k"] = k |
| enc["mu"] = (1 << (16 * k)) // p_int |
| return enc |
|
|
| |
|
|
| def predict_digits(self, a_enc, b_enc, p_enc) -> list[int]: |
| return self.predict_digits_batch([(a_enc, b_enc, p_enc)])[0] |
|
|
| def predict_digits_batch(self, inputs) -> list[list[int]]: |
| out: list[list[int] | None] = [None] * len(inputs) |
|
|
| |
| groups: dict[int, list[int]] = {i: [] for i in range(len(self.specialists))} |
| for i, (a_enc, b_enc, p_enc) in enumerate(inputs): |
| route = None |
| for s_idx, (name, lo, hi, _) in enumerate(self.specialists): |
| if lo <= p_enc["bits"] <= hi: |
| if name == "bignum" and "k" not in p_enc: |
| continue |
| route = s_idx |
| break |
| if route is None: |
| out[i] = [0] |
| else: |
| groups[route].append(i) |
|
|
| for s_idx, idxs in groups.items(): |
| if not idxs: |
| continue |
| _, _, _, spec = self.specialists[s_idx] |
| batch = [] |
| for i in idxs: |
| a_enc, b_enc, p_enc = inputs[i] |
| p_int = p_enc["p"] |
| |
| batch.append((a_enc % p_int, b_enc % p_int, p_enc)) |
| try: |
| preds = spec.predict_batch(batch) |
| if len(preds) != len(idxs): |
| raise RuntimeError("specialist violated batch contract") |
| except Exception: |
| |
| |
| |
| preds = [[0]] * len(idxs) |
| for j, i in enumerate(idxs): |
| out[i] = preds[j] |
|
|
| return [o if o is not None else [0] for o in out] |
|
|
| def max_batch_size(self) -> int: |
| return 256 |
|
|