Two same-seed runs now produce bit-equal eval_summary.json, alpha_rl_train_summary.json,
and diag.jsonl (modulo wall-clock elapsed_s). The 5-phase falsification chain landed:
Phase 2 PER tree-rebuild: __threadfence is NOT a grid-wide barrier; multiple blocks
raced across sum-tree levels. Fix: Grid=(1) Block=(1024) + __syncthreads
in rl_per_tree_rebuild.cu.
Phase 2.3 cuBLAS GEMM_DFALT + TF32 default-math allowed split-K non-deterministic
accumulation at 3 sites. New crates/ml-alpha/src/cublas_determinism.rs
applies CUBLAS_PEDANTIC_MATH via FOXHUNT_DETERMINISTIC env toggle
(0=TF32 prod, 1=PEDANTIC dev default, 2=DEFAULT_MATH control).
Phase 2.6 Two bugs surfaced sequentially in the backward kernel chain:
(1) rl_iqn_tau_cos_features had a multi-block r/w race on prng_state[batch]
— all N_TAU=32 blocks read seed; only tau_idx==0 wrote back; no
inter-block barrier. Fix: split into READ-ONLY rl_iqn_tau_cos_features
+ new sibling rl_iqn_advance_prng_state launched on same stream
(kernel-launch ordering = grid-wide barrier).
(2) OutcomeHead::new called near_zero_xavier without scoped_init_seed,
falling back to time+thread-id RNG. Stayed dormant until first done
event activated non-sentinel labels and divergent weights flowed via
grad_h_t_outcome into encoder gradient. Fix: add seed param + install
scoped_init_seed(dqn_seed.wrapping_add(0x0CE0)) guard.
Validation (./scripts/determinism-check.sh --quick, RTX 3050, b=128, 200+50 steps):
- All 200 rows of checksums.* leaves match (rel-tol 1e-5, abs-tol 1e-7)
- eval_summary.json, alpha_rl_train_summary.json byte-equal between runs
- diag.jsonl byte-equal modulo elapsed_s
- Eval pnl identical run-A vs run-B at seed 42
Pre-fix baseline (Phase 2.5 measurement): same-seed eval pnl spread $450k
($187k vs -$261k). Post-fix: $0 spread.
Speed cost: ~1.5ms/step amortised; ~10-15% slower than TF32 production
(PEDANTIC tax — acceptable in dev, toggle to FOXHUNT_DETERMINISTIC=0 for prod).
Mapped-pinned discipline: all 11 NEW memcpy_dtoh sites in diagnostic dump methods
+ per-step checksum readback use a new pub(crate) helper
read_slice_d_into<T: Copy>(stream, src, dst) — MappedRecordBuffer + raw
memcpy_dtod_async + raw_stream_sync + volatile read. Generic over T (f32, f64,
i32, u32, u8). Satisfies feedback_no_htod_htoh_only_mapped_pinned + hook guard.
Bundled Tier 1.5 fast-dev-cycle infrastructure (spec
docs/superpowers/specs/2026-06-02-fast-dev-cycle.md):
- scripts/local-mid-smoke.sh b=128, 2000+500, ~10min on RTX 3050
- scripts/determinism-check.sh runs mid-smoke twice, diffs checksums
- scripts/tier1_5_verdict.py behavioral kill verdict
- AdamW checkpoint save/load (crates/ml-alpha/src/trainer/optim.rs)
- IntegratedTrainer checkpoint save/load (resume from checkpoint)
- 15 Phase 1 checksum leaves in build_diag_value
- Env-gated dump methods (FOXHUNT_DETERMINISM_DEBUG_PER/MAMBA2/RL/BACKWARD)
for future divergence-chasing — never run in production
Documentation:
- docs/superpowers/specs/2026-06-02-determinism-foundation.md
- docs/superpowers/specs/2026-06-02-fast-dev-cycle.md
- docs/superpowers/plans/2026-06-02-determinism-foundation-implementation.md
- docs/superpowers/notes/2026-06-02-determinism-phase{1,2,2.2,2.5,2.6}-*.md
- Adjacent specs/plans/notes from the analytical chain that surfaced determinism
as the load-bearing blocker (eval-summary, eval-boundary, regime-observer,
multi-head policy, regime-invariance, Phase 3 IQN-complement post-mortem)
Unlocks: every controller / architecture / reward-shaping A/B from this commit
onward attributes outcome differences to the change, not random-init kernel-race
drift cascading through training x eval LOB-sim trajectories. The eval-collapse
investigation (pearl_reward_signal_anti_aligned_with_pnl, multi-head spec,
regime-invariance spec) is now testable with trustworthy verdicts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
10 KiB
Next Session — Multi-Head Policy Implementation Plan
Date: 2026-06-02 (handoff document) Status: Ready-to-execute plan for next session Linked specs:
- Primary:
docs/superpowers/specs/2026-06-02-multi-head-policy-with-r-multiple.md - Parent:
docs/superpowers/specs/2026-06-02-regime-invariance-four-interventions.md - Existing #1 plan:
docs/superpowers/plans/2026-06-02-regime-invariance-implementation.mdBranch at handoff:ml-alpha-regime-observerat SHA63fc16f17(v5.2) Memory snapshot:pearl_grwwh_eval_catastrophic_collapse(just saved)
§0. State at handoff
alpha-rl-grwwh verdict (just landed):
- train +$481M wr 0.312 hold 15.1 — strong train signal
- eval −$160.5M wr 0.264 pf 0.83 sharpe −1.70 — catastrophic generalization failure
- 3rd consecutive eval-collapse on single-policy + Phase 5 hold-bonus architecture
Empirically validated: the scaffold-fade approach (v4/v5/v5.1/v5.2) cannot fix the eval generalization gap because the gap is architectural — single policy network with Phase 5 reward shape over-fits to train regime.
Direction confirmed: ship multi-head policy + R-multiple reward together per the spec.
§1. Pre-flight before any code
§1.1 sp-critical-reviewer on the multi-head spec FIRST
The spec at 2026-06-02-multi-head-policy-with-r-multiple.md has been written with the FIVE BLOCKERs from the previous v6 spec review explicitly addressed. Before any implementation:
- Dispatch sp-critical-reviewer agent on the multi-head spec
- Reviewer should especially verify:
PolicyHeadstruct layout incrates/ml-alpha/src/networks/policy_head.rs(referenced but not personally verified in spec §9)EXPECTED_LEAVES = 679value referenced in §3.4 still matches current test- No prior MoE attempts in foxhunt (grep for existing multi-head code)
- The aux-prior-loss math (§1.4) doesn't break PPO surrogate convexity
- If reviewer BLOCKS, fix and re-review BEFORE implementation
- If reviewer FIX-AND-PROCEED, apply edits then start §2
§1.2 Verify the per-step diag / eval_summary discrepancy bug
Per pearl_grwwh_eval_catastrophic_collapse: at eval step 4863 the per-step diag showed +$74M but eval_summary showed −$160M. This is a $234M discrepancy. Investigate BEFORE shipping a new run that relies on per-step diag for verdict tracking:
- Pull diag.jsonl rows 22000-22826 from PVC for grwwh (
/feature-cache/alpha-rl-runs/63fc16f17/fold1/diag.jsonl) - Check if the last ~137 eval steps show a catastrophic per-step pnl collapse — if YES, per-step is honest, the agent really did crash at the end
- If NO collapse visible in per-step → the per-step diag's
realized_pnl_cum_usdaccumulator differs from eval_summary'stotal_pnl_usdcalculation — file a separate bug/diagnostic spec
This investigation gates whether per-step diag can be trusted for kill-criteria in §3.1 falsification gates. The reviewer-mandated kill gates rely on per-step signal being authoritative.
§2. Decide bundling: ship #1 + multi-head together OR sequentially
Option A: Bundle (single commit, v6.0)
- R-multiple reward + multi-head policy in one commit
- Faster to verdict (one cluster smoke instead of two)
- Higher risk: if eval fails, can't isolate which intervention failed
- Aligns with the §0.5 finding that #1 alone is insufficient
Option B: Sequential (#1 first, then multi-head)
- Ship #1 R-multiple first, get cluster verdict
- IF #1 eval positive: stop (cheapest win)
- IF #1 eval negative: ship multi-head on top
- More cluster smokes burned
- Cleaner attribution
Recommendation: Option A (bundle)
The grwwh result rules out #1-alone as sufficient. v5.2 with Phase 5 still active failed eval; #1 just removes the contamination (R-multiple replaces hold-bonus). A clean single policy on R-multiple alone would likely still over-fit to fold-1 trend regime — same failure mode, different reward shape.
Multi-head is the architectural fix. Bundle.
§3. Implementation ordering (assuming Option A bundle)
Phase A: Foundation (sequential, atomic commit)
-
Slot retire + reallocate (
crates/ml-alpha/src/rl/isv_slots.rs)- Delete:
RL_SURFER_SCAFFOLD_WEIGHT_INDEX(753),RL_SURFER_BREAK_EVEN_WR_INDEX(754),RL_SURFER_K_SHARPNESS_INDEX(755),RL_SURFER_WARMUP_TRADES_INDEX(756) - Allocate (R-multiple, per existing #1 plan):
RL_ATR_EMA_INDEX(753),RL_R_MULTIPLE_CLAMP(754),RL_ATR_EMA_ALPHA(755),RL_ATR_FLOOR(756) - Allocate (multi-head, per multi-head spec §2):
RL_POLICY_NUM_HEADS_INDEX(757),RL_POLICY_GATING_ENTROPY_FLOOR_INDEX(758),RL_POLICY_HEAD_ENTROPY_FLOOR_INDEX(759),RL_POLICY_AUX_PRIOR_BETA_INDEX(760) RL_SLOTS_END = 761
- Delete:
-
Delete obsolete code
- Delete:
crates/ml-alpha/cuda/rl_surfer_scaffold_controller.cu - Delete: surfer-scaffold launch + module load in
crates/ml-alpha/src/trainer/integrated.rs(6 touchpoints per coder analysis) - Delete: surfer-scaffold bootstrap entries in
with_controllers_bootstrapped - Delete: surfer-scaffold registration in
crates/ml-alpha/build.rs
- Delete:
-
New kernels (5 total)
rl_atr_ema_update.cu— ATR EMA producer (R-multiple input)rl_policy_mixture_forward.cu— K head logits + gate → mixture logits via LogSumExprl_policy_mixture_backward.cu— backward through mixturerl_policy_aux_prior_loss.cu— Σ_k g_k × β × KL(p_k ∥ p_prior_k) + gradient- Modify:
rl_fused_reward_pipeline.cuPhase 5 — replace hold-bonus withr = clamp(Δpnl / max(atr_at_entry, floor), ±5)
Phase B: Rust integration
-
Network refactor (
crates/ml-alpha/src/networks/policy_head.rs)- PolicyHead struct: single Linear → Vec for K heads + Linear for gating
- Adam optimizer states per tensor
- Forward pass through new mixture kernels
- Backward pass with per-head + gate gradients
-
Trainer integration (
crates/ml-alpha/src/trainer/integrated.rs)- Allocate K head weights at construction (init with action-bias per spec §1.3)
- Allocate gating weights
- Add
unit_atr_at_entry_dper-batch buffer (R-multiple capture) - Wire 5 new kernel launches in
step_with_lobsim - Bootstrap new ISV slots (β=0.05, entropy floors per spec)
Phase C: Diag + tests
-
Diag emission (
build_diag_value)policy.head_count = Kpolicy.head_k_entropy[k]for k in 0..Kpolicy.gate_entropypolicy.gate_probs[k]for k in 0..Kpolicy.head_pairwise_kl[i,j]for diagnosticrewards.atr_ema_usdrewards.r_multiple_last- EXPECTED_LEAVES: 679 → 679 + 7 (multi-head) + 2 (R-multiple) − 1 (drop pure_pnl_mode/surfer_scaffold_weight) = 687
-
Test migration
tests/reward_alignment_invariants.rs: I1-I3 → assertrewards.atr_ema_usd ≥ 0andrewards.r_multiple_last ∈ [-5, +5]. Delete surfer_scaffold_weight assertions.- New test:
tests/multi_head_policy_invariants.rs- I1: gate_probs[k] in [0, 1] for all k, Σ = 1
- I2: gate_entropy ≥ entropy_floor (with epsilon)
- I3: head_pairwise_kl > 0 (at least one pair distinct)
- I4: each head's entropy ≥ head_entropy_floor
Phase D: Local validation + cluster
-
Local validation
- cargo check + cargo build --release
cargo test reward_alignment_invariants multi_head_policy_invariants --ignored --nocapture- 200-step local smoke (matches existing test pattern)
- GPU compute-sanitizer racecheck on the new kernels
-
Cluster smoke
- Pin SHA via argo-alpha-rl.sh --sha
- L40S, fold-1, b=1024, 20k train + 5k eval
- Apply §4.1 per-step kill criteria from multi-head spec
- Apply §4.2 verdict gates
§4. Failure-mode adaptive replanning
If multi-head + R-multiple cluster smoke ALSO produces negative eval:
Diagnostic step 1: per-regime eval stratification per spec §4.3. If specialization spread < 0.02 → heads didn't diverge → aux prior β too low. Boost β to 0.15 and retry.
Diagnostic step 2: if specialization shows but eval still negative → the heads' priors are wrong (e.g., trend+reversion+flat doesn't cover the actual eval-period regime). Try different prior set (e.g., add momentum-breakout head).
Diagnostic step 3: if priors are diverse and eval still negative → architectural ceiling hit. Pivot to:
- Cross-fold training (#3 from parent spec) — train on mixed folds 0/1/2
- Regime features wired into encoder (#2 from parent spec)
- Behavior cloning warm-start from a profitable rule-based bot
Termination: if 3 multi-head variants all fail eval, the conclusion is the architecture cannot extract alpha from foxhunt's MBP-10 dataset at b=1024 with 20k step budget. Different data scale or different model architecture required.
§5. Discipline reminders (DO NOT VIOLATE)
feedback_no_feature_flags— surfer-scaffold deletion in SAME commit as new code, no togglesfeedback_single_source_of_truth_no_duplicates— slot reuse 753-756 with rename, no v2_ suffixesfeedback_local_smoke_before_cluster— Phase D step 8 BEFORE step 9, no exceptionsfeedback_kill_runs_on_anomaly_quickly— kill at first anomaly per spec §4.1, don't wait for verdictfeedback_no_atomicadd— all new kernels use block-tree-reducepearl_argo_branch_ref_trap_strikes_again— cluster command pins exact SHA, NOT branch tipfeedback_going_in_circles_pattern— if 2 multi-head variants in a row fail with new failure modes, STOP iterating and reconsider foundation
§6. Session opening checklist (for the next session)
- Read
pearl_grwwh_eval_catastrophic_collapse(just saved) - Read
docs/superpowers/specs/2026-06-02-multi-head-policy-with-r-multiple.md - Read
docs/superpowers/specs/2026-06-02-regime-invariance-four-interventions.md(parent) - Read this plan
- Dispatch sp-critical-reviewer on multi-head spec (per §1.1)
- Resolve reviewer findings
- Investigate per-step / eval_summary discrepancy (per §1.2)
- Decide bundling (per §2) — likely Option A
- Begin Phase A implementation (per §3)
Total estimated session time: 4-6 hours for full bundle, cluster smoke runs overnight if needed.