Skip to content

Fasttrain - #12

Draft
danielkiv wants to merge 26 commits into
mainfrom
fasttrain
Draft

Fasttrain#12
danielkiv wants to merge 26 commits into
mainfrom
fasttrain

Conversation

@danielkiv

Copy link
Copy Markdown
Collaborator

No description provided.

danielkiv and others added 26 commits April 23, 2026 23:22
Window was built on CPU in __init__ and moved lazily on first forward, which
caused a host-device sync + reallocation on the first SSIM call and again on
any channel-count change. Registering as a non-persistent buffer lets the
module's own .to(device) / autocast move it alongside the model weights and
drops the first-call stall.

Also: ignore slurm_logs/ so job stdout/stderr don't clutter git status.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…taLoader knobs

Steady-state wallclock was dominated by forward/backward compute (profiled
via --profile-steps: ~85% of each step at bs=32). Applied the standard
A100 checklist for that regime:

- bf16 autocast preferred over fp16 on A100 (same memory savings, 8-bit
  exponent means no GradScaler and no skipped steps from loss-scale
  blowups). Falls back to fp16 + scaler only if bf16 unsupported.
- TF32 matmuls + cudnn.benchmark for free A100 throughput.
- channels_last memory layout on model + inputs (default on, opt-out via
  --no-channels-last) — tensor cores prefer it.
- torch.compile(mode="default") on by default (--no-compile to skip).
  reduce-overhead mode was tried first but its CUDA-graph retraces at
  every train<->val boundary made epochs multi-minute; default mode
  compiles once and survives mode switches.
- Fused AdamW (single-kernel optimizer step).
- DataLoader defaults bumped to num_workers=8, prefetch_factor=4 so the
  GPU doesn't stall between steps.
- Train DataLoader uses in_order=False (PyTorch 2.6+) so a worker with a
  warm NFS page cache can deliver before a worker stuck on a cold read;
  val stays ordered for deterministic metrics.
- Loss and component accumulators kept on-device to remove the per-step
  .item() syncs that were serialising the hot loop.

Also:
- --profile-steps N: sync-wrapped timing around data / h2d / forward /
  backward / optimizer, prints a breakdown and exits. Used to confirm
  where the time actually goes before committing to optimizations.
- --deterministic: bundles in_order=True, seeded DataLoader workers,
  cudnn.deterministic, TF32 off, torch.use_deterministic_algorithms, and
  CUBLAS_WORKSPACE_CONFIG. Slower than default mode; use for the canonical
  submission training run where bit-reproducibility matters.
- --compile / --no-compile and --channels-last / --no-channels-last
  exposed for per-run A/B.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- train.py entry lists the A100-tuned defaults (bf16, TF32, channels_last,
  torch.compile default mode, fused AdamW, worker/prefetch tuning,
  in_order=False) plus the --profile-steps and --deterministic flags.
- dataset.py entry mentions MultiPixelEmbeddingDataset (alpha+tessera fusion).
- losses.py entry notes the SSIMLoss window-as-buffer fix.
- Add slurm_logs/ to the tree (matches .gitignore).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the LightUNet core (DoubleConv, UpsampleBlock) in line with the
GN+GELU convention already used by ConvGNAct, the v35 MultiTaskPredictionHead,
ConvNeXtBlock, ASPP, and the Tessera compression stem. LightUNet was the
lone BN+ReLU holdout.

- Batch-size-independent: no running stats, so bs=32 training no longer
  suffers from noisy batch statistics.
- Mixed-precision safe: GroupNorm has no running stats to keep in fp32,
  eliminating the bf16/fp32 drift path that BatchNorm exposes under AMP.
- Identical behavior in train() vs eval(): no running-stats flip at
  val time, so val metrics more faithfully reflect training loss.
- Cleaner torch.compile: GN avoids the graph-break patterns that the
  running-mean/running-var updates trigger in compiled modules.
- Consolidates _group_count() to the top of the file (was duplicated).

Group counts are picked by the existing _group_count helper (first of
16, 8, 4, 2 that divides channels), matching the rest of the codebase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…odernization CLI

Three orthogonal levers, each gated behind its own flag so the baseline
and any combination can be A/B'd cleanly.

New model block (core/model.py, committed separately):
- BottleneckAttnBlock: Pre-LN transformer block (MHSA + MLP) on the
  LightUNet x4 bottleneck (1024 tokens x c4 channels, H/8 x W/8).
  Uses F.scaled_dot_product_attention so PyTorch auto-dispatches to
  flash-attn on A100. Off by default; enabled via LightUNet kwarg
  use_bottleneck_attn=True, surfaced through TesseraIoUFusionLightUNet
  and build_model.

New CLI flags (train.py):
- --bottleneck-attn: wires use_bottleneck_attn through build_model.
  Adds ~1.2M params to the 5.44M base (5.44 -> 6.62M).
- --augment / --no-augment (BooleanOptionalAction, default --no-augment):
  D4 dihedral group augmentation (4 rot90 x 2 hflip = 8 variants) applied
  on-the-fly per sample in the DataLoader worker. Val loader is always
  augment=False regardless. Default off so baseline runs are directly
  comparable; pass --augment to enable.

Dataset plumbing (core/dataset.py):
- _sample_d4 / _apply_d4 helpers: rot90 + hflip, geometrically exact (no
  interpolation / value change), so labels remain valid.
- augment kwarg threaded through PixelEmbeddingDataset,
  MultiPixelEmbeddingDataset, and LatentTokenDataset. Applied AFTER crop
  so the final training patch is transformed uniformly across image,
  target, and valid_mask.
- Per-sample CPU cost ~15-20 ms (rot90 + flip + ascontiguousarray on a
  192ch x 256x256 float32). Fully hidden behind num_workers prefetch;
  wallclock impact <5%.

Also:
- record bottleneck_attn and augment_d4 in training_params.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
D4 augmentation costs ~5% wallclock (CPU work hidden behind the GPU
step via prefetching) and effectively 8x's the unique sample views per
epoch — no step-count or optimizer-schedule change. Net win for every
run; baseline A/B still available via --no-augment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
train.py wraps the model in torch.compile (since 0481008), which adds
an _orig_mod. prefix to every state_dict key. predict.py's strict
load_state_dict then failed — job 80326 crashed on all 4 ablations with
"Error(s) in loading state_dict for TesseraIoUFusionLightUNet: Missing
key(s): alpha_unet.inc...; Unexpected key(s): _orig_mod.alpha_unet.inc..."

Strip the prefix at load time so inference doesn't require the compile
wrapper. Also pass use_bottleneck_attn through from training_params.json
so the instantiated predict model matches the trained architecture when
the attn flag was set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…osine

After the v48 ablation landed within 0.03 val_loss across {base, aug,
attn, attn_aug} (best: base @ 0.996), architecture-space tweaks are
exhausted — the gap is data/regularization, not capacity. This adds
three orthogonal levers gated behind flags, plus an A/B sbatch that
tests them as a bundle against the current recipe.

1. Aux Tversky on the main fraction output (core/losses.py, train.py):
   Under --loss-preset presence_centered the main Tversky was hardcoded
   off (presence head owned IoU). New --aux-tversky-weight re-enables it
   as a direct IoU surrogate on the submitted channels. Default 0.0
   preserves existing behavior.

2. Augmentation subgroup (core/dataset.py, train.py):
   --augment-mode {d4, flip_rot180, hflip, none}. The v48 aug runs
   regressed under full D4 — likely because AlphaEarth/Tessera features
   encode N-S / sun-angle priors that break under rot90. flip_rot180 is
   the Z/2xZ/2 subgroup (4 variants, orientation-preserving). Default
   d4 preserves the existing behavior set by 762a855.

3. EMA + cosine LR (train.py):
   ModelEMA tracks a step-wise shadow of params at --ema-decay (default
   0.9995). Val + best/last checkpoints use EMA weights. New
   --scheduler {plateau, cosine}; cosine anneals over --epochs with
   eta_min = lr * 1e-2. Both default off.

run_ab_modernize.bash: sbatch array {A: control 30ep/d4/plateau/no-ema,
B: 60ep/flip_rot180/cosine/ema/aux_tversky=0.5}, one H100 per task
(partition=gpu, gres=gpu:h100:1).

Also: consolidated _unwrapped_state_dict into a _load_state_dict_any
helper that works for both wrapped and unwrapped models (needed for
EMA's store/restore swap during validation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without warmup, ModelEMA(decay=0.9995) initialized the shadow from random
weights and updated at the asymptotic decay from step 1. After 5 epochs
(~315 steps at bs=32), 0.9995^315 ≈ 0.85 — so 85% of the EMA "weights"
were still random init. Run 80335_1 hit exactly this: train_loss tracked
A's run, but val_loss (computed on EMA shadow) read 7.74 → 5.64 over the
first 5 epochs vs A's 1.70 → 1.31, because validation was running on
mostly-random weights.

Schedule now is decay_t = min(target, (1+t)/(warmup+t)) with warmup=10.
At step 0 the shadow == live model exactly; by step 100 decay ≈ 0.92
(12-step avg); by step 1000 decay ≈ 0.991 (110-step avg); asymptotes to
the target. Self-adapts to short runs without the user picking a separate
warmup step count.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds {default, reduce-overhead, max-autotune, max-autotune-no-cudagraphs}.
Default unchanged. The interesting one is max-autotune-no-cudagraphs:
gets Triton kernel autotuning (typically +5-15% steady-state on H100 for
conv-heavy UNets) without the CUDA-graph-driven train<->val retracing
that made reduce-overhead/max-autotune unusable here (commit 0481008).

Compile-time goes from ~1 min (default) to ~5-10 min (autotune) for the
first epoch — net win for runs >=30 epochs, neutral for shorter ones.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
--compile now bundles: max-autotune-no-cudagraphs mode, persistent
Inductor cache at ~/.cache/embed2heights_inductor, dynamic=None with
mark_dynamic on the batch axis (one graph covers full + ragged batches,
no dropped samples), and channels_last memory layout. Drops --compile-mode
and --channels-last since there was no external caller setting them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
--compile now also enables CUDA AMP (bf16 on A100/H100, fp16 fallback).
In practice these two were always flipped together; one less knob.

Remove BottleneckAttnBlock + --bottleneck-attn. The v48 ablation sweep
showed attention was neutral-to-worse on both train and val loss in
both the no-aug and +aug arms — not even fitting training data better.
Deletes ~45 lines from core/model.py and one CLI flag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two orthogonal mechanisms behind a pair of CLI flags, both off by default
so the existing recipe is untouched:

1. --fusion-mode gated_feature

   New TesseraIoUFusionGatedLightUNet promotes Tessera from a 16-ch
   presence side-stream to a peer feature stream at base_ch width. A 1x1
   spatial gate over concat(alpha_feat, tessera_feat) mixes them as
        fused = G * alpha_feat + (1-G) * tessera_feat
   Gate conv is zero-init with +bias so sigmoid(b) ≈ 1 at step 0 →
   identical t=0 behavior to alpha-only; Tessera contribution is learned
   as a residual correction (mirrors the existing presence_delta_head /
   softplus-Δ patterns elsewhere in the codebase). Presence-logit
   residual path is preserved when tessera_presence_ch > 0.

   Targets the failure mode where the legacy fusion only touches
   presence logits — height and fraction heads never see Tessera signal.

2. --uncertainty-weighting

   New UncertaintyWeightedLoss wrapper around ImprovedCompositeLoss
   replaces hand-tuned aux/structure/presence_tversky scalars with three
   learned log-variances (Kendall, Gal & Cipolla 2018) over the natural
   task groups: presence (BCE+Tversky), fraction (MAE+Tversky), height
   (boost+aux). Total = Σ 0.5·exp(-s_i)·L_i + 0.5·s_i.

   Optimizer now sees criterion.parameters() so the log-vars get
   trained alongside the model. grad_clip widened to all optimizer
   param groups (was model.parameters() only).

predict.py: pulls fusion_mode from training_params.json. Drops the
stale use_bottleneck_attn kwarg (the param was removed in deb6d6c).

Both flags compose. Either can be tested against champion N
(0.4644 val) on its own to isolate effect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three orthogonal upgrades to the GMU-style fusion that produced the new
champion (uw_gated_F = 0.5072), all behind new CLI flags so the existing
recipe is unchanged when defaults are kept.

* `--gate-mode rich`  — replaces the linear-then-sigmoid gate
  (Conv1x1 → σ) with a 2-layer MLP (Conv1x1 → GN → GELU → Conv1x1 → σ).
  The GMU paper (Arevalo et al. 2017, §5.1) explicitly recommends a
  non-linear gate to model "more complex interactions between
  modalities" beyond a single hyperplane.

* `--gate-untied`  — independent gates G_AE and G_TES instead of the
  tied G/(1-G) form. Mirrors the multimodal GMU (Figure 2a) instead of
  the bimodal Figure 2b. Both gates start at sigmoid(±4) so initial
  fused output ≈ AE-only — same warm-start property as before.

* `--modality-dropout`  — per-sample inverted dropout on Tessera
  features during training. Forces the AE branch to remain
  self-sufficient and prevents the gate from collapsing onto Tessera
  on noisy test patches.

Internals: factored gate construction into `_build_fusion_gate` and
`_apply_fusion_gate` helpers; modality dropout via `_maybe_drop_modality`.
The existing TesseraIoUFusionGatedLightUNet is rewired to use these
helpers, with new constructor kwargs `gate_mode`, `gate_untied`,
`modality_dropout`. predict.py reads the values from
training_params.json so the architecture is reconstructed exactly.

Each flag is independently testable; expected to compose cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The single-scale gated_feature fusion (champion uw_gated_F = 0.5072)
applies one fusion policy at the decoder output. But water (large,
contiguous), building edges (sharp, fine), and tree heights (mid-scale
phenology) each benefit from different fusion behavior. A single gate
at one scale cannot optimize for all of them simultaneously.

This commit adds `--fusion-mode pyramid_gated`: a parallel Tessera
encoder pyramid that produces features at the same 4 scales as the AE
encoder's intermediates, with one gate per scale. Each gate's
warm-start is AE-only (same bias trick). Each scale learns its own
fusion policy — coarse scales likely open toward Tessera for
water/seasonal context, fine scales likely keep AE for sharp edges.

* New `TesseraPyramidStem` mirrors LightUNet's encoder so its outputs
  match channel/spatial shape with [x1,x2,x3,x4].
* New `TesseraIoUFusionPyramidGatedLightUNet` runs the AE encoder and
  Tessera pyramid in parallel, fuses per-scale, then runs the AE
  decoder using fused features as both bottleneck and skip connections.
* Composes with the rich/untied/dropout knobs from the previous commit
  via the same `--gate-mode`, `--gate-untied`, `--modality-dropout`
  flags. So it can be tested as multi-scale + simple gate (isolating
  the multi-scale effect) or stacked with rich gating.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the file from git tracking. The local copy is preserved at the
working-tree path (the A/B modernize sweep is closed, so the driver is
no longer worth carrying as a tracked artifact).
…height/data

Tracks the 12 sbatch drivers used across recent experiments (A/B/C
modernize, ablation, baselines, best-of, EDA, the gated_feature →
seed-bag → rich/pyramid sweep). Excludes run_ab_modernize.bash which
was intentionally untracked earlier.

All drivers now read training data from the canonical shared path
/projects/bcrm/emb2height/data/train (was the per-user
tools/data/embed2heights/data/train symlink). The new path is the
official challenge data location and contains all four GeoFM
embeddings (alphaearth, tessera, terramind_s1/s2, thor_s1/s2) plus
labels and the catalog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the complete pipeline from group-fold training to a leaderboard
submission archive. Driven by Dingqi's group_code_5fold_seed42 splits
(2-letter geographic codes, train/val groups disjoint per fold), which
gave a much more honest estimate of leaderboard score than the random
val split.

Honest 5-fold result on the gated_feature recipe:
  fold 0: 0.4766  fold 1: 0.5224  fold 2: 0.4979
  fold 3: 0.4986  fold 4: 0.5041
  mean ± std = 0.4999 ± 0.0147

Compared with prior baseline's fold-0 score of ~0.42, gated_feature
adds ~+0.057 on the most-leakage-honest fold — strong evidence the
recipe is leaderboard-relevant.

Files:
  - splits/group_code_5fold_seed42/  : the 5 disjoint-group splits
  - run_gated_F_5fold.bash           : array job that trains each fold
  - run_gated_F_5fold_eval.bash      : per-fold predict + evaluate
  - tools/ensemble_predictions.py    : averages N fold predictions
                                       into a single output dir,
                                       verifies the 946-file count
  - run_submission_ensemble.bash     : test-set predict for each fold
                                       → ensemble → zip submission

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Concise record of how we got from N (0.4644) to uw_gated_F (0.5072 on
random val, 0.4999 ± 0.0147 on 5-fold group-stratified). Captures:

- The single architectural change that mattered (GMU-style gated
  fusion of AE↔Tessera at the trunk, with zero-init residual warm-start)
- Per-axis deltas showing the gain landed on the heads that never
  received Tessera signal under the old fusion (water IoU + heights)
- What got tried in the same round and was retired (uncertainty
  weighting, rich/untied/dropout gate variants, multi-scale gating)
- Compile setup that enabled the iteration cadence
- Augmentation findings — closed across multiple commits, recipe pinned
  to --no-augment
- Honest leaderboard estimate from group-stratified folds and the
  submission ensemble pipeline

Cross-references commits and existing reports without duplicating them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generalizes the bimodal gated_feature fusion (uw_gated_F = 0.5072
random val, 0.4999 ± 0.0147 5-fold) to the k=4 untied multimodal-GMU
form (Arevalo et al. 2017, §3.1, Figure 2a):

    F_i, i ∈ {AE, TES, TM, THOR}        per-modality features at
                                         (B, base_ch, 256, 256)
    N_i = GroupNorm(F_i)                 per-modality normalization
    H   = concat(N_AE, N_TES, N_TM, N_THOR)
    G_i = sigmoid(W_i · H + b_i)         independent gates
    F_fused = Σ_i G_i ⊙ F_i

Anchored warm-start preserves the bimodal hygiene:
    b_AE   = +4 → G_AE  ≈ 0.98 at t=0    (anchor open)
    b_TES  = -4 → G_TES ≈ 0.02 at t=0    (closed, residual)
    b_TM   = -4                          (closed, residual)
    b_THOR = -4                          (closed, residual)

So fused at t=0 ≈ 0.98·F_AE — model is bit-near-equivalent to AE-only
at init. Tessera, TerraMind, THOR each learn to seep in as residual
corrections, same discipline that produced the 2-modality breakthrough.

Modern improvement vs vanilla GMU: per-modality GroupNorm on the gate's
concat input. Prevents one stream's feature magnitudes from drowning
gate logits. (Considered DynMM-style hard one-hot routing — Xue &
Marculescu 2023 — and decided against for v1: dense prediction with
4 mostly-correlated modalities benefits more from soft averaging than
per-instance routing decisions.)

TerraMind and THOR are token-level (16x16x768 each, S1 + S2) — added
TokenUpsamplingStem that bilinear-upsamples to 256x256 with a small
convolutional refinement, kept lightweight by doing the channel
projection BEFORE upsample so we never materialize 16x the tokens at
full channel width.

Added:
  - core/model.py  : TokenUpsamplingStem, MultiGFMGatedLightUNet
  - core/dataset.py: find_multi_gfm_file_pairs (+ embedding-only),
                     MultiGFMDataset (returns imgs as 2-tuple
                     (pixel_imgs, token_imgs))
  - train.py       : --terramind-s{1,2}-train-emb-dir, --thor-s{1,2}
                     -train-emb-dir flags; tuple-aware dataloading
                     (channels_last, mark_dynamic, h2d transfer);
                     --model-type multi_gfm dispatch
  - predict.py     : matching plumbing for label-free test inference
  - run_multi_gfm.bash : initial training run on fold_0 (hardest
                     group-fold, our most honest signal)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ntion

Three orthogonal architectural moves drawn from the GeoFM blueprints,
each behind its own CLI flag, all backwards-compatible.

* Hierarchical bipartite multi-GFM (`--fusion-mode hierarchical` with
  `--model-type multi_gfm`). Replaces the flat 4-way Arevalo Figure 2(a)
  GMU with a 3-stage tree of bimodal Figure 2(b) GMUs grouped by the
  pretraining temporal scope: stage 1 fuses AE↔Tessera (annual) and
  TM↔THOR (single-epoch); stage 2 fuses the two grouped streams. Each
  gate uses the same warm-start as the bimodal champion (anchor open,
  secondary closed at init), so the model is bit-near AE-only at t=0.

* Dirichlet auxiliary loss (`--dirichlet-weight`). Adds a 4-channel
  softplus head producing concentration parameters α for {bld, veg, wat,
  background}; the Dirichlet NLL of the fractional simplex target is
  added to the total loss with the given weight. Models the simplex
  constraint that BCE+MAE+Tversky doesn't enforce. The dirichlet head
  is always built (~600 params, harmless when weight=0) so checkpoints
  remain compatible across runs. Pads bvw[<0] and bg=clamp(1-Σ,0) to a
  valid simplex inside the loss.

* Cross-task spatial attention (`--cross-task-attention`). Adds a
  per-pixel sigmoid attention map computed from the land-cover
  fractions, multiplied into the height_trunk features as a residual
  gate (zero-init weights → no-op at t=0). Forces sharp segmentation
  boundaries to gate the height feature map at training time,
  mitigating regression blur — different mechanism than the existing
  FiLM (which produces per-channel scale+shift instead of per-pixel
  attention).

Plumbing: enable_dirichlet (always True) and enable_cross_task_attn
flow through MultiTaskPredictionHead via the model wrappers
(TesseraIoUFusionGatedLightUNet, MultiGFMGatedLightUNet,
HierarchicalMultiGFMGatedLightUNet). predict.py reads
cross_task_attention from training_params.json so inference matches.

Submission driver run_tier1_2_fold0.bash launches a 3-task array on
fold 0 (the hardest group-fold, our most leakage-honest signal)
covering hierarchical, dirichlet, and ctaskattn — three parallel
isolations against the gated_feature champion (0.4766 on fold 0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds three sections to the GMU breakthrough log:

* Cross-task spatial attention (`ctaskattn_v1`): one extra piece in
  the multi-task head — a per-pixel sigmoid attention map from the
  land-cover fractions, multiplied into height_trunk features as a
  residual gate (zero-init → no-op at t=0). Fold-0 result: 0.4810
  (+0.0044 over gated_feature champion at 0.4766), gain concentrated
  on iou_water (+0.026). Within seed noise on a single fold but every
  per-axis number moved correctly — worth a 5-fold confirmation.

* What didn't pan out in the same round: hierarchical bipartite GMU
  (-0.010, grouping prior over-constrained the model), Dirichlet
  auxiliary loss (-0.014, alpha values diverged but predictions
  didn't improve). Both closed.

* "How to run these models" — concrete commands for training, label-
  free test prediction, paired eval, and a per-variant flag table
  covering ctaskattn, gated_feature, multi_gfm, hierarchical, and
  dirichlet variants. Mirrors the auto-loaded config behavior of
  predict.py so users don't have to repeat training-time flags.

Submission pipeline pointers updated for the new champion candidate
(ctaskattn) — same scaffold as gated_F's 5-fold ensemble, one extra
flag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Older checkpoints (e.g. multi_gfm_v1, trained before commit b689aff)
predate the dirichlet_head and cross_task_attn modules now built into
MultiTaskPredictionHead. Loading them under strict=True fails on
"Missing key(s)" for those heads, even though the heads are unused at
inference.

Switches model.load_state_dict to strict=False, then explicitly checks
the missing keys: only "dirichlet_head" / "cross_task_attn" prefixes
are tolerated. Anything else is treated as a critical mismatch and
raised. Unexpected keys remain a hard error.

Forward-compatible: future optional heads can be added to the
ignorable list as new architectural variants land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Predict + evaluate the Tier 1+2 fold-0 experiments (multi_gfm_v1,
hierarchical_v1, dirichlet_v1, ctaskattn_v1) against the
gated_feature champion. Pairs each multi_gfm experiment with all 4
GeoFM test embedding directories; pairs each tessera_iou_fusion
experiment with the AE+Tessera-only test paths. Evaluates all 5 runs
(including gated_F_fold0) under fold 0's val groups so deltas are
directly comparable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two slurm drivers that mirror the gated_feature submission flow but
for the cross-task-attention recipe:

  - run_ctaskattn_5fold.bash       : array job training folds 1-4
                                     (fold 0 is reused from ctaskattn_v1
                                     since the recipe is identical)
  - run_ctaskattn_submission.bash  : predicts each fold checkpoint on
                                     the test set, averages, zips to
                                     runs/submission/ctaskattn_5fold_ensemble.zip

Submit chained:
  TRAIN=$(sbatch --parsable run_ctaskattn_5fold.bash)
  sbatch --dependency=afterok:$TRAIN run_ctaskattn_submission.bash

Honest score expectation if the +0.0044 fold-0 delta holds:
  gated_feature 5-fold mean: 0.4999 ± 0.0147
  ctaskattn 5-fold expected: ~0.504 + bagging gain (~0.005) → ~0.51

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes every run_*.bash driver from git tracking. The local files are
preserved at the working-tree paths — they just stop being part of the
repo. Per user preference, no .gitignore entries; the scripts simply
won't be re-added unless someone explicitly stages them.

Untracked files: run_ab_eval, run_ablation_eval, run_baselines,
run_best, run_c_noaug, run_ctaskattn_5fold, run_ctaskattn_submission,
run_eda, run_gated_F_5fold, run_gated_F_5fold_eval, run_gated_rich,
run_multi_gfm, run_pyramid_gated, run_quad_eval, run_submission_ensemble,
run_tier1_2_eval, run_tier1_2_fold0, run_uw_gated,
run_uw_gated_F_seeds, run_uw_gated_eval.

Note: log entries that hyperlink to these scripts (e.g.
GMU_FUSION_BREAKTHROUGH.md) point to local paths; those references
remain valid for any reader running on the cluster.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant