1ec333ff6d
CI / Sync project version with tag (push) Has been skipped
CI / Lint (ruff check) (push) Successful in 1m36s
CI / Format (ruff format) (push) Successful in 1m41s
CI / Sync project version with tag (pull_request) Has been skipped
CI / Type check (ty) (push) Successful in 2m17s
CI / Lint (ruff check) (pull_request) Successful in 1m19s
CI / Format (ruff format) (pull_request) Successful in 2m15s
CI / Type check (ty) (pull_request) Successful in 4m13s
CI / Tests (pull_request) Successful in 7m18s
CI / Bump version, tag, and update changelog on merge to master (pull_request) Has been skipped
CI / Tests (push) Successful in 11m5s
CI / Bump version, tag, and update changelog on merge to master (push) Has been skipped
# Conflicts: # giant/config.py
152 lines
6.4 KiB
Python
152 lines
6.4 KiB
Python
"""Tests for `giant model summary` (gitea #46)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from typer.testing import CliRunner
|
|
|
|
from giant import config as gconfig
|
|
from giant.cli import app
|
|
from giant.materials import MATERIAL_PROPERTIES
|
|
from giant.model.summary import _NOT_BUILD_TIME, _built_modules, _vocab_caveats, summarize_model
|
|
|
|
INFERENCE_OVERRIDES = gconfig.INFERENCE_OVERRIDES
|
|
|
|
runner = CliRunner()
|
|
|
|
_PDG_VOCAB = 300
|
|
_MAT_VOCAB = len(MATERIAL_PROPERTIES)
|
|
|
|
|
|
def _cfg(overrides: dict | None = None) -> dict:
|
|
return gconfig.merge_cli_overrides(gconfig.DEFAULT_CONFIG, None, overrides or {})
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def default_summary():
|
|
return summarize_model(_cfg(), pdg_vocab=_PDG_VOCAB, mat_vocab=_MAT_VOCAB)
|
|
|
|
|
|
def test_default_config_builds_both_stages_with_a_real_tree(default_summary):
|
|
assert set(default_summary.modules) >= {"stage1", "stage2"}
|
|
for module in default_summary.modules.values():
|
|
assert sum(p.numel() for p in module.parameters()) > 0
|
|
stage1 = default_summary.modules["stage1"]
|
|
assert hasattr(stage1, "cond_enc")
|
|
assert hasattr(stage1, "trunk")
|
|
assert {"input_proj", "blocks", "out_proj"} <= {n for n, _ in stage1.trunk.named_children()}
|
|
|
|
|
|
def test_every_in_scope_leaf_is_classified(default_summary):
|
|
in_scope = {
|
|
p
|
|
for p in gconfig.leaf_paths(gconfig.DEFAULT_CONFIG)
|
|
if p.split(".", 1)[0] in ("conditioning", "stage1_model", "stage2_model")
|
|
}
|
|
classified = set(default_summary.consumed) | set(default_summary.inert) | set(default_summary.elsewhere)
|
|
assert classified == in_scope
|
|
|
|
|
|
def test_not_build_time_allow_list_has_no_stale_entries():
|
|
in_scope = set(gconfig.leaf_paths(gconfig.DEFAULT_CONFIG))
|
|
stale = set(_NOT_BUILD_TIME) - in_scope
|
|
assert not stale, f"_NOT_BUILD_TIME entries no longer in DEFAULT_CONFIG: {sorted(stale)}"
|
|
|
|
|
|
def test_inference_overrides_allow_list_has_no_stale_entries():
|
|
in_scope = set(gconfig.leaf_paths(gconfig.DEFAULT_CONFIG))
|
|
stale = set(INFERENCE_OVERRIDES) - in_scope
|
|
assert not stale, f"INFERENCE_OVERRIDES entries no longer in DEFAULT_CONFIG: {sorted(stale)}"
|
|
|
|
|
|
def test_default_config_overridable_lists_every_allowlisted_path(default_summary):
|
|
assert set(default_summary.overridable) == set(INFERENCE_OVERRIDES)
|
|
|
|
|
|
def test_router_disabled_by_default_so_its_fields_are_inert(default_summary):
|
|
assert "stage1_model.router.n_experts" in default_summary.inert
|
|
assert "stage1_model.router.temperature" in default_summary.inert
|
|
|
|
|
|
def test_markov_history_leaves_attention_dims_inert_but_history_itself_consumed(default_summary):
|
|
assert "stage2_model.autoregressive.attn_n_heads" in default_summary.inert
|
|
assert "stage2_model.autoregressive.attn_n_layers" in default_summary.inert
|
|
assert "stage2_model.autoregressive.history" in default_summary.consumed
|
|
|
|
|
|
def test_single_literal_branch_fields_are_correctly_seen_as_consumed(default_summary):
|
|
"""Regression guard: n_sec.owner ("stage2"), n_sec.mode ("head") and
|
|
particle_type.target ("onehot") each branch as `== "one specific other
|
|
literal"` in giant/model/builders.py|models.py. A naive single generic
|
|
sentinel probe lands in the same "not that literal" bucket as the
|
|
current value and never crosses the boundary that actually matters --
|
|
this is exactly what _STRING_ALTERNATIVES exists to fix."""
|
|
assert "stage2_model.n_sec.owner" in default_summary.consumed
|
|
assert "stage2_model.n_sec.mode" in default_summary.consumed
|
|
assert "stage2_model.particle_type.target" in default_summary.consumed
|
|
|
|
|
|
def test_stage1_wgan_generator_swaps_flow_time_dim_for_critic_dims():
|
|
summary = summarize_model(_cfg({"stage1_model": {"generator": "wgan"}}), pdg_vocab=_PDG_VOCAB, mat_vocab=_MAT_VOCAB)
|
|
assert "stage1_model.flow.time_dim" in summary.inert
|
|
assert "stage1_model.wgan.noise_dim" in summary.consumed
|
|
assert "stage1_model.wgan.critic_hidden_dim" in summary.consumed
|
|
|
|
|
|
def test_stage2_one_shot_decoder_makes_autoregressive_block_inert():
|
|
summary = summarize_model(
|
|
_cfg({"stage2_model": {"decoder": "one_shot"}}), pdg_vocab=_PDG_VOCAB, mat_vocab=_MAT_VOCAB
|
|
)
|
|
assert "stage2_model.autoregressive.history" in summary.inert
|
|
assert "history_encoder" not in {n for n, _ in summary.modules["stage2"].named_children()}
|
|
|
|
|
|
def test_energy_router_enabled_consumes_core_fields_but_not_process_only_fields():
|
|
summary = summarize_model(
|
|
_cfg({"stage1_model": {"router": {"enabled": True, "type": "energy", "n_experts": 4}}}),
|
|
pdg_vocab=_PDG_VOCAB,
|
|
mat_vocab=_MAT_VOCAB,
|
|
)
|
|
assert "stage1_model.router.n_experts" in summary.consumed
|
|
assert "stage1_model.router.temperature" in summary.consumed
|
|
# emb_dim/hidden_dim are pdg/process-router-only kwargs -- build_router's
|
|
# signature filter drops them for an energy router.
|
|
assert "stage1_model.router.hidden_dim" in summary.inert
|
|
assert "stage1_model.router.emb_dim" in summary.inert
|
|
|
|
|
|
def test_vocab_caveat_text_for_embedding_particle_conditioning():
|
|
cfg = _cfg({"conditioning": {"particle": {"type": "embedding"}}})
|
|
caveats = _vocab_caveats(cfg)
|
|
assert any("pdg_vocab" in c and "embedding" in c for c in caveats)
|
|
assert not any("mat_vocab" in c for c in caveats)
|
|
|
|
|
|
def test_pdg_vocab_flag_changes_embedding_table_size():
|
|
cfg = _cfg({"conditioning": {"particle": {"type": "embedding"}}})
|
|
small = _built_modules(cfg, pdg_vocab=10, mat_vocab=_MAT_VOCAB)
|
|
big = _built_modules(cfg, pdg_vocab=1000, mat_vocab=_MAT_VOCAB)
|
|
assert big["stage1"].cond_enc.pdg_emb.weight.numel() > small["stage1"].cond_enc.pdg_emb.weight.numel()
|
|
|
|
|
|
def test_invalid_combo_exits_nonzero_with_validate_config_message(tmp_path: Path):
|
|
config_path = tmp_path / "bad.toml"
|
|
config_path.write_text('[meta]\nconfig_version = 3\n\n[stage2_model.particle_type]\ntarget = "embedding"\n')
|
|
result = runner.invoke(app, ["model", "summary", "--config", str(config_path)])
|
|
assert result.exit_code == 1
|
|
assert "requires conditioning.particle.type = 'embedding'" in result.output
|
|
|
|
|
|
def test_cli_default_smoke():
|
|
result = runner.invoke(app, ["model", "summary"])
|
|
assert result.exit_code == 0, result.output
|
|
assert "stage1" in result.output
|
|
assert "stage2" in result.output
|
|
assert "parameters" in result.output
|
|
assert "trunk" in result.output
|
|
assert "inert under this config" in result.output
|
|
assert "inference-overridable without retraining" in result.output
|
|
assert "stage2_model.n_sec.sampling" in result.output
|