5c576fa8f3
CI / Sync project version with tag (pull_request) Has been skipped
CI / Type check (ty) (pull_request) Successful in 1m2s
CI / Lint (ruff check) (pull_request) Successful in 1m6s
CI / Format (ruff format) (pull_request) Successful in 1m6s
CI / Tests (pull_request) Successful in 2m46s
CI / Bump version, tag, and update changelog on merge to master (pull_request) Has been skipped
CI / Publish package to Gitea package registry (pull_request) Has been skipped
CI / Update README badges (version, test count) (pull_request) Has been skipped
sample_secondaries_ar ran all k_max=15 slots for every row regardless of each row's own predicted secondary count, even though the baseline checkpoint's rollout measured only 0.382 secondaries/step — so ~97% of stage-2 model calls generated tokens sec_valid then masked away. Compact the loop to the still-active row set at each slot: drop a row the moment its n_sec_pred is exhausted (or, under n_sec.mode="stop_token", the moment its own stop logit fires), so slot k's model calls cost O(active rows) instead of O(B). Exact — rows are independent given their own history — verified by comparing the compacted path against a new full_length=True escape hatch that reproduces the original uncompacted behavior bit-for-bit under deterministic noise. full_length=True is required by _assemble_stage2_ar_inputs_scheduled's scheduled-sampling self-sample, whose training contract needs a real prediction at every slot up to k_max regardless of a row's own count, so training behavior is unchanged. AttentionHistory's KV cache and MarkovHistory's O(1) state are kept aligned to the shrinking active set via a new HistoryEncoder.select_cache / Stage2Autoregressive.select_history_cache. Also fixes a latent bug the refactor surfaced: derived_n_sec (stop-token mode) could be overwritten by a later spurious re-fire of the stop logit on a row that had already stopped; now tracked via an explicit `finished` mask so only the first stop slot is recorded, matching the documented contract. No architecture or checkpoint-format change — every existing v0.3.0 Stage2Autoregressive checkpoint (flow/wgan, markov/attention, head/stop_token) picks up the speedup automatically on its next `giant rollout`/`giant predict`, no retraining needed. Measured (CPU, hidden_dim=512/6 blocks, k_max=15, batch 512, mean n_sec≈0.38 matching the baseline checkpoint's own rollout): 17.6-22.9x fewer wall-clock seconds for the AR loop alone (attention/markov history respectively). Directional only — baseline.toml's GPU inference-cost comment is updated accordingly, flagged stale pending a real rollout re-measurement via eval_cost_per_step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HPt7bVLZYFJe5cG6V7ahqC
155 lines
7.0 KiB
TOML
155 lines
7.0 KiB
TOML
# GIANT reference baseline (v0.3 schema).
|
|
#
|
|
# The fixed comparison point every future architecture variant is measured
|
|
# against. Chosen so that each experimental axis the roadmap cares about
|
|
# (routed trunk, WGAN generators, attention history, shared conditioning,
|
|
# embedding/onehot conditioning) is a *single* edit away from this file.
|
|
#
|
|
# Rationale for the choices below, from the runs already on record
|
|
# (analysis_runs/ + the `giant` W&B project):
|
|
#
|
|
# * flow, not wgan, for both stages. Ranking the five existing rollouts by
|
|
# mean Jensen-Shannon divergence against the Geant4 reference, the plain
|
|
# non-routed flow model wins (0.172) over the routed flow runs
|
|
# (0.197/0.200) and both WGAN runs (0.218/0.234) — and it beats them by
|
|
# ~7x on per-event total deposited energy and by 3-10x on every
|
|
# per-PDG marginal. WGAN stays a variant, not the reference.
|
|
#
|
|
# * no router. The routed runs are not better, and soft-mixing 10 small
|
|
# experts costs ~10x per-pass throughput at train time (29k samples/s vs
|
|
# the WGAN runs' 52-116k), which is what made those runs take ~110 h for
|
|
# 30 epochs.
|
|
#
|
|
# * hidden_dim 512 / 6 blocks per stage. The best-scoring rollout so far
|
|
# was hidden_dim 1024, but at 4x the trunk FLOPs of 512. 512/6 sits in
|
|
# the same weight class as the variants it will be compared against and
|
|
# leaves headroom to train it properly rather than cheaply.
|
|
#
|
|
# * dropout 0.0. Training set is ~5e8 steps against <1e7 parameters;
|
|
# capacity overfitting is not the binding constraint, and every recent
|
|
# run used 0.0.
|
|
#
|
|
# Known weak spots, now measured against this exact config rather than
|
|
# extrapolated from the pre-v0.3 field (analysis_341dfb14, best.pt @ epoch
|
|
# 50/50, full writeup: knowledge-base/experiments/
|
|
# giant-baseline-flow-ar-rollout-validation.md). Unlike every pre-v0.3
|
|
# checkpoint (which under-produced steps/event by 1.6-5x), this baseline
|
|
# OVER-produces steps/event by 1.32x (1.86e5 vs Geant4 1.41e5) and
|
|
# under-produces secondaries/event by 0.84x (5.97e4 vs 7.14e4) — the sign on
|
|
# steps flipped with the v0.3 autoregressive pivot, so don't assume it still
|
|
# undershoots. Secondary-species hallucination (zero photons, hallucinated
|
|
# `-14` muon antineutrinos) that broke every prior checkpoint is gone; the
|
|
# remaining species gap is a total absence of hadronic/nuclear secondaries
|
|
# (protons, neutrons, ion recoils), not miscalibration of the ones produced.
|
|
# Total deposited energy/event is +1.9% high but its event-to-event spread is
|
|
# ~16x too narrow (31 MeV vs Geant4's 491 MeV). Per-step deposited energy is
|
|
# the worst per-step marginal (KS 0.179 vs 0.004-0.071 for the others).
|
|
|
|
[meta]
|
|
# REQUIRED. Without it config.migrate_config reads this file as v0.2 and
|
|
# rewrites it from V02_FIXED_FACTS — silently forcing decoder = "one_shot",
|
|
# particle_type.target = "physical" and the v0.2 default sizes, while still
|
|
# passing validate_config.
|
|
config_version = 3
|
|
|
|
[conditioning]
|
|
# Physical-property MLPs rather than learned vocab embeddings: computable for
|
|
# any PDG code / material, which is what the held-out-species and
|
|
# held-out-material generalization comparisons need.
|
|
out_dim = 128
|
|
share_stages = false
|
|
|
|
# n_layers = 2 rather than the v0.3 default of 1: v0.2's conditioning MLP was
|
|
# always 2 deep (see _migration.V02_FIXED_FACTS), so this keeps the encoder
|
|
# identical to the architecture that produced the results cited above.
|
|
[conditioning.particle]
|
|
type = "physical"
|
|
emb_dim = 16
|
|
n_layers = 2
|
|
|
|
[conditioning.material]
|
|
type = "physical"
|
|
emb_dim = 16
|
|
n_layers = 2
|
|
|
|
[stage1_model]
|
|
generator = "flow"
|
|
hidden_dim = 512
|
|
n_res_blocks = 6
|
|
dropout = 0.0
|
|
|
|
[stage2_model]
|
|
# The v0.3 pivot: autoregressive in descending-energy order with a
|
|
# categorical species target, which is the agreed response to the 2026-08-03
|
|
# secondary-species failure. Flow (not the schema default wgan) so the
|
|
# baseline varies only the decoder relative to the best v0.2 result.
|
|
#
|
|
# COST, measured (RTX 4070, bs 4096, 10 ODE steps), not estimated — but see
|
|
# the row-compaction note below, which changes the INFERENCE side of this:
|
|
# training flow AR 29.5k samp/s vs flow one-shot 190.7k samp/s (6.5x)
|
|
# inference flow AR 8.5k step/s vs flow one-shot 68.7k step/s (8.1x)
|
|
# Accepted deliberately: one-shot is the configuration whose secondary
|
|
# species distribution failed, and that failure is what v0.3 exists to fix.
|
|
#
|
|
# Row compaction (landed after the above measurement): at inference,
|
|
# sample.sample_secondaries_ar used to loop `for k in range(k_max)`
|
|
# unconditionally — all 15 slots regardless of predicted n_sec — so a flow
|
|
# AR token cost k_max * steps = 150 stage-2 calls per physics step. It now
|
|
# drops a row from the batch the moment its own secondary count is
|
|
# exhausted, so the real inference cost is ~n_sec * steps stage-2 calls
|
|
# (this checkpoint's own rollout measured 0.382 secondaries/step — see
|
|
# giant-baseline-flow-ar-rollout-validation.md), not k_max * steps. A CPU
|
|
# micro-benchmark at that multiplicity (giant/model/history.py's
|
|
# hidden_dim=512/6-block shape, k_max=15, batch 512) measured 17.6-22.9x
|
|
# fewer wall-clock seconds for the AR loop alone (markov/attention history
|
|
# respectively) — directional only (CPU, synthetic n_sec distribution, not
|
|
# an end-to-end rollout); the 8.1x inference ratio above is now stale and
|
|
# should be re-measured on GPU via a real rollout + `eval_cost_per_step`
|
|
# once one is run against this checkpoint. Training cost (the 6.5x/29.5k
|
|
# figures) is untouched by this: teacher_forcing = "always" here never
|
|
# calls the AR sampler at train time (see [stage2_model.autoregressive]).
|
|
decoder = "autoregressive"
|
|
generator = "flow"
|
|
hidden_dim = 512
|
|
n_res_blocks = 6
|
|
dropout = 0.0
|
|
k_max = 15
|
|
|
|
[stage2_model.autoregressive]
|
|
history = "markov"
|
|
teacher_forcing = "always"
|
|
|
|
[stage2_model.particle_type]
|
|
target = "onehot"
|
|
# Decoupled from conditioning.particle.emb_dim (gitea #29). 32 classes + the
|
|
# "other" bucket keeps essentially all real secondary species out of "other"
|
|
# without making the head expensive.
|
|
n_classes = 32
|
|
other_policy = "sample"
|
|
|
|
[train]
|
|
epochs = 50
|
|
# Sized for ONE NVIDIA L40S on deepthought2 (46068 MiB; the box has two, and
|
|
# CLAUDE.md's shared-machine rule allows a single GPU). From a measured
|
|
# linear fit of this exact config's training step on the local RTX 4070:
|
|
# peak reserved MiB = 0.9736 * batch_size + 115
|
|
# so 36864 reserves ~36.0 GiB, i.e. 78% of the card, leaving ~10 GiB of
|
|
# headroom for fragmentation and the CUDA context. Throughput is already
|
|
# flat above bs~4096 on the 4070, so this is chosen for occupancy on the
|
|
# larger card, not for step efficiency — and it sits next to the 43008/32768
|
|
# of the runs lr = 3e-4 was proven at.
|
|
batch_size = 36864
|
|
lr = 3e-4
|
|
warmup_epochs = 3
|
|
weight_decay = 0.01
|
|
ema_decay = 0.9999
|
|
val_fraction = 0.1
|
|
num_workers = 4
|
|
seed = 0
|
|
# The marginal/KL pass is expensive (~5000 s on top of an epoch), so keep it
|
|
# to every 10th epoch; the cheap per-epoch val loss still runs every epoch.
|
|
validate_every = 10
|
|
validate_steps = 10
|
|
wandb = true
|
|
wandb_project = "giant"
|