summaryrefslogtreecommitdiff
path: root/src/data.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/data.py')
-rw-r--r--src/data.py134
1 files changed, 117 insertions, 17 deletions
diff --git a/src/data.py b/src/data.py
index 26fb6b9..411ba7e 100644
--- a/src/data.py
+++ b/src/data.py
@@ -7,6 +7,18 @@ import torch
from src.config import Config
+# Special token constants for digits mode (Addendum 8, E8)
+EOS_ID = 10
+SEP_ID = 11 # '#' separator between scratchpad and final target
+PAUSE_ID = 12 # '<p>' pause/filler token (Arm C)
+RAND_START_ID = 13 # 'a' (token IDs 13..28 represent 'a'..'p' for Arms D1/D2)
+SYM_C = 29 # 'c'
+SYM_EQ = 30 # '='
+SYM_D = 31 # 'd'
+SYM_COLON = 32 # ':'
+NOISE_SLOT_ID = 33 # '<noise>' placeholder for Arm D3 continuous dynamic noise
+
+
def sieve_primes(limit: int) -> list[int]:
"""All primes <= limit (inclusive)."""
if limit < 2:
@@ -33,15 +45,51 @@ def encode_int(n: int, cfg: Config) -> list[int]:
return [int(d) for d in str(n)]
+def make_structured_trace(n: int, target_prime: int, primes_list: list[int]) -> list[int]:
+ """Generates trace tokens for candidate search and trial division:
+ For each candidate c in [n+1 .. target_prime]:
+ c, =, digits(c), d, digits(p), :, 0/1, ...
+ """
+ tokens = []
+ for c in range(n + 1, target_prime + 1):
+ tokens.append(SYM_C)
+ tokens.append(SYM_EQ)
+ tokens.extend([int(d) for d in str(c)])
+ for p in primes_list:
+ if p * p > c:
+ break
+ tokens.append(SYM_D)
+ tokens.extend([int(d) for d in str(p)])
+ tokens.append(SYM_COLON)
+ if c % p == 0:
+ tokens.append(0) # divisible -> composite found, halt checks for c
+ break
+ else:
+ tokens.append(1) # not divisible -> check next prime
+ return tokens
+
+
def decode_tokens(ts, cfg: Config) -> int:
- """Decode a token sequence, stopping at EOS. -1 if nothing decodable."""
+ """Decode a token sequence, stopping at EOS. If SEP_ID is present, decodes digits AFTER SEP_ID.
+ -1 if nothing decodable."""
if cfg.vocab_mode == "integers":
return int(ts[0]) if len(ts) else -1
+
+ if cfg.scratch_mode != "none":
+ if SEP_ID in ts:
+ idx = ts.index(SEP_ID)
+ ts = ts[idx + 1:]
+ else:
+ return -1
+
digits = []
for t in ts:
if t == cfg.eos_id:
break
- digits.append(int(t))
+ if 0 <= t <= 9:
+ digits.append(int(t))
+ else:
+ break
return int("".join(map(str, digits))) if digits else -1
@@ -67,7 +115,6 @@ def get_splits(cfg: Config) -> tuple[list[int], list[int]]:
train, val = sorted(inputs[n_val:]), sorted(inputs[:n_val])
if cfg.train_frac < 1.0:
n_tr = max(1, round(len(train) * cfg.train_frac))
- # deterministic subsample: seeded shuffle, take first n_tr
sub = random.Random(cfg.seed + 1000) # distinct stream from split shuffle
sub.shuffle(train)
train = sorted(train[:n_tr])
@@ -75,29 +122,79 @@ def get_splits(cfg: Config) -> tuple[list[int], list[int]]:
def build_examples(inputs: list[int], cfg: Config) -> list[tuple[list[int], list[int]]]:
- """[(input_tokens, target_tokens+EOS), ...]. task_mode selects the target function."""
+ """[(input_tokens, target_tokens+EOS), ...]. task_mode and scratch_mode select format."""
margin = max(100, int(cfg.range_end * 0.05) + 50)
- primes = sieve_primes(cfg.range_end + margin) if cfg.task_mode != "is_prime" else []
+ primes = sieve_primes(cfg.range_end + margin) if (cfg.task_mode != "is_prime" or cfg.scratch_mode == "structured") else []
out = []
for n in inputs:
+ x_toks = encode_int(n, cfg)
if cfg.task_mode == "is_prime":
p = 1 if is_prime_n(n) else 0
+ ans_toks = encode_int(p, cfg) + [cfg.eos_id]
+ if cfg.scratch_mode == "none":
+ out.append((x_toks, ans_toks))
+ elif cfg.scratch_mode == "filler":
+ filler = [PAUSE_ID] * cfg.scratch_len
+ y_toks = filler + [SEP_ID] + ans_toks
+ out.append((x_toks, y_toks))
+ else:
+ out.append((x_toks, ans_toks))
else:
p = next_prime(n, primes)
- out.append((encode_int(n, cfg), encode_int(p, cfg) + [cfg.eos_id]))
+ ans_toks = encode_int(p, cfg) + [cfg.eos_id]
+ if cfg.scratch_mode == "none":
+ out.append((x_toks, ans_toks))
+ elif cfg.scratch_mode == "structured":
+ trace = make_structured_trace(n, p, primes)
+ y_toks = trace + [SEP_ID] + ans_toks
+ out.append((x_toks, y_toks))
+ elif cfg.scratch_mode == "filler":
+ filler = [PAUSE_ID] * cfg.scratch_len
+ y_toks = filler + [SEP_ID] + ans_toks
+ out.append((x_toks, y_toks))
+ elif cfg.scratch_mode in ("random_learned", "random_frozen"):
+ rng = random.Random(cfg.seed + n * 37)
+ rand_toks = [RAND_START_ID + rng.randint(0, 15) for _ in range(cfg.scratch_len)]
+ y_toks = rand_toks + [SEP_ID] + ans_toks
+ out.append((x_toks, y_toks))
+ elif cfg.scratch_mode == "random_noise":
+ noise_toks = [NOISE_SLOT_ID] * cfg.scratch_len
+ y_toks = noise_toks + [SEP_ID] + ans_toks
+ out.append((x_toks, y_toks))
+ else:
+ out.append((x_toks, ans_toks))
return out
def _global_lengths(cfg: Config) -> tuple[int, int]:
- """(in_max, out_max): fixed global lengths so batch layout == singleton layout (codex BLOCKER fix)."""
+ """(in_max, out_max): fixed global lengths so batch layout == singleton layout."""
if cfg.vocab_mode == "integers":
- return 1, 2 # [value], [value, EOS]
+ return 1, 2
+ in_max = len(str(cfg.range_end))
if cfg.task_mode == "is_prime":
- return len(str(cfg.range_end)), 2 # digit token "1"/"0" + EOS
- margin = max(100, int(cfg.range_end * 0.05) + 50)
- primes = sieve_primes(cfg.range_end + margin)
- max_target = next_prime(cfg.range_end, primes)
- return len(str(cfg.range_end)), len(str(max_target)) + 1 # digits + EOS
+ base_out = 2
+ else:
+ margin = max(100, int(cfg.range_end * 0.05) + 50)
+ primes = sieve_primes(cfg.range_end + margin)
+ max_target = next_prime(cfg.range_end, primes)
+ base_out = len(str(max_target)) + 1
+
+ if cfg.scratch_mode == "none":
+ return in_max, base_out
+ elif cfg.scratch_mode in ("filler", "random_learned", "random_frozen", "random_noise"):
+ return in_max, cfg.scratch_len + 1 + base_out
+ elif cfg.scratch_mode == "structured":
+ margin = max(100, int(cfg.range_end * 0.05) + 50)
+ primes = sieve_primes(cfg.range_end + margin)
+ # sample max trace length across the entire range
+ max_trace_len = 0
+ for n in range(cfg.range_start, cfg.range_end + 1):
+ p = next_prime(n, primes)
+ tr = make_structured_trace(n, p, primes)
+ if len(tr) > max_trace_len:
+ max_trace_len = len(tr)
+ return in_max, max_trace_len + 1 + base_out
+ return in_max, base_out
def pad_inputs(x: torch.Tensor, cfg: Config) -> torch.Tensor:
@@ -110,16 +207,19 @@ def pad_inputs(x: torch.Tensor, cfg: Config) -> torch.Tensor:
def make_batch(examples, cfg: Config) -> dict[str, torch.Tensor]:
- """Fixed global layout (not batch-max): x left-padded to in_max, y right-padded to out_max."""
- xs, ys = zip(*examples)
+ """Fixed global layout: x left-padded to in_max, y right-padded to out_max."""
in_max, out_max = _global_lengths(cfg)
B = len(examples)
x = torch.full((B, in_max), cfg.pad_id, dtype=torch.long)
y = torch.full((B, out_max), cfg.pad_id, dtype=torch.long)
+ loss_mask = torch.zeros((B, out_max), dtype=torch.bool)
for i, (xi, yi) in enumerate(examples):
x[i, in_max - len(xi):] = torch.tensor(xi, dtype=torch.long)
y[i, : len(yi)] = torch.tensor(yi, dtype=torch.long)
- # teacher-forced decoder input: BOS(=EOS reuse) then shifted y
+ if cfg.scratch_mode in ("filler", "random_learned", "random_frozen", "random_noise"):
+ loss_mask[i, cfg.scratch_len : len(yi)] = True
+ else:
+ loss_mask[i, : len(yi)] = True
y_in = torch.cat([torch.full((B, 1), cfg.eos_id, dtype=torch.long), y[:, :-1]], dim=1)
y_mask = y != cfg.pad_id
- return {"x": x, "y": y, "y_in": y_in, "y_mask": y_mask}
+ return {"x": x, "y": y, "y_in": y_in, "y_mask": y_mask, "loss_mask": loss_mask}