Delete docs/v0.3.0-design.md and strip all references to it
CI / Format (ruff format) (push) Failing after 28s
CI / Lint (ruff check) (push) Successful in 29s
CI / Sync project version with tag (push) Has been skipped
CI / Lint (ruff check) (pull_request) Successful in 33s
CI / Type check (ty) (push) Successful in 37s
CI / Format (ruff format) (pull_request) Failing after 37s
CI / Sync project version with tag (pull_request) Has been skipped
CI / Type check (ty) (pull_request) Successful in 37s
CI / Tests (pull_request) Successful in 2m49s
CI / Tests (push) Successful in 2m55s

The design doc and its followups doc are no longer needed as a live
reference now that the v0.3.0 redesign is implemented — comments and
docstrings across the codebase cited it extensively (file path, "design
doc §X.Y", "decision N", or bare "§X.Y" section numbers) as design
rationale. Removed docs/ and edited every citing comment/docstring to
drop the now-dangling reference while keeping the substantive
explanation next to it. CLAUDE.md's v0.3.0 roadmap bullet loses its
trailing pointer to the deleted file.

Verified: no remaining "docs/v0.3.0", "design doc", "decision N", or
"§N.N" references (repo-wide grep); ruff and ty clean; full test suite
on the heaviest-touched modules (network, sample, rollout, migration,
config, train) passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 11:19:02 +02:00
parent f46628141d
commit 878e9ddca3
41 changed files with 263 additions and 1346 deletions
+4 -4
View File
@@ -1,13 +1,13 @@
"""Frozen snapshot of `giant/model/network.py` as it stood at the v0.3.0
"step 1" commit (eb6dd27), i.e. the last commit before the step-2 §5
decomposition (see `docs/v0.3.0-design.md`).
"step 1" commit (eb6dd27), i.e. the last commit before the step-2
composable-parts decomposition.
This is a deliberate verbatim copy, not an import of the live module — the
whole point is that this file's classes keep behaving exactly as v0.2 did
even after `giant/model/network.py` itself is rewritten, so
`tests/test_migration_v02_v03.py` has a stable "old" side to diff the new
`build_models`/`Stage1Model`/`Stage2OneShot` against (design doc §4.3's
bit-identical acceptance test). Do not edit this file to track future
`build_models`/`Stage1Model`/`Stage2OneShot` against (the bit-identical
acceptance test). Do not edit this file to track future
`network.py` changes — it exists specifically to stop tracking them.
"""
+2 -3
View File
@@ -1,6 +1,5 @@
"""Tests for `giant train`'s stage-prefixed CLI flags (docs/v0.3.0-design.md
decision 7 / docs/v0.3.0-followups.md item 2): --stage1-*/--stage2-* must
independently override each stage's config block, and must take precedence
"""Tests for `giant train`'s stage-prefixed CLI flags: --stage1-*/--stage2-*
must independently override each stage's config block, and must take precedence
over the older shared flags (--mode/--hidden-dim/--n-critic/... ) that still
apply the same value to both stages for backward compatibility."""
+4 -4
View File
@@ -585,8 +585,8 @@ def test_validate_config_embedding_target_passes_with_embedding_conditioning():
def test_validate_config_mixed_particle_material_conditioning_is_valid():
"""docs/v0.3.0-design.md §3.1: the particle and material conditioning
axes are configured independently and may mix freely — e.g. material
"""The particle and material conditioning axes are configured
independently and may mix freely — e.g. material
"physical" with particle "embedding" — and the data pipeline
(giant/data/transforms.py) now implements that end-to-end, so
validate_config must not reject it."""
@@ -638,8 +638,8 @@ def test_validate_config_stop_token_not_implemented():
def test_validate_config_n_sec_truth_rejected_for_rollout_capable_checkpoint():
"""docs/v0.3.0-design.md §9: 'n_sec.mode = "truth" is invalid for a
rollout-capable checkpoint' both stages active means giant rollout
"""'n_sec.mode = "truth" is invalid for a rollout-capable checkpoint'
both stages active means giant rollout
could load this checkpoint, but 'truth' has no ground truth to draw
n_sec from at rollout time."""
cfg = _cfg_with(
+1 -2
View File
@@ -210,8 +210,7 @@ def test_build_topn_map_from_files_fewer_values_than_n_classes(tmp_path):
def test_build_pdg_topn_map_from_files_pools_primary_and_secondary_pdg(tmp_path):
"""A species that's rare as a primary but common as a secondary must
still rank by its pooled (primary + secondary) count, not just its
primary-role count alone the whole point of pooling both roles
(docs/v0.3.0-design.md §8)."""
primary-role count alone the whole point of pooling both roles."""
path = tmp_path / "a.parquet"
# primary pdg: mostly 11 (electron), one lone 22 (photon)
pdg = [11] * 5 + [22] * 1
+13 -15
View File
@@ -1,13 +1,12 @@
"""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."
"""Migration acceptance test for v0.3.0 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
section), so a real trained checkpoint can't be used here — a separate
portal-machine follow-up with a real checkpoint is planned instead. 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
@@ -134,7 +133,7 @@ def _run_migration_check(mode: str, conditioning: str) -> None:
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).
# migrated v0.2 checkpoint.
assert new_stage1.n_sec_head is not None
assert new_stage2.n_sec_head is None
@@ -199,12 +198,11 @@ def test_migrate_legacy_model_config_shape():
def test_migrate_legacy_model_config_nonzero_expert_dims_raises():
"""docs/v0.3.0-followups.md item 8 regression: a v0.2 checkpoint's
model_config carrying a non-default expert_hidden_dim/expert_n_blocks
must fail loudly through this path too (§4.2) not just
giant.config.migrate_config's parallel TOML-load path. Silently dropping
these keys (build_router's kwarg filtering) would resize the experts
instead of refusing."""
"""Regression: a v0.2 checkpoint's model_config carrying a non-default
expert_hidden_dim/expert_n_blocks must fail loudly through this path too
not just giant.config.migrate_config's parallel TOML-load path.
Silently dropping these keys (build_router's kwarg filtering) would
resize the experts instead of refusing."""
legacy_cfg = _legacy_model_config(mode="flow", conditioning="physical")
legacy_cfg["router"] = {
"enabled": True,
+9 -9
View File
@@ -84,7 +84,7 @@ def test_stage1_model_gradients_flow():
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."""
it moves to stage 2."""
model = Stage1Model(
pdg_vocab=3, mat_vocab=2, particle_cfg=PARTICLE_CFG, material_cfg=MATERIAL_CFG
)
@@ -206,7 +206,7 @@ def test_condition_encoder_onehot_is_a_true_one_hot_vector():
assert torch.all(pdg_e.sum(dim=-1) == 1.0)
# --- Stage2OneShot particle_type architecture (docs/v0.3.0-design.md decision 2) --
# --- Stage2OneShot particle_type architecture --------------------------------
def _build_stage2(target: str, generator: str, emb_dim: int = 6) -> Stage2OneShot:
@@ -300,7 +300,7 @@ def test_stage2_oneshot_forward_shape_onehot_flow_excludes_type():
assert out.shape == (B, k_max * CONT_SLOT_DIM)
# --- MarkovHistory (docs/v0.3.0-design.md §6.2) -----------------------------
# --- MarkovHistory -----------------------------------------------------------
def test_markov_history_shape():
@@ -314,8 +314,8 @@ def test_markov_history_shape():
def test_markov_history_uses_start_vector_when_no_prev():
"""Slot 0's own raw feature must be ignored — a learned start vector is
substituted there instead (a reasonable default not specified by the
design doc, see Stage2Autoregressive's docstring)."""
substituted there instead (a reasonable default, see
Stage2Autoregressive's docstring)."""
hist = MarkovHistory(in_dim=4, out_dim=6)
B, K = 2, 3
has_prev = (torch.arange(K) >= 1).unsqueeze(0).expand(B, -1)
@@ -328,7 +328,7 @@ def test_markov_history_uses_start_vector_when_no_prev():
assert torch.allclose(out_a[:, 1:], out_b[:, 1:])
# --- AttentionHistory (docs/v0.3.0-design.md §6.2, v0.3.0 step 7) ----------
# --- AttentionHistory (v0.3.0 step 7) ---------------------------------------
def test_attention_history_shape():
@@ -392,7 +392,7 @@ def test_attention_history_step_matches_forward():
assert torch.allclose(stepped, expected, atol=1e-5)
# --- Stage2Autoregressive (docs/v0.3.0-design.md §6, v0.3.0 step 5) ---------
# --- Stage2Autoregressive (v0.3.0 step 5) -----------------------------------
def _build_stage2_ar(
@@ -671,8 +671,8 @@ def test_build_models_share_stages_true_shared_params_are_in_both_stage_paramete
"""The shared encoder's parameters must actually appear in both stages'
own `.parameters()` that's what makes each stage's independent
optimizer include (and update) them, which is the actual mechanism behind
"shared weights, forced common representation" (docs/v0.3.0-design.md
§3.1), not just object identity on `.cond_enc`."""
"shared weights, forced common representation", not just object identity
on `.cond_enc`."""
built = build_models(_minimal_model_config(share_stages=True))
stage1, stage2 = built["stage1"], built["stage2"]
assert stage1 is not None and stage2 is not None
+5 -5
View File
@@ -155,7 +155,7 @@ def test_run_train_job_second_run_hits_cache(tmp_path, data, monkeypatch):
def test_run_train_job_builds_caches_and_persists_pdg_topn_map(tmp_path, data):
"""DEFAULT_CONFIG's stage2_model.particle_type.target defaults to
"onehot" (docs/v0.3.0-design.md §3.3/§8) a plain _tiny_cfg() run must
"onehot" a plain _tiny_cfg() run must
build the shared pdg top-N map, cache it in the setup-cache sidecar, and
persist it into the checkpoint, with no extra config needed."""
echo1 = _run(data, tmp_path / "out1")
@@ -242,7 +242,7 @@ def test_run_train_job_new_val_fraction_is_partial_hit(tmp_path, data, monkeypat
def test_run_train_job_custom_k_max_end_to_end(tmp_path, data):
"""docs/v0.3.0-followups.md item 3 regression: stage2_model.k_max other
"""Regression: stage2_model.k_max other
than the K_MAX module constant's default (15) must not produce a shape
mismatch between the data pipeline (loader.py/transforms.py padding) and
the model (network.py's trunks, sized from this same config value)."""
@@ -254,9 +254,9 @@ def test_run_train_job_custom_k_max_end_to_end(tmp_path, data):
def test_run_train_job_mixed_particle_material_conditioning_end_to_end(tmp_path, data):
"""docs/v0.3.0-followups.md item 4 regression: conditioning.particle.type
"""Regression: conditioning.particle.type
and conditioning.material.type are configured independently and may mix
freely (docs/v0.3.0-design.md §3.1) e.g. particle "embedding" with
freely e.g. particle "embedding" with
material "physical" end-to-end through the real data pipeline, not
just accepted by validate_config."""
cfg = _tiny_cfg()
@@ -286,7 +286,7 @@ def test_run_train_job_mixed_particle_material_conditioning_end_to_end(tmp_path,
def test_run_train_job_share_stages_end_to_end(tmp_path, data):
"""docs/v0.3.0-followups.md item 5 regression: conditioning.share_stages
"""Regression: conditioning.share_stages
= true must actually train (not raise NotImplementedError), and the
resulting checkpoint's two stages must reload into a single shared
ConditionEncoder instance rather than two independent ones."""
+10 -10
View File
@@ -373,7 +373,7 @@ def _models_v3(
particle_cfg = {"type": conditioning, "emb_dim": emb_dim, "n_layers": 1}
material_cfg = {"type": conditioning, "emb_dim": emb_dim, "n_layers": 1}
# A fresh v0.3.0 Stage1Model — no n_sec_head_k_max, unlike _models() above
# (decision 1 moves n_sec ownership to stage 2 by default).
# (n_sec ownership moves to stage 2 by default).
s1 = Stage1Model(
pdg_vocab=3,
mat_vocab=2,
@@ -470,9 +470,9 @@ def _run_v3(
def test_rollout_stage2_owns_n_sec_when_stage1_has_no_head(fake_material_props):
"""A fresh v0.3.0 Stage1Model has no n_sec_head (decision 1) — n_sec
must come from Stage2's own head instead, and the run must still
complete and conserve energy."""
"""A fresh v0.3.0 Stage1Model has no n_sec_head — n_sec must come from
Stage2's own head instead, and the run must still complete and
conserve energy."""
s1, s2 = _models_v3()
rec = _run_v3(s1, s2)
assert len(rec["event_id"]) > 0
@@ -501,7 +501,7 @@ def test_rollout_physical_target_decoder_generator_matrix(
):
"""Every (decoder, stage2 generator) combination under
particle_type.target="physical" must run to completion and conserve
energy the matrix docs/v0.3.0-design.md §7 calls out for comparison."""
energy."""
s1, s2 = _models_v3(decoder=decoder, generator2=generator2)
rec = _run_v3(s1, s2)
assert len(rec["event_id"]) > 0
@@ -555,10 +555,10 @@ def test_rollout_onehot_target_missing_topn_map_raises(fake_material_props):
_run_v3(s1, s2, pdg_topn_map=None)
# --- conditioning.{particle,material}.type = "onehot" (docs/v0.3.0-followups.md
# item 7) — a separate axis from stage2_model.particle_type.target above: this
# is what feeds cond_cat's extra top-N columns for ConditionEncoder's own
# "onehot" mode, not the secondary-species decode. ---------------------------
# --- conditioning.{particle,material}.type = "onehot" — a separate axis from
# stage2_model.particle_type.target above: this is what feeds cond_cat's
# extra top-N columns for ConditionEncoder's own "onehot" mode, not the
# secondary-species decode. ---------------------------------------------
COND_PDG_TOPN_MAP = TopNMap(class_map=dict(PDG_MAP), other_members={})
COND_MAT_TOPN_MAP = TopNMap(class_map={"G4_AIR": 0, "G4_PbWO4": 1}, other_members={})
@@ -653,7 +653,7 @@ def test_rollout_embedding_target_end_to_end(decoder):
def test_l1_dist_collector_populated_only_for_embedding_target():
"""§11.3: the L1-distance diagnostic only makes sense under
"""The L1-distance diagnostic only makes sense under
particle_type.target="embedding" a physical-target run must leave the
collector empty rather than silently accumulating garbage."""
s1, s2 = _models_v3(target="physical")
+4 -4
View File
@@ -896,8 +896,8 @@ def test_build_models_routed_pair_composed_router_is_drop_in_for_sample_flow():
cond_cont, cond_cat = _cond(B, pdg=3, mat=2)
stage1_norm, n_sec_pred = sample_flow(stage1, cond_cont, cond_cat, steps=2)
assert stage1_norm.shape == (B, X_DIM)
# A fresh v0.3.0 Stage1Model has no n_sec_head (decision 1 moves it to
# stage 2) — sample_flow returns n_sec_pred=None here, and n_sec must be
# A fresh v0.3.0 Stage1Model has no n_sec_head (it moves to stage 2) —
# sample_flow returns n_sec_pred=None here, and n_sec must be
# asked of stage2 instead, using the just-sampled stage1_norm as context.
assert n_sec_pred is None
n_sec_pred = stage2.predict_n_sec(cond_cont, cond_cat, stage1_norm).argmax(dim=-1)
@@ -1092,8 +1092,8 @@ def test_build_models_routed_pair_is_drop_in_for_sample_flow():
cond_cont, cond_cat = _cond(B, pdg=3, mat=2)
stage1_norm, n_sec_pred = sample_flow(stage1, cond_cont, cond_cat, steps=2)
assert stage1_norm.shape == (B, X_DIM)
# A fresh v0.3.0 Stage1Model has no n_sec_head (decision 1 moves it to
# stage 2) — sample_flow returns n_sec_pred=None here, and n_sec must be
# A fresh v0.3.0 Stage1Model has no n_sec_head (it moves to stage 2) —
# sample_flow returns n_sec_pred=None here, and n_sec must be
# asked of stage2 instead, using the just-sampled stage1_norm as context.
assert n_sec_pred is None
n_sec_pred = stage2.predict_n_sec(cond_cont, cond_cat, stage1_norm).argmax(dim=-1)
+3 -3
View File
@@ -1,6 +1,6 @@
"""Tests for giant/sample.py's v0.3.0 stage-model sampling — the AR loop
(`sample_secondaries_ar`) and non-"physical" `particle_type.target` coverage
for the one-shot samplers (docs/v0.3.0-design.md step 6)."""
for the one-shot samplers."""
import pytest
import torch
@@ -38,7 +38,7 @@ def _cond(B: int, pdg: int = 3, mat: int = 2) -> tuple[torch.Tensor, torch.Tenso
def _conditioning_for(target: str) -> str:
# target="embedding" regresses against the conditioning's own embedding
# table (docs/v0.3.0-design.md §3.3) — only meaningful when the
# table — only meaningful when the
# conditioning axis is itself "embedding".
return "embedding" if target == "embedding" else "physical"
@@ -103,7 +103,7 @@ def _expected_type_dim(target: str, emb_dim: int) -> int:
return PARTICLE_PHYS_DIM if target == "physical" else emb_dim
# ── Stage-1 n_sec ownership (decision 1) ────────────────────────────────────
# ── Stage-1 n_sec ownership ──────────────────────────────────────────────────
def test_sample_flow_returns_none_n_sec_when_stage1_owns_no_head():
+6 -6
View File
@@ -90,7 +90,7 @@ def test_wandb_run_config_handles_missing_model_config():
assert wcfg["model_config"] == {}
# --- AR helper functions (v0.3.0 step 5, docs/v0.3.0-design.md §6) ---------
# --- AR helper functions (v0.3.0 step 5) ---------
def test_stick_fraction_matches_sigmoid_of_logit():
@@ -119,7 +119,7 @@ def test_ar_has_prev_false_only_at_slot_zero():
assert has_prev.tolist() == [[False, True, True, True, True]]
# --- _stage2_tf_prob (docs/v0.3.0-design.md §3.3, v0.3.0 step 7) -----------
# --- _stage2_tf_prob (v0.3.0 step 7) -----------
def test_stage2_tf_prob_always_is_constant_one():
@@ -569,7 +569,7 @@ def test_metrics_csv_columns_are_stage_prefixed():
def test_wgan_stage_trainer_skips_generator_step_when_no_grad_this_batch():
"""Regression test: on a non-generator-step batch, if this stage's model
has no n_sec_head (n_sec defaults to stage 2, decision 1), g_loss is a
has no n_sec_head (n_sec defaults to stage 2), g_loss is a
graph-less zero .backward() must not be called on it."""
cfg = _base_cfg()
cfg["stage1_model"]["generator"] = "wgan"
@@ -672,11 +672,11 @@ def test_train_end_to_end_ar_attention_history_scheduled_teacher_forcing(
def test_ar_wgan_onehot_grad_norm_instrumentation_populates_metrics():
"""§11.4 differentiability validation-obligation instrumentation: the
"""Differentiability validation-obligation instrumentation: the
trunk-gradient-norm-by-slice columns must appear and actually fire for
generator='wgan' + particle_type.target='onehot' under decoder=
'autoregressive' (added at v0.3.0 step 5 per the design doc's
instruction to accrue evidence during the architecture comparison)."""
'autoregressive' (added at v0.3.0 step 5 to accrue evidence during the
architecture comparison)."""
cfg = _base_cfg()
cfg["stage2_model"]["decoder"] = "autoregressive"
cfg["stage2_model"]["particle_type"] = {"target": "onehot", "lambda": 1.0}
+1 -1
View File
@@ -1,5 +1,5 @@
"""Tests for the secondary-type embedding-distance diagnostic
(giant.analysis.type_embedding_distance) the §11.3 diagnostic."""
(giant.analysis.type_embedding_distance)."""
from __future__ import annotations
+2 -2
View File
@@ -11,8 +11,8 @@ _K_MAX = 5
def _tiny_models(particle_type_cfg: dict | None = None):
"""A fresh v0.3.0 pair: Stage1Model owns no n_sec_head (decision 1), so
n_sec always comes from Stage2OneShot."""
"""A fresh v0.3.0 pair: Stage1Model owns no n_sec_head, so n_sec always
comes from Stage2OneShot."""
s1 = Stage1Model(
pdg_vocab=3,
mat_vocab=2,