File size: 7,500 Bytes
bcc8d35 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | """Shared fixtures + house test conventions.
Conventions (inherited from the predecessor's 65-test suite):
* regression tests are NAMED AFTER THE INCIDENT they prevent, incident in
the docstring;
* every guard is tested in BOTH directions (fires when it must, silent when
it must);
* undefined statistics assert NaN, never 0;
* every test file's docstring carries its run command;
* end-to-end tests use a tiny random-init model on CPU with >=2 layers and
>=2 KV groups (1-layer/1-group models cannot surface interior/GQA bugs).
Run all CPU tests: uv run pytest -m "not gpu"
"""
from __future__ import annotations
import os
import pytest
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def heavy_job_blocking_gpu_test(lock_path: str | None = None) -> str | None:
"""The label of ANOTHER process's heavy-job lock, or None if the lock is free (or ours).
THE UNGUARDED-GPU-TEST DEFECT (P2 R2 review). The `gpu` marker is a label, not a guard: `pytest -k <pattern>`
without `-m "not gpu"` ran CUDA tests on GPU 0 while a 4-rank training run held the cards (21.7 GB in use of 23);
the test OOM'd, and the only reason the run survived is that its allocator had already reached steady state. A gpu
test is a heavy job like any other and must not start beside one. Probes the flock and releases it at once, so tests
that take the lock themselves (`apply_resource_guards(heavy=True)`) behave exactly as before."""
import fcntl
from align.utils import guard
path = lock_path or guard.LOCK_PATH
if not os.path.exists(path):
return None
with open(path, "a+") as fh:
fh.seek(0)
label = fh.read().strip()
try:
fcntl.flock(fh.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
return None if f"pid={os.getpid()}" in label.split() else (label or "unknown holder")
fcntl.flock(fh.fileno(), fcntl.LOCK_UN)
return None
@pytest.fixture(autouse=True)
def _gpu_tests_do_not_start_beside_a_heavy_job(request):
if request.node.get_closest_marker("gpu") is None:
return
holder = heavy_job_blocking_gpu_test()
if holder is not None:
pytest.skip(f"gpu test not started: the heavy-job lock is held by {holder!r} (never two heavy jobs at once)")
@pytest.fixture
def tmp_workdir(tmp_path_factory):
"""A scratch dir that honors the path policy: under the repo on /apps,
never /tmp (which is noexec AND on a different device on this box)."""
base = os.path.join(REPO_ROOT, ".tmp", "tests")
os.makedirs(base, exist_ok=True)
import tempfile
d = tempfile.mkdtemp(dir=base)
yield d
# best-effort cleanup; stale dirs under .tmp/tests are harmless
import shutil
shutil.rmtree(d, ignore_errors=True)
@pytest.fixture
def tiny_model_constants():
"""Fixture constants for the CPU tiny-model harness, consumed by
section-04's model tests. >=2 layers and >=2 KV groups is a hard house
rule: a 1-layer or 1-group model cannot surface interior-layer or
GQA-indexing bugs."""
return {
"n_layers": 2,
"hidden_size": 64,
"n_heads": 4,
"n_kv_heads": 2,
"head_dim": 16,
"intermediate_size": 128,
"vocab_size": 256,
"max_seq_len": 128,
"rope_theta": 1e6,
"seed": 0,
}
#: HF-cache snapshot of Mistral-7B-v0.3 (config + 3 shards + tokenizer). NOT present
#: on this box as of 2026-10-12 (hub/ has no models--mistralai--* entry); anything that
#: needs the WEIGHTS or the snapshot config.json must still skip when it is absent.
V03_TOKENIZER_SNAPSHOT = (
"/apps/voxtral/.cache/huggingface/hub/models--mistralai--Mistral-7B-v0.3/"
"snapshots/caa1feb0e54d415e2df31207e5f4e273e33509b1"
)
#: Tokenizer files vendored in-repo (byte-identical to the snapshot's; see
#: assets/tokenizer/mistral-7b-v0.3/PROVENANCE.md). Durable: the legacy SFT
#: checkpoint dirs these were copied from rotate.
V03_TOKENIZER_VENDORED = os.path.join(REPO_ROOT, "assets", "tokenizer", "mistral-7b-v0.3")
def resolve_v03_tokenizer_dir() -> str | None:
"""First directory holding a v0.3 tokenizer.json: ALIGN_TOKENIZER_SRC override,
the HF snapshot, then the vendored copy. None if none exists."""
env = os.environ.get("ALIGN_TOKENIZER_SRC")
for c in ([env] if env else []) + [V03_TOKENIZER_SNAPSHOT, V03_TOKENIZER_VENDORED]:
if c and os.path.isfile(os.path.join(c, "tokenizer.json")):
return c
return None
#: Resolved at import so tests can `isdir`-style gate on it.
V03_TOKENIZER_DIR = resolve_v03_tokenizer_dir()
#: Predecessor eval sets used as REAL gold sources for the ceiling tests
#: (MATH golds are LaTeX, GSM8K golds are bare integers -- they break
#: extraction in different ways; synthetic '42' golds prove nothing).
#: Section-03 regenerates these under nmo with hash receipts; these paths are
#: parameterized into the ceiling functions, never hardcoded inside nmo/.
GOLD_SOURCES = (
"/apps/development/rlhf/mistral-prm-ppo/artifacts/data/eval/math500.json",
"/apps/development/rlhf/mistral-prm-ppo/artifacts/data/eval/gsm8k_test.json",
)
@pytest.fixture(scope="session")
def v03_tokenizer():
"""The real Mistral-7B-v0.3 tokenizer, loaded by the in-repo align.tokenizer
(stdlib-only; its parity with transformers is certified by the oracle test in
tests/test_tokenizer.py). CPU-only, fast. Skips LOUDLY if no copy exists --
never downloads."""
d = resolve_v03_tokenizer_dir()
if d is None:
pytest.skip(
f"no v0.3 tokenizer.json at {V03_TOKENIZER_SNAPSHOT} or {V03_TOKENIZER_VENDORED}; "
"boundary certification cannot run against a fake tokenizer"
)
from align.tokenizer import load_tokenizer
return load_tokenizer(d)
@pytest.fixture
def clean_script_env():
"""Environment for tests that SPAWN nmo scripts in a subprocess.
The launching harness exports TMPDIR pointing at a scratchpad on the
HOME device; the section-01 path guard correctly REFUSES that env at
script startup (it did so in production on this guard's first day).
Tests that spawn scripts must therefore pass an env whose TMPDIR is on
/apps — via this fixture, so no lane rediscovers the leak. The guard's
own refusal test poisons TMPDIR deliberately and must NOT use this."""
env = dict(os.environ)
env["TMPDIR"] = os.path.join(REPO_ROOT, ".tmp")
os.makedirs(env["TMPDIR"], exist_ok=True)
return env
@pytest.fixture
def fake_host_state(monkeypatch):
"""Factory: monkeypatch align.utils.guard.host_state to a fixed reading.
Guard tests must never depend on real host load (CI would flake exactly
when the shared box is busy -- the moment the guards matter most)."""
def _set(
mem_available_gb: float,
load_per_core: float = 0.5,
disk_in_flight: int = 0,
d_state: int = 0,
):
from align.utils import guard
state = {
"ram_total_gb": 186.0,
"ram_available_gb": mem_available_gb,
"swap_total_gb": 0.0,
"load1": load_per_core * 48,
"load5": 0.0,
"n_cores": 48,
"load_per_core": load_per_core,
"disk_in_flight": disk_in_flight,
"d_state_procs": d_state,
}
monkeypatch.setattr(guard, "host_state", lambda: dict(state))
return state
return _set
|