Make config dataclasses the single source of truth for DEFAULT_CONFIG
CI / Format (ruff format) (push) Successful in 31s
CI / Lint (ruff check) (push) Successful in 31s
CI / Sync project version with tag (push) Has been skipped
CI / Lint (ruff check) (pull_request) Successful in 44s
CI / Type check (ty) (push) Successful in 46s
CI / Format (ruff format) (pull_request) Successful in 39s
CI / Sync project version with tag (pull_request) Has been skipped
CI / Type check (ty) (pull_request) Successful in 39s
CI / Tests (pull_request) Successful in 3m38s
CI / Tests (push) Successful in 3m45s
CI / Format (ruff format) (push) Successful in 31s
CI / Lint (ruff check) (push) Successful in 31s
CI / Sync project version with tag (push) Has been skipped
CI / Lint (ruff check) (pull_request) Successful in 44s
CI / Type check (ty) (push) Successful in 46s
CI / Format (ruff format) (pull_request) Successful in 39s
CI / Sync project version with tag (pull_request) Has been skipped
CI / Type check (ty) (pull_request) Successful in 39s
CI / Tests (pull_request) Successful in 3m38s
CI / Tests (push) Successful in 3m45s
DEFAULT_CONFIG and build_models/build_critics/StageSpec.from_config's inline .get(key, default) fallbacks had already drifted: two keys (stage2_model.decoder, stage2_model.particle_type.target) resolved differently depending on whether a config dict came from merge_cli_overrides (fully populated, correct) or was hand-built and partial (fell back to stale v0.2-shaped literals). Introduce frozen dataclasses (GiantConfig and its nested blocks) in giant/config.py as the actual single declaration of every default; DEFAULT_CONFIG is now generated from them instead of hand-maintained, and build_models, build_critics, and StageSpec.from_config consume the dataclasses instead of duplicating literal fallbacks, so this class of drift can't recur. Router/n_sec sub-blocks keep an `extra` catch-all for their genuinely dynamic keys (composed-router axes, runtime-seeded centers_init, legacy_owner). Fixing the fallback surfaced the same latent bug in two existing partial-config callers that had been silently depending on it: a test fixture in test_train.py and scripts/warm_setup_cache.py's minimal cfg (now merged against DEFAULT_CONFIG instead of hand-rolled, closing the gap for good). See issues.md Issue 1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,80 @@ def test_conditioning_enum_has_onehot():
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Config dataclasses (issues.md Issue 1)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_giant_config_to_dict_matches_default_config():
|
||||
"""DEFAULT_CONFIG is generated from GiantConfig().to_dict() (not
|
||||
hand-maintained), so the two cannot structurally drift apart — but this
|
||||
pins the *equality* too, catching e.g. a stray in-place mutation of
|
||||
DEFAULT_CONFIG added elsewhere after import."""
|
||||
assert gconfig.GiantConfig().to_dict() == gconfig.DEFAULT_CONFIG
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cls",
|
||||
[
|
||||
gconfig.ConditioningAxisConfig,
|
||||
gconfig.ConditioningConfig,
|
||||
gconfig.FlowConfig,
|
||||
gconfig.DdpmConfig,
|
||||
gconfig.Stage1WganConfig,
|
||||
gconfig.Stage2WganConfig,
|
||||
gconfig.RouterConfig,
|
||||
gconfig.Stage2RouterConfig,
|
||||
gconfig.NSecConfig,
|
||||
gconfig.ParticleTypeConfig,
|
||||
gconfig.AutoregressiveConfig,
|
||||
gconfig.Stage1ModelConfig,
|
||||
gconfig.Stage2ModelConfig,
|
||||
gconfig.TrainConfig,
|
||||
gconfig.GiantConfig,
|
||||
],
|
||||
)
|
||||
def test_config_dataclass_from_dict_round_trips_through_to_dict(cls):
|
||||
assert cls.from_dict(cls().to_dict()) == cls()
|
||||
assert cls.from_dict(None) == cls()
|
||||
|
||||
|
||||
def test_stage2_model_config_defaults_match_documented_v030_intent():
|
||||
"""The two keys issues.md Issue 1 found drifted between DEFAULT_CONFIG
|
||||
and build_models/StageSpec.from_config's own .get(key, default)
|
||||
fallbacks — pinned directly against the dataclass that is now their
|
||||
shared single source of truth."""
|
||||
spec = gconfig.Stage2ModelConfig()
|
||||
assert spec.decoder == "autoregressive"
|
||||
assert spec.particle_type.target == "onehot"
|
||||
|
||||
|
||||
def test_router_config_extra_round_trips_composed_axis_keys():
|
||||
d = {"enabled": True, "type": "composed", "axis0_type": "energy", "axis0_n_experts": 4}
|
||||
router = gconfig.RouterConfig.from_dict(d)
|
||||
assert router.enabled is True
|
||||
assert router.extra == {"axis0_type": "energy", "axis0_n_experts": 4}
|
||||
assert router.to_dict()["axis0_type"] == "energy"
|
||||
|
||||
|
||||
def test_stage2_router_config_tie_to_stage1_not_leaked_into_extra():
|
||||
router = gconfig.Stage2RouterConfig.from_dict({"tie_to_stage1": True})
|
||||
assert router.tie_to_stage1 is True
|
||||
assert "tie_to_stage1" not in router.extra
|
||||
|
||||
|
||||
def test_stage1_router_config_has_no_tie_to_stage1_key():
|
||||
"""Stage 1's router schema must not gain stage 2's tie_to_stage1 key —
|
||||
that would change every future run's saved config.toml shape."""
|
||||
assert "tie_to_stage1" not in gconfig.RouterConfig().to_dict()
|
||||
|
||||
|
||||
def test_n_sec_config_extra_round_trips_legacy_owner():
|
||||
n_sec = gconfig.NSecConfig.from_dict({"mode": "head", "legacy_owner": "stage1"})
|
||||
assert n_sec.legacy_owner == "stage1"
|
||||
assert n_sec.to_dict() == {"mode": "head", "lambda": 0.1, "legacy_owner": "stage1"}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _deep_merge
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user