Stoicheia-code / tests /test_noising.py
Ericu950's picture
Stoicheia: training and evaluation code
5952424 verified
Raw
History Blame Contribute Delete
12.7 kB
"""Correctness properties for the elastic masked-diffusion noising."""
import sys
from pathlib import Path
import torch
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from train.noising import NoiseConfig, noise_sequence, _pick_halfword_span
def _seq(n=64):
g = torch.Generator().manual_seed(0)
chars = torch.randint(0, 24, (n,), generator=g)
# random word ends every ~5 chars, last is sentence-final
boundary = torch.zeros(n, dtype=torch.uint8)
i = 4
while i < n - 1:
boundary[i] = 1
i += int(torch.randint(3, 7, (1,), generator=g).item())
boundary[-1] = 2
return chars, boundary
def test_labels_only_at_masked_positions_fixed_patterns():
cfg = NoiseConfig(w_span=1, w_word=0, w_elastic=0, w_iid=0, w_halfword=0, w_substitute=0)
g = torch.Generator().manual_seed(1)
chars, boundary = _seq()
for _ in range(50):
out = noise_sequence(chars, boundary, cfg, g)
assert not out["rebuilt"]
masked = out["input_ids"] == cfg.mask_id
supervised = out["labels"] != -100
# every supervised position is masked, and its label is the original char
assert torch.equal(masked, supervised)
assert torch.equal(out["labels"][supervised], chars[supervised])
# non-masked inputs are unchanged originals
assert torch.equal(out["input_ids"][~masked], chars[~masked])
def test_iid_rate_matches_t():
cfg = NoiseConfig(w_span=0, w_word=0, w_elastic=0, w_iid=1, w_halfword=0, w_substitute=0,
beta_a=1e6, beta_b=2e6) # t ~ 1/3 tightly
g = torch.Generator().manual_seed(2)
fracs = []
for _ in range(200):
chars, boundary = _seq(256)
out = noise_sequence(chars, boundary, cfg, g)
fracs.append((out["input_ids"] == cfg.mask_id).float().mean().item())
mean = sum(fracs) / len(fracs)
assert 0.28 < mean < 0.39, mean
def test_word_masking_respects_boundaries():
cfg = NoiseConfig(w_span=0, w_word=1, w_elastic=0, w_iid=0, w_halfword=0, w_substitute=0)
g = torch.Generator().manual_seed(3)
chars, boundary = _seq()
ends = set((boundary >= 1).nonzero(as_tuple=True)[0].tolist())
starts = {0} | {e + 1 for e in ends}
for _ in range(50):
out = noise_sequence(chars, boundary, cfg, g)
masked = (out["input_ids"] == cfg.mask_id).tolist()
# each maximal masked run must start at a word start and end at a word end
i = 0
while i < len(masked):
if masked[i]:
j = i
while j + 1 < len(masked) and masked[j + 1]:
j += 1
assert i in starts, f"masked run starts mid-word at {i}"
assert j in ends, f"masked run ends mid-word at {j}"
i = j + 1
else:
i += 1
def test_halfword_span_always_within_the_word_and_hits_all_anchors():
"""Unit-level: _pick_halfword_span must always return a sub-range of [s,e), and over many
draws should hit all three anchors (begin/end/middle) — weighted toward the end (Greek is
suffixal, so ending-restoration is the primary use case)."""
cfg = NoiseConfig()
g = torch.Generator().manual_seed(7)
s, e = 100, 112 # a 12-char synthetic word
touches_start = touches_end = interior_only = 0
for _ in range(2000):
a, b = _pick_halfword_span(s, e, cfg, g)
assert s <= a < b <= e, f"halfword span [{a},{b}) escapes word range [{s},{e})"
if a == s:
touches_start += 1
if b == e:
touches_end += 1
if a > s and b < e:
interior_only += 1
assert touches_start > 0 and touches_end > 0 and interior_only > 0
assert touches_end > touches_start # weighted toward the end (endings/inflection)
def test_halfword_pattern_only_masks_within_single_words():
"""Integration-level smoke test: halfword-only noising must not crash and every masked
position must fall inside SOME word's char range (never the inter-word separator itself,
which doesn't exist as a char anyway, but guards against off-by-one word-range bugs)."""
cfg = NoiseConfig(w_span=0, w_word=0, w_elastic=0, w_iid=0, w_halfword=1, w_substitute=0)
g = torch.Generator().manual_seed(9)
chars, boundary = _seq(256)
ends = sorted((boundary >= 1).nonzero(as_tuple=True)[0].tolist())
starts = [0] + [e + 1 for e in ends[:-1]]
in_word = torch.zeros(len(chars), dtype=torch.bool)
for ws, we in zip(starts, [e + 1 for e in ends]):
in_word[ws:we] = True
for _ in range(100):
out = noise_sequence(chars, boundary, cfg, g)
masked = out["input_ids"] == cfg.mask_id
assert bool((masked & ~in_word).any()) is False
def test_elastic_preserves_visible_chars_and_lengths():
cfg = NoiseConfig(w_span=0, w_word=0, w_elastic=1, w_iid=0, w_halfword=0, w_substitute=0)
g = torch.Generator().manual_seed(4)
chars, boundary = _seq()
for _ in range(80):
out = noise_sequence(chars, boundary, cfg, g)
assert out["rebuilt"]
inp, lab = out["input_ids"], out["labels"]
assert inp.numel() == lab.numel() == out["boundary"].numel()
assert inp.numel() >= chars.numel() # elastic only ever grows
# visible (non-mask) positions carry original chars, in order
visible = inp[inp != cfg.mask_id]
# reconstruct target: concatenation of visible + gap targets must recover the original
recon = []
for tok, l in zip(inp.tolist(), lab.tolist()):
if tok != cfg.mask_id:
recon.append(tok)
elif l != cfg.blank_id and l != -100:
recon.append(l)
assert recon == chars.tolist(), "elastic gap targets don't reconstruct the source"
# every gap has at least the true chars then >=elastic_extra_min blanks
blanks = (lab == cfg.blank_id).sum().item()
assert blanks >= cfg.elastic_extra_min
def test_substitute_never_uses_mask_token_and_label_is_true_char():
"""DENOISING pattern: corrupted positions show a WRONG letter (never MASK), and the label
is always the true original character, always different from what's shown."""
cfg = NoiseConfig(w_span=0, w_word=0, w_elastic=0, w_iid=0, w_halfword=0, w_substitute=1)
g = torch.Generator().manual_seed(11)
chars, boundary = _seq(256)
saw_any = False
for _ in range(80):
out = noise_sequence(chars, boundary, cfg, g)
assert not out["rebuilt"]
inp, lab = out["input_ids"], out["labels"]
assert not (inp == cfg.mask_id).any(), "substitute must never emit the MASK token"
corrupted = lab != -100
if corrupted.any():
saw_any = True
# every corrupted position actually differs from the true char (genuinely wrong)
assert bool((inp[corrupted] != chars[corrupted]).all())
# label is always the true original character
assert torch.equal(lab[corrupted], chars[corrupted])
# untouched positions are unchanged originals
assert torch.equal(inp[~corrupted], chars[~corrupted])
assert saw_any
def test_channel_dropout_frequencies():
"""Per-position keep masks should average out to roughly the configured marginal rates
(mixture of fully-known / fully-unknown / patchy-uniform-rate)."""
cfg = NoiseConfig()
g = torch.Generator().manual_seed(5)
chars, boundary = _seq(256)
kb_frac = kd_frac = kp_frac = 0.0
N = 400
for _ in range(N):
out = noise_sequence(chars, boundary, cfg, g)
kb_frac += out["keep_bnd_mask"].float().mean().item()
kd_frac += out["keep_dia_mask"].float().mean().item()
kp_frac += out["keep_punct_mask"].float().mean().item()
# E[rate] = p_full*1 + p_none*0 + p_patchy*0.5
exp_bnd = cfg.p_bnd_full + (1 - cfg.p_bnd_full - cfg.p_bnd_none) * 0.5
exp_dia = cfg.p_dia_full + (1 - cfg.p_dia_full - cfg.p_dia_none) * 0.5
exp_punct = cfg.p_punct_full + (1 - cfg.p_punct_full - cfg.p_punct_none) * 0.5
assert abs(kb_frac / N - exp_bnd) < 0.07, kb_frac / N
assert abs(kd_frac / N - exp_dia) < 0.07, kd_frac / N
assert abs(kp_frac / N - exp_punct) < 0.07, kp_frac / N
def test_channel_masks_are_genuinely_patchy_sometimes():
"""At least some draws must have a channel PARTIALLY known (not all-or-nothing) — this is
the actual point: some word-breaks/accents legible, others not, within one sequence."""
cfg = NoiseConfig()
g = torch.Generator().manual_seed(8)
chars, boundary = _seq(256)
patchy = 0
for _ in range(300):
out = noise_sequence(chars, boundary, cfg, g)
m = out["keep_bnd_mask"]
if 0 < m.float().mean().item() < 1:
patchy += 1
assert patchy > 0
def test_mixture_covers_all_patterns():
cfg = NoiseConfig()
g = torch.Generator().manual_seed(6)
chars, boundary = _seq()
rebuilt = fixed = 0
for _ in range(200):
out = noise_sequence(chars, boundary, cfg, g)
rebuilt += out["rebuilt"]; fixed += not out["rebuilt"]
assert rebuilt > 0 and fixed > 0
def _seq_with_lacuna(n=64, lac_start=20, lac_len=6):
"""A sequence with a real (whole-document) lacuna baked in: MASK_ID at [lac_start,
lac_start+lac_len), matching insc/data/iphi.py's text_to_full_planes() convention."""
chars, boundary = _seq(n)
MASK_ID = 24
chars = chars.clone()
chars[lac_start:lac_start + lac_len] = MASK_ID
boundary = boundary.clone()
boundary[lac_start:lac_start + lac_len] = 3 # UNK_BND
is_real_lacuna = torch.zeros(n, dtype=torch.bool)
is_real_lacuna[lac_start:lac_start + lac_len] = True
return chars, boundary, is_real_lacuna, lac_start, lac_len
def test_is_real_lacuna_none_is_fully_backward_compatible():
"""Passing is_real_lacuna=None (the default) must reproduce byte-identical output to the
pre-existing call signature, for every existing caller.
NOTE: _sample_t()'s Beta.sample() and the span pattern's Geometric.sample() don't actually
consume the passed `g` (a pre-existing bug in this file, unrelated to is_real_lacuna) --
they draw from torch's GLOBAL RNG state instead. So reproducibility across two calls
requires pinning torch.manual_seed() globally before each one, not just seeding two local
generator objects identically."""
cfg = NoiseConfig()
chars, boundary = _seq()
torch.manual_seed(42)
g1 = torch.Generator().manual_seed(42)
out1 = noise_sequence(chars, boundary, cfg, g1)
torch.manual_seed(42)
g2 = torch.Generator().manual_seed(42)
out2 = noise_sequence(chars, boundary, cfg, g2, is_real_lacuna=None)
assert torch.equal(out1["input_ids"], out2["input_ids"])
assert torch.equal(out1["labels"], out2["labels"])
def test_real_lacuna_never_selected_as_synthetic_target_fixed_patterns():
"""span/word/halfword/iid/substitute must never choose a real-lacuna position as an
ADDITIONAL synthetic-masking target -- its label must stay -100 and its input must stay
exactly MASK_ID (never substituted to a wrong letter, never re-labeled)."""
for w in ("w_span", "w_word", "w_halfword", "w_iid", "w_substitute"):
cfg = NoiseConfig(**{"w_span": 0, "w_word": 0, "w_elastic": 0, "w_iid": 0,
"w_halfword": 0, "w_substitute": 0, w: 1})
chars, boundary, is_real_lacuna, s, L = _seq_with_lacuna()
g = torch.Generator().manual_seed(0)
for trial in range(30):
out = noise_sequence(chars, boundary, cfg, g, is_real_lacuna=is_real_lacuna)
assert (out["labels"][s:s + L] == -100).all(), w
assert (out["input_ids"][s:s + L] == 24).all(), w # still exactly MASK_ID
def test_real_lacuna_excluded_from_elastic_rebuild_too():
cfg = NoiseConfig(w_span=0, w_word=0, w_elastic=1, w_iid=0, w_halfword=0, w_substitute=0)
chars, boundary, is_real_lacuna, s, L = _seq_with_lacuna()
g = torch.Generator().manual_seed(0)
for trial in range(30):
out = noise_sequence(chars, boundary, cfg, g, is_real_lacuna=is_real_lacuna)
# the lacuna's mask_id run must still be present somewhere, contiguous, with label -100
ids = out["input_ids"]; lab = out["labels"]
mask_positions = (ids == 24).nonzero(as_tuple=True)[0]
assert mask_positions.numel() >= L
# every genuinely-MASK_ID position with label -100 exists (the real lacuna survives
# copy-through unlabeled even though the sequence may have grown/shrunk elsewhere)
assert ((ids == 24) & (lab == -100)).sum() >= L