frame prototype v0.1: byte-state arm beats index twin (digits 0.275 vs 0.045, twin has only 200 keys for 999 states); RSA says the codebook kept C ancestry; entry channel real vs failing random control
Browse files- proto_frame/proto_phase0.py +215 -0
proto_frame/proto_phase0.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Frame prototype Phase 0 — instruments + full extraction (v0.1).
|
| 2 |
+
|
| 3 |
+
1. T5 walk table of C (instrument-first: entry loss needs whole-walk
|
| 4 |
+
census; only the wordpiece walk existed).
|
| 5 |
+
2. Boundary-context profiles per state (prev/next byte distributions
|
| 6 |
+
at attested sites) for the byte-structural codebook init.
|
| 7 |
+
3. Extraction v2: ALL sites, no cap. Unmasked anchor readouts
|
| 8 |
+
(byte-anchored first subtoken + last subtoken, k per side) AND
|
| 9 |
+
masked-span readouts (bert: [MASK]*k, read first mask; t5:
|
| 10 |
+
<extra_id_0> replacing span, read sentinel in encoder) — the
|
| 11 |
+
reading-vs-guessing control. CUDA, <18GB.
|
| 12 |
+
"""
|
| 13 |
+
import json
|
| 14 |
+
import sys
|
| 15 |
+
|
| 16 |
+
sys.path.insert(0, r"E:\mirel\geolip-bytelex")
|
| 17 |
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
| 18 |
+
import numpy as np
|
| 19 |
+
import torch
|
| 20 |
+
import transformers
|
| 21 |
+
from transformers import AutoTokenizer, AutoModel, T5EncoderModel
|
| 22 |
+
import transformers.utils.logging as hlog
|
| 23 |
+
|
| 24 |
+
hlog.set_verbosity_error()
|
| 25 |
+
CODEX = r"E:\mirel\data\bytelex\codex_v1.txt"
|
| 26 |
+
WORDS = r"E:\mirel\data\bytelex\words_of_C.json"
|
| 27 |
+
OUTDIR = r"E:\mirel\data\bytelex\proto_frame"
|
| 28 |
+
SEPS = set(b" \t\n\r.,;:!?\"'()[]{}")
|
| 29 |
+
L_BERT, L_T5 = 8, 6
|
| 30 |
+
DEV = "cuda"
|
| 31 |
+
BATCH = 256
|
| 32 |
+
|
| 33 |
+
states = json.load(open(WORDS, encoding="utf-8"))
|
| 34 |
+
sid_of = {s["text"]: i for i, s in enumerate(states)}
|
| 35 |
+
tkB = AutoTokenizer.from_pretrained("bert-base-uncased")
|
| 36 |
+
tkA = AutoTokenizer.from_pretrained("google/flan-t5-small")
|
| 37 |
+
|
| 38 |
+
# ---- 1. t5 walk table of C
|
| 39 |
+
t5_walk = []
|
| 40 |
+
for s in states:
|
| 41 |
+
ids = tkA(s["text"], add_special_tokens=False)["input_ids"]
|
| 42 |
+
toks = tkA.convert_ids_to_tokens(ids)
|
| 43 |
+
t5_walk.append({"text": s["text"], "count": s["count"],
|
| 44 |
+
"k": len(ids), "seg": "|".join(toks),
|
| 45 |
+
"ids": ids,
|
| 46 |
+
"whole": len(ids) == 1})
|
| 47 |
+
n_whole = sum(w["whole"] for w in t5_walk)
|
| 48 |
+
with open(rf"{OUTDIR}\t5_walk_of_C.json", "w", encoding="utf-8") as f:
|
| 49 |
+
json.dump(t5_walk, f, indent=0)
|
| 50 |
+
print(f"[P0] t5 walk of C: {n_whole}/999 whole", flush=True)
|
| 51 |
+
|
| 52 |
+
# ---- sites (ALL, no cap) + 2. boundary-context profiles
|
| 53 |
+
lines = open(CODEX, "rb").read().decode("ascii").split("\n")
|
| 54 |
+
sites = []
|
| 55 |
+
prev_ctx = np.zeros((999, 256))
|
| 56 |
+
next_ctx = np.zeros((999, 256))
|
| 57 |
+
for li, ln in enumerate(lines):
|
| 58 |
+
if not ln:
|
| 59 |
+
continue
|
| 60 |
+
raw = ln.encode("ascii") + b" "
|
| 61 |
+
lo = None
|
| 62 |
+
for j, ch in enumerate(raw):
|
| 63 |
+
if ch in SEPS:
|
| 64 |
+
if lo is not None:
|
| 65 |
+
sid = sid_of.get(ln[lo:j])
|
| 66 |
+
if sid is not None:
|
| 67 |
+
sites.append((li, lo, j, sid))
|
| 68 |
+
prev_ctx[sid, raw[lo - 1] if lo else 32] += 1
|
| 69 |
+
next_ctx[sid, ch] += 1
|
| 70 |
+
lo = None
|
| 71 |
+
elif lo is None:
|
| 72 |
+
lo = j
|
| 73 |
+
np.savez_compressed(rf"{OUTDIR}\ctx_profiles.npz",
|
| 74 |
+
prev_ctx=prev_ctx, next_ctx=next_ctx)
|
| 75 |
+
print(f"[P0] {len(sites)} sites (uncapped), ctx profiles saved",
|
| 76 |
+
flush=True)
|
| 77 |
+
|
| 78 |
+
mB = AutoModel.from_pretrained("bert-base-uncased").to(DEV).eval()
|
| 79 |
+
mT = T5EncoderModel.from_pretrained("google/flan-t5-small").to(DEV).eval()
|
| 80 |
+
embT = mT.get_input_embeddings()
|
| 81 |
+
SENT = tkA.convert_tokens_to_ids("<extra_id_0>")
|
| 82 |
+
MASK = tkB.mask_token_id
|
| 83 |
+
|
| 84 |
+
N = len(sites)
|
| 85 |
+
H_B = np.zeros((N, 2, 768), dtype=np.float16) # first,last
|
| 86 |
+
H_T = np.zeros((N, 2, 512), dtype=np.float32)
|
| 87 |
+
M_B = np.zeros((N, 768), dtype=np.float16) # masked readout
|
| 88 |
+
M_T = np.zeros((N, 512), dtype=np.float32)
|
| 89 |
+
E_T = np.zeros((N, 512), dtype=np.float32) # t5 input emb (first)
|
| 90 |
+
KK = np.zeros((N, 2), dtype=np.int16) # k_B, k_T
|
| 91 |
+
|
| 92 |
+
# ---- unmasked pass, batched by line
|
| 93 |
+
by_line = {}
|
| 94 |
+
for k, (li, lo, hi, sid) in enumerate(sites):
|
| 95 |
+
by_line.setdefault(li, []).append(k)
|
| 96 |
+
line_ids = sorted(by_line)
|
| 97 |
+
tokcacheB, tokcacheT = {}, {}
|
| 98 |
+
with torch.no_grad():
|
| 99 |
+
for bs in range(0, len(line_ids), BATCH):
|
| 100 |
+
chunk = line_ids[bs:bs + BATCH]
|
| 101 |
+
texts = [lines[li] for li in chunk]
|
| 102 |
+
eb = tkB(texts, return_offsets_mapping=True, padding=True,
|
| 103 |
+
return_tensors="pt")
|
| 104 |
+
et = tkA(texts, return_offsets_mapping=True, padding=True,
|
| 105 |
+
return_tensors="pt")
|
| 106 |
+
hb = mB(input_ids=eb["input_ids"].to(DEV),
|
| 107 |
+
attention_mask=eb["attention_mask"].to(DEV),
|
| 108 |
+
output_hidden_states=True).hidden_states[L_BERT].cpu()
|
| 109 |
+
ht = mT(input_ids=et["input_ids"].to(DEV),
|
| 110 |
+
attention_mask=et["attention_mask"].to(DEV),
|
| 111 |
+
output_hidden_states=True).hidden_states[L_T5].cpu()
|
| 112 |
+
em = embT(et["input_ids"].to(DEV)).cpu()
|
| 113 |
+
for r, li in enumerate(chunk):
|
| 114 |
+
offB = eb["offset_mapping"][r].tolist()
|
| 115 |
+
offT = et["offset_mapping"][r].tolist()
|
| 116 |
+
idsB = eb["input_ids"][r].tolist()
|
| 117 |
+
idsT = et["input_ids"][r].tolist()
|
| 118 |
+
tokcacheB[li] = (idsB, offB)
|
| 119 |
+
tokcacheT[li] = (idsT, offT)
|
| 120 |
+
for k in by_line[li]:
|
| 121 |
+
_, lo, hi, sid = sites[k]
|
| 122 |
+
ixB = [i for i, (s, t) in enumerate(offB)
|
| 123 |
+
if t > s and s < hi and t > lo]
|
| 124 |
+
ixT = [i for i, (s, t) in enumerate(offT)
|
| 125 |
+
if t > s and s < hi and t > lo]
|
| 126 |
+
if not ixB or not ixT:
|
| 127 |
+
KK[k] = (0, 0)
|
| 128 |
+
continue
|
| 129 |
+
H_B[k, 0] = hb[r, ixB[0]].numpy()
|
| 130 |
+
H_B[k, 1] = hb[r, ixB[-1]].numpy()
|
| 131 |
+
H_T[k, 0] = ht[r, ixT[0]].numpy()
|
| 132 |
+
H_T[k, 1] = ht[r, ixT[-1]].numpy()
|
| 133 |
+
E_T[k] = em[r, ixT[0]].numpy()
|
| 134 |
+
KK[k] = (len(ixB), len(ixT))
|
| 135 |
+
if (bs // BATCH) % 5 == 0:
|
| 136 |
+
print(f"[P0-unmasked] {bs}/{len(line_ids)} lines", flush=True)
|
| 137 |
+
print("[P0] unmasked pass done", flush=True)
|
| 138 |
+
|
| 139 |
+
# ---- masked pass: one sequence per SITE, id-spliced
|
| 140 |
+
def masked_batchB(ks):
|
| 141 |
+
seqs, poss = [], []
|
| 142 |
+
for k in ks:
|
| 143 |
+
li, lo, hi, sid = sites[k]
|
| 144 |
+
idsB, offB = tokcacheB[li]
|
| 145 |
+
ix = [i for i, (s, t) in enumerate(offB)
|
| 146 |
+
if t > s and s < hi and t > lo]
|
| 147 |
+
pre = [idsB[i] for i, (s, t) in enumerate(offB)
|
| 148 |
+
if t > s and t <= lo]
|
| 149 |
+
post = [idsB[i] for i, (s, t) in enumerate(offB)
|
| 150 |
+
if t > s and s >= hi]
|
| 151 |
+
seqs.append([tkB.cls_token_id] + pre + [MASK] * max(len(ix), 1)
|
| 152 |
+
+ post + [tkB.sep_token_id])
|
| 153 |
+
poss.append(1 + len(pre))
|
| 154 |
+
return seqs, poss
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def masked_batchT(ks):
|
| 158 |
+
seqs, poss = [], []
|
| 159 |
+
for k in ks:
|
| 160 |
+
li, lo, hi, sid = sites[k]
|
| 161 |
+
ln = lines[li]
|
| 162 |
+
src = ln[:lo] + "<extra_id_0>" + ln[hi:]
|
| 163 |
+
ids = tkA(src, add_special_tokens=False)["input_ids"]
|
| 164 |
+
try:
|
| 165 |
+
p = ids.index(SENT)
|
| 166 |
+
except ValueError:
|
| 167 |
+
p = 0
|
| 168 |
+
seqs.append(ids)
|
| 169 |
+
poss.append(p)
|
| 170 |
+
return seqs, poss
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def run_masked(model, seqs, poss, layer, pad_id):
|
| 174 |
+
mx = max(len(s) for s in seqs)
|
| 175 |
+
ids = torch.full((len(seqs), mx), pad_id, dtype=torch.long)
|
| 176 |
+
att = torch.zeros((len(seqs), mx), dtype=torch.long)
|
| 177 |
+
for i, s in enumerate(seqs):
|
| 178 |
+
ids[i, :len(s)] = torch.tensor(s)
|
| 179 |
+
att[i, :len(s)] = 1
|
| 180 |
+
with torch.no_grad():
|
| 181 |
+
h = model(input_ids=ids.to(DEV), attention_mask=att.to(DEV),
|
| 182 |
+
output_hidden_states=True).hidden_states[layer].cpu()
|
| 183 |
+
return h[torch.arange(len(seqs)), torch.tensor(poss)]
|
| 184 |
+
|
| 185 |
+
order = [k for k in range(N) if KK[k, 0] > 0]
|
| 186 |
+
for bs in range(0, len(order), BATCH):
|
| 187 |
+
ks = order[bs:bs + BATCH]
|
| 188 |
+
sq, ps = masked_batchB(ks)
|
| 189 |
+
out = run_masked(mB, sq, ps, L_BERT, tkB.pad_token_id)
|
| 190 |
+
for j, k in enumerate(ks):
|
| 191 |
+
M_B[k] = out[j].numpy()
|
| 192 |
+
sq, ps = masked_batchT(ks)
|
| 193 |
+
out = run_masked(mT, sq, ps, L_T5, tkA.pad_token_id)
|
| 194 |
+
for j, k in enumerate(ks):
|
| 195 |
+
M_T[k] = out[j].numpy()
|
| 196 |
+
if (bs // BATCH) % 20 == 0:
|
| 197 |
+
print(f"[P0-masked] {bs}/{len(order)} sites", flush=True)
|
| 198 |
+
|
| 199 |
+
np.savez_compressed(
|
| 200 |
+
rf"{OUTDIR}\frame_dump_v2.npz",
|
| 201 |
+
H_B=H_B, H_T=H_T, M_B=M_B, M_T=M_T, E_T=E_T, KK=KK,
|
| 202 |
+
sid=np.array([s[3] for s in sites], dtype=np.int32),
|
| 203 |
+
line=np.array([s[0] for s in sites], dtype=np.int32),
|
| 204 |
+
lo=np.array([s[1] for s in sites], dtype=np.int32),
|
| 205 |
+
hi=np.array([s[2] for s in sites], dtype=np.int32))
|
| 206 |
+
meta = {"n_sites": N, "skipped": int(N - len(order)),
|
| 207 |
+
"layers": {"bert": L_BERT, "t5": L_T5},
|
| 208 |
+
"t5_whole": n_whole,
|
| 209 |
+
"env": {"transformers": transformers.__version__,
|
| 210 |
+
"torch": torch.__version__}}
|
| 211 |
+
with open(rf"{OUTDIR}\phase0_meta.json", "w", encoding="utf-8") as f:
|
| 212 |
+
json.dump(meta, f, indent=1)
|
| 213 |
+
print(f"[P0] COMPLETE: {len(order)}/{N} sites, "
|
| 214 |
+
f"vram peak {torch.cuda.max_memory_allocated()/2**30:.1f}GB",
|
| 215 |
+
flush=True)
|