LightNest / tests /test_coder_limits.py
LIghtJUNction's picture
Cursor
Add the October 17 experiment source without the result archives.
3f69d68 verified
Raw History Blame Contribute Delete
7.16 kB
import random
from pathlib import Path
import pytest
import torch
from lightnest import coder
def test_input_limits_are_explicit():
with pytest.raises(ValueError, match='character'):
coder.encode_say('返' * 257, None)
with pytest.raises(ValueError, match='unsupported'):
coder.encode_say('\U0001f9ea', None)
with pytest.raises(ValueError, match='limit'):
coder.encode_explain(b'\0' * 96, None)
with pytest.raises(ValueError, match='aligned'):
coder.encode_edit(b'\0', '返回第一个数', None)
def test_batch_fails_instead_of_retrying(monkeypatch):
calls = []
def oversize(rng):
calls.append(1)
return [0] * 257, 1
monkeypatch.setattr(coder, '_sample', oversize)
with pytest.raises(ValueError, match='context'):
coder.make_batch(random.Random(0), 1, torch.device('cpu'))
assert len(calls) == 1
with pytest.raises(ValueError, match='positive'):
coder.make_batch(random.Random(0), 0, torch.device('cpu'))
def test_generation_rejects_context_overflow():
model = coder.Coder(dim=8, layers=1, heads=2)
with pytest.raises(ValueError, match='context'):
coder._generate(model, [coder.BOS] * 250, coder.BYTE_IDS, 96)
with pytest.raises(ValueError, match='length'):
model(torch.zeros((1, 257), dtype=torch.long))
def test_checkpoint_exact_vocab(tmp_path):
path = tmp_path / 'small.pt'
model = coder.Coder(dim=8, layers=1, heads=2)
coder.save_model(model, path)
loaded = coder.load_model(path, torch.device('cpu'))
assert loaded.dim == 8
blob = torch.load(path, weights_only=True)
blob['vocab'][0], blob['vocab'][1] = blob['vocab'][1], blob['vocab'][0]
torch.save(blob, path)
with pytest.raises(ValueError, match='incompatible'):
coder.load_model(path, torch.device('cpu'))
def test_explain_file_does_not_read_whole_file(tmp_path, monkeypatch):
path = tmp_path / 'large.bin'
with path.open('wb') as f:
f.truncate(1024 * 1024)
monkeypatch.setattr(Path, 'read_bytes', lambda *a: pytest.fail('unbounded read'))
with pytest.raises(ValueError):
coder.explain_file(None, path)
def test_training_subset_applies_to_all_modes(monkeypatch):
chosen = tuple(coder.JOBS[:2])
names = {job.name for job in chosen}
codes = {job.code for job in chosen}
observed = set()
def say(sentence, program):
observed.add('say')
assert program in codes
assert sentence in {s for job in chosen for s in job.say_train}
return [0], 0
def edit(prior, sentence, program):
observed.add('edit')
assert prior in codes and program in codes
assert sentence in {s for s, name in coder.EDIT_TRAIN if name in names}
return [0], 0
def explain(program, sentence):
observed.add('explain')
assert program in codes
assert sentence in {job.explain for job in chosen}
return [0], 0
monkeypatch.setattr(coder, 'encode_say', say)
monkeypatch.setattr(coder, 'encode_edit', edit)
monkeypatch.setattr(coder, 'encode_explain', explain)
rng = random.Random(10)
for _ in range(100):
coder._sample(rng, chosen)
assert observed == {'say', 'edit', 'explain'}
def test_semantic_augmentation_changes_bytes_preserves_branches():
from lightnest.phrase import by_name, heldout_probes
from lightnest.rv32 import simulate
order = coder.TEMP_REGISTERS[1:] + coder.TEMP_REGISTERS[:1]
for name in ('add', 'max', 'sum', 'range', 'count_neg'):
job = by_name(name)
transformed = coder.augment_program(job.code, register_order=order, nop_prefix=1)
assert transformed != job.code
assert len(transformed) == len(job.code) + 4
assert all(simulate(transformed, xs) == expected
for xs, expected in heldout_probes(name, seed=88212, trials=12))
with pytest.raises(ValueError, match='permute'):
coder.augment_program(by_name('add').code, register_order=(0, 10, 11, 12))
def test_augmentation_bank_is_train_only_and_context_bounded():
withheld = {'abs', 'mul', 'sum_squares', 'count_even', 'last'}
jobs = tuple(job for job in coder.JOBS if job.name not in withheld)
bank = coder.training_augmentations(jobs, 91241)
assert set(bank) == {job.name for job in jobs}
assert set(bank).isdisjoint(withheld)
for job in jobs:
for variant in bank[job.name]:
assert len(variant) <= coder.MAX_PROGRAM_BYTES
ids, _ = coder.encode_explain(variant, job.explain)
assert len(ids) <= coder.MAX_LEN
for target in jobs:
sentence = max(target.say_train, key=len)
ids, _ = coder.encode_edit(variant, '改成' + sentence, target.code)
assert len(ids) <= coder.MAX_LEN
for _ in range(10):
tokens, _ = coder.make_batch(random.Random(15), 8, torch.device('cpu'), jobs, bank)
assert tokens.shape[1] <= coder.MAX_LEN
def test_augmentation_only_changes_training_inputs(monkeypatch):
jobs = tuple(coder.JOBS[:2])
bank = {job.name: (b'\x13\0\0\0' + job.code,) for job in jobs}
canonical = {job.code for job in jobs}
variants = {value for values in bank.values() for value in values}
modes = set()
def say(sentence, target):
modes.add('say')
assert target in canonical
return [0], 0
def edit(prior, sentence, target):
modes.add('edit')
assert prior in variants and target in canonical
return [0], 0
def explain(prior, sentence):
modes.add('explain')
assert prior in variants
return [0], 0
monkeypatch.setattr(coder, 'encode_say', say)
monkeypatch.setattr(coder, 'encode_edit', edit)
monkeypatch.setattr(coder, 'encode_explain', explain)
rng = random.Random(721)
for _ in range(100):
coder._sample(rng, jobs, bank)
assert modes == {'say', 'edit', 'explain'}
def test_augmentation_rejects_unknown_opcodes_and_oversize():
with pytest.raises(ValueError, match='opcode'):
coder.augment_program(b'\xff' * 4)
with pytest.raises(ValueError, match='limit'):
coder.augment_program(bytes.fromhex('13000000') * 23, nop_prefix=2)
def test_checkpoint_size_budget_checked_before_load(tmp_path, monkeypatch):
path = tmp_path / 'large.pt'
with path.open('wb') as out:
out.truncate(100 * 1024**2)
monkeypatch.setattr(torch, 'load', lambda *a, **k: pytest.fail('must reject before tensor load'))
with pytest.raises(ValueError, match='size budget'):
coder.load_model(path, torch.device('cpu'))
def test_checkpoint_shape_budget_checked_before_construction(tmp_path, monkeypatch):
path = tmp_path / 'small.pt'
coder.save_model(coder.Coder(16, 1, 4), path)
data = torch.load(path, weights_only=True)
data['dim'] = 1000000
torch.save(data, path)
monkeypatch.setattr(coder, 'Coder', lambda *a, **k: pytest.fail('must reject before model allocation'))
with pytest.raises(ValueError, match='parameter budget'):
coder.load_model(path, torch.device('cpu'))