align / code /tests /test_audit_g2_lora_export_fixes.py
viai957's picture
Add files using upload-large-folder tool
bcc8d35 verified
Raw History Blame Contribute Delete
9.24 kB
"""Regression tests for the 2026-10-14 audit findings U03-3, U03-4, U05-1, U11-8, U11-9, U11-18
(LoRA export fidelity, adapter-to-base binding on resume, export-check gating and GPU pinning).
Run: cd /apps/development/rlhf/align && CUDA_VISIBLE_DEVICES= .venv/bin/python -m pytest \
tests/test_audit_g2_lora_export_fixes.py -q -p no:cacheprovider
"""
from __future__ import annotations
import importlib.util
import logging
import os
import pytest
import torch
from test_qwen3_parity import make_tiny, rescale_
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def _load(name):
spec = importlib.util.spec_from_file_location(name, os.path.join(REPO, "scripts", f"{name}.py"))
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
return m
exp = _load("qwen3_export_check")
# ---------------------------------------------------------------------------
# U05-1 / U03-3: the bf16 merge cast is lossy; the export must MEASURE and (optionally) refuse
# ---------------------------------------------------------------------------
def _bf16_lora(b_scale: float):
from align.ppo.lora import LoRAConfig, inject_lora, lora_modules
m = make_tiny().to(torch.bfloat16)
inject_lora(m, LoRAConfig(r=4, alpha=8.0), adapter="sft", generator=torch.Generator().manual_seed(1))
g = torch.Generator().manual_seed(2)
with torch.no_grad():
for _, mod in lora_modules(m):
mod.adapters["sft"].B.copy_(b_scale * torch.randn(mod.adapters["sft"].B.shape, generator=g))
return m
def test_export_reports_retained_delta_and_warns_when_bf16_drops_most_of_it(caplog):
from align.ppo.lora import export_merged_state_dict
m = _bf16_lora(1e-5) # the shipped PPO lr=1e-6 regime: delta << half an ulp of W
rep: dict = {}
with caplog.at_level(logging.WARNING):
export_merged_state_dict(m, {k: v.detach().clone() for k, v in m.state_dict().items()}, "sft", report=rep)
assert rep["cosine"] < 0.5 and rep["dropped_fraction"] > 0.5, rep
assert rep["layers"] > 0 and rep["min_layer_cosine"] <= rep["cosine"] + 1e-6
assert any("bf16" in r.message and "cosine" in r.message for r in caplog.records)
def test_export_refuses_below_min_cosine_and_passes_a_well_resolved_delta(caplog):
from align.ppo.lora import export_merged_state_dict
full = lambda m: {k: v.detach().clone() for k, v in m.state_dict().items()} # noqa: E731
small = _bf16_lora(1e-5)
with pytest.raises(ValueError, match="cosine"):
export_merged_state_dict(small, full(small), "sft", min_cosine=0.9)
big = _bf16_lora(5.0) # delta >> ulp: the cast preserves it
rep: dict = {}
with caplog.at_level(logging.WARNING):
export_merged_state_dict(big, full(big), "sft", report=rep, min_cosine=0.9)
assert rep["cosine"] > 0.95
assert not any("cosine" in r.message for r in caplog.records)
def test_export_of_an_exactly_zero_adapter_is_reported_not_nan():
from align.ppo.lora import export_merged_state_dict
m = _bf16_lora(0.0)
rep: dict = {}
export_merged_state_dict(m, {k: v.detach().clone() for k, v in m.state_dict().items()}, "sft", report=rep)
assert rep["delta_norm"] == 0.0 and rep["cosine"] == 1.0
# ---------------------------------------------------------------------------
# U03-4: a LoRA resume binds the adapter to the base it was trained on
# ---------------------------------------------------------------------------
def test_lora_payload_carries_a_base_fingerprint_and_a_foreign_base_is_refused(tmp_workdir, monkeypatch):
import test_qwen3_stage as T
tr, _, _ = T._build(tmp_workdir, "a")
payload = tr.train_module.optim_state_dict()
assert isinstance(payload.get("base_fingerprint"), str) and len(payload["base_fingerprint"]) == 64
# the same base resumes fine
tr_same, _, _ = T._build(tmp_workdir, "same")
tr_same.train_module.load_optim_state_dict(payload)
# a different base with an identical key set / shapes (repointed snapshot) must not
monkeypatch.setattr(T, "make_tiny", lambda: rescale_(make_tiny(), seed=7))
tr_other, _, _ = T._build(tmp_workdir, "other")
with pytest.raises(RuntimeError, match="base"):
tr_other.train_module.load_optim_state_dict(payload)
def test_legacy_payload_without_a_fingerprint_still_resumes_with_a_warning(tmp_workdir, caplog):
import test_qwen3_stage as T
tr, _, _ = T._build(tmp_workdir, "a")
payload = tr.train_module.optim_state_dict()
payload.pop("base_fingerprint")
with caplog.at_level(logging.WARNING):
tr.train_module.load_optim_state_dict(payload)
assert any("fingerprint" in r.message for r in caplog.records)
def test_fingerprint_tracks_content_not_just_keys():
from align.train.qwen3_stage import base_fingerprint
a, b = make_tiny(), make_tiny()
assert base_fingerprint(a.state_dict()) == base_fingerprint(b.state_dict())
with torch.no_grad():
b.model.layers[0].attn.q_norm.weight.mul_(1.001)
assert base_fingerprint(a.state_dict()) != base_fingerprint(b.state_dict())
other = rescale_(make_tiny(), seed=7)
assert base_fingerprint(a.state_dict()) != base_fingerprint(other.state_dict())
# ---------------------------------------------------------------------------
# U11-8: the exit status gates every check the script advertises
# ---------------------------------------------------------------------------
def _good_report():
return {
"verdict": {"passed": True},
"merge_vs_trainer_file": {"bitwise_mismatches": 0},
"snapshot_mapping_roundtrip": {"bitwise_equal": True},
"merge_changed_tensors_vs_snapshot": 12,
"merge_relative_weight_change": {"n": 12, "min": 1e-4, "mean": 4e-4, "max": 1e-3},
"adapter_vs_merged": {"verdict": {"passed": True}},
}
def test_export_ok_passes_a_complete_report():
assert exp.export_ok(_good_report()) is True
@pytest.mark.parametrize("mutate", [
lambda r: r.update(snapshot_mapping_roundtrip={"bitwise_equal": False}),
lambda r: r.update(merge_changed_tensors_vs_snapshot=0), # zero adapter
lambda r: r.update(merge_relative_weight_change={"n": 12, "min": 0.0, "mean": 1e-4, "max": 1e-3}),
lambda r: r.pop("merge_changed_tensors_vs_snapshot"), # check did not run
lambda r: r.pop("snapshot_mapping_roundtrip"),
lambda r: r.update(adapter_vs_merged={"verdict": {"passed": False}}),
lambda r: r.pop("adapter_vs_merged"),
lambda r: r.update(merge_vs_trainer_file={"bitwise_mismatches": 2}),
lambda r: r.update(verdict={"passed": False}),
lambda r: r.pop("verdict"),
])
def test_export_ok_fails_each_unmet_check(mutate):
r = _good_report()
mutate(r)
assert exp.export_ok(r) is False
# ---------------------------------------------------------------------------
# U11-9: merged-bf16 vs adapter-applied forward
# ---------------------------------------------------------------------------
def _lp(t=64, v=50, seed=0):
g = torch.Generator().manual_seed(seed)
return torch.log_softmax(3 * torch.randn(t, v, generator=g), -1)
def test_adapter_vs_merged_passes_identical_and_fails_a_dropped_delta():
a = _lp()
same = exp.evaluate_adapter_vs_merged(exp.compare_adapter_vs_merged([a], [a.clone()]))
assert same["passed"] is True
other = _lp(seed=5) # an unrelated distribution: what a merge that lost the delta looks like
bad = exp.evaluate_adapter_vs_merged(exp.compare_adapter_vs_merged([a], [other]))
assert bad["passed"] is False
near = a + 0.02 * torch.randn(a.shape, generator=torch.Generator().manual_seed(3))
ok = exp.evaluate_adapter_vs_merged(exp.compare_adapter_vs_merged([a], [torch.log_softmax(near, -1)]))
assert ok["passed"] is True
def test_adapter_vs_merged_shape_mismatch_raises():
with pytest.raises(ValueError):
exp.compare_adapter_vs_merged([_lp(8)], [_lp(9)])
def test_merge_fidelity_stats_expose_the_dead_zone():
from align.ppo.lora import lora_state_dict
m = _bf16_lora(1e-5)
base = {k: v.detach().clone() for k, v in m.state_dict().items() if ".adapters." not in k}
lora = lora_state_dict(m, "sft")
merged = exp.merge_lora_state(base, lora, 2.0)
st = exp.merge_fidelity_stats(base, merged, lora, 2.0)
assert st["cosine"] < 0.5 and st["dropped_fraction"] > 0.5 and st["layers"] > 0
# ---------------------------------------------------------------------------
# U11-18: the vLLM child is pinned to the PARENT's first visible device
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("cvd,expect", [("2,3", "2"), ("3", "3"), (" 1 , 2", "1"), ("GPU-abc,GPU-def", "GPU-abc"),
(None, "0"), ("", "0")])
def test_first_visible_gpu_follows_the_parent_pinning(cvd, expect):
env = {} if cvd is None else {"CUDA_VISIBLE_DEVICES": cvd}
assert exp._first_visible_gpu(env) == expect
def test_rollout_env_uses_the_pinned_device_not_physical_zero(monkeypatch):
monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "2,3")
assert exp._rollout_env(exp._first_visible_gpu())["CUDA_VISIBLE_DEVICES"] == "2"