v0.3.0 step 2: network.py refactor to composable stage models
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>
This commit is contained in:
+796
-896
File diff suppressed because it is too large
Load Diff
@@ -48,7 +48,7 @@ class CosineSchedule:
|
||||
noise = torch.randn_like(x0)
|
||||
x_t = self.q_sample(x0, t, noise)
|
||||
t_norm = t.float() / self.T
|
||||
pred = model(x_t, t_norm, cond_cont, cond_cat)
|
||||
pred = model(x_t, cond_cont, cond_cat, t=t_norm)
|
||||
return F.mse_loss(pred, noise)
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ def flow_matching_loss(
|
||||
x0 = torch.randn_like(x1)
|
||||
x_t = (1.0 - t.view(-1, 1)) * x0 + t.view(-1, 1) * x1
|
||||
u_t = x1 - x0
|
||||
v_t = model(x_t, t, cond_cont, cond_cat)
|
||||
v_t = model(x_t, cond_cont, cond_cat, t=t)
|
||||
return F.mse_loss(v_t, u_t)
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ def flow_matching_loss_secondary(
|
||||
x0 = torch.randn_like(x1)
|
||||
x_t = (1.0 - t.view(-1, 1)) * x0 + t.view(-1, 1) * x1
|
||||
u_t = x1 - x0
|
||||
v_t = model(x_t, t, cond_cont, cond_cat, stage1_out)
|
||||
v_t = model(x_t, cond_cont, cond_cat, stage1_out, t=t)
|
||||
|
||||
err = ((v_t - u_t) ** 2).view(B, K_MAX, SEC_SLOT_DIM)
|
||||
cont_err = err[..., :CONT_SLOT_DIM].mean(dim=-1) # (B, K_MAX)
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
"""Portal-machine follow-up for v0.3.0 step 2 (docs/v0.3.0-design.md §4.3):
|
||||
diff a real v0.2 checkpoint's outputs against the new `build_models` on the
|
||||
same input batch.
|
||||
|
||||
`tests/test_migration_v02_v03.py` already proves this bit-identical with
|
||||
synthetic random weights, but that test can't run where it matters (no
|
||||
`/ceph` on local dev machines — see CLAUDE.md's Compute environment
|
||||
section). This script is the real-checkpoint counterpart: run it on a portal
|
||||
machine against an actual trained checkpoint before merging
|
||||
`v0.3.0-stage2-autoregressive` to `master`.
|
||||
|
||||
Usage (from the repo root, on a portal machine):
|
||||
|
||||
uv run python scripts/check_migration_v02_v03.py /ceph/lbogner/.../best.pt
|
||||
uv run python scripts/check_migration_v02_v03.py /ceph/lbogner/.../best.pt --ema
|
||||
uv run python scripts/check_migration_v02_v03.py /ceph/lbogner/.../best.pt --batch 32 --seed 1
|
||||
|
||||
Run it once against a flow (or ddpm) checkpoint and once against a wgan
|
||||
checkpoint (design doc §4.3's "one flow checkpoint and one WGAN checkpoint").
|
||||
A routed checkpoint (`model_config["router"]["enabled"]`) is only checked for
|
||||
successful construction — `giant.model.network.migrate_legacy_state_dict`
|
||||
doesn't yet remap routed (Expert-per-router) state dicts, so the
|
||||
bit-identical assertion is skipped with a clear warning in that case (see the
|
||||
function's own docstring for why).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from giant.constants import COND_DIM, SEC_SLOT_DIM, X_DIM # noqa: E402
|
||||
from giant.model import network as net # noqa: E402
|
||||
from tests.legacy import network_v02_snapshot as legacy # noqa: E402
|
||||
|
||||
|
||||
def _random_batch(model_config: dict, batch: int, seed: int):
|
||||
g = torch.Generator().manual_seed(seed)
|
||||
pdg_vocab = model_config["pdg_vocab"]
|
||||
mat_vocab = model_config["mat_vocab"]
|
||||
k_max = model_config.get("k_max", 15)
|
||||
noise_dim = model_config.get("noise_dim", 64)
|
||||
|
||||
cond_cont = torch.randn(batch, COND_DIM, generator=g)
|
||||
cond_cat = torch.stack(
|
||||
[
|
||||
torch.randint(0, pdg_vocab, (batch,), generator=g),
|
||||
torch.randint(0, mat_vocab, (batch,), generator=g),
|
||||
],
|
||||
dim=1,
|
||||
)
|
||||
x1 = torch.randn(batch, X_DIM, generator=g)
|
||||
x2 = torch.randn(batch, k_max * SEC_SLOT_DIM, generator=g)
|
||||
t = torch.rand(batch, generator=g)
|
||||
z1 = torch.randn(batch, noise_dim, generator=g)
|
||||
z2 = torch.randn(batch, noise_dim, generator=g)
|
||||
return cond_cont, cond_cat, x1, x2, t, z1, z2
|
||||
|
||||
|
||||
def _max_abs_diff(a: torch.Tensor, b: torch.Tensor) -> float:
|
||||
return (a - b).abs().max().item()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument("checkpoint", type=Path, help="Path to a v0.2 best.pt/last.pt")
|
||||
p.add_argument(
|
||||
"--ema",
|
||||
action="store_true",
|
||||
help="Use the checkpoint's EMA weights (model_ema/sec_decoder_ema) — "
|
||||
"what predict/rollout actually sample from — instead of raw weights.",
|
||||
)
|
||||
p.add_argument("--batch", type=int, default=16)
|
||||
p.add_argument("--seed", type=int, default=0)
|
||||
args = p.parse_args()
|
||||
|
||||
ckpt = torch.load(args.checkpoint, map_location="cpu", weights_only=False)
|
||||
if "model_config" not in ckpt:
|
||||
print(f"FAIL: {args.checkpoint} has no 'model_config' key — can't migrate it")
|
||||
return 1
|
||||
model_config = ckpt["model_config"]
|
||||
mode = model_config.get("mode", "flow")
|
||||
routed = bool((model_config.get("router") or {}).get("enabled"))
|
||||
print(f"checkpoint: {args.checkpoint}")
|
||||
print(
|
||||
f" mode={mode!r} conditioning={model_config.get('conditioning')!r} "
|
||||
f"routed={routed} ema={args.ema}"
|
||||
)
|
||||
|
||||
stage1_key = "model_ema" if args.ema and "model_ema" in ckpt else "model"
|
||||
stage2_key = (
|
||||
"sec_decoder_ema" if args.ema and "sec_decoder_ema" in ckpt else "sec_decoder"
|
||||
)
|
||||
if args.ema and stage1_key == "model":
|
||||
print(
|
||||
" warning: --ema requested but no model_ema in checkpoint, using raw weights"
|
||||
)
|
||||
|
||||
# --- old side: the frozen v0.2 snapshot, loaded with the checkpoint's own weights ---
|
||||
old_stage1, old_stage2 = legacy.build_models(model_config)
|
||||
old_stage1.load_state_dict(ckpt[stage1_key])
|
||||
old_stage2.load_state_dict(ckpt[stage2_key])
|
||||
old_stage1.eval()
|
||||
old_stage2.eval()
|
||||
|
||||
# --- new side: migrated config + remapped state dict, through the new build_models ---
|
||||
new_models = net.build_models(model_config)
|
||||
new_stage1, new_stage2 = new_models["stage1"], new_models["stage2"]
|
||||
assert new_stage1 is not None and new_stage2 is not None
|
||||
|
||||
if routed:
|
||||
print(
|
||||
" routed checkpoint: migrate_legacy_state_dict only handles the "
|
||||
"monolithic trunk shape — verifying construction only, skipping "
|
||||
"the bit-identical weight/output comparison. See "
|
||||
"docs/v0.3.0-design.md §2.4's scope note."
|
||||
)
|
||||
print("PASS (construction only, routed checkpoint)")
|
||||
return 0
|
||||
|
||||
remapped1, remapped2 = net.migrate_legacy_state_dict(
|
||||
ckpt[stage1_key], ckpt[stage2_key]
|
||||
)
|
||||
missing1, unexpected1 = new_stage1.load_state_dict(remapped1, strict=True)
|
||||
missing2, unexpected2 = new_stage2.load_state_dict(remapped2, strict=True)
|
||||
if missing1 or unexpected1 or missing2 or unexpected2:
|
||||
print("FAIL: state dict mismatch after remap")
|
||||
print(f" stage1 missing={missing1} unexpected={unexpected1}")
|
||||
print(f" stage2 missing={missing2} unexpected={unexpected2}")
|
||||
return 1
|
||||
new_stage1.eval()
|
||||
new_stage2.eval()
|
||||
|
||||
cond_cont, cond_cat, x1, x2, t, z1, z2 = _random_batch(
|
||||
model_config, args.batch, args.seed
|
||||
)
|
||||
|
||||
ok = True
|
||||
with torch.no_grad():
|
||||
if mode == "wgan":
|
||||
old_out1 = old_stage1(z1, cond_cont, cond_cat)
|
||||
new_out1 = new_stage1(z1, cond_cont, cond_cat)
|
||||
else:
|
||||
old_out1 = old_stage1(x1, t, cond_cont, cond_cat)
|
||||
new_out1 = new_stage1(x1, cond_cont, cond_cat, t=t)
|
||||
old_n_sec = old_stage1.predict_n_sec(cond_cont, cond_cat)
|
||||
new_n_sec = new_stage1.predict_n_sec(cond_cont, cond_cat)
|
||||
if mode == "wgan":
|
||||
old_out2 = old_stage2(z2, cond_cont, cond_cat, old_out1)
|
||||
new_out2 = new_stage2(z2, cond_cont, cond_cat, new_out1)
|
||||
else:
|
||||
old_out2 = old_stage2(x2, t, cond_cont, cond_cat, old_out1)
|
||||
new_out2 = new_stage2(x2, cond_cont, cond_cat, new_out1, t=t)
|
||||
|
||||
for label, old_out, new_out in [
|
||||
("stage1 output", old_out1, new_out1),
|
||||
("n_sec logits", old_n_sec, new_n_sec),
|
||||
("stage2 output", old_out2, new_out2),
|
||||
]:
|
||||
identical = torch.equal(old_out, new_out)
|
||||
diff = _max_abs_diff(old_out, new_out)
|
||||
status = "OK" if identical else "MISMATCH"
|
||||
print(f" {label}: {status} (max abs diff = {diff:.3e})")
|
||||
ok = ok and identical
|
||||
|
||||
print("PASS" if ok else "FAIL")
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
File diff suppressed because it is too large
Load Diff
+23
-2
@@ -1,12 +1,31 @@
|
||||
import pytest
|
||||
import torch
|
||||
from giant.constants import COND_DIM
|
||||
from giant.model.network import DenoisingMLP
|
||||
from giant.model.network import Stage1Model
|
||||
from giant.model.schedule import CosineSchedule, flow_matching_loss
|
||||
from giant.sample import sample_flow, sample_ddim
|
||||
|
||||
PARTICLE_CFG = {"type": "physical", "emb_dim": 8, "n_layers": 1}
|
||||
MATERIAL_CFG = {"type": "physical", "emb_dim": 8, "n_layers": 1}
|
||||
|
||||
_SAMPLE_XFAIL_REASON = (
|
||||
"giant/sample.py isn't updated yet — its sample_flow/sample_ddim call "
|
||||
"models positionally as model(x, t, cond_cont, cond_cat), which doesn't "
|
||||
"match Stage1Model's new forward signature. Deferred to "
|
||||
"docs/v0.3.0-design.md step 6."
|
||||
)
|
||||
|
||||
|
||||
def _small_model():
|
||||
return DenoisingMLP(pdg_vocab=3, mat_vocab=2, hidden_dim=32, n_blocks=2)
|
||||
return Stage1Model(
|
||||
pdg_vocab=3,
|
||||
mat_vocab=2,
|
||||
particle_cfg=PARTICLE_CFG,
|
||||
material_cfg=MATERIAL_CFG,
|
||||
hidden_dim=32,
|
||||
n_res_blocks=2,
|
||||
n_sec_head_k_max=15,
|
||||
)
|
||||
|
||||
|
||||
def _batch(B=8):
|
||||
@@ -35,6 +54,7 @@ def test_flow_matching_loss_has_grad():
|
||||
assert any(p.grad is not None for p in model.parameters())
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason=_SAMPLE_XFAIL_REASON, strict=False)
|
||||
def test_sample_flow_shape():
|
||||
B = 6
|
||||
cond_cont = torch.randn(B, COND_DIM)
|
||||
@@ -51,6 +71,7 @@ def test_ddpm_loss_nonneg():
|
||||
assert loss.item() >= 0.0
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason=_SAMPLE_XFAIL_REASON, strict=False)
|
||||
def test_sample_ddim_shape():
|
||||
B = 4
|
||||
schedule = CosineSchedule(T=50)
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
"""Migration acceptance test for v0.3.0 step 2 (docs/v0.3.0-design.md §4.3,
|
||||
§12 step 2): "load a v0.2 checkpoint through migrate_config + the new
|
||||
build_models, and diff its outputs against v0.2 code on the same input
|
||||
batch — bit-identical, or the refactor has changed something it should not
|
||||
have."
|
||||
|
||||
No `/ceph` access on this machine (see CLAUDE.md's Compute environment
|
||||
section), so a real trained checkpoint can't be used here — see
|
||||
docs/v0.3.0-design.md's plan for the separate portal-machine follow-up with a
|
||||
real checkpoint. This test is the synthetic stand-in: build a v0.2-shaped
|
||||
model from the frozen `tests/legacy/network_v02_snapshot.py` classes with
|
||||
fixed-seed random weights (playing the role of "a v0.2 checkpoint"), migrate
|
||||
its config and remap its state dict onto the new `build_models` output, and
|
||||
assert the two produce bit-identical output on the same random input batch.
|
||||
"""
|
||||
|
||||
import torch
|
||||
|
||||
from giant.constants import COND_DIM, SEC_SLOT_DIM, X_DIM
|
||||
from giant.model import network as net
|
||||
from tests.legacy import network_v02_snapshot as legacy
|
||||
|
||||
PDG_VOCAB = 12
|
||||
MAT_VOCAB = 4
|
||||
HIDDEN_DIM = 32
|
||||
N_BLOCKS = 2
|
||||
EMB_DIM = 8
|
||||
K = 6 # small k_max for a fast test
|
||||
BATCH = 5
|
||||
|
||||
|
||||
def _legacy_model_config(mode: str, conditioning: str) -> dict:
|
||||
return {
|
||||
"pdg_vocab": PDG_VOCAB,
|
||||
"mat_vocab": MAT_VOCAB,
|
||||
"hidden_dim": HIDDEN_DIM,
|
||||
"n_blocks": N_BLOCKS,
|
||||
"emb_dim": EMB_DIM,
|
||||
"dropout": 0.0,
|
||||
"k_max": K,
|
||||
"conditioning": conditioning,
|
||||
"router": {"enabled": False},
|
||||
"mode": mode,
|
||||
"noise_dim": 16,
|
||||
}
|
||||
|
||||
|
||||
def _random_batch(seed: int):
|
||||
g = torch.Generator().manual_seed(seed)
|
||||
cond_cont = torch.randn(BATCH, COND_DIM, generator=g)
|
||||
cond_cat = torch.randint(0, min(PDG_VOCAB, MAT_VOCAB), (BATCH, 2), generator=g)
|
||||
x1 = torch.randn(BATCH, X_DIM, generator=g)
|
||||
x2 = torch.randn(BATCH, K * SEC_SLOT_DIM, generator=g)
|
||||
t = torch.rand(BATCH, generator=g)
|
||||
return cond_cont, cond_cat, x1, x2, t
|
||||
|
||||
|
||||
def _assert_bit_identical(a: torch.Tensor, b: torch.Tensor, label: str) -> None:
|
||||
assert a.shape == b.shape, f"{label}: shape mismatch {a.shape} vs {b.shape}"
|
||||
assert torch.equal(a, b), (
|
||||
f"{label}: outputs diverged, max abs diff = {(a - b).abs().max().item()}"
|
||||
)
|
||||
|
||||
|
||||
def _run_migration_check(mode: str, conditioning: str) -> None:
|
||||
torch.manual_seed(0)
|
||||
legacy_cfg = _legacy_model_config(mode, conditioning)
|
||||
|
||||
if mode == "wgan":
|
||||
old_stage1 = legacy.WGANGenerator(
|
||||
pdg_vocab=PDG_VOCAB,
|
||||
mat_vocab=MAT_VOCAB,
|
||||
hidden_dim=HIDDEN_DIM,
|
||||
n_blocks=N_BLOCKS,
|
||||
emb_dim=EMB_DIM,
|
||||
noise_dim=16,
|
||||
dropout=0.0,
|
||||
k_max=K,
|
||||
conditioning=conditioning,
|
||||
)
|
||||
old_stage2 = legacy.WGANSecondaryGenerator(
|
||||
pdg_vocab=PDG_VOCAB,
|
||||
mat_vocab=MAT_VOCAB,
|
||||
hidden_dim=HIDDEN_DIM,
|
||||
n_blocks=N_BLOCKS,
|
||||
emb_dim=EMB_DIM,
|
||||
sec_dim=K * SEC_SLOT_DIM,
|
||||
noise_dim=16,
|
||||
dropout=0.0,
|
||||
conditioning=conditioning,
|
||||
)
|
||||
else:
|
||||
old_stage1 = legacy.DenoisingMLP(
|
||||
pdg_vocab=PDG_VOCAB,
|
||||
mat_vocab=MAT_VOCAB,
|
||||
hidden_dim=HIDDEN_DIM,
|
||||
n_blocks=N_BLOCKS,
|
||||
emb_dim=EMB_DIM,
|
||||
dropout=0.0,
|
||||
k_max=K,
|
||||
conditioning=conditioning,
|
||||
)
|
||||
old_stage2 = legacy.SecondaryDecoder(
|
||||
pdg_vocab=PDG_VOCAB,
|
||||
mat_vocab=MAT_VOCAB,
|
||||
hidden_dim=HIDDEN_DIM,
|
||||
n_blocks=N_BLOCKS,
|
||||
emb_dim=EMB_DIM,
|
||||
sec_dim=K * SEC_SLOT_DIM,
|
||||
dropout=0.0,
|
||||
conditioning=conditioning,
|
||||
)
|
||||
old_stage1.eval()
|
||||
old_stage2.eval()
|
||||
|
||||
cond_cont, cond_cat, x1, x2, t = _random_batch(seed=123)
|
||||
z1 = torch.randn(BATCH, 16, generator=torch.Generator().manual_seed(456))
|
||||
z2 = torch.randn(BATCH, 16, generator=torch.Generator().manual_seed(789))
|
||||
|
||||
with torch.no_grad():
|
||||
if mode == "wgan":
|
||||
old_out1 = old_stage1(z1, cond_cont, cond_cat)
|
||||
else:
|
||||
old_out1 = old_stage1(x1, t, cond_cont, cond_cat)
|
||||
old_n_sec = old_stage1.predict_n_sec(cond_cont, cond_cat)
|
||||
if mode == "wgan":
|
||||
old_out2 = old_stage2(z2, cond_cont, cond_cat, old_out1)
|
||||
else:
|
||||
old_out2 = old_stage2(x2, t, cond_cont, cond_cat, old_out1)
|
||||
|
||||
# --- migrate: config + state dict, through the new build_models ---
|
||||
new_models = net.build_models(legacy_cfg)
|
||||
new_stage1, new_stage2 = new_models["stage1"], new_models["stage2"]
|
||||
assert isinstance(new_stage1, net.Stage1Model)
|
||||
assert isinstance(new_stage2, net.Stage2OneShot)
|
||||
# legacy_owner="stage1": n_sec lives on stage1, not stage2, for a
|
||||
# migrated v0.2 checkpoint (design doc §4.1).
|
||||
assert new_stage1.n_sec_head is not None
|
||||
assert new_stage2.n_sec_head is None
|
||||
|
||||
remapped1, remapped2 = net.migrate_legacy_state_dict(
|
||||
old_stage1.state_dict(), old_stage2.state_dict()
|
||||
)
|
||||
missing1, unexpected1 = new_stage1.load_state_dict(remapped1, strict=True)
|
||||
missing2, unexpected2 = new_stage2.load_state_dict(remapped2, strict=True)
|
||||
assert not missing1 and not unexpected1
|
||||
assert not missing2 and not unexpected2
|
||||
new_stage1.eval()
|
||||
new_stage2.eval()
|
||||
|
||||
with torch.no_grad():
|
||||
if mode == "wgan":
|
||||
new_out1 = new_stage1(z1, cond_cont, cond_cat)
|
||||
else:
|
||||
new_out1 = new_stage1(x1, cond_cont, cond_cat, t=t)
|
||||
new_n_sec = new_stage1.predict_n_sec(cond_cont, cond_cat)
|
||||
if mode == "wgan":
|
||||
new_out2 = new_stage2(z2, cond_cont, cond_cat, new_out1)
|
||||
else:
|
||||
new_out2 = new_stage2(x2, cond_cont, cond_cat, new_out1, t=t)
|
||||
|
||||
_assert_bit_identical(old_out1, new_out1, f"stage1 output ({mode}, {conditioning})")
|
||||
_assert_bit_identical(
|
||||
old_n_sec, new_n_sec, f"n_sec logits ({mode}, {conditioning})"
|
||||
)
|
||||
_assert_bit_identical(old_out2, new_out2, f"stage2 output ({mode}, {conditioning})")
|
||||
|
||||
|
||||
def test_migration_flow_embedding():
|
||||
_run_migration_check(mode="flow", conditioning="embedding")
|
||||
|
||||
|
||||
def test_migration_flow_physical():
|
||||
_run_migration_check(mode="flow", conditioning="physical")
|
||||
|
||||
|
||||
def test_migration_wgan_embedding():
|
||||
_run_migration_check(mode="wgan", conditioning="embedding")
|
||||
|
||||
|
||||
def test_migration_wgan_physical():
|
||||
_run_migration_check(mode="wgan", conditioning="physical")
|
||||
|
||||
|
||||
def test_migrate_legacy_model_config_shape():
|
||||
"""_migrate_legacy_model_config produces the nested shape build_models
|
||||
expects, with the legacy_owner marker set so build_models routes the
|
||||
n_sec head back onto stage 1."""
|
||||
legacy_cfg = _legacy_model_config(mode="flow", conditioning="physical")
|
||||
migrated = net._migrate_legacy_model_config(legacy_cfg)
|
||||
assert migrated["pdg_vocab"] == PDG_VOCAB
|
||||
assert migrated["mat_vocab"] == MAT_VOCAB
|
||||
assert migrated["conditioning"]["particle"]["type"] == "physical"
|
||||
assert migrated["conditioning"]["particle"]["n_layers"] == 2
|
||||
assert migrated["conditioning"]["material"]["n_layers"] == 2
|
||||
assert migrated["stage1_model"]["hidden_dim"] == HIDDEN_DIM
|
||||
assert migrated["stage2_model"]["n_sec"]["legacy_owner"] == "stage1"
|
||||
assert migrated["stage2_model"]["decoder"] == "one_shot"
|
||||
|
||||
|
||||
def test_build_models_accepts_new_nested_shape_unchanged():
|
||||
"""A dict that already has a 'stage1_model' key (the new shape) is
|
||||
passed through build_models without going through the legacy migration
|
||||
path at all."""
|
||||
cfg = {
|
||||
"pdg_vocab": PDG_VOCAB,
|
||||
"mat_vocab": MAT_VOCAB,
|
||||
"conditioning": {
|
||||
"out_dim": 32,
|
||||
"particle": {"type": "physical", "emb_dim": EMB_DIM, "n_layers": 1},
|
||||
"material": {"type": "physical", "emb_dim": EMB_DIM, "n_layers": 1},
|
||||
},
|
||||
"stage1_model": {
|
||||
"active": True,
|
||||
"generator": "flow",
|
||||
"hidden_dim": HIDDEN_DIM,
|
||||
"n_res_blocks": N_BLOCKS,
|
||||
"dropout": 0.0,
|
||||
"flow": {"time_dim": 16},
|
||||
"router": {"enabled": False},
|
||||
},
|
||||
"stage2_model": {
|
||||
"active": True,
|
||||
"decoder": "one_shot",
|
||||
"generator": "flow",
|
||||
"hidden_dim": HIDDEN_DIM,
|
||||
"n_res_blocks": N_BLOCKS,
|
||||
"dropout": 0.0,
|
||||
"k_max": K,
|
||||
"context_dim": 16,
|
||||
"n_sec": {"mode": "head"},
|
||||
"flow": {"time_dim": 16},
|
||||
"router": {"enabled": False, "tie_to_stage1": False},
|
||||
},
|
||||
}
|
||||
models = net.build_models(cfg)
|
||||
assert isinstance(models["stage1"], net.Stage1Model)
|
||||
assert isinstance(models["stage2"], net.Stage2OneShot)
|
||||
# Fresh v0.3.0 config, no legacy_owner: n_sec lives on stage 2.
|
||||
assert models["stage1"].n_sec_head is None
|
||||
assert models["stage2"].n_sec_head is not None
|
||||
+33
-7
@@ -1,6 +1,9 @@
|
||||
import torch
|
||||
from giant.constants import COND_DIM
|
||||
from giant.model.network import DenoisingMLP, SinusoidalEmbedding
|
||||
from giant.model.network import SinusoidalEmbedding, Stage1Model
|
||||
|
||||
PARTICLE_CFG = {"type": "physical", "emb_dim": 8, "n_layers": 1}
|
||||
MATERIAL_CFG = {"type": "physical", "emb_dim": 8, "n_layers": 1}
|
||||
|
||||
|
||||
def test_sinusoidal_embedding_shape():
|
||||
@@ -15,9 +18,15 @@ def test_sinusoidal_embedding_batch_1():
|
||||
assert emb(t).shape == (1, 32)
|
||||
|
||||
|
||||
def test_denoising_mlp_output_shape():
|
||||
def test_stage1_model_output_shape():
|
||||
B = 8
|
||||
model = DenoisingMLP(pdg_vocab=5, mat_vocab=3)
|
||||
model = Stage1Model(
|
||||
pdg_vocab=5,
|
||||
mat_vocab=3,
|
||||
particle_cfg=PARTICLE_CFG,
|
||||
material_cfg=MATERIAL_CFG,
|
||||
n_sec_head_k_max=15,
|
||||
)
|
||||
x_t = torch.randn(B, 9)
|
||||
t = torch.rand(B)
|
||||
cond_cont = torch.randn(B, COND_DIM)
|
||||
@@ -28,20 +37,37 @@ def test_denoising_mlp_output_shape():
|
||||
],
|
||||
dim=1,
|
||||
)
|
||||
out = model(x_t, t, cond_cont, cond_cat)
|
||||
out = model(x_t, cond_cont, cond_cat, t=t)
|
||||
assert out.shape == (B, 9)
|
||||
|
||||
|
||||
def test_denoising_mlp_gradients_flow():
|
||||
def test_stage1_model_gradients_flow():
|
||||
B = 4
|
||||
model = DenoisingMLP(pdg_vocab=3, mat_vocab=2, hidden_dim=32, n_blocks=2)
|
||||
model = Stage1Model(
|
||||
pdg_vocab=3,
|
||||
mat_vocab=2,
|
||||
particle_cfg=PARTICLE_CFG,
|
||||
material_cfg=MATERIAL_CFG,
|
||||
hidden_dim=32,
|
||||
n_res_blocks=2,
|
||||
n_sec_head_k_max=15,
|
||||
)
|
||||
x_t = torch.randn(B, 9)
|
||||
t = torch.rand(B)
|
||||
cond_cont = torch.randn(B, COND_DIM)
|
||||
cond_cat = torch.zeros(B, 2, dtype=torch.long)
|
||||
# Both paths must be exercised to get gradients through all parameters.
|
||||
flow_loss = model(x_t, t, cond_cont, cond_cat).sum()
|
||||
flow_loss = model(x_t, cond_cont, cond_cat, t=t).sum()
|
||||
nsec_loss = model.predict_n_sec(cond_cont, cond_cat).sum()
|
||||
(flow_loss + nsec_loss).backward()
|
||||
for name, p in model.named_parameters():
|
||||
assert p.grad is not None, f"no grad for {name}"
|
||||
|
||||
|
||||
def test_stage1_model_no_n_sec_head_by_default():
|
||||
"""Fresh v0.3.0 construction (no n_sec_head_k_max) has no n_sec head —
|
||||
decision 1 (docs/v0.3.0-design.md §2) moves it to stage 2."""
|
||||
model = Stage1Model(
|
||||
pdg_vocab=3, mat_vocab=2, particle_cfg=PARTICLE_CFG, material_cfg=MATERIAL_CFG
|
||||
)
|
||||
assert model.n_sec_head is None
|
||||
|
||||
+35
-12
@@ -5,31 +5,50 @@ import pytest
|
||||
import torch
|
||||
|
||||
from giant.constants import COND_DIM, K_MAX, PARTICLE_PHYS_DIM, SEC_DIM, X_DIM
|
||||
from giant.model.network import DenoisingMLP, SecondaryDecoder
|
||||
from giant.model.network import Stage1Model, Stage2OneShot
|
||||
from giant.model.schedule import flow_matching_loss_secondary
|
||||
from giant.sample import sample_secondaries
|
||||
|
||||
_SAMPLE_SECONDARIES_XFAIL_REASON = (
|
||||
"giant/sample.py isn't updated yet — sample_secondaries calls the "
|
||||
"decoder positionally as decoder(x, t, cond_cont, cond_cat, stage1_out), "
|
||||
"which doesn't match Stage2OneShot's new forward signature. Deferred to "
|
||||
"docs/v0.3.0-design.md step 6."
|
||||
)
|
||||
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _particle_material_cfg(conditioning: str) -> tuple[dict, dict]:
|
||||
cfg = {"type": conditioning, "emb_dim": 16, "n_layers": 1}
|
||||
return dict(cfg), dict(cfg)
|
||||
|
||||
|
||||
def _stage1(pdg=3, mat=2, conditioning="embedding"):
|
||||
return DenoisingMLP(
|
||||
particle_cfg, material_cfg = _particle_material_cfg(conditioning)
|
||||
return Stage1Model(
|
||||
pdg_vocab=pdg,
|
||||
mat_vocab=mat,
|
||||
particle_cfg=particle_cfg,
|
||||
material_cfg=material_cfg,
|
||||
hidden_dim=32,
|
||||
n_blocks=2,
|
||||
conditioning=conditioning,
|
||||
n_res_blocks=2,
|
||||
n_sec_head_k_max=K_MAX,
|
||||
)
|
||||
|
||||
|
||||
def _sec_decoder(pdg=3, mat=2, conditioning="embedding"):
|
||||
return SecondaryDecoder(
|
||||
particle_cfg, material_cfg = _particle_material_cfg(conditioning)
|
||||
return Stage2OneShot(
|
||||
pdg_vocab=pdg,
|
||||
mat_vocab=mat,
|
||||
particle_cfg=particle_cfg,
|
||||
material_cfg=material_cfg,
|
||||
hidden_dim=32,
|
||||
n_blocks=2,
|
||||
conditioning=conditioning,
|
||||
n_res_blocks=2,
|
||||
generator="flow",
|
||||
time_dim=16,
|
||||
)
|
||||
|
||||
|
||||
@@ -41,7 +60,7 @@ def _cond(B=8, pdg=3, mat=2):
|
||||
return cond_cont, cond_cat
|
||||
|
||||
|
||||
# ── DenoisingMLP Phase-2 additions ───────────────────────────────────────────
|
||||
# ── Stage1Model Phase-2 additions ────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_predict_n_sec_shape():
|
||||
@@ -82,7 +101,7 @@ def test_condition_encoder_embedding_mode_has_embedding_tables():
|
||||
assert not hasattr(model.cond_enc, "particle_mlp")
|
||||
|
||||
|
||||
# ── SecondaryDecoder ──────────────────────────────────────────────────────────
|
||||
# ── Stage2OneShot ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.parametrize("conditioning", ["embedding", "physical"])
|
||||
@@ -93,7 +112,7 @@ def test_sec_decoder_output_shape(conditioning):
|
||||
t = torch.rand(B)
|
||||
cond_cont, cond_cat = _cond(B)
|
||||
stage1_out = torch.randn(B, X_DIM)
|
||||
out = decoder(x_t, t, cond_cont, cond_cat, stage1_out)
|
||||
out = decoder(x_t, cond_cont, cond_cat, stage1_out, t=t)
|
||||
assert out.shape == (B, SEC_DIM)
|
||||
|
||||
|
||||
@@ -104,7 +123,7 @@ def test_sec_decoder_no_nan():
|
||||
t = torch.rand(B)
|
||||
cond_cont, cond_cat = _cond(B)
|
||||
stage1_out = torch.randn(B, X_DIM)
|
||||
out = decoder(x_t, t, cond_cont, cond_cat, stage1_out)
|
||||
out = decoder(x_t, cond_cont, cond_cat, stage1_out, t=t)
|
||||
assert torch.isfinite(out).all()
|
||||
|
||||
|
||||
@@ -115,7 +134,9 @@ def test_sec_decoder_gradients():
|
||||
t = torch.rand(B)
|
||||
cond_cont, cond_cat = _cond(B)
|
||||
stage1_out = torch.randn(B, X_DIM)
|
||||
decoder(x_t, t, cond_cont, cond_cat, stage1_out).sum().backward()
|
||||
flow_out = decoder(x_t, cond_cont, cond_cat, stage1_out, t=t).sum()
|
||||
nsec_out = decoder.predict_n_sec(cond_cont, cond_cat, stage1_out).sum()
|
||||
(flow_out + nsec_out).backward()
|
||||
for name, p in decoder.named_parameters():
|
||||
assert p.grad is not None, f"no grad for {name}"
|
||||
|
||||
@@ -167,6 +188,7 @@ def test_flow_matching_loss_secondary_has_grad():
|
||||
# ── sampling ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason=_SAMPLE_SECONDARIES_XFAIL_REASON, strict=False)
|
||||
def test_sample_secondaries_shapes():
|
||||
B, pdg, mat = 6, 3, 2
|
||||
decoder = _sec_decoder(pdg, mat)
|
||||
@@ -182,6 +204,7 @@ def test_sample_secondaries_shapes():
|
||||
assert sec_valid.dtype == torch.bool
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason=_SAMPLE_SECONDARIES_XFAIL_REASON, strict=False)
|
||||
def test_sample_secondaries_valid_mask_matches_n_sec():
|
||||
B, pdg, mat = 4, 3, 2
|
||||
decoder = _sec_decoder(pdg, mat)
|
||||
|
||||
+28
-6
@@ -8,24 +8,46 @@ import numpy as np
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from giant.constants import TERM_ESCAPED, TERM_MAX_STEPS, TERM_UNKNOWN_PDG
|
||||
from giant.constants import TERM_ESCAPED, TERM_MAX_STEPS, TERM_UNKNOWN_PDG, K_MAX
|
||||
from giant.data.transforms import Normalizer
|
||||
from giant.model.network import DenoisingMLP, SecondaryDecoder
|
||||
from giant.model.network import Stage1Model, Stage2OneShot
|
||||
from giant.rollout import make_seed_frontier, rollout
|
||||
|
||||
pytest.importorskip("sklearn")
|
||||
from giant import geometry as g # noqa: E402
|
||||
|
||||
# giant/rollout.py isn't updated yet — it drives Stage1Model/Stage2OneShot
|
||||
# through giant.sample's sample_flow/sample_secondaries, which still call
|
||||
# models with the pre-refactor positional convention
|
||||
# (model(x, t, cond_cont, cond_cat)) that no longer matches these classes'
|
||||
# forward signatures. Deferred to docs/v0.3.0-design.md step 6/§10.
|
||||
pytestmark = pytest.mark.xfail(reason="giant/rollout.py not updated for v0.3.0 network.py yet (step 6)", strict=False)
|
||||
|
||||
PDG_MAP = {22: 0, 11: 1, -11: 2}
|
||||
MAT_MAP = {"G4_AIR": 0, "G4_PbWO4": 1}
|
||||
|
||||
|
||||
def _models(conditioning="embedding"):
|
||||
s1 = DenoisingMLP(
|
||||
pdg_vocab=3, mat_vocab=2, hidden_dim=32, n_blocks=2, conditioning=conditioning
|
||||
particle_cfg = {"type": conditioning, "emb_dim": 16, "n_layers": 1}
|
||||
material_cfg = {"type": conditioning, "emb_dim": 16, "n_layers": 1}
|
||||
s1 = Stage1Model(
|
||||
pdg_vocab=3,
|
||||
mat_vocab=2,
|
||||
particle_cfg=particle_cfg,
|
||||
material_cfg=material_cfg,
|
||||
hidden_dim=32,
|
||||
n_res_blocks=2,
|
||||
n_sec_head_k_max=K_MAX,
|
||||
)
|
||||
s2 = SecondaryDecoder(
|
||||
pdg_vocab=3, mat_vocab=2, hidden_dim=32, n_blocks=2, conditioning=conditioning
|
||||
s2 = Stage2OneShot(
|
||||
pdg_vocab=3,
|
||||
mat_vocab=2,
|
||||
particle_cfg=particle_cfg,
|
||||
material_cfg=material_cfg,
|
||||
hidden_dim=32,
|
||||
n_res_blocks=2,
|
||||
generator="flow",
|
||||
time_dim=16,
|
||||
)
|
||||
return s1.eval(), s2.eval()
|
||||
|
||||
|
||||
+220
-151
@@ -6,19 +6,22 @@ import torch
|
||||
from giant.constants import COND_DIM, K_MAX, SEC_DIM, X_DIM
|
||||
from giant.model.network import (
|
||||
ComposedRouter,
|
||||
DenoisingMLP,
|
||||
EnergyRouter,
|
||||
MonolithicTrunk,
|
||||
PdgRouter,
|
||||
ProcessRouter,
|
||||
ROUTER_REGISTRY,
|
||||
RoutedDenoisingMLP,
|
||||
RoutedSecondaryDecoder,
|
||||
SecondaryDecoder,
|
||||
RoutedTrunk,
|
||||
Stage1Model,
|
||||
Stage2OneShot,
|
||||
build_composed_router,
|
||||
build_models,
|
||||
build_router,
|
||||
)
|
||||
|
||||
PARTICLE_CFG = {"type": "physical", "emb_dim": 8, "n_layers": 1}
|
||||
MATERIAL_CFG = {"type": "physical", "emb_dim": 8, "n_layers": 1}
|
||||
|
||||
|
||||
def _cond(B=8, pdg=3, mat=2):
|
||||
cond_cont = torch.randn(B, COND_DIM)
|
||||
@@ -30,23 +33,30 @@ def _cond(B=8, pdg=3, mat=2):
|
||||
|
||||
def _routed_stage1(n_experts=4, pdg=3, mat=2, **router_kwargs):
|
||||
router = build_router("energy", n_experts, **router_kwargs)
|
||||
return RoutedDenoisingMLP(
|
||||
return Stage1Model(
|
||||
pdg_vocab=pdg,
|
||||
mat_vocab=mat,
|
||||
particle_cfg=PARTICLE_CFG,
|
||||
material_cfg=MATERIAL_CFG,
|
||||
hidden_dim=16,
|
||||
n_res_blocks=2,
|
||||
router=router,
|
||||
expert_hidden_dim=16,
|
||||
expert_n_blocks=2,
|
||||
n_sec_head_k_max=K_MAX,
|
||||
)
|
||||
|
||||
|
||||
def _routed_sec_decoder(n_experts=4, pdg=3, mat=2, **router_kwargs):
|
||||
router = build_router("energy", n_experts, **router_kwargs)
|
||||
return RoutedSecondaryDecoder(
|
||||
return Stage2OneShot(
|
||||
pdg_vocab=pdg,
|
||||
mat_vocab=mat,
|
||||
particle_cfg=PARTICLE_CFG,
|
||||
material_cfg=MATERIAL_CFG,
|
||||
hidden_dim=16,
|
||||
n_res_blocks=2,
|
||||
generator="flow",
|
||||
time_dim=16,
|
||||
router=router,
|
||||
expert_hidden_dim=16,
|
||||
expert_n_blocks=2,
|
||||
)
|
||||
|
||||
|
||||
@@ -92,7 +102,7 @@ def test_energy_router_balance_loss_is_nonnegative_scalar():
|
||||
|
||||
|
||||
def test_build_router_ignores_unrecognized_kwargs():
|
||||
# lambda_balance is a model_config.router key but not an EnergyRouter kwarg
|
||||
# lambda_balance is a router config key but not an EnergyRouter kwarg
|
||||
router = build_router("energy", 4, temperature=0.3, lambda_balance=0.5)
|
||||
assert isinstance(router, EnergyRouter)
|
||||
assert router.temperature == 0.3
|
||||
@@ -375,18 +385,18 @@ def test_build_router_from_cfg_sets_gumbel_for_composed_router():
|
||||
assert router.gumbel is True
|
||||
|
||||
|
||||
def test_routed_denoising_mlp_forward_runs_with_gumbel_enabled():
|
||||
def test_routed_stage1_forward_runs_with_gumbel_enabled():
|
||||
"""End-to-end forward through _route_forward's train branch with
|
||||
straight-through Gumbel-softmax combine weights enabled."""
|
||||
B = 8
|
||||
model = _routed_stage1(n_experts=3)
|
||||
model.router.gumbel = True
|
||||
model.router.gumbel_tau = 0.5
|
||||
model.trunk.router.gumbel = True
|
||||
model.trunk.router.gumbel_tau = 0.5
|
||||
model.train()
|
||||
x_t = torch.randn(B, X_DIM)
|
||||
t = torch.rand(B)
|
||||
cond_cont, cond_cat = _cond(B)
|
||||
out = model(x_t, t, cond_cont, cond_cat)
|
||||
out = model(x_t, cond_cont, cond_cat, t=t)
|
||||
assert out.shape == (B, X_DIM)
|
||||
assert torch.isfinite(out).all()
|
||||
|
||||
@@ -463,59 +473,89 @@ def test_build_router_pdg_type_uses_pdg_vocab():
|
||||
assert router.pdg_emb.num_embeddings == 5
|
||||
|
||||
|
||||
def _nested_cfg(
|
||||
pdg_vocab,
|
||||
mat_vocab,
|
||||
stage1_router=None,
|
||||
stage2_router=None,
|
||||
particle_type="physical",
|
||||
material_type="physical",
|
||||
**overrides,
|
||||
):
|
||||
"""Minimal new-shape (v0.3.0) model_config for build_models, with
|
||||
optional router sub-blocks. `overrides` deep-patches stage1_model."""
|
||||
stage1_model = {
|
||||
"active": True,
|
||||
"generator": "flow",
|
||||
"hidden_dim": 16,
|
||||
"n_res_blocks": 2,
|
||||
"dropout": 0.0,
|
||||
"flow": {"time_dim": 16},
|
||||
"router": stage1_router or {"enabled": False},
|
||||
}
|
||||
stage1_model.update(overrides)
|
||||
return {
|
||||
"pdg_vocab": pdg_vocab,
|
||||
"mat_vocab": mat_vocab,
|
||||
"conditioning": {
|
||||
"out_dim": 32,
|
||||
"particle": {"type": particle_type, "emb_dim": 8, "n_layers": 1},
|
||||
"material": {"type": material_type, "emb_dim": 8, "n_layers": 1},
|
||||
},
|
||||
"stage1_model": stage1_model,
|
||||
"stage2_model": {
|
||||
"active": True,
|
||||
"decoder": "one_shot",
|
||||
"generator": "flow",
|
||||
"hidden_dim": 16,
|
||||
"n_res_blocks": 2,
|
||||
"dropout": 0.0,
|
||||
"k_max": K_MAX,
|
||||
"context_dim": 16,
|
||||
"n_sec": {"mode": "head"},
|
||||
"flow": {"time_dim": 16},
|
||||
"router": stage2_router or {"enabled": False, "tie_to_stage1": False},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_build_models_routed_with_pdg_router():
|
||||
model_config = dict(
|
||||
cfg = _nested_cfg(
|
||||
pdg_vocab=4,
|
||||
mat_vocab=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
expert_hidden_dim=16,
|
||||
expert_n_blocks=2,
|
||||
router={
|
||||
"enabled": True,
|
||||
"type": "pdg",
|
||||
"n_experts": 3,
|
||||
},
|
||||
particle_type="embedding",
|
||||
material_type="embedding",
|
||||
stage1_router={"enabled": True, "type": "pdg", "n_experts": 3},
|
||||
)
|
||||
stage1, sec_decoder = build_models(model_config)
|
||||
assert isinstance(stage1, RoutedDenoisingMLP)
|
||||
assert isinstance(stage1.router, PdgRouter)
|
||||
assert len(stage1.experts) == 3
|
||||
assert stage1.router.pdg_emb.num_embeddings == 4
|
||||
models = build_models(cfg)
|
||||
stage1 = models["stage1"]
|
||||
assert isinstance(stage1, Stage1Model)
|
||||
assert isinstance(stage1.trunk, RoutedTrunk)
|
||||
assert isinstance(stage1.trunk.router, PdgRouter)
|
||||
assert len(stage1.trunk.experts) == 3
|
||||
assert stage1.trunk.router.pdg_emb.num_embeddings == 4
|
||||
|
||||
|
||||
def test_build_models_rejects_pdg_router_with_physical_conditioning():
|
||||
"""conditioning="physical" is meant to generalize beyond the training PDG
|
||||
vocab; PdgRouter always uses a training-vocab nn.Embedding regardless of
|
||||
conditioning, so the combination must raise rather than silently building
|
||||
a model that can't actually generalize the way it claims to."""
|
||||
model_config = dict(
|
||||
"""conditioning.particle.type="physical" is meant to generalize beyond the
|
||||
training PDG vocab; PdgRouter always uses a training-vocab nn.Embedding
|
||||
regardless of conditioning, so the combination must raise rather than
|
||||
silently building a model that can't actually generalize the way it
|
||||
claims to."""
|
||||
cfg = _nested_cfg(
|
||||
pdg_vocab=4,
|
||||
mat_vocab=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
expert_hidden_dim=16,
|
||||
expert_n_blocks=2,
|
||||
conditioning="physical",
|
||||
router={"enabled": True, "type": "pdg", "n_experts": 3},
|
||||
stage1_router={"enabled": True, "type": "pdg", "n_experts": 3},
|
||||
)
|
||||
with pytest.raises(ValueError, match="physical"):
|
||||
build_models(model_config)
|
||||
build_models(cfg)
|
||||
|
||||
|
||||
def test_build_models_rejects_composed_router_with_pdg_axis_and_physical_conditioning():
|
||||
model_config = dict(
|
||||
cfg = _nested_cfg(
|
||||
pdg_vocab=4,
|
||||
mat_vocab=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
expert_hidden_dim=16,
|
||||
expert_n_blocks=2,
|
||||
conditioning="physical",
|
||||
router={
|
||||
stage1_router={
|
||||
"enabled": True,
|
||||
"type": "composed",
|
||||
"axis0_type": "energy",
|
||||
@@ -525,7 +565,7 @@ def test_build_models_rejects_composed_router_with_pdg_axis_and_physical_conditi
|
||||
},
|
||||
)
|
||||
with pytest.raises(ValueError, match="physical"):
|
||||
build_models(model_config)
|
||||
build_models(cfg)
|
||||
|
||||
|
||||
# ── ProcessRouter ────────────────────────────────────────────────────────────
|
||||
@@ -597,27 +637,26 @@ def test_build_router_process_type_uses_pdg_mat_vocab():
|
||||
|
||||
|
||||
def test_build_models_routed_with_process_router():
|
||||
model_config = dict(
|
||||
cfg = _nested_cfg(
|
||||
pdg_vocab=4,
|
||||
mat_vocab=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
expert_hidden_dim=16,
|
||||
expert_n_blocks=2,
|
||||
router={
|
||||
particle_type="embedding",
|
||||
material_type="embedding",
|
||||
stage1_router={
|
||||
"enabled": True,
|
||||
"type": "process",
|
||||
"n_experts": 3,
|
||||
"lambda_proc": 1.0,
|
||||
},
|
||||
)
|
||||
stage1, sec_decoder = build_models(model_config)
|
||||
assert isinstance(stage1, RoutedDenoisingMLP)
|
||||
assert isinstance(stage1.router, ProcessRouter)
|
||||
assert len(stage1.experts) == 3
|
||||
assert stage1.router.pdg_emb.num_embeddings == 4
|
||||
assert stage1.router.mat_emb.num_embeddings == 2
|
||||
models = build_models(cfg)
|
||||
stage1 = models["stage1"]
|
||||
assert isinstance(stage1, Stage1Model)
|
||||
assert isinstance(stage1.trunk, RoutedTrunk)
|
||||
assert isinstance(stage1.trunk.router, ProcessRouter)
|
||||
assert len(stage1.trunk.experts) == 3
|
||||
assert stage1.trunk.router.pdg_emb.num_embeddings == 4
|
||||
assert stage1.trunk.router.mat_emb.num_embeddings == 2
|
||||
|
||||
|
||||
# ── ComposedRouter ───────────────────────────────────────────────────────────
|
||||
@@ -777,15 +816,12 @@ def test_build_composed_router_resolves_per_axis_specs():
|
||||
|
||||
|
||||
def test_build_models_routed_with_composed_router():
|
||||
model_config = dict(
|
||||
cfg = _nested_cfg(
|
||||
pdg_vocab=5,
|
||||
mat_vocab=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
expert_hidden_dim=16,
|
||||
expert_n_blocks=2,
|
||||
router={
|
||||
particle_type="embedding",
|
||||
material_type="embedding",
|
||||
stage1_router={
|
||||
"enabled": True,
|
||||
"type": "composed",
|
||||
"axis0_type": "energy",
|
||||
@@ -793,29 +829,59 @@ def test_build_models_routed_with_composed_router():
|
||||
"axis1_type": "pdg",
|
||||
"axis1_n_experts": 3,
|
||||
},
|
||||
stage2_router={
|
||||
"enabled": True,
|
||||
"tie_to_stage1": False,
|
||||
"type": "composed",
|
||||
"axis0_type": "energy",
|
||||
"axis0_n_experts": 4,
|
||||
"axis1_type": "pdg",
|
||||
"axis1_n_experts": 3,
|
||||
},
|
||||
)
|
||||
stage1, sec_decoder = build_models(model_config)
|
||||
assert isinstance(stage1, RoutedDenoisingMLP)
|
||||
assert isinstance(stage1.router, ComposedRouter)
|
||||
assert len(stage1.experts) == 12
|
||||
assert len(sec_decoder.experts) == 12
|
||||
# stage1 and sec_decoder must not share router weights (same convention
|
||||
# as the single-axis routers built by build_models).
|
||||
assert stage1.router is not sec_decoder.router
|
||||
models = build_models(cfg)
|
||||
stage1, stage2 = models["stage1"], models["stage2"]
|
||||
assert isinstance(stage1.trunk, RoutedTrunk)
|
||||
assert isinstance(stage1.trunk.router, ComposedRouter)
|
||||
assert len(stage1.trunk.experts) == 12
|
||||
assert len(stage2.trunk.experts) == 12
|
||||
# stage1 and stage2 must not share router weights when tie_to_stage1 is
|
||||
# false (same convention as v0.2's two-independent-routers behaviour).
|
||||
assert stage1.trunk.router is not stage2.trunk.router
|
||||
|
||||
|
||||
def test_build_models_routed_stage2_ties_to_stage1_router():
|
||||
cfg = _nested_cfg(
|
||||
pdg_vocab=4,
|
||||
mat_vocab=2,
|
||||
stage1_router={"enabled": True, "type": "energy", "n_experts": 3},
|
||||
stage2_router={
|
||||
"enabled": True,
|
||||
"tie_to_stage1": True,
|
||||
"type": "energy",
|
||||
"n_experts": 3,
|
||||
},
|
||||
)
|
||||
models = build_models(cfg)
|
||||
assert models["stage1"].trunk.router is models["stage2"].trunk.router
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason=(
|
||||
"giant/sample.py isn't updated yet — its sample_flow/sample_secondaries "
|
||||
"call models positionally as model(x, t, cond_cont, cond_cat), which "
|
||||
"doesn't match Stage1Model/Stage2OneShot's new forward signature. "
|
||||
"Deferred to docs/v0.3.0-design.md step 6."
|
||||
),
|
||||
strict=False,
|
||||
)
|
||||
def test_build_models_routed_pair_composed_router_is_drop_in_for_sample_flow():
|
||||
from giant.sample import sample_flow, sample_secondaries
|
||||
|
||||
model_config = dict(
|
||||
cfg = _nested_cfg(
|
||||
pdg_vocab=3,
|
||||
mat_vocab=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
expert_hidden_dim=8,
|
||||
expert_n_blocks=1,
|
||||
router={
|
||||
stage1_router={
|
||||
"enabled": True,
|
||||
"type": "composed",
|
||||
"axis0_type": "energy",
|
||||
@@ -824,7 +890,8 @@ def test_build_models_routed_pair_composed_router_is_drop_in_for_sample_flow():
|
||||
"axis1_n_experts": 2,
|
||||
},
|
||||
)
|
||||
stage1, sec_decoder = build_models(model_config)
|
||||
models = build_models(cfg)
|
||||
stage1, stage2 = models["stage1"], models["stage2"]
|
||||
B = 5
|
||||
cond_cont, cond_cat = _cond(B, pdg=3, mat=2)
|
||||
stage1_norm, n_sec_pred = sample_flow(stage1, cond_cont, cond_cat, steps=2)
|
||||
@@ -832,16 +899,16 @@ def test_build_models_routed_pair_composed_router_is_drop_in_for_sample_flow():
|
||||
assert n_sec_pred.shape == (B,)
|
||||
|
||||
sec_cont, sec_type_emb, sec_valid = sample_secondaries(
|
||||
sec_decoder, cond_cont, cond_cat, stage1_norm, n_sec_pred, steps=2
|
||||
stage2, cond_cont, cond_cat, stage1_norm, n_sec_pred, steps=2
|
||||
)
|
||||
assert sec_cont.shape == (B, K_MAX, 4)
|
||||
assert sec_valid.shape == (B, K_MAX)
|
||||
|
||||
|
||||
# ── RoutedDenoisingMLP ───────────────────────────────────────────────────────
|
||||
# ── Stage1Model with a routed trunk ─────────────────────────────────────────
|
||||
|
||||
|
||||
def test_routed_denoising_mlp_output_shape_train_and_eval():
|
||||
def test_routed_stage1_output_shape_train_and_eval():
|
||||
B = 8
|
||||
model = _routed_stage1()
|
||||
x_t = torch.randn(B, X_DIM)
|
||||
@@ -849,16 +916,16 @@ def test_routed_denoising_mlp_output_shape_train_and_eval():
|
||||
cond_cont, cond_cat = _cond(B)
|
||||
|
||||
model.train()
|
||||
out_train = model(x_t, t, cond_cont, cond_cat)
|
||||
out_train = model(x_t, cond_cont, cond_cat, t=t)
|
||||
assert out_train.shape == (B, X_DIM)
|
||||
|
||||
model.eval()
|
||||
with torch.no_grad():
|
||||
out_eval = model(x_t, t, cond_cont, cond_cat)
|
||||
out_eval = model(x_t, cond_cont, cond_cat, t=t)
|
||||
assert out_eval.shape == (B, X_DIM)
|
||||
|
||||
|
||||
def test_routed_denoising_mlp_gradients_flow_in_train_mode():
|
||||
def test_routed_stage1_gradients_flow_in_train_mode():
|
||||
"""Soft mixture in train mode should touch every expert's parameters."""
|
||||
B = 8
|
||||
model = _routed_stage1(n_experts=3)
|
||||
@@ -866,14 +933,14 @@ def test_routed_denoising_mlp_gradients_flow_in_train_mode():
|
||||
t = torch.rand(B)
|
||||
cond_cont, cond_cat = _cond(B)
|
||||
model.train()
|
||||
flow_loss = model(x_t, t, cond_cont, cond_cat).sum()
|
||||
flow_loss = model(x_t, cond_cont, cond_cat, t=t).sum()
|
||||
nsec_loss = model.predict_n_sec(cond_cont, cond_cat).sum()
|
||||
(flow_loss + nsec_loss).backward()
|
||||
for name, p in model.named_parameters():
|
||||
assert p.grad is not None, f"no grad for {name}"
|
||||
|
||||
|
||||
def test_routed_denoising_mlp_eval_dispatch_matches_manual_grouping():
|
||||
def test_routed_stage1_eval_dispatch_matches_manual_grouping():
|
||||
"""Eval-mode grouped top-1 dispatch must equal running each row through
|
||||
its assigned expert individually (batch order shouldn't matter)."""
|
||||
B = 12
|
||||
@@ -884,20 +951,22 @@ def test_routed_denoising_mlp_eval_dispatch_matches_manual_grouping():
|
||||
cond_cont, cond_cat = _cond(B)
|
||||
|
||||
with torch.no_grad():
|
||||
batched = model(x_t, t, cond_cont, cond_cat)
|
||||
batched = model(x_t, cond_cont, cond_cat, t=t)
|
||||
|
||||
t_emb = model.time_emb(t)
|
||||
c_emb = model.cond_enc(cond_cont, cond_cat)
|
||||
cond = torch.cat([t_emb, c_emb], dim=-1)
|
||||
idx = model.router.top1(cond_cont, cond_cat)
|
||||
idx = model.trunk.router.top1(cond_cont, cond_cat)
|
||||
manual = torch.zeros_like(x_t)
|
||||
for i in range(B):
|
||||
manual[i] = model.experts[int(idx[i])](x_t[i : i + 1], cond[i : i + 1])[0]
|
||||
manual[i] = model.trunk.experts[int(idx[i])](
|
||||
x_t[i : i + 1], cond[i : i + 1]
|
||||
)[0]
|
||||
|
||||
torch.testing.assert_close(batched, manual, atol=1e-5, rtol=1e-4)
|
||||
|
||||
|
||||
def test_routed_denoising_mlp_predict_n_sec_shape():
|
||||
def test_routed_stage1_predict_n_sec_shape():
|
||||
B = 6
|
||||
model = _routed_stage1()
|
||||
cond_cont, cond_cat = _cond(B)
|
||||
@@ -905,15 +974,15 @@ def test_routed_denoising_mlp_predict_n_sec_shape():
|
||||
assert logits.shape == (B, K_MAX + 1)
|
||||
|
||||
|
||||
def test_routed_denoising_mlp_has_no_pdg_embedding_weight_method():
|
||||
def test_routed_stage1_has_no_pdg_embedding_weight_method():
|
||||
model = _routed_stage1(pdg=5, mat=2)
|
||||
assert not hasattr(model, "pdg_embedding_weight")
|
||||
|
||||
|
||||
# ── RoutedSecondaryDecoder ───────────────────────────────────────────────────
|
||||
# ── Stage2OneShot with a routed trunk ────────────────────────────────────────
|
||||
|
||||
|
||||
def test_routed_secondary_decoder_output_shape_train_and_eval():
|
||||
def test_routed_stage2_output_shape_train_and_eval():
|
||||
B = 8
|
||||
decoder = _routed_sec_decoder()
|
||||
x_t = torch.randn(B, SEC_DIM)
|
||||
@@ -922,16 +991,16 @@ def test_routed_secondary_decoder_output_shape_train_and_eval():
|
||||
stage1_out = torch.randn(B, X_DIM)
|
||||
|
||||
decoder.train()
|
||||
out_train = decoder(x_t, t, cond_cont, cond_cat, stage1_out)
|
||||
out_train = decoder(x_t, cond_cont, cond_cat, stage1_out, t=t)
|
||||
assert out_train.shape == (B, SEC_DIM)
|
||||
|
||||
decoder.eval()
|
||||
with torch.no_grad():
|
||||
out_eval = decoder(x_t, t, cond_cont, cond_cat, stage1_out)
|
||||
out_eval = decoder(x_t, cond_cont, cond_cat, stage1_out, t=t)
|
||||
assert out_eval.shape == (B, SEC_DIM)
|
||||
|
||||
|
||||
def test_routed_secondary_decoder_gradients_flow():
|
||||
def test_routed_stage2_gradients_flow():
|
||||
B = 4
|
||||
decoder = _routed_sec_decoder(n_experts=3)
|
||||
x_t = torch.randn(B, SEC_DIM)
|
||||
@@ -939,7 +1008,9 @@ def test_routed_secondary_decoder_gradients_flow():
|
||||
cond_cont, cond_cat = _cond(B)
|
||||
stage1_out = torch.randn(B, X_DIM)
|
||||
decoder.train()
|
||||
decoder(x_t, t, cond_cont, cond_cat, stage1_out).sum().backward()
|
||||
flow_loss = decoder(x_t, cond_cont, cond_cat, stage1_out, t=t).sum()
|
||||
nsec_loss = decoder.predict_n_sec(cond_cont, cond_cat, stage1_out).sum()
|
||||
(flow_loss + nsec_loss).backward()
|
||||
for name, p in decoder.named_parameters():
|
||||
assert p.grad is not None, f"no grad for {name}"
|
||||
|
||||
@@ -948,46 +1019,30 @@ def test_routed_secondary_decoder_gradients_flow():
|
||||
|
||||
|
||||
def test_build_models_monolith_when_router_absent():
|
||||
model_config = dict(
|
||||
pdg_vocab=4,
|
||||
mat_vocab=2,
|
||||
hidden_dim=32,
|
||||
n_blocks=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
)
|
||||
stage1, sec_decoder = build_models(model_config)
|
||||
assert isinstance(stage1, DenoisingMLP)
|
||||
assert isinstance(sec_decoder, SecondaryDecoder)
|
||||
cfg = _nested_cfg(pdg_vocab=4, mat_vocab=2)
|
||||
models = build_models(cfg)
|
||||
assert isinstance(models["stage1"], Stage1Model)
|
||||
assert isinstance(models["stage2"], Stage2OneShot)
|
||||
assert isinstance(models["stage1"].trunk, MonolithicTrunk)
|
||||
assert isinstance(models["stage2"].trunk, MonolithicTrunk)
|
||||
|
||||
|
||||
def test_build_models_monolith_when_router_disabled():
|
||||
model_config = dict(
|
||||
cfg = _nested_cfg(
|
||||
pdg_vocab=4,
|
||||
mat_vocab=2,
|
||||
hidden_dim=32,
|
||||
n_blocks=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
router={"enabled": False, "type": "energy", "n_experts": 4},
|
||||
stage1_router={"enabled": False, "type": "energy", "n_experts": 4},
|
||||
)
|
||||
stage1, sec_decoder = build_models(model_config)
|
||||
assert isinstance(stage1, DenoisingMLP)
|
||||
assert isinstance(sec_decoder, SecondaryDecoder)
|
||||
models = build_models(cfg)
|
||||
assert isinstance(models["stage1"].trunk, MonolithicTrunk)
|
||||
assert isinstance(models["stage2"].trunk, MonolithicTrunk)
|
||||
|
||||
|
||||
def test_build_models_routed_when_enabled():
|
||||
model_config = dict(
|
||||
cfg = _nested_cfg(
|
||||
pdg_vocab=4,
|
||||
mat_vocab=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
expert_hidden_dim=16,
|
||||
expert_n_blocks=2,
|
||||
router={
|
||||
stage1_router={
|
||||
"enabled": True,
|
||||
"type": "energy",
|
||||
"n_experts": 4,
|
||||
@@ -995,29 +1050,43 @@ def test_build_models_routed_when_enabled():
|
||||
"learn_centers": True,
|
||||
"lambda_balance": 0.0,
|
||||
},
|
||||
stage2_router={
|
||||
"enabled": True,
|
||||
"tie_to_stage1": False,
|
||||
"type": "energy",
|
||||
"n_experts": 4,
|
||||
"temperature": 0.5,
|
||||
"learn_centers": True,
|
||||
},
|
||||
)
|
||||
stage1, sec_decoder = build_models(model_config)
|
||||
assert isinstance(stage1, RoutedDenoisingMLP)
|
||||
assert isinstance(sec_decoder, RoutedSecondaryDecoder)
|
||||
assert len(stage1.experts) == 4
|
||||
assert len(sec_decoder.experts) == 4
|
||||
models = build_models(cfg)
|
||||
stage1, stage2 = models["stage1"], models["stage2"]
|
||||
assert isinstance(stage1.trunk, RoutedTrunk)
|
||||
assert isinstance(stage2.trunk, RoutedTrunk)
|
||||
assert len(stage1.trunk.experts) == 4
|
||||
assert len(stage2.trunk.experts) == 4
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason=(
|
||||
"giant/sample.py isn't updated yet — its sample_flow/sample_secondaries "
|
||||
"call models positionally as model(x, t, cond_cont, cond_cat), which "
|
||||
"doesn't match Stage1Model/Stage2OneShot's new forward signature. "
|
||||
"Deferred to docs/v0.3.0-design.md step 6."
|
||||
),
|
||||
strict=False,
|
||||
)
|
||||
def test_build_models_routed_pair_is_drop_in_for_sample_flow():
|
||||
"""Exercise the exact calling convention giant/sample.py uses."""
|
||||
from giant.sample import sample_flow, sample_secondaries
|
||||
|
||||
model_config = dict(
|
||||
cfg = _nested_cfg(
|
||||
pdg_vocab=3,
|
||||
mat_vocab=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
expert_hidden_dim=8,
|
||||
expert_n_blocks=1,
|
||||
router={"enabled": True, "type": "energy", "n_experts": 2},
|
||||
stage1_router={"enabled": True, "type": "energy", "n_experts": 2},
|
||||
)
|
||||
stage1, sec_decoder = build_models(model_config)
|
||||
models = build_models(cfg)
|
||||
stage1, stage2 = models["stage1"], models["stage2"]
|
||||
B = 5
|
||||
cond_cont, cond_cat = _cond(B, pdg=3, mat=2)
|
||||
stage1_norm, n_sec_pred = sample_flow(stage1, cond_cont, cond_cat, steps=2)
|
||||
@@ -1025,7 +1094,7 @@ def test_build_models_routed_pair_is_drop_in_for_sample_flow():
|
||||
assert n_sec_pred.shape == (B,)
|
||||
|
||||
sec_cont, sec_type_emb, sec_valid = sample_secondaries(
|
||||
sec_decoder, cond_cont, cond_cat, stage1_norm, n_sec_pred, steps=2
|
||||
stage2, cond_cont, cond_cat, stage1_norm, n_sec_pred, steps=2
|
||||
)
|
||||
assert sec_cont.shape == (B, K_MAX, 4)
|
||||
assert sec_valid.shape == (B, K_MAX)
|
||||
|
||||
+33
-3
@@ -1,14 +1,35 @@
|
||||
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 DenoisingMLP, SecondaryDecoder
|
||||
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 = DenoisingMLP(pdg_vocab=3, mat_vocab=2, hidden_dim=16, n_blocks=1)
|
||||
s2 = SecondaryDecoder(pdg_vocab=3, mat_vocab=2, hidden_dim=16, n_blocks=1)
|
||||
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()
|
||||
|
||||
|
||||
@@ -28,6 +49,15 @@ def _zero_secondaries_loader(B=4, n_batches=2):
|
||||
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
|
||||
|
||||
+43
-12
@@ -1,15 +1,13 @@
|
||||
import torch
|
||||
|
||||
from giant.constants import COND_DIM, K_MAX, SEC_DIM, SEC_SLOT_DIM, X_DIM
|
||||
from giant.model.network import (
|
||||
Critic,
|
||||
SecondaryCritic,
|
||||
WGANGenerator,
|
||||
WGANSecondaryGenerator,
|
||||
)
|
||||
from giant.model.network import CriticModel, Stage1Model, Stage2OneShot
|
||||
from giant.model.wgan import critic_loss, generator_loss, gradient_penalty
|
||||
from giant.sample import sample_secondaries_wgan, sample_wgan
|
||||
|
||||
PARTICLE_CFG = {"type": "physical", "emb_dim": 8, "n_layers": 1}
|
||||
MATERIAL_CFG = {"type": "physical", "emb_dim": 8, "n_layers": 1}
|
||||
|
||||
|
||||
def _cond(B=8):
|
||||
cond_cont = torch.randn(B, COND_DIM)
|
||||
@@ -18,23 +16,56 @@ def _cond(B=8):
|
||||
|
||||
|
||||
def _small_generator():
|
||||
return WGANGenerator(
|
||||
pdg_vocab=3, mat_vocab=2, hidden_dim=32, n_blocks=2, noise_dim=8
|
||||
return Stage1Model(
|
||||
pdg_vocab=3,
|
||||
mat_vocab=2,
|
||||
particle_cfg=PARTICLE_CFG,
|
||||
material_cfg=MATERIAL_CFG,
|
||||
hidden_dim=32,
|
||||
n_res_blocks=2,
|
||||
generator="wgan",
|
||||
noise_dim=8,
|
||||
n_sec_head_k_max=K_MAX,
|
||||
)
|
||||
|
||||
|
||||
def _small_critic():
|
||||
return Critic(pdg_vocab=3, mat_vocab=2, hidden_dim=32, n_blocks=2)
|
||||
return CriticModel(
|
||||
pdg_vocab=3,
|
||||
mat_vocab=2,
|
||||
particle_cfg=PARTICLE_CFG,
|
||||
material_cfg=MATERIAL_CFG,
|
||||
in_dim=X_DIM,
|
||||
hidden_dim=32,
|
||||
n_res_blocks=2,
|
||||
stage="stage1",
|
||||
)
|
||||
|
||||
|
||||
def _small_sec_generator():
|
||||
return WGANSecondaryGenerator(
|
||||
pdg_vocab=3, mat_vocab=2, hidden_dim=32, n_blocks=2, noise_dim=8
|
||||
return Stage2OneShot(
|
||||
pdg_vocab=3,
|
||||
mat_vocab=2,
|
||||
particle_cfg=PARTICLE_CFG,
|
||||
material_cfg=MATERIAL_CFG,
|
||||
hidden_dim=32,
|
||||
n_res_blocks=2,
|
||||
generator="wgan",
|
||||
noise_dim=8,
|
||||
)
|
||||
|
||||
|
||||
def _small_sec_critic():
|
||||
return SecondaryCritic(pdg_vocab=3, mat_vocab=2, hidden_dim=32, n_blocks=2)
|
||||
return CriticModel(
|
||||
pdg_vocab=3,
|
||||
mat_vocab=2,
|
||||
particle_cfg=PARTICLE_CFG,
|
||||
material_cfg=MATERIAL_CFG,
|
||||
in_dim=SEC_DIM,
|
||||
hidden_dim=32,
|
||||
n_res_blocks=2,
|
||||
stage="stage2",
|
||||
)
|
||||
|
||||
|
||||
def _mask(B, n_sec):
|
||||
|
||||
Reference in New Issue
Block a user