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
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:
@@ -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)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user