File size: 10,826 Bytes
201dc77 | 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 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | """TOML config loader. Run: uv run pytest tests/test_config_toml.py -v -m "not gpu"
The loader replaced OmegaConf; these tests pin the three behaviours OmegaConf
gave us that the stage scripts silently depend on:
* struct mode at EVERY depth (a typo'd key raises, naming its dotted path);
* CLI override coercion from the field annotation (`lr=1e-5` is a float);
* every shipped configs/*.toml loads, and survives a dump/reload round trip.
"""
from __future__ import annotations
import glob
import os
import tomllib
import pytest
from align.config import (
ConfigError,
RunConfig,
TrainConfig,
dumps_toml,
load_config,
load_toml,
to_dict,
)
from tests.conftest import REPO_ROOT
CONFIG_DIR = os.path.join(REPO_ROOT, "configs")
SHIPPED = sorted(glob.glob(os.path.join(CONFIG_DIR, "*.toml")))
def _write(tmp_workdir, text, name="c.toml"):
p = os.path.join(tmp_workdir, name)
with open(p, "w") as f:
f.write(text)
return p
def test_every_legacy_yaml_has_a_toml():
"""Conversion completeness: 7 stage configs shipped as YAML, 7 as TOML."""
names = {os.path.basename(p) for p in SHIPPED}
assert {
"sft.toml", "pretrain.toml", "trace.toml", "eval.toml",
"dpo_argilla.toml", "dpo_steppairs.toml", "grpo_stageb.toml",
} <= names
@pytest.mark.parametrize("path", SHIPPED, ids=os.path.basename)
def test_shipped_toml_parses_and_has_no_yaml_residue(path):
with open(path, "rb") as f:
tomllib.load(f)
text = open(path).read()
assert "OmegaConf" not in text and ".yaml" not in text
@pytest.mark.parametrize("name", ["sft", "pretrain", "trace"])
def test_runconfig_tomls_round_trip(name, tmp_workdir):
"""load -> to_dict -> dumps_toml -> load is the identity. None-valued
keys are omitted by the dump and come back as the dataclass default."""
cfg = load_config(os.path.join(CONFIG_DIR, f"{name}.toml"))
again = load_config(RunConfig, _write(tmp_workdir, dumps_toml(cfg)))
assert to_dict(again) == to_dict(cfg)
assert isinstance(again.optimizer.betas, tuple)
def test_sft_values_match_the_legacy_yaml():
cfg = load_config(os.path.join(CONFIG_DIR, "sft.toml"))
assert cfg.run_name == "sft-stage-11"
assert cfg.optimizer.name == "adamw8bit" and cfg.optimizer.grad_accum == 2
assert cfg.optimizer.lr == 1e-5 and cfg.optimizer.betas == (0.9, 0.95)
assert cfg.train.global_batch_rows == 24 and cfg.train.micro_batch_size == 3
assert cfg.model.attn_implementation is None # YAML null -> omitted key
assert cfg.gates.epoch_budget_s == 14400.0
@pytest.mark.parametrize("name", ["dpo_argilla", "dpo_steppairs"])
def test_dpo_tomls_load_through_dpo_stage(name):
from align.train.dpo_stage import load_stage_config
cfg = load_stage_config(os.path.join(CONFIG_DIR, f"{name}.toml"))
assert "logps_chosen" in cfg.armed_tripwires
assert "length_drift" in cfg.armed_tripwires
def test_grpo_stageb_toml_invariants():
"""The regression-tested GRPO pins survive the format change."""
from align.rl.stage_config import load_stage_config
cfg = load_stage_config(os.path.join(CONFIG_DIR, "grpo_stageb.toml"))
assert cfg.grpo.beta == 0.0 and cfg.grpo.load_ref_policy is False
assert (cfg.grpo.clip_lower, cfg.grpo.clip_higher) == (0.2, 0.272)
assert cfg.grpo.num_samples_per_prompt_rollout >= 8
assert isinstance(cfg.grpo.loss_denominator, float)
assert cfg.grpo.use_rho_correction is True
def test_eval_toml_is_a_plain_dict_with_overrides():
cfg = load_toml(os.path.join(CONFIG_DIR, "eval.toml"), ["eval.limit=8", "eval.temperature=0.7"])
assert cfg["eval"]["limit"] == 8
assert cfg["eval"]["temperature"] == 0.7
assert cfg["eval"]["verifier"]["outer_timeout_seconds"] >= 4 * cfg["eval"]["verifier"]["timeout_seconds"]
def test_unknown_key_names_full_dotted_path(tmp_workdir):
p = _write(tmp_workdir, "[optimizer]\nlr = 1e-6\nnot_a_field = 3\n")
with pytest.raises(ConfigError, match=r"optimizer\.not_a_field"):
load_config(RunConfig, p)
p = _write(tmp_workdir, "[optimzer]\nlr = 1e-6\n")
with pytest.raises(ConfigError, match="optimzer"):
load_config(RunConfig, p)
def test_unknown_override_raises_at_any_depth(tmp_workdir):
p = _write(tmp_workdir, "run_name = 'x'\n")
with pytest.raises(ConfigError, match=r"train\.nope"):
load_config(RunConfig, p, ["train.nope=1"])
with pytest.raises(ConfigError, match="top_level_typo"):
load_config(RunConfig, p, ["--top_level_typo=1"])
def test_override_coercion_from_annotation(tmp_workdir):
p = _write(tmp_workdir, "run_name = 'x'\n")
cfg = load_config(
RunConfig,
p,
[
"--optimizer.lr=1e-5",
"--train.epochs=2",
"--train.full_ft=false",
"--train.max_micro_steps=40",
"--optimizer.betas=[0.8, 0.9]",
"--tripwires.armed=logps_chosen,length_drift",
"--trace.tier1_layers=[1, 2, 3]",
"--model.attn_implementation=sdpa",
"--run_name=hello",
],
)
assert isinstance(cfg.optimizer.lr, float) and cfg.optimizer.lr == 1e-5
assert isinstance(cfg.train.epochs, int) and cfg.train.epochs == 2
assert cfg.train.full_ft is False
assert cfg.train.max_micro_steps == 40
assert cfg.optimizer.betas == (0.8, 0.9)
assert cfg.tripwires.armed == ("logps_chosen", "length_drift")
assert cfg.trace.tier1_layers == [1, 2, 3]
assert cfg.model.attn_implementation == "sdpa"
assert cfg.run_name == "hello"
def test_optional_null_override_and_bad_values(tmp_workdir):
p = _write(tmp_workdir, "[train]\nmax_micro_steps = 5\n")
assert load_config(RunConfig, p).train.max_micro_steps == 5
assert load_config(RunConfig, p, ["train.max_micro_steps=null"]).train.max_micro_steps is None
with pytest.raises(ConfigError, match=r"train\.epochs"):
load_config(RunConfig, p, ["train.epochs=abc"])
with pytest.raises(ConfigError, match=r"train\.epochs"):
load_config(RunConfig, p, ["train.epochs=2.5"])
with pytest.raises(ConfigError, match=r"train\.full_ft"):
load_config(RunConfig, p, ["train.full_ft=maybe"])
with pytest.raises(ConfigError, match=r"train\.epochs"):
load_config(RunConfig, _write(tmp_workdir, "[train]\nepochs = true\n", "b.toml"))
def test_int_widens_to_float_but_not_the_reverse(tmp_workdir):
p = _write(tmp_workdir, "[optimizer]\nweight_decay = 1\n[train]\nseed = 3.0\n")
cfg = load_config(RunConfig, p)
assert isinstance(cfg.optimizer.weight_decay, float)
assert cfg.train.seed == 3 and isinstance(cfg.train.seed, int)
def test_two_call_shapes_agree(tmp_workdir):
p = _write(tmp_workdir, "[train]\nepochs = 3\n")
a = load_config(p, ["train.seed=9"])
b = load_config(RunConfig, p, ["train.seed=9"])
assert to_dict(a) == to_dict(b)
assert isinstance(a.train, TrainConfig)
def test_shipped_ppo_stage_toml_loads_through_the_struct_mode_loader():
"""The PPO stage config ships as a DRAFT: it must still parse through the
struct-mode loader (an unknown key or a missing launch-refusal field is a
typo that would otherwise only surface at launch), and it must keep its
pre-registration TBD markers so nobody mistakes a legacy default for a
frozen decision."""
from align.ppo.stage_config import load_stage_config
path = os.path.join(CONFIG_DIR, "ppo_stage.toml")
cfg = load_stage_config(path)
assert cfg.arm == "prm" and cfg.ppo.temperature == cfg.rollout.temperature
assert cfg.rows_per_iteration == 64
text = open(path).read()
assert text.count("[TBD pre-reg freeze]") >= 4 and "NOT LAUNCHABLE AS SHIPPED" in text
# ---------------------------------------------------------------------------
# G0: clipping on by default, armed tripwires, control-plane bounds
# ---------------------------------------------------------------------------
def test_grad_clipping_is_on_by_default_and_every_runconfig_toml_states_it():
"""Default max_grad_norm is 1.0 (OLMoE/OLMo 2). The pre-G0 default None
left every SFT/pretrain run unclipped. Each RunConfig-shaped TOML with an
[optimizer] table must NAME the value rather than inherit it."""
from align.config import OptimizerConfig
assert OptimizerConfig().max_grad_norm == 1.0
for name in ("sft", "pretrain"):
raw = tomllib.load(open(os.path.join(CONFIG_DIR, f"{name}.toml"), "rb"))
assert raw["optimizer"]["max_grad_norm"] == 1.0, name
assert load_config(os.path.join(CONFIG_DIR, f"{name}.toml")).optimizer.max_grad_norm == 1.0
# explicit opt-out still possible
assert load_config(RunConfig, os.path.join(CONFIG_DIR, "sft.toml"),
["optimizer.max_grad_norm=null"]).optimizer.max_grad_norm is None
@pytest.mark.parametrize("name", ["sft", "pretrain"])
def test_sft_and_pretrain_arm_exactly_the_grad_norm_tripwire(name):
"""grad_norm is the only tripwire these stages can FEED; arming
logps_chosen/length_drift there would halt at step one (no inputs). The
armed names must also be ones the Trainer can build."""
from align.train.trainer import TRIPWIRE_BUILDERS
cfg = load_config(os.path.join(CONFIG_DIR, f"{name}.toml"))
assert cfg.tripwires.armed == ("grad_norm",)
assert set(cfg.tripwires.armed) <= set(TRIPWIRE_BUILDERS)
def test_control_plane_train_fields_names_defaults_and_toml(tmp_workdir):
"""The control-plane agent reads these EXACT names (handoff/guards-configs.md)."""
t = TrainConfig()
assert t.max_wall_seconds is None
assert t.max_consecutive_skipped_steps == 3 and isinstance(t.max_consecutive_skipped_steps, int)
assert t.max_skipped_fraction == 0.05
p = _write(tmp_workdir,
"[train]\nmax_wall_seconds = 3600\nmax_consecutive_skipped_steps = 5\n"
"max_skipped_fraction = 0.1\n")
cfg = load_config(RunConfig, p)
assert cfg.train.max_wall_seconds == 3600.0 and isinstance(cfg.train.max_wall_seconds, float)
assert cfg.train.max_consecutive_skipped_steps == 5 and cfg.train.max_skipped_fraction == 0.1
assert load_config(RunConfig, p, ["train.max_wall_seconds=null"]).train.max_wall_seconds is None
# struct mode: a typo'd name raises instead of silently disabling the bound
with pytest.raises(ConfigError, match="max_wall_second"):
load_config(RunConfig, _write(tmp_workdir, "[train]\nmax_wall_second = 1\n", "t.toml"))
with pytest.raises(ConfigError, match="max_consecutive_skipped_steps"):
load_config(RunConfig, p, ["train.max_consecutive_skipped_steps=2.5"])
# round trip through dump
rt = _write(tmp_workdir, dumps_toml(to_dict(cfg)), "rt.toml")
assert to_dict(load_config(RunConfig, rt)) == to_dict(cfg)
|