Secondary species vocabulary is accidentally sized by conditioning.particle.emb_dim #29
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: medium-high (design defect, on the critical path of the v0.3 pivot).
giant/pipeline.py:189andgiant/model/models.py:19-25both size the stage-2particle-type head from
conditioning.particle.emb_dim:Under the default config that coupling is between two unrelated things:
conditioning.particle.type = "physical"(soemb_dimmeans "output width ofthe physical-property MLP") and
stage2_model.particle_type.target = "onehot"(so
emb_dimsilently means "number of secondary species classes"). Thedefault
emb_dim = 16therefore fixes the secondary species vocabulary at15 PDG codes + one
otherbucket, and the only way to widen it to 32species is to widen the conditioning MLP's output to 32 as well.
The comment at
pipeline.py:177-182presents this as a deliberate optimisation("both key off
conditioning.particle.emb_dim, so at most one PDG scan isneeded"). Sharing the scan is fine; sharing the class count is what's wrong
— and the scan can be shared anyway by building one map at
max(cond_n_classes, type_n_classes)and slicing.The reason this is more than cosmetic: the entire v0.3.0 redesign exists because
the 2026-08-03 WGAN benchmark produced zero photon secondaries and ~4M
hallucinated
-14s. The species vocabulary is the thing being fixed, and it iscurrently not independently settable.
Fix: add
stage2_model.particle_type.n_classes(default0→ inheritconditioning.particle.emb_dim, preserving today's behaviour and every existingcheckpoint), thread it through
stage2_type_dim/stage2_trunk_sec_dim/pipeline.py, and store the resolved value in the checkpoint. See also Issue 19(class-balanced type loss) for the class-imbalance half of the same problem.
Migrated from
issues.md(v0.3.0 branch review, 2026-08-13), Issue 3.Fixed in
32aa5a5on v0.3.0-stage2-autoregressive.Added
stage2_model.particle_type.n_classes(default 0 = inheritconditioning.particle.emb_dim, fully back-compat) and a singleresolve_type_n_classeshelper (giant/model/models.py) now used everywhere the coupling used to be implicit: pipeline.py's PDG top-N map build, Stage2OneShot/Stage2Autoregressive's type head sizing, build_models/build_critics, and StageSpec's training loss width.The checkpoint's single shared
pdg_topn_mapis now split:pdg_topn_mapstays conditioning-only, and a newsec_type_topn_mapkey covers the secondary-species decode side, built independently through the existing (axis, n_classes)-keyed setup cache — no extra parquet scan in the common case where the two still resolve to the same N. Threaded through giant predict/giant rollout's decode path (giant/rollout.py, giant/cli.py). A checkpoint with no sec_type_topn_map key (pre-#29) falls back to reusing pdg_topn_map in checkpoint_io.py, reproducing the old shared behavior exactly — no existing checkpoint becomes unloadable.Decisions made during planning (with the user): commit directly on this branch rather than a new one; represent the split as an additive sec_type_topn_map checkpoint key rather than conditionally reusing pdg_topn_map; build the two top-N maps independently rather than the issue's proposed build-at-max-and-slice, since giant/data/setup_cache.py's topn_maps cache is already keyed by (axis, n_classes) and already avoids redundant scans across runs.
All four checks (pytest, ruff format, ruff check, ty check) clean. Gitea #44 (class-balanced secondary particle-type loss), referenced by this issue for context, is a separate open issue and was left untouched.