tier-4 SRT member: composed_encoding.py
Browse files- composed_encoding.py +229 -30
composed_encoding.py
CHANGED
|
@@ -50,7 +50,9 @@ def prompt_str(x: int, y: int, p: int, W: int, base: int, subpad: bool = False)
|
|
| 50 |
|
| 51 |
|
| 52 |
def gen_len(W: int, scratch: bool = False, cursor: bool = False,
|
| 53 |
-
subpad: bool = False
|
|
|
|
|
|
|
| 54 |
"""Tokens generated after '=': W acc blocks, N restage, quotient, answer.
|
| 55 |
scratch=True: each quotient limb is followed by the running remainder
|
| 56 |
(W limbs, LSB-first) — externalizes the division state, which the model
|
|
@@ -66,16 +68,94 @@ def gen_len(W: int, scratch: bool = False, cursor: bool = False,
|
|
| 66 |
per-step diagnostic showed failing (steps 0-4 = copies, perfect; real
|
| 67 |
division steps 5+ at 0.04-0.15 even teacher-forced). Tier-2's compact
|
| 68 |
success could memorize qd*p over 48 primes; tier 3's 400 primes need the
|
| 69 |
-
generic circuit, so generate it like the (perfectly learned) acc rows.
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
|
| 74 |
def build_example(x: int, y: int, p: int, W: int, base: int, scratch: bool = False,
|
| 75 |
-
cursor: bool = False, subpad: bool = False
|
|
|
|
|
|
|
| 76 |
"""Return (text, ann). x,y < p < base**W."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
assert not cursor or scratch, "cursor requires scratch"
|
| 78 |
assert not subpad or cursor, "subpad requires cursor"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
N = x * y
|
| 80 |
y_limbs = to_limbs(y, W, base)
|
| 81 |
x_limbs = to_limbs(x, W, base)
|
|
@@ -98,17 +178,103 @@ def build_example(x: int, y: int, p: int, W: int, base: int, scratch: bool = Fal
|
|
| 98 |
pos += 2 * W
|
| 99 |
assert acc == N
|
| 100 |
|
| 101 |
-
parts.append(NMARK + limb_str(N, 2 * W, base, msb_first=True)) # restage MSB
|
| 102 |
-
pos += 1 + 2 * W
|
| 103 |
-
|
| 104 |
-
q_limbs, rems, answer = long_division(N, p, W, base)
|
| 105 |
n_msb = to_limbs(N, 2 * W, base, msb_first=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
r_prev = 0
|
| 107 |
-
for i, (qd, r) in enumerate(zip(q_limbs, rems)):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
if cursor: # copy the consumed dividend limb
|
| 109 |
parts.append(limb_char(n_msb[i]))
|
| 110 |
pos += 1
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
if not scratch: # compact: remainder is internal state
|
| 113 |
rstr = to_limbs(r, W, base, msb_first=True)
|
| 114 |
for j in range(W):
|
|
@@ -125,15 +291,23 @@ def build_example(x: int, y: int, p: int, W: int, base: int, scratch: bool = Fal
|
|
| 125 |
parts.append(limb_str(qdp, W + 1, base))
|
| 126 |
pos += W + 1
|
| 127 |
if scratch: # emit remainder LSB-first (local borrow)
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
| 131 |
b = 0
|
| 132 |
for k in range(W):
|
| 133 |
-
|
| 134 |
b = 1 if num_l[k] - qdp_l[k] - b < 0 else 0
|
| 135 |
-
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
r_prev = r
|
| 138 |
parts.append(REVMARK)
|
| 139 |
pos += 1
|
|
@@ -145,8 +319,10 @@ def build_example(x: int, y: int, p: int, W: int, base: int, scratch: bool = Fal
|
|
| 145 |
|
| 146 |
prompt = prompt_str(x, y, p, W, base, subpad)
|
| 147 |
text = prompt + "".join(parts)
|
| 148 |
-
assert len(text) == len(prompt) + gen_len(W, scratch, cursor, subpad
|
| 149 |
-
|
|
|
|
|
|
|
| 150 |
ann = [dict() for _ in range(len(text))]
|
| 151 |
base_i = len(prompt)
|
| 152 |
for gi, var, val in ann_g:
|
|
@@ -154,8 +330,10 @@ def build_example(x: int, y: int, p: int, W: int, base: int, scratch: bool = Fal
|
|
| 154 |
return text, ann
|
| 155 |
|
| 156 |
|
| 157 |
-
def var_specs(W: int, base: int, scratch: bool = False, subpad: bool = False
|
| 158 |
-
|
|
|
|
|
|
|
| 159 |
if subpad: # multiply-carry + subtract-borrow chains
|
| 160 |
specs += [("spcarry", base), ("sborrow", 2)]
|
| 161 |
if not scratch: # scratch emits remainders: no probe
|
|
@@ -164,8 +342,13 @@ def var_specs(W: int, base: int, scratch: bool = False, subpad: bool = False):
|
|
| 164 |
|
| 165 |
|
| 166 |
def decode_answer(gen_chars: str, W: int, base: int, scratch: bool = False,
|
| 167 |
-
cursor: bool = False, subpad: bool = False
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
return parse_limbs(gen_chars[off:off + W], base)
|
| 170 |
|
| 171 |
|
|
@@ -173,19 +356,35 @@ if __name__ == "__main__":
|
|
| 173 |
import random
|
| 174 |
rng = random.Random(3)
|
| 175 |
for base in (10, 100):
|
| 176 |
-
for scratch, cursor, subpad
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
for _ in range(4000):
|
| 179 |
-
W = rng.randint(1,
|
| 180 |
p = rng.randrange(max(2, base ** (W - 1)), base ** W)
|
| 181 |
x, y = rng.randrange(p), rng.randrange(p)
|
| 182 |
-
text, ann = build_example(x, y, p, W, base, scratch, cursor,
|
|
|
|
|
|
|
| 183 |
plen = len(prompt_str(x, y, p, W, base, subpad))
|
| 184 |
-
assert decode_answer(text[plen:], W, base, scratch, cursor,
|
| 185 |
-
|
|
|
|
|
|
|
|
|
|
| 186 |
assert len(ann) == len(text)
|
| 187 |
print(f"composed base={base} scratch={scratch} cursor={cursor} "
|
| 188 |
-
f"subpad={subpad}
|
|
|
|
| 189 |
print("\ntokens/example (prompt+gen+NL):")
|
| 190 |
for tier, Wd in [(3, 5), (4, 10), (5, 20), (6, 39), (7, 78)]:
|
| 191 |
for base, W in ((10, Wd), (100, (Wd + 1) // 2)):
|
|
|
|
| 50 |
|
| 51 |
|
| 52 |
def gen_len(W: int, scratch: bool = False, cursor: bool = False,
|
| 53 |
+
subpad: bool = False, stepidx: bool = False,
|
| 54 |
+
skiptriv: bool = False, subnum: bool = False,
|
| 55 |
+
bemit: bool = False, srt: bool = False) -> int:
|
| 56 |
"""Tokens generated after '=': W acc blocks, N restage, quotient, answer.
|
| 57 |
scratch=True: each quotient limb is followed by the running remainder
|
| 58 |
(W limbs, LSB-first) — externalizes the division state, which the model
|
|
|
|
| 68 |
per-step diagnostic showed failing (steps 0-4 = copies, perfect; real
|
| 69 |
division steps 5+ at 0.04-0.15 even teacher-forced). Tier-2's compact
|
| 70 |
success could memorize qd*p over 48 primes; tier 3's 400 primes need the
|
| 71 |
+
generic circuit, so generate it like the (perfectly learned) acc rows.
|
| 72 |
+
stepidx=True (requires cursor): each step opens with its 2-digit ASCII
|
| 73 |
+
index, and the restage pairs every dividend limb with its index — a
|
| 74 |
+
content-addressable name per step. Tier-4 diagnostic (W=10, 20 steps):
|
| 75 |
+
steps 0-9 at ~1.00, cliff to ~0.45 at 10+; rem fails worst (0.36 late)
|
| 76 |
+
despite all-local operands, so the wall is step MISBINDING under
|
| 77 |
+
relative-position (ALiBi) attention — the number of identical-looking
|
| 78 |
+
distractor blocks grows with step index. Indexed steps turn 'previous
|
| 79 |
+
step's remainder' and 'my dividend limb' into content lookups.
|
| 80 |
+
skiptriv=True (requires stepidx): x,y < p guarantees Q = N//p < p, so the
|
| 81 |
+
first W division steps ALWAYS emit qd=0 — provably trivial filler that
|
| 82 |
+
doubles the distractor count, dilutes gradient (250 of 828 tokens at
|
| 83 |
+
W=10), and pushes real steps ~250 tokens further from the prompt's p
|
| 84 |
+
field. Skip them: division = W real steps keeping their TRUE indices
|
| 85 |
+
(so restage lookups still match), starting from r_prev = N's top half.
|
| 86 |
+
Also re-emits p LSB-first ('m' + W limbs) right after the restage, so
|
| 87 |
+
qd*p rows read a local p copy instead of a ~700-token fetch (tier-4
|
| 88 |
+
stepidx diagnostic: qdp 0.78-0.84 was the residual fetch weakness).
|
| 89 |
+
subnum=True (requires subpad): each step copies its numerator
|
| 90 |
+
num = r_prev*base + cursor limb (W+1 limbs, LSB) between cursor and qd,
|
| 91 |
+
so qd / qd*p / rem are all WITHIN-step local arithmetic. skiptriv
|
| 92 |
+
diagnostic: qdp hit 0.9949 once p became a local read while rem
|
| 93 |
+
plateaued at 0.43 fetching the previous step's rem block -- every pure
|
| 94 |
+
copy in this stack reaches ~1.00, every fetch+arithmetic fusion stalls;
|
| 95 |
+
decouple them (the subpad lesson, applied to the subtraction).
|
| 96 |
+
bemit=True (requires subnum): the remainder block becomes (borrow, limb)
|
| 97 |
+
pairs -- the borrow bit is EMITTED as an ASCII 0/1 token before each rem
|
| 98 |
+
limb, so b_k sits in context when rem[k] is predicted and b_{k+1} is a
|
| 99 |
+
local function of (num[k], qdp[k], b_k), all adjacent. v4 diagnostic:
|
| 100 |
+
rem errors were 58% unstructured noise, 0.54 even at b=0 -- the
|
| 101 |
+
install-only borrow supervision never built a circuit; emit the chain
|
| 102 |
+
(the scratch lesson, applied to the borrow state)."""
|
| 103 |
+
restage = 1 + (3 if stepidx else 1) * 2 * W
|
| 104 |
+
pcopy = (1 + W) if skiptriv else 0
|
| 105 |
+
if srt:
|
| 106 |
+
# stepi + cursor + num complement (W+2) + qd + |qd|*p (W+1) + pairs
|
| 107 |
+
per_step = 2 + 1 + (W + 2) + 1 + (W + 1) + 2 * (W + 2)
|
| 108 |
+
final = 1 + 2 * W # sign + correction pairs
|
| 109 |
+
return (W * (1 + 2 * W) + restage + pcopy + W * per_step
|
| 110 |
+
+ final + 1 + W)
|
| 111 |
+
per_step = ((2 if stepidx else 0) + (1 if cursor else 0)
|
| 112 |
+
+ ((W + 1) if subnum else 0) + 1
|
| 113 |
+
+ ((W + 1) if subpad else 0)
|
| 114 |
+
+ ((2 * W) if bemit else (W if scratch else 0)))
|
| 115 |
+
n_steps = W if skiptriv else 2 * W
|
| 116 |
+
return W * (1 + 2 * W) + restage + pcopy + n_steps * per_step + 1 + W
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def _srt_qd(num: int, p: int, base: int) -> int:
|
| 120 |
+
"""Signed SRT digit from |num|'s top-4 / p's top-3 limbs (exact integer
|
| 121 |
+
rounding). Guarantees |num - qd*p| < p given |num| < base*p (verified:
|
| 122 |
+
0 violations / 500k tier-4 selections, worst |s|/p = 0.54)."""
|
| 123 |
+
if num == 0:
|
| 124 |
+
return 0
|
| 125 |
+
sgn = 1 if num > 0 else -1
|
| 126 |
+
an = abs(num)
|
| 127 |
+
nw = 1
|
| 128 |
+
while base ** nw <= an:
|
| 129 |
+
nw += 1
|
| 130 |
+
pw = 1
|
| 131 |
+
while base ** pw <= p:
|
| 132 |
+
pw += 1
|
| 133 |
+
n4 = an // base ** max(0, nw - 4)
|
| 134 |
+
p3 = p // base ** max(0, pw - 3)
|
| 135 |
+
sh = max(0, nw - 4) - max(0, pw - 3)
|
| 136 |
+
if sh >= 0:
|
| 137 |
+
a, b = n4 * base ** sh, p3
|
| 138 |
+
else:
|
| 139 |
+
a, b = n4, p3 * base ** -sh
|
| 140 |
+
q = (2 * a + b) // (2 * b) # round(a/b), exact
|
| 141 |
+
return sgn * min(base - 1, q)
|
| 142 |
|
| 143 |
|
| 144 |
def build_example(x: int, y: int, p: int, W: int, base: int, scratch: bool = False,
|
| 145 |
+
cursor: bool = False, subpad: bool = False, stepidx: bool = False,
|
| 146 |
+
skiptriv: bool = False, subnum: bool = False,
|
| 147 |
+
bemit: bool = False, srt: bool = False):
|
| 148 |
"""Return (text, ann). x,y < p < base**W."""
|
| 149 |
+
assert not subnum or subpad, "subnum requires subpad"
|
| 150 |
+
assert not bemit or subnum, "bemit requires subnum"
|
| 151 |
+
assert not srt or (skiptriv and bemit), "srt requires skiptriv+bemit"
|
| 152 |
+
assert not srt or base == 10, "srt: signed-digit tokens are base-10 only"
|
| 153 |
assert not cursor or scratch, "cursor requires scratch"
|
| 154 |
assert not subpad or cursor, "subpad requires cursor"
|
| 155 |
+
assert not stepidx or cursor, "stepidx requires cursor"
|
| 156 |
+
assert not skiptriv or stepidx, "skiptriv requires stepidx"
|
| 157 |
+
assert not stepidx or 2 * W <= 100, "stepidx: 2-digit indices cap at 100 steps"
|
| 158 |
+
assert not skiptriv or (x < p and y < p), "skiptriv: Q < p needs x,y < p"
|
| 159 |
N = x * y
|
| 160 |
y_limbs = to_limbs(y, W, base)
|
| 161 |
x_limbs = to_limbs(x, W, base)
|
|
|
|
| 178 |
pos += 2 * W
|
| 179 |
assert acc == N
|
| 180 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
n_msb = to_limbs(N, 2 * W, base, msb_first=True)
|
| 182 |
+
if stepidx: # restage as (index, limb) pairs
|
| 183 |
+
parts.append(NMARK + "".join(f"{i:02d}" + limb_char(n_msb[i])
|
| 184 |
+
for i in range(2 * W)))
|
| 185 |
+
pos += 1 + 3 * 2 * W
|
| 186 |
+
else:
|
| 187 |
+
parts.append(NMARK + limb_str(N, 2 * W, base, msb_first=True)) # restage MSB
|
| 188 |
+
pos += 1 + 2 * W
|
| 189 |
+
|
| 190 |
+
if skiptriv: # local p copy for the qd*p rows
|
| 191 |
+
parts.append(PLSB + limb_str(p, W, base))
|
| 192 |
+
pos += 1 + W
|
| 193 |
+
|
| 194 |
+
if srt:
|
| 195 |
+
# SRT-style signed-digit division: qd in {-9..9} chosen by a top-4/
|
| 196 |
+
# top-3 leading-limbs lookup; remainder kept in (-p, p) as 10's-
|
| 197 |
+
# complement limbs. Redundancy absorbs lookup error: wherever the
|
| 198 |
+
# ratio sits near a digit boundary BOTH neighbours are valid
|
| 199 |
+
# (|num - qd*p| < p either way), so the knife-edge cases that broke
|
| 200 |
+
# exact-compare qd (0.96 ceiling, all errors off-by-one at margin
|
| 201 |
+
# ~1e-3) become don't-cares. Verified: 0 invariant violations /
|
| 202 |
+
# 500k selections, worst |s|/p = 0.54. num = 10*s + d in complement
|
| 203 |
+
# is a pure shift-append COPY (no arithmetic).
|
| 204 |
+
K = W + 2
|
| 205 |
+
r = N // base ** W # after the W trivial steps
|
| 206 |
+
for i in range(W, 2 * W):
|
| 207 |
+
d = n_msb[i]
|
| 208 |
+
num = r * base + d
|
| 209 |
+
parts.append(f"{i:02d}" + limb_char(d)) # stepi + cursor
|
| 210 |
+
pos += 3
|
| 211 |
+
num_c = to_limbs((num + base ** K) % base ** K, K, base)
|
| 212 |
+
parts.append("".join(limb_char(v) for v in num_c))
|
| 213 |
+
pos += K
|
| 214 |
+
qd = _srt_qd(num, p, base)
|
| 215 |
+
assert abs(num - qd * p) < p, (x, y, p, i, num, qd)
|
| 216 |
+
ann_g.append((pos, "qhat", qd + base - 1)) # 19-class signed label
|
| 217 |
+
parts.append(chr(0x90 + base - 1 + qd))
|
| 218 |
+
pos += 1
|
| 219 |
+
aq = abs(qd) * p
|
| 220 |
+
p_l = to_limbs(p, W, base) + [0]
|
| 221 |
+
c = 0
|
| 222 |
+
for k in range(W + 1): # carry into limb k of |qd|*p
|
| 223 |
+
ann_g.append((pos + k, "spcarry", c))
|
| 224 |
+
c = (abs(qd) * p_l[k] + c) // base
|
| 225 |
+
parts.append(limb_str(aq, W + 1, base))
|
| 226 |
+
pos += W + 1
|
| 227 |
+
s = num - qd * p
|
| 228 |
+
s_c = to_limbs((s + base ** K) % base ** K, K, base)
|
| 229 |
+
aq_l = to_limbs(aq, K, base)
|
| 230 |
+
ch = 0
|
| 231 |
+
for k in range(K): # (chain-bit, limb) pairs
|
| 232 |
+
parts.append(str(ch) + limb_char(s_c[k]))
|
| 233 |
+
if qd >= 0: # subtract: borrow chain
|
| 234 |
+
ch = 1 if num_c[k] - aq_l[k] - ch < 0 else 0
|
| 235 |
+
else: # negative digit: add carries
|
| 236 |
+
ch = (num_c[k] + aq_l[k] + ch) // base
|
| 237 |
+
pos += 2 * K
|
| 238 |
+
r = s
|
| 239 |
+
neg = 1 if r < 0 else 0 # final: ans = r + p if r<0
|
| 240 |
+
parts.append(str(neg))
|
| 241 |
+
pos += 1
|
| 242 |
+
r_c = to_limbs((r + base ** (W + 1)) % base ** (W + 1), W + 1, base)
|
| 243 |
+
pn_l = to_limbs(p * neg, W + 1, base)
|
| 244 |
+
answer = r + p * neg
|
| 245 |
+
a_l = to_limbs(answer, W, base)
|
| 246 |
+
ch = 0
|
| 247 |
+
for k in range(W): # (carry, limb) correction pairs
|
| 248 |
+
parts.append(str(ch) + limb_char(a_l[k]))
|
| 249 |
+
ch = (r_c[k] + pn_l[k] + ch) // base
|
| 250 |
+
pos += 2 * W
|
| 251 |
+
assert answer == N % p, (x, y, p)
|
| 252 |
+
q_limbs = [] # skip the classic loop below
|
| 253 |
+
|
| 254 |
+
if not srt:
|
| 255 |
+
q_limbs, rems, answer = long_division(N, p, W, base)
|
| 256 |
r_prev = 0
|
| 257 |
+
for i, (qd, r) in enumerate(zip(q_limbs, rems) if not srt else ()):
|
| 258 |
+
if skiptriv and i < W: # provably qd=0 (Q < p): skip
|
| 259 |
+
assert qd == 0
|
| 260 |
+
r_prev = r
|
| 261 |
+
continue
|
| 262 |
+
if stepidx: # the step announces its own name
|
| 263 |
+
parts.append(f"{i:02d}")
|
| 264 |
+
pos += 2
|
| 265 |
if cursor: # copy the consumed dividend limb
|
| 266 |
parts.append(limb_char(n_msb[i]))
|
| 267 |
pos += 1
|
| 268 |
+
if subnum: # numerator copy: r_prev*B + limb
|
| 269 |
+
num = r_prev * base + n_msb[i]
|
| 270 |
+
parts.append(limb_str(num, W + 1, base))
|
| 271 |
+
pos += W + 1
|
| 272 |
+
# Install the TRUE quotient digit, not the leading-limbs estimate: at
|
| 273 |
+
# tier 4 (10-digit p) qhat_estimate disagrees with qd in 46.6% of
|
| 274 |
+
# steps (top-2/top-1 degrades with divisor width), so the probe was
|
| 275 |
+
# supervising noise — the model reached qd 0.975 by correcting it,
|
| 276 |
+
# but the install should shape where the true digit is computed.
|
| 277 |
+
ann_g.append((pos, "qhat", qd))
|
| 278 |
if not scratch: # compact: remainder is internal state
|
| 279 |
rstr = to_limbs(r, W, base, msb_first=True)
|
| 280 |
for j in range(W):
|
|
|
|
| 291 |
parts.append(limb_str(qdp, W + 1, base))
|
| 292 |
pos += W + 1
|
| 293 |
if scratch: # emit remainder LSB-first (local borrow)
|
| 294 |
+
num_l = [n_msb[i]] + to_limbs(r_prev, W, base)
|
| 295 |
+
qdp_l = to_limbs(qd * p, W + 1, base)
|
| 296 |
+
if bemit: # (borrow, limb) pairs: chain in-context
|
| 297 |
+
r_l = to_limbs(r, W, base)
|
| 298 |
b = 0
|
| 299 |
for k in range(W):
|
| 300 |
+
parts.append(str(b) + limb_char(r_l[k]))
|
| 301 |
b = 1 if num_l[k] - qdp_l[k] - b < 0 else 0
|
| 302 |
+
pos += 2 * W
|
| 303 |
+
else:
|
| 304 |
+
if subpad: # borrow chain of num - qd*p
|
| 305 |
+
b = 0
|
| 306 |
+
for k in range(W):
|
| 307 |
+
ann_g.append((pos + k, "sborrow", b))
|
| 308 |
+
b = 1 if num_l[k] - qdp_l[k] - b < 0 else 0
|
| 309 |
+
parts.append(limb_str(r, W, base))
|
| 310 |
+
pos += W
|
| 311 |
r_prev = r
|
| 312 |
parts.append(REVMARK)
|
| 313 |
pos += 1
|
|
|
|
| 319 |
|
| 320 |
prompt = prompt_str(x, y, p, W, base, subpad)
|
| 321 |
text = prompt + "".join(parts)
|
| 322 |
+
assert len(text) == len(prompt) + gen_len(W, scratch, cursor, subpad, stepidx,
|
| 323 |
+
skiptriv, subnum, bemit, srt) + 1, \
|
| 324 |
+
(len(text), len(prompt), gen_len(W, scratch, cursor, subpad, stepidx,
|
| 325 |
+
skiptriv, subnum, bemit, srt))
|
| 326 |
ann = [dict() for _ in range(len(text))]
|
| 327 |
base_i = len(prompt)
|
| 328 |
for gi, var, val in ann_g:
|
|
|
|
| 330 |
return text, ann
|
| 331 |
|
| 332 |
|
| 333 |
+
def var_specs(W: int, base: int, scratch: bool = False, subpad: bool = False,
|
| 334 |
+
srt: bool = False):
|
| 335 |
+
specs = [("ans", base), ("qhat", 2 * base - 1 if srt else base),
|
| 336 |
+
("acarry", base)]
|
| 337 |
if subpad: # multiply-carry + subtract-borrow chains
|
| 338 |
specs += [("spcarry", base), ("sborrow", 2)]
|
| 339 |
if not scratch: # scratch emits remainders: no probe
|
|
|
|
| 342 |
|
| 343 |
|
| 344 |
def decode_answer(gen_chars: str, W: int, base: int, scratch: bool = False,
|
| 345 |
+
cursor: bool = False, subpad: bool = False,
|
| 346 |
+
stepidx: bool = False, skiptriv: bool = False,
|
| 347 |
+
subnum: bool = False, bemit: bool = False,
|
| 348 |
+
srt: bool = False) -> int:
|
| 349 |
+
# the answer block after the R-mark is W plain limbs in every mode
|
| 350 |
+
off = gen_len(W, scratch, cursor, subpad, stepidx, skiptriv, subnum, bemit,
|
| 351 |
+
srt) - W
|
| 352 |
return parse_limbs(gen_chars[off:off + W], base)
|
| 353 |
|
| 354 |
|
|
|
|
| 356 |
import random
|
| 357 |
rng = random.Random(3)
|
| 358 |
for base in (10, 100):
|
| 359 |
+
for scratch, cursor, subpad, stepidx, skiptriv, subnum, bemit, srt in (
|
| 360 |
+
(False, False, False, False, False, False, False, False),
|
| 361 |
+
(True, False, False, False, False, False, False, False),
|
| 362 |
+
(True, True, False, False, False, False, False, False),
|
| 363 |
+
(True, True, True, False, False, False, False, False),
|
| 364 |
+
(True, True, True, True, False, False, False, False),
|
| 365 |
+
(True, True, True, True, True, False, False, False),
|
| 366 |
+
(True, True, True, True, True, True, False, False),
|
| 367 |
+
(True, True, True, True, True, True, True, False),
|
| 368 |
+
(True, True, True, True, True, True, True, True)):
|
| 369 |
+
if srt and base != 10:
|
| 370 |
+
continue
|
| 371 |
for _ in range(4000):
|
| 372 |
+
W = rng.randint(1, 12)
|
| 373 |
p = rng.randrange(max(2, base ** (W - 1)), base ** W)
|
| 374 |
x, y = rng.randrange(p), rng.randrange(p)
|
| 375 |
+
text, ann = build_example(x, y, p, W, base, scratch, cursor,
|
| 376 |
+
subpad, stepidx, skiptriv, subnum, bemit,
|
| 377 |
+
srt)
|
| 378 |
plen = len(prompt_str(x, y, p, W, base, subpad))
|
| 379 |
+
assert decode_answer(text[plen:], W, base, scratch, cursor,
|
| 380 |
+
subpad, stepidx, skiptriv, subnum, bemit,
|
| 381 |
+
srt) \
|
| 382 |
+
== (x * y) % p, (x, y, p, W, base, scratch, cursor, subpad,
|
| 383 |
+
stepidx, skiptriv, subnum, bemit, srt)
|
| 384 |
assert len(ann) == len(text)
|
| 385 |
print(f"composed base={base} scratch={scratch} cursor={cursor} "
|
| 386 |
+
f"subpad={subpad} stepidx={stepidx} skiptriv={skiptriv} "
|
| 387 |
+
f"subnum={subnum} bemit={bemit} srt={srt}: 4000/4000 decode OK")
|
| 388 |
print("\ntokens/example (prompt+gen+NL):")
|
| 389 |
for tier, Wd in [(3, 5), (4, 10), (5, 20), (6, 39), (7, 78)]:
|
| 390 |
for base, W in ((10, Wd), (100, (Wd + 1) // 2)):
|