9ce55e5013
Decomposes the ten permutation classes in giant/model/network.py into the reusable parts from docs/v0.3.0-design.md §5: ConditionEncoder (now independently configurable per particle/material axis), ContextAdapter, Trunk/MonolithicTrunk/RoutedTrunk/ExpertTrunk, and the stage classes Stage1Model/Stage2OneShot/CriticModel (Stage2Autoregressive stubbed, raises NotImplementedError until step 4/5). build_models/build_critics now return a dict keyed by stage and accept the new nested config shape, with routed WGAN reachable for the first time (the old --mode wgan --router rejection is gone) and stage2_model.router.tie_to_stage1 sharing a literal Router instance. A v0.2 checkpoint's flat model_config auto-migrates via _migrate_legacy_model_config + migrate_legacy_state_dict, preserving the n_sec_head's attachment to Stage1Model (legacy_owner="stage1", design doc §4.1). tests/test_migration_v02_v03.py proves this bit-identical against a frozen v0.2 snapshot (tests/legacy/network_v02_snapshot.py) for both flow and wgan, both conditioning modes. scripts/check_migration_v02_v03.py is the real-checkpoint counterpart for a portal machine with /ceph access. giant/model/schedule.py's flow-matching/DDPM loss helpers are updated to the new model-call convention (t as a keyword). giant/sample.py, giant/rollout.py, and giant/validate.py are not yet updated (deferred to design doc step 6) — their exercising tests are marked xfail with that reasoning rather than silently broken. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
84 lines
3.0 KiB
Python
84 lines
3.0 KiB
Python
import numpy as np
|
|
import pytest
|
|
import torch
|
|
|
|
from giant.constants import COND_DIM, K_MAX, SEC_SLOT_DIM, X_DIM
|
|
from giant.model.network import Stage1Model, Stage2OneShot
|
|
from giant.validate import validate_marginals
|
|
|
|
_PARTICLE_CFG = {"type": "physical", "emb_dim": 8, "n_layers": 1}
|
|
_MATERIAL_CFG = {"type": "physical", "emb_dim": 8, "n_layers": 1}
|
|
|
|
|
|
def _tiny_models():
|
|
s1 = Stage1Model(
|
|
pdg_vocab=3,
|
|
mat_vocab=2,
|
|
particle_cfg=_PARTICLE_CFG,
|
|
material_cfg=_MATERIAL_CFG,
|
|
hidden_dim=16,
|
|
n_res_blocks=1,
|
|
n_sec_head_k_max=K_MAX,
|
|
)
|
|
s2 = Stage2OneShot(
|
|
pdg_vocab=3,
|
|
mat_vocab=2,
|
|
particle_cfg=_PARTICLE_CFG,
|
|
material_cfg=_MATERIAL_CFG,
|
|
hidden_dim=16,
|
|
n_res_blocks=1,
|
|
generator="flow",
|
|
time_dim=16,
|
|
)
|
|
return s1.eval(), s2.eval()
|
|
|
|
|
|
def _zero_secondaries_loader(B=4, n_batches=2):
|
|
"""A val_loader whose every batch has n_sec=0 (real side) — matches the
|
|
(cond_cont, cond_cat, target_s1, n_sec, sec_cont, proc_idx) tuple shape
|
|
StreamingStepsDataset yields."""
|
|
batches = []
|
|
for _ in range(n_batches):
|
|
cond_cont = torch.randn(B, COND_DIM)
|
|
cond_cat = torch.zeros(B, 2, dtype=torch.long)
|
|
x1 = torch.randn(B, X_DIM)
|
|
n_sec = torch.zeros(B, dtype=torch.long)
|
|
sec_cont = torch.zeros(B, K_MAX, SEC_SLOT_DIM)
|
|
proc_idx = torch.zeros(B, dtype=torch.long)
|
|
batches.append((cond_cont, cond_cat, x1, n_sec, sec_cont, proc_idx))
|
|
return batches
|
|
|
|
|
|
@pytest.mark.xfail(
|
|
reason=(
|
|
"giant/validate.py isn't updated yet — it calls the stage models "
|
|
"(sample_secondaries et al.) with the old positional convention, "
|
|
"which doesn't match Stage1Model/Stage2OneShot's new forward "
|
|
"signature. Deferred to docs/v0.3.0-design.md step 6/§10."
|
|
),
|
|
strict=False,
|
|
)
|
|
def test_validate_marginals_all_zero_secondaries_returns_nan_phys_kl(monkeypatch):
|
|
"""If n_sec_pred collapses to 0 across the whole validated set (realistic
|
|
during early/unstable training), phys_kl must degrade to NaN instead of
|
|
crashing on the empty-array .min()/.max() reduction inside
|
|
_histogram_kl -- a regression the old species/bincount code this
|
|
replaced explicitly guarded against."""
|
|
s1, s2 = _tiny_models()
|
|
loader = _zero_secondaries_loader()
|
|
|
|
# Force the Stage-1 n_sec head's prediction to 0 for every sample too, so
|
|
# the generated side's valid-slot mask is also empty (real side is
|
|
# already all n_sec=0 by construction of the fake loader above).
|
|
def _fake_sample_flow(model, cond_cont, cond_cat, **kw):
|
|
B = cond_cont.size(0)
|
|
return torch.randn(B, X_DIM), torch.zeros(B, dtype=torch.long)
|
|
|
|
monkeypatch.setattr("giant.validate.sample_flow", _fake_sample_flow)
|
|
|
|
result = validate_marginals(s1, loader, sec_decoder=s2, n_batches=2)
|
|
|
|
assert np.asarray(result["phys_real"]).shape == (0, 2)
|
|
assert np.asarray(result["phys_generated"]).shape == (0, 2)
|
|
assert np.isnan(np.asarray(result["phys_kl"])).all()
|