File size: 6,549 Bytes
5952424 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | """Intrinsic eval — the metrics tracked during pretraining, on a genuinely held-out split
(every HOLDOUT_MOD-th record, never trained on; see train/data.py::eligible).
bits_per_char masked-char cross-entropy in bits, at a fixed reference mask rate (t=0.30)
boundary_f1 word-boundary prediction F1 with the boundary channel forced UNKNOWN
(the model must segment scriptio continua from scratch)
dia_acc diacritic restoration accuracy, channel forced UNKNOWN
punct_acc punctuation restoration accuracy, channel forced UNKNOWN
demo a fixed smoke reconstruction, for a human sanity check every checkpoint
"""
from __future__ import annotations
import json, math
from pathlib import Path
import numpy as np
import torch
from data.normalize import ALPHABET
from model.char_bert import CharBertConfig, CharBertEncoder
from train.collate import collate
from train.noising import NoiseConfig
ALIST = list(ALPHABET)
UNK_BND, UNK_DIA, UNK_PUNCT = 3, 48, 6
def held_out_records(shards, n, seed=1234):
import pyarrow.parquet as pq
d = Path(shards)
idx = pq.read_table(d / "index.parquet")
offs = idx.column("offset").to_numpy(); lens = idx.column("length").to_numpy()
tier = idx.column("tier").to_numpy(zero_copy_only=False)
chars = np.memmap(d / "chars.bin", dtype=np.uint8, mode="r")
bnd = np.memmap(d / "boundary.bin", dtype=np.uint8, mode="r")
dia = np.memmap(d / "dia.bin", dtype=np.uint8, mode="r")
cap = np.memmap(d / "cap.bin", dtype=np.uint8, mode="r")
punct = np.memmap(d / "punct.bin", dtype=np.uint8, mode="r")
from train.data import HOLDOUT_MOD
gold = np.flatnonzero((tier == "pristine") & (lens >= 64) & (lens <= 4096))
gold = gold[gold % HOLDOUT_MOD == 0]
rng = np.random.default_rng(seed)
pick = rng.choice(gold, size=min(n, len(gold)), replace=False)
recs = []
for i in pick:
o, l = int(offs[i]), int(lens[i])
recs.append(dict(chars=np.array(chars[o:o+l]), boundary=np.array(bnd[o:o+l]),
dia=np.array(dia[o:o+l]), cap=np.array(cap[o:o+l]),
punct=np.array(punct[o:o+l])))
return recs
@torch.no_grad()
def evaluate(model, recs, device, T=1024, micro=8, rate=0.30):
model.eval()
# fixed reference noise: iid at t=rate, boundary/dia/punct channels forced UNKNOWN
ncfg = NoiseConfig(w_span=0, w_word=0, w_elastic=0, w_iid=1, w_halfword=0, w_substitute=0,
beta_a=1e6, beta_b=1e6 * (1 - rate) / rate,
p_bnd_full=0.0, p_bnd_none=1.0, p_dia_full=0.0, p_dia_none=1.0,
p_punct_full=0.0, p_punct_none=1.0)
g = torch.Generator().manual_seed(0)
tot_bits = tot_n = 0
b_tp = b_fp = b_fn = 0
dia_ok = dia_n = 0
punct_ok = punct_n = 0
for i in range(0, len(recs), micro):
batch = collate(recs[i:i+micro], ncfg, T, g)
batch = {k: (v.to(device) if torch.is_tensor(v) else v) for k, v in batch.items()}
with torch.autocast("cuda", dtype=torch.bfloat16, enabled=device.type == "cuda"):
out = model(batch)
lab = batch["labels"]; m = lab != -100
if m.any():
logp = torch.log_softmax(out["char"][m].float(), -1)
nll = -logp.gather(1, lab[m][:, None]).squeeze(1)
tot_bits += (nll.sum() / math.log(2)).item(); tot_n += int(m.sum())
bl = batch["bnd_lab"]; bm = bl != -100
if bm.any():
pred = out["boundary"][bm].argmax(-1)
gold_fin = bl[bm] >= 1; pred_fin = pred >= 1
b_tp += int((gold_fin & pred_fin).sum()); b_fp += int((~gold_fin & pred_fin).sum())
b_fn += int((gold_fin & ~pred_fin).sum())
dl = batch["dia_lab"]; dm = dl != -100
if dm.any():
dia_ok += int((out["dia"][dm].argmax(-1) == dl[dm]).sum()); dia_n += int(dm.sum())
pl = batch["punct_lab"]; pm = pl != -100
if pm.any():
punct_ok += int((out["punct"][pm].argmax(-1) == pl[pm]).sum()); punct_n += int(pm.sum())
prec = b_tp / (b_tp + b_fp + 1e-9); rec = b_tp / (b_tp + b_fn + 1e-9)
bf1 = 2 * prec * rec / (prec + rec + 1e-9)
model.train()
return dict(bits_per_char=round(tot_bits / max(tot_n, 1), 4),
boundary_f1=round(bf1, 4), dia_acc=round(dia_ok / max(dia_n, 1), 4),
punct_acc=round(punct_ok / max(punct_n, 1), 4), n_eval=tot_n)
@torch.no_grad()
def restore_demo(model, device, text="καιολογοσ", mask_slice=(2, 5)):
"""Mask chars [i:j] and show the model's top-1 reconstruction (the κα---ογοσ demo)."""
from data.normalize import Stats, normalize_record
st = Stats()
r = normalize_record(text, st, with_punct=True)
if r is None:
return "?"
chars, boundary, dia, cap, punct = r
ids = torch.tensor(chars, dtype=torch.long)
inp = ids.clone(); inp[mask_slice[0]:mask_slice[1]] = 24 # mask_id
batch = dict(input_ids=inp[None].to(device),
boundary=torch.full((1, len(ids)), UNK_BND, device=device),
dia=torch.full((1, len(ids)), UNK_DIA, device=device),
punct=torch.full((1, len(ids)), UNK_PUNCT, device=device),
seg_id=torch.ones(1, len(ids), dtype=torch.long, device=device))
with torch.autocast("cuda", dtype=torch.bfloat16, enabled=device.type == "cuda"):
out = model(batch)
pred = out["char"][0].argmax(-1).cpu()
recon = "".join(ALIST[c] if c < 24 else "?" for c in pred[mask_slice[0]:mask_slice[1]])
full = "".join(ALIST[inp[k]] if inp[k] < 24 else recon[k-mask_slice[0]]
for k in range(len(ids)))
return full
def load_model(ckpt_path, device):
sd = torch.load(ckpt_path, map_location=device)
c = sd["cfg"]
# metadata-conditioned finetunes (finetune_whole.py) carry e_region/e_century
# embeddings; size them from the state dict itself so both kinds of checkpoint load
msd = sd["model"]
n_region = msd["e_region.weight"].shape[0] if "e_region.weight" in msd else 0
n_century = msd["e_century.weight"].shape[0] if "e_century.weight" in msd else 0
mcfg = CharBertConfig(attn_impl="sdpa", d_model=c["d_model"], n_heads=c["d_model"] // 64,
depth=c["depth"], char_window=c["char_window"],
qk_norm=c.get("qk_norm", True),
n_region=n_region, n_century=n_century)
model = CharBertEncoder(mcfg).to(device)
model.load_state_dict(msd)
return model, c
|