ensemble / tests /test_bpe_expert.py
thefinalboss's picture
v0.2: global shared BPE tokenizer + persistent central brain memory + brain save/load. BPE lifts quality ceiling (TinyStories 150KB D=5000: next-token acc 94.5% -> 98.5%, latency 112ms -> 28ms, RAM halved). BrainMemory grows via thinking and survives save/load. 75 tests.
22d1ad7 verified
Raw
History Blame Contribute Delete
5.38 kB
"""Tests for the global BPE tokenizer integration in ENSEMBLE."""
from __future__ import annotations
import os
import pytest
from ensemble import Expert, Brain
from palimseste.bpe import BPETokenizer
CORPUS = (
"the mitochondria is the powerhouse of the cell. "
"what is the capital of france. the capital of france is paris. "
"what is pi. pi is approximately three point one four. "
"two plus two equals four. three times three equals nine. "
) * 6
MATH_QA = [
("what is pi", "pi is approximately three point one four"),
("what is two plus two", "two plus two equals four"),
("what is three times three", "three times three equals nine"),
] * 4
GEO_QA = [
("what is the capital of france", "the capital of france is paris"),
("what is the capital of japan", "the capital of japan is tokyo"),
] * 4
@pytest.fixture
def bpe():
return Expert.build_bpe(CORPUS, vocab_size=400, D=3000)
class TestBuildBPE:
def test_returns_bpe_tokenizer(self, bpe):
assert isinstance(bpe, BPETokenizer)
assert bpe.vocab_size_actual > 100 # learned some merges
def test_bpe_is_deterministic(self):
a = Expert.build_bpe(CORPUS, vocab_size=400, D=3000, seed=0)
b = Expert.build_bpe(CORPUS, vocab_size=400, D=3000, seed=0)
assert a.id2token == b.id2token
assert a.merges == b.merges
class TestBPEExpert:
def test_from_text_with_bpe(self, bpe):
e = Expert.from_text(CORPUS, domain="bio", D=3000, tokenizer=bpe)
assert e._tokenizer_type == "bpe"
assert e.n_traces > 0
assert e.model.tokenizer is bpe
def test_from_qa_pairs_with_bpe(self, bpe):
e = Expert.from_qa_pairs(MATH_QA, domain="math", D=3000, tokenizer=bpe)
assert e._tokenizer_type == "bpe"
assert e.n_traces > 0
def test_default_is_char(self):
e = Expert.from_text("hello world. " * 20, D=2000)
assert e._tokenizer_type == "char"
def test_bpe_answer_is_string(self, bpe):
e = Expert.from_qa_pairs(MATH_QA, domain="math", D=3000, tokenizer=bpe)
a = e.answer("what is pi")
assert isinstance(a, str)
def test_bpe_answer_better_than_char(self, bpe):
"""BPE should produce a more complete answer than char-level on the same data."""
char_expert = Expert.from_qa_pairs(MATH_QA, domain="m", D=3000)
bpe_expert = Expert.from_qa_pairs(MATH_QA, domain="m", D=3000, tokenizer=bpe)
char_ans = char_expert.answer("what is two plus two")
bpe_ans = bpe_expert.answer("what is two plus two")
expected = "two plus two equals four"
# BPE answer should be at least as long / complete as the char one
assert len(bpe_ans) >= len(char_ans)
# BPE should contain the key correct substring
assert "two plus two" in bpe_ans.lower()
class TestBPESaveLoad:
def test_round_trip_with_shared_bpe(self, bpe, tmp_path):
e = Expert.from_qa_pairs(MATH_QA, domain="math", D=3000, tokenizer=bpe)
result = e.save(tmp_path / "math.exp")
e2 = Expert.load(result.path, tokenizer=bpe)
assert e2._tokenizer_type == "bpe"
assert e2.n_traces == e.n_traces
assert e2.signature_hv == e.signature_hv
assert e2.model._self_hv == e.model._self_hv
def test_bpe_expert_smaller_than_source(self, bpe, tmp_path):
e = Expert.from_qa_pairs(MATH_QA, domain="math", D=3000, tokenizer=bpe)
result = e.save(tmp_path / "math.exp")
assert result.expert_size_bytes < result.source_size_bytes
def test_load_bpe_without_tokenizer_raises(self, bpe, tmp_path):
e = Expert.from_qa_pairs(MATH_QA, domain="math", D=3000, tokenizer=bpe)
result = e.save(tmp_path / "math.exp")
with pytest.raises(ValueError, match="BPE"):
Expert.load(result.path)
def test_no_vocab_json_for_bpe(self, bpe, tmp_path):
"""BPE experts must NOT serialize a per-expert vocab (it's global)."""
e = Expert.from_qa_pairs(MATH_QA, domain="math", D=3000, tokenizer=bpe)
result = e.save(tmp_path / "math.exp")
assert not os.path.exists(os.path.join(result.path, "vocab.json"))
class TestBPEBrain:
def test_brain_with_bpe_experts(self, bpe):
math = Expert.from_qa_pairs(MATH_QA, domain="math", D=3000, tokenizer=bpe)
geo = Expert.from_qa_pairs(GEO_QA, domain="geography", D=3000, tokenizer=bpe)
brain = Brain()
brain.add_expert(math)
brain.add_expert(geo)
res = brain.query("what is pi", max_new_tokens=40)
assert res.dominant_expert == "math"
res2 = brain.query("what is the capital of france", max_new_tokens=40)
assert res2.dominant_expert == "geography"
def test_mixed_char_and_bpe_brain(self, bpe):
"""A brain can mix char-level and BPE experts (different tokenizers)."""
bpe_expert = Expert.from_qa_pairs(MATH_QA, domain="math", D=3000, tokenizer=bpe)
char_expert = Expert.from_qa_pairs(GEO_QA, domain="geo", D=3000)
brain = Brain()
brain.add_expert(bpe_expert)
brain.add_expert(char_expert)
# both should be queryable without crashing
r1 = brain.query("what is pi")
r2 = brain.query("what is the capital of france")
assert isinstance(r1.answer, str)
assert isinstance(r2.answer, str)