Add unknown-key validation to config.toml merge (issues.md Issue 2)
CI / Lint (ruff check) (push) Successful in 30s
CI / Format (ruff format) (push) Successful in 29s
CI / Sync project version with tag (push) Has been skipped
CI / Lint (ruff check) (pull_request) Successful in 32s
CI / Type check (ty) (push) Successful in 34s
CI / Format (ruff format) (pull_request) Successful in 34s
CI / Sync project version with tag (pull_request) Has been skipped
CI / Type check (ty) (pull_request) Successful in 35s
CI / Tests (pull_request) Successful in 3m30s
CI / Tests (push) Successful in 3m42s

A typo like `n_res_block` for `n_res_blocks` previously merged cleanly,
passed validate_config, and silently trained a model that didn't match
config.toml's documented settings. merge_cli_overrides now rejects any
key not present in DEFAULT_CONFIG's schema via validate_config_keys,
with a did-you-mean suggestion, while still allowing the genuinely
dynamic composed-router axis keys and centers_init. Checkpoint
model_config loading is untouched, so old checkpoints keep loading.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 14:47:29 +02:00
parent 9bf5874308
commit 01acbfed61
3 changed files with 1353 additions and 1 deletions
+91
View File
@@ -798,6 +798,97 @@ def test_validate_config_ar_checks_skipped_under_one_shot():
gconfig.validate_config(cfg) # must not raise
# ---------------------------------------------------------------------------
# validate_config_keys / merge_cli_overrides unknown-key rejection
# ---------------------------------------------------------------------------
def test_validate_config_keys_default_config_passes():
gconfig.validate_config_keys(gconfig.DEFAULT_CONFIG) # must not raise
def test_validate_config_keys_rejects_unknown_top_level_key():
cfg = _cfg_with(**{"bogus_section.foo": 1})
try:
gconfig.validate_config_keys(cfg)
assert False, "expected ValueError"
except ValueError as e:
assert "bogus_section" in str(e)
def test_validate_config_keys_rejects_unknown_nested_key_with_close_match_hint():
cfg = _cfg_with(**{"stage1_model.n_res_block": 12})
try:
gconfig.validate_config_keys(cfg)
assert False, "expected ValueError"
except ValueError as e:
assert "stage1_model.n_res_block" in str(e)
assert "n_res_blocks" in str(e)
def test_validate_config_keys_allows_composed_router_axis_keys():
cfg = _cfg_with(
**{
"stage1_model.router.enabled": True,
"stage1_model.router.type": "composed",
"stage1_model.router.axis0_type": "energy",
"stage1_model.router.axis0_n_experts": 4,
"stage1_model.router.axis1_type": "pdg",
"stage1_model.router.axis1_emb_dim": 8,
}
)
gconfig.validate_config_keys(cfg) # must not raise
def test_validate_config_keys_allows_centers_init():
cfg = _cfg_with(**{"stage1_model.router.centers_init": [-1.0, 0.0, 1.0]})
gconfig.validate_config_keys(cfg) # must not raise
def test_validate_config_keys_rejects_unrelated_unknown_router_key():
cfg = _cfg_with(**{"stage1_model.router.n_expert": 4}) # typo for n_experts
try:
gconfig.validate_config_keys(cfg)
assert False, "expected ValueError"
except ValueError as e:
assert "stage1_model.router.n_expert" in str(e)
assert "n_experts" in str(e)
def test_validate_config_keys_skips_meta_section():
cfg = _cfg_with()
cfg["meta"] = {"config_version": 3, "git_hash": "abc123"}
gconfig.validate_config_keys(cfg) # must not raise
def test_merge_cli_overrides_rejects_typo_in_toml_file(tmp_path):
path = tmp_path / "config.toml"
path.write_text("[meta]\nconfig_version = 3\n\n[stage1_model]\nn_res_block = 12\n")
try:
gconfig.merge_cli_overrides(gconfig.DEFAULT_CONFIG, path, {})
assert False, "expected ValueError"
except ValueError as e:
assert "n_res_block" in str(e)
def test_merge_cli_overrides_rejects_typo_in_cli_overrides():
try:
gconfig.merge_cli_overrides(
gconfig.DEFAULT_CONFIG,
None,
{"stage1_model": {"n_res_block": 12}},
)
assert False, "expected ValueError"
except ValueError as e:
assert "n_res_block" in str(e)
@pytest.mark.parametrize("fixture_name", ["default.toml", "wgan_h128_b4_physical.toml"])
def test_merge_cli_overrides_real_config_fixtures_pass_key_validation(fixture_name, monkeypatch):
monkeypatch.setattr(gconfig, "git_hash", lambda: "c3bf3abebfe29a10fe42b9cbafbb3460ab78d243")
gconfig.merge_cli_overrides(gconfig.DEFAULT_CONFIG, _CONFIGS_DIR / fixture_name, {}) # must not raise
# ---------------------------------------------------------------------------
# checkpoint config-mismatch warnings (unchanged surface, still exercised)
# ---------------------------------------------------------------------------