Download code/tests/test_build_data_e2e.py from viai957/align: direct link, hf CLI and curl.
- Browser
- Download file 12.1 kB
-
https://huggingface.co/viai957/align/resolve/main/code/tests/test_build_data_e2e.py
- Command line
-
hf download hf://viai957/align/code/tests/test_build_data_e2e.py
-
curl -L -o test_build_data_e2e.py https://huggingface.co/viai957/align/resolve/main/code/tests/test_build_data_e2e.py
12.1 kB
| """build_data.py end-to-end on miniature inputs. | |
| Run: uv run pytest tests/test_build_data_e2e.py -m "not gpu" -v | |
| The build has ONE legal order -- eval sets, then the decontamination hash set, | |
| then every training corpus excluded against it -- because any other order | |
| produces a corpus that was never checked against the thing it will be | |
| evaluated on, and nothing downstream can tell the difference. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import math | |
| import os | |
| import pytest | |
| from align.data import build as bd | |
| from align.data.sources import DataSources | |
| # --------------------------------------------------------------------------- | |
| # Miniature source fixtures: same file layout as the real snapshots, ~20 rows. | |
| # --------------------------------------------------------------------------- | |
| def _write_rows(path: str, rows: list[dict]) -> None: | |
| """JSONL fixture (sources.iter_local_rows reads it like a parquet; the | |
| suite stays pyarrow-free).""" | |
| os.makedirs(os.path.dirname(path), exist_ok=True) | |
| with open(path, "w") as f: | |
| for r in rows: | |
| f.write(json.dumps(r) + "\n") | |
| def mini_sources(tmp_workdir) -> DataSources: | |
| root = os.path.join(tmp_workdir, "src") | |
| os.makedirs(root, exist_ok=True) | |
| # --- eval sets ------------------------------------------------------- | |
| m500 = os.path.join(root, "math500", "test.jsonl") | |
| os.makedirs(os.path.dirname(m500), exist_ok=True) | |
| with open(m500, "w") as f: | |
| for i in range(6): | |
| f.write( | |
| json.dumps( | |
| { | |
| "problem": f"Eval math problem {i}. What is ${i}+1$?", | |
| "answer": str(i + 1), | |
| "subject": "Algebra", | |
| "level": 3, | |
| "solution": "trivial", | |
| "unique_id": f"test/algebra/{i}.json", | |
| } | |
| ) | |
| + "\n" | |
| ) | |
| gsm = os.path.join(root, "gsm8k", "test-00000-of-00001.jsonl") | |
| _write_rows( | |
| gsm, | |
| [ | |
| {"question": f"Eval gsm problem {i}?", "answer": f"reasoning\n#### {i}"} | |
| for i in range(4) | |
| ], | |
| ) | |
| # --- MATH corpus for the subject join -------------------------------- | |
| for subject, n in (("algebra", 6), ("geometry", 6)): | |
| _write_rows( | |
| os.path.join(root, "hendrycks", subject, "train-00000-of-00001.jsonl"), | |
| [ | |
| { | |
| "problem": f"Train problem {subject} {i}.", | |
| "solution": "s", | |
| "type": subject.capitalize(), | |
| "level": "Level 3", | |
| } | |
| for i in range(n) | |
| ], | |
| ) | |
| # --- PRM800K annotation records -------------------------------------- | |
| def node(comps, chosen=None): | |
| return { | |
| "completions": [{"text": t, "rating": r, "flagged": False} for t, r in comps], | |
| "chosen_completion": chosen, | |
| "human_completion": None, | |
| } | |
| recs = [] | |
| for subject in ("algebra", "geometry"): | |
| for i in range(6): | |
| problem = f"Train problem {subject} {i}." | |
| # A clean solution (SFT-eligible) ... | |
| recs.append( | |
| { | |
| "question": {"problem": problem, "ground_truth_answer": str(i)}, | |
| "label": { | |
| "finish_reason": "solution", | |
| "steps": [ | |
| node([(f"First we consider case {i}.", 1)], chosen=0), | |
| node([(f"# Answer\n\n{i}", 1)], chosen=0), | |
| ], | |
| }, | |
| "is_quality_control_question": False, | |
| } | |
| ) | |
| # ... and an annotation with a rated sibling pair (Arm B site). | |
| recs.append( | |
| { | |
| "question": {"problem": problem, "ground_truth_answer": str(i)}, | |
| "label": { | |
| "finish_reason": "found_error", | |
| "steps": [ | |
| node([(f"First we consider case {i}.", 1)], chosen=0), | |
| node( | |
| [ | |
| (f"Then the answer is {i}.", 1), | |
| (f"Then the answer is {i + 99}.", -1), | |
| ("This restates the problem.", 0), | |
| ], | |
| chosen=0, | |
| ), | |
| ], | |
| }, | |
| "is_quality_control_question": False, | |
| } | |
| ) | |
| # One record that is contaminated: it is a MATH-500 eval problem. | |
| recs.append( | |
| { | |
| "question": { | |
| "problem": "Eval math problem 0. What is $0+1$?", | |
| "ground_truth_answer": "1", | |
| }, | |
| "label": { | |
| "finish_reason": "solution", | |
| "steps": [ | |
| node([("Leaked step.", 1)], chosen=0), | |
| node([("# Answer\n\n1", 1)], chosen=0), | |
| ], | |
| }, | |
| "is_quality_control_question": False, | |
| } | |
| ) | |
| prm_dir = os.path.join(root, "prm800k") | |
| os.makedirs(prm_dir, exist_ok=True) | |
| with open(os.path.join(prm_dir, "phase1_train.jsonl"), "w") as f: | |
| for r in recs[:4]: | |
| f.write(json.dumps(r) + "\n") | |
| with open(os.path.join(prm_dir, "phase2_train.jsonl"), "w") as f: | |
| for r in recs[4:]: | |
| f.write(json.dumps(r) + "\n") | |
| # --- argilla preference rows ----------------------------------------- | |
| arg = os.path.join(root, "argilla", "train.jsonl") | |
| _write_rows( | |
| arg, | |
| [ | |
| { | |
| "metadata": "{}", | |
| "instruction": f"Argilla problem {i}?", | |
| "chosen_response": "A longer chosen response with detail. " * (2 + i % 2), | |
| "chosen_rating": 9.0 if i % 4 else 5.0, | |
| "rejected_response": "Short rejected response.", | |
| "rejected_rating": 7.0, | |
| } | |
| for i in range(8) | |
| ], | |
| ) | |
| return DataSources( | |
| prm800k_dir=prm_dir, | |
| math500_file=m500, | |
| gsm8k_test_file=gsm, | |
| hendrycks_math_dir=os.path.join(root, "hendrycks"), | |
| argilla_file=arg, | |
| revisions={ | |
| "prm800k": "mini", | |
| "math500": "mini", | |
| "gsm8k": "mini", | |
| "hendrycks_math": "mini", | |
| "argilla": "mini", | |
| }, | |
| ) | |
| def mini_pins() -> bd.PinnedYields: | |
| """The miniature build's own pinned counts. Production defaults are the | |
| measured real-corpus numbers; a test that reused them would either be | |
| skipped or would teach us to relax the assertion.""" | |
| return bd.PinnedYields( | |
| math500=6, | |
| gsm8k=4, | |
| decontamination=10, | |
| sft_rows=12, | |
| argilla_raw=8, | |
| argilla_filtered=6, | |
| arm_b_sites=12, | |
| arm_b_capped=12, | |
| arm_b_problems=12, | |
| arm_b_curated=8, | |
| descope_min_pairs=1, | |
| expected_chosen_longer=None, | |
| held_out_frac=0.2, | |
| ) | |
| def test_strict_build_order_and_receipts(mini_sources, mini_pins, tmp_workdir, v03_tokenizer): | |
| """Eval sets build FIRST, then the decontamination hash set, then training | |
| corpora excluded against it, then shards. Every artifact has a receipt; | |
| the final manifest lists all hashes + counts. Attempting to build a | |
| training corpus before the eval hash set exists raises.""" | |
| out = os.path.join(tmp_workdir, "data") | |
| # Firing direction: the SFT stage refuses to run without its upstream. | |
| with pytest.raises(FileNotFoundError, match="eval"): | |
| bd.build_sft_corpus(mini_sources, out, v03_tokenizer, mini_pins) | |
| manifest = bd.build_all(mini_sources, out, v03_tokenizer, pins=mini_pins) | |
| assert manifest["stages"] == ["eval_sets", "decontamination", "sft", "arm_a", "arm_b"] | |
| assert manifest["counts"]["math500"] == 6 | |
| assert manifest["counts"]["gsm8k_test"] == 4 | |
| assert manifest["counts"]["decontamination_hashes"] == 10 | |
| # The planted contaminated record is dropped, loudly and countably. | |
| assert manifest["counts"]["sft_rows"] == 12 | |
| assert manifest["counts"]["sft_contaminated"] == 1 | |
| assert manifest["counts"]["sft_rejected_boundary"] == 0 | |
| # Stamped from a constant, never by hand: section-11's gates require | |
| # build_version >= 2 and RAISE on a missing field, so an unstamped build is | |
| # refused rather than assumed current. 1 = the doubled-marker builds. | |
| assert manifest["build_version"] == bd.BUILD_VERSION >= 2 | |
| assert manifest["boundary_spec"]["vocab_absorbers"] == 0 | |
| assert manifest["tokenizer_hash"] | |
| for key in ("math500", "gsm8k_test", "sft-train", "arm_a-train", "arm_b-train"): | |
| assert manifest["hashes"][key], f"missing content hash for {key}" | |
| # The corpus outcome-agreement statistic is RECORDED, with its decision | |
| # attached -- it is a measured property of the data, not a gate, and the | |
| # manifest is where it stays visible across rebuilds. | |
| # Arm B carries its OWN length baseline: its pairs share a step prefix, so | |
| # Arm A's number would arm the tripwire against a different distribution. | |
| lb = manifest["length_bias_baseline"] | |
| assert not math.isnan(lb["arm_b_chosen_longer_fraction"]) | |
| assert not math.isnan(lb["arm_b_chosen_longer_fraction_step_basis"]) | |
| # Arm A's pinned baseline is CHARACTER-based and must not be overwritten by | |
| # the token-based measurement the shard writer computes. | |
| assert lb["arm_a_chosen_longer_fraction"] == pytest.approx( | |
| manifest["counts"].get("arm_a_chosen_longer_fraction", lb["arm_a_chosen_longer_fraction"]) | |
| ) | |
| agreement = manifest["corpus_outcome_agreement"] | |
| assert ( | |
| agreement["n"] | |
| == agreement["agree"] | |
| + agreement["equivalent_unnormalized"] | |
| + agreement["genuinely_divergent"] | |
| ) | |
| assert agreement["rows_kept"] is True | |
| assert "anchor" in agreement["note"] | |
| on_disk = json.loads(open(os.path.join(out, "manifest.json")).read()) | |
| assert on_disk == manifest | |
| def test_split_by_problem_never_by_record(mini_sources, mini_pins, tmp_workdir, v03_tokenizer): | |
| """No problem_id appears on both sides of any split (SFT vs held-out, pair | |
| train vs held-out preference-accuracy set). | |
| Incident: PRM800K holds many annotation records per problem, so a | |
| record-level split reports an inflated validation number that looks like | |
| generalization.""" | |
| out = os.path.join(tmp_workdir, "data") | |
| bd.build_all(mini_sources, out, v03_tokenizer, pins=mini_pins) | |
| for corpus in ("sft", "arm_b"): | |
| train = bd.problem_ids_in(os.path.join(out, "shards", corpus, "train")) | |
| held = bd.problem_ids_in(os.path.join(out, "shards", corpus, "held_out")) | |
| assert train, f"{corpus} train split is empty" | |
| assert held, f"{corpus} held-out split is empty" | |
| assert not (train & held), f"{corpus} leaks problems across the split" | |
| def test_all_outputs_under_apps(tmp_workdir): | |
| """Every writable path statvfs-resolves onto the /apps device (uses the | |
| section-01 guard; tested here in the firing direction with a doctored | |
| output dir). | |
| Incident: / has ~13GB free and /tmp is a noexec tmpfs on a DIFFERENT | |
| device, so 'not the root device' is not enough -- it must be /apps.""" | |
| ok = os.path.join(tmp_workdir, "data") | |
| bd.assert_build_paths(bd.output_paths(ok)) # silent direction | |
| doctored = "/tmp/nmo-should-never-write-here" | |
| try: | |
| with pytest.raises(RuntimeError, match="/apps"): | |
| bd.assert_build_paths(bd.output_paths(doctored)) | |
| finally: | |
| # The guard creates each path before stat'ing it, so the firing | |
| # direction leaves a directory on the very filesystem this repo is | |
| # forbidden to write to. Remove it. | |
| import shutil | |
| shutil.rmtree(doctored, ignore_errors=True) | |