Download source/tests/test_filler.py from gcoderw/DEG-5B-E2B-1018: direct link, hf CLI and curl.
- Browser
- Download file 7.11 kB
-
https://huggingface.co/gcoderw/DEG-5B-E2B-1018/resolve/main/source/tests/test_filler.py
- Command line
-
hf download hf://gcoderw/DEG-5B-E2B-1018/source/tests/test_filler.py
-
curl -L -o test_filler.py https://huggingface.co/gcoderw/DEG-5B-E2B-1018/resolve/main/source/tests/test_filler.py
7.11 kB
| """Filler augmentation for long-context anchor training (SPEC.md §12.4), on a tiny random | |
| Qwen3.5 (CPU): a short record embedded in unrelated filler text keeps every gold span on | |
| its own text and every target of the plain example; negative (filler only) examples | |
| train top-level anchored arrays as empty; the corpus uses train-split documents only | |
| and refuses ExtractBench; micro-batches materialize filler draws before packing (the | |
| memory planner sees real examples) within the context limit. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import random | |
| import zlib | |
| from pathlib import Path | |
| import pytest | |
| torch = pytest.importorskip("torch") | |
| pytest.importorskip("transformers") | |
| from test_anchors import NESTED, TEAM_CONTEXT # noqa: E402 | |
| from test_array_decode import _team_record # noqa: E402 | |
| from test_model import SNAPSHOT, tiny_lm # noqa: E402 | |
| from deg.model import load_model, load_tokenizer # noqa: E402 | |
| FILLER_WORDS = ("The quarterly filing discusses liquidity, covenants and segment revenue " | |
| "across fiscal periods; management notes risks in supply and demand. ") | |
| def model(): | |
| if SNAPSHOT is None: | |
| pytest.skip("Qwen3.5-0.8B tokenizer not cached") | |
| m = load_model(None, device="cpu", dtype=torch.float32, lm=tiny_lm(), | |
| tokenizer=load_tokenizer(SNAPSHOT)) | |
| torch.manual_seed(1) | |
| with torch.no_grad(): | |
| for p in m.heads.parameters(): | |
| p.normal_(0.0, 0.5) | |
| return m | |
| def _val(doc_id: str) -> bool: | |
| from deg.datagen.v3 import VAL_MODULUS | |
| return zlib.crc32(doc_id.encode()) % VAL_MODULUS == 0 | |
| def corpus(tmp_path: Path): | |
| from deg.train import FillerCorpus | |
| (tmp_path / "text").mkdir() | |
| rows = [] | |
| val_id = next(f"doc-{i}" for i in range(10_000) if _val(f"doc-{i}")) | |
| for i, doc_id in enumerate(["doc-a", "doc-b", val_id]): | |
| body = (f"[{doc_id}] " if doc_id == val_id else "") + FILLER_WORDS * (40 + 10 * i) | |
| tp = tmp_path / "text" / f"{doc_id}.txt" | |
| tp.write_text(body) | |
| rows.append({"id": doc_id, "text_path": str(tp)}) | |
| (tmp_path / "docs.jsonl").write_text("".join(json.dumps(r) + "\n" for r in rows)) | |
| c = FillerCorpus(tmp_path) | |
| assert {d for d, _ in c.docs} == {"doc-a", "doc-b"} # the v3 val doc is never filler | |
| return c | |
| def _targets(ex, text: str): | |
| """Every training target as (kind, ptr, gold text), independent of token positions.""" | |
| def at(i: int, j: int) -> str: | |
| return text[ex.ctx.offsets[i][0]:ex.ctx.offsets[j][1]] | |
| out = [] | |
| for it in ex.items: | |
| if it.kind == "anchor": | |
| out.append(("anchor", it.ptr, tuple(at(t, t) for t in it.anchors))) | |
| elif it.kind == "extract": | |
| out.append(("extract", it.ptr, tuple( | |
| at(a, b) if a >= 0 else None for a, b in zip(it.starts, it.ends)))) | |
| else: | |
| out.append((it.kind, it.ptr, tuple(it.target or ()), it.aux)) | |
| return sorted(out, key=repr) | |
| # ----------------------------------------------------------------------- filler | |
| def test_corpus_refuses_extractbench(tmp_path): | |
| from deg.train import FillerCorpus | |
| d = tmp_path / "ExtractBench" | |
| d.mkdir() | |
| (d / "docs.jsonl").write_text("") | |
| with pytest.raises(ValueError): | |
| FillerCorpus(d) | |
| def test_filler_record_keeps_every_target_on_its_text(model, corpus): | |
| from deg.train import deg_example, filler_record, record_text | |
| rec = _team_record() | |
| plain = deg_example(model, rec, random.Random(0), shuffle_p=0.0, anchor_count=True) | |
| want = _targets(plain, record_text(rec)) | |
| assert any(t[0] == "anchor" for t in want) and any(t[0] == "count" for t in want) | |
| seen_offsets = set() | |
| for seed in range(6): | |
| rng = random.Random(seed) | |
| aug = filler_record(model, rec, corpus, rng, 200) | |
| text = aug["context"] | |
| assert TEAM_CONTEXT in text and len(text) > len(TEAM_CONTEXT) + 200 | |
| shift = text.index(TEAM_CONTEXT) | |
| seen_offsets.add(shift) | |
| for k, (a, b) in aug["spans"].items(): | |
| ra, rb = rec["spans"][k] | |
| assert (a - shift, b - shift) == (ra, rb) and text[a:b] == TEAM_CONTEXT[ra:rb] | |
| ex = deg_example(model, aug, random.Random(0), shuffle_p=0.0, anchor_count=True) | |
| assert len(ex.ctx.ids) > len(plain.ctx.ids) + 150 | |
| assert _targets(ex, text) == want # same targets, same gold text | |
| assert len(seen_offsets) > 2 # random placement, including the middle | |
| def test_negative_example_trains_top_level_anchors_empty(model, corpus): | |
| from deg.train import negative_example | |
| ex = negative_example(model, _team_record(), corpus, random.Random(0), 150, | |
| anchor_count=True) | |
| assert ex is not None and ex.aug == "negative" | |
| assert 140 <= len(ex.ctx.offsets) <= 151 | |
| anchors = [it for it in ex.items if it.kind == "anchor"] | |
| counts = [it for it in ex.items if it.kind == "count"] | |
| assert [it.ptr for it in anchors] == ["/teams"] and anchors[0].anchors == [] | |
| assert len(counts) == 1 and counts[0].aux and counts[0].target.index(1.0) == 0 | |
| assert not any(it.kind in ("extract", "choice", "score", "noul") for it in ex.items) | |
| min1 = {**NESTED, "properties": {"teams": {**NESTED["properties"]["teams"], | |
| "minItems": 1}}} | |
| assert negative_example(model, {**_team_record(), "schema": min1}, corpus, | |
| random.Random(0), 150) is None | |
| def test_filler_draws_materialize_before_packing_within_the_limit(model, corpus): | |
| from deg.train import ( | |
| FillerDraw, | |
| Source, | |
| batches, | |
| deg_example, | |
| filler_example, | |
| ) | |
| rec = _team_record() | |
| ex = deg_example(model, rec, random.Random(0), shuffle_p=0.0, anchor_count=True) | |
| ex.rec = rec | |
| src = Source(Path("x.jsonl")) | |
| src.pool = [ex] | |
| max_ctx = 300 | |
| seen: list[list] = [] | |
| def fits(batch): | |
| seen.append(list(batch)) | |
| return True | |
| def augment(e, r): | |
| return FillerDraw(e, r.randint(len(e.ctx.ids) + 64, max_ctx), r.random() < 0.3) | |
| def materialize(d, r): | |
| return filler_example(model, d, corpus, r, max_ctx, anchor_count=True) | |
| gen = batches([src], None, random.Random(0), 10_000, 0.0, 4, draw=8, | |
| materialize=materialize, fits=fits, augment=augment) | |
| kinds = set() | |
| for _ in range(6): | |
| for e in next(gen): | |
| assert len(e.ctx.ids) <= max_ctx | |
| kinds.add(e.aug) | |
| assert kinds == {"filler", "negative"} | |
| assert seen and all(not isinstance(e, FillerDraw) for b in seen for e in b) | |
| def test_filler_example_falls_back_to_the_plain_example(model, corpus): | |
| from deg.train import FillerDraw, deg_example, filler_example | |
| rec = _team_record() | |
| ex = deg_example(model, rec, random.Random(0)) | |
| ex.rec = rec | |
| # no room for filler under the context limit -> the plain example | |
| got = filler_example(model, FillerDraw(ex, len(ex.ctx.ids) + 500), corpus, | |
| random.Random(0), len(ex.ctx.ids) + 3) | |
| assert got is ex | |