Closes the TrainingPersist loop for the regime defense per the
KELLY_F_SMOOTH precedent (pearl_kelly_cap_signal_driven_floors).
Three layers:
(1) Per-step: alpha_regime_vol_update_kernel tracks min(vol_obs > 1e-12)
in new slot 553 (REGIME_VOL_OBS_MIN_INDEX). Filtered against artifact-
zero observations from stationary snapshots where curr == prev.
(2) Per-cell: stacker_threshold_controller_update gains a fifth branch
that reads vol_ref (slot 550) at cell-end, computes
`target = floor_target_ratio × vol_ref`, slow-EMAs the floor anchor
(slot 552) toward the target with rate `floor_update_rate` (0.1 per
cell). Subfloor 1e-12 inside the kernel guards against the anchor
collapsing to zero.
(3) Per-invocation: alpha_baseline reads
`config/ml/alpha_baseline_state.json` at startup and seeds slot 552
from the `regime_vol_ref_floor` field. At end of main(), the learned
floor is written back via tmp+rename atomic write so concurrent
walk-forward invocations see a consistent file. Matches the
cross-fold-persistent shape of KELLY_F_SMOOTH.
Block extended to 15 slots (539..=553). Smoke + kernel unit test
pass -1/-1 for the new floor-controller indices (backward compat).
Walk-forward CV verdict (Q1 fxcache, 3 sequential folds):
iteration fold-A fold-B fold-C mean ± SD
pre-defense (no regime) +91.52 -21.44 +46.74 +38.94 ± 56.88
hardcoded 1e-9 floor -19.77 +65.04 +6.45 +17.24 ± 43.42
learned floor (0.5 × cell_min) +74.78 -12.72 +8.80 +23.62 ± 45.59
learned floor (0.1 × vol_ref) -19.53 -26.51 +15.46 -10.20 ± 22.49
Controller infrastructure is structurally correct (loop closes, floor
persists across invocations, kernel + disk + ISV all roundtrip). The
TUNING is data-dependent — single-quarter CV doesn't have enough
regime diversity to anchor the floor against. Multi-quarter fxcache
validation is the next step (built cluster-side on the 9-quarter
2024-Q1..2026-Q1 ES futures dataset, downloaded as a single artifact
for local CV).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two coupled fixes to the vol-EMA regime detector exposed by walk-forward
CV after all features were promoted to always-on:
(1) Bootstrap window for vol_ref (slot 551 = REGIME_VOL_REF_SAMPLES)
- Replace the Pearl-A "first observation replaces directly" bootstrap
with a running mean over the first N=100 vol_ema observations, then
switch to β-tracking. For IID observations the running-mean estimator
has variance σ²/N — a 100-sample mean is 10× less noisy than the
single-shot replace.
(2) Permanent floor on vol_ref (slot 552 = REGIME_VOL_REF_FLOOR)
- The bootstrap alone exposed the asymmetric deadband-deadlock: if
vol_ref converged to a tiny value during a calm initial stretch,
vol_ref / vol_ema fired the moment any realistic vol resumed and
Kelly stayed trapped at regime_scale_floor=0.25 forever. Floor
lives in ISV slot (TrainingPersist) with a hardcoded 1e-12 sub-floor
inside the kernel as numerical-underflow guard.
- Host seeds slot 552 with 1e-9. Future controller kernel will refine
this from observed cell-level vol minima with cross-fold persistence.
Walk-forward CV on Q1 fxcache (3 folds, window=700K, train_frac=0.6):
cost fold-A fold-B fold-C mean ± SD
0.00 -19.77 +65.04 (100%) +6.45 +17.24 ± 43.42
Fold B turnaround is the headline: -47.64 (bootstrap-only) → +65.04
(bootstrap + floor) confirms the floor is the load-bearing fix.
Cross-fold std-dev compressed 24% at cost=0; mean dropped from +38.94
(pre-defense) to +17.24 (with defense). Classic mean/variance trade.
Block extended to 14 slots (539..=552). Kernel sig: vol_ref_floor
moved from f32 scalar to vol_ref_floor_index i32, so the anchor is
named/addressable in ISV.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds two coupled interventions on the regime fragility exposed by
walk-forward CV (mean Sharpe +27 ± 56 at half-tick across 3 folds —
std-dev ≈ mean means the strategy is regime-dependent).
(1) Vol-EMA regime detector (new ISV slots 549/550)
- New alpha_regime_vol_update.cu kernel: per inference step, reads B
parallel-env mid prices, computes cross-env mean squared log-return,
and maintains ISV[549]=REGIME_VOL_EMA (Wiener-α with 0.4 floor +
Pearl A bootstrap) and ISV[550]=REGIME_VOL_REF (slow tracker β=0.005,
≈200-step horizon).
- Block-tree-reduce (no atomicAdd), guards against zero/non-finite mids.
(2) Pre-emptive Kelly attenuation (modified stacker controller)
- stacker_threshold_controller.cu takes 3 new args: regime_vol_ema_idx,
regime_vol_ref_idx, regime_scale_floor.
- Multiplies its reactive Sharpe-error Kelly output by
regime_scale = clamp(vol_ref / vol_ema, 0.25, 1.0)
- Disabled when indices = -1 (backward-compatible smoke + kernel test).
(3) Cost-aware training (--train-cost-hi)
- alpha_compose_backtest --train-cost-hi: when > --train-cost, each
training epoch samples cost ~ U[lo, hi] so the Q-network learns
cost-conservative behaviour across the realistic ES range.
(4) Wiring
- alpha_compose_backtest --regime-scale enables both per-step regime
kernel firing during eval AND the regime hookup in the per-episode
controller call. Mapped-pinned mids buffers, all compute device-side.
- ExecutionEnv exposes current_mid() so the host gather reads the
active snapshot mid per env without leaking the private cursor field.
Smoke + test sites pass -1/-1 for regime indices (backward compat).
Doc: docs/isv-slots.md ledger for slots 549/550.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
T15: training rewrite mirroring T14 eval. N_par parallel envs
lockstep H steps per epoch; ONE batched C51 update at B = N_par * H.
Expected ~15× speedup vs sequential.
- NEW kernel alpha_h_enriched_store_batched_kernel for batched
h_enriched slot writes
- Training section greenfielded: legacy sequential loop deleted
- CLI flag --n-train-par (default 50)
- Terminal next-state slot zeroed; done=1 at horizon masks Q_next
contribution in Bellman projection — no terminal Mamba2 forward
- docs/isv-slots.md updated per kernel-audit-doc hook
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Phase E.4.A T14 backtest at B=1 with per-step stream.synchronize()
was running ~150μs/step × 9M steps = ~22 min — dominated by sync
overhead, not GPU compute. RTX 3050 Ti to L40S swap wouldn't help
(launch overhead is the bottleneck, not FLOPS).
Solution: batched parallel envs. N=cli.n_eval_episodes environments
run in LOCKSTEP per cell — ONE sync per step (instead of N syncs).
Expected ~30× speedup at N=500.
Changes:
1. ExecutionEnv snapshots → Arc<Vec<SnapshotRow>>
- new() wraps Vec into Arc internally (backward compat)
- new_arc() takes pre-existing Arc (for parallel envs)
- snapshots_arc() accessor for snapshot sharing
- 50MB × N memory duplication avoided
2. alpha_window_push_batched_kernel (NEW CUDA)
- Same chronological shift+insert semantics as single-env kernel
- Grid (state_dim_blocks, B, 1): one thread per (batch, feature)
- launcher: launch_alpha_window_push_batched
3. MappedI32 (per-binary) gains len param + read_all()
- smoke & backtest pass len=1 for existing single-int use
- backtest passes len=N for batched action readback
4. backtest binary eval loop GREENFIELDED
- Legacy sequential 'for ep in 0..N { for step in ... }' loop
body deleted entirely
- New: 'for step in 0..horizon' outer, lockstep over N envs
- Build N envs sharing snapshots_arc at cell start
- Per step: gather N states (CPU loop, <100μs for N=500) →
write to mapped-pinned [N, STATE_DIM] → push kernel B=N →
Mamba2 batched forward → C51 batched forward → Thompson
batched → ONE sync → read N actions → step N envs on CPU
- ISV-continual moved from per-episode to per-cell (single fire
with aggregate stats)
5. docs/isv-slots.md updated per kernel-audit hook
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Phase E.4.A T8 wiring stored Mamba2's per-step cache.h_enriched
into h_enriched_buf_dev via a dtoh+htod sequence:
let h_host = stream.clone_dtoh(cache.h_enriched.cuda_data())?;
let mut buf_host = stream.clone_dtoh(&h_enriched_buf_dev)?; // <- whole buffer
for j in 0..hidden_dim { buf_host[slot_offset + j] = h_host[j]; }
stream.memcpy_htod(&buf_host, &mut h_enriched_buf_dev)?; // <- whole buffer
This violates feedback_cpu_is_read_only AND
feedback_no_htod_htoh_only_mapped_pinned. Worse, the buffer-wide
dtoh+htod every step is ~20K floats × 600 steps × 500 eps × 30 cells
= ~9M roundtrips totaling significant PCIe latency in the backtest.
Fix: new tiny CUDA kernel alpha_h_enriched_store_kernel in
alpha_window_push.cu (one thread per hidden-dim feature, writes
src[j] → buf[slot_offset + j]). Replaces the dtoh/htod sequence
in both smoke and backtest binaries.
Estimated speed-up at backtest scale: 3-6× on the temporal eval
path. Pure-GPU per-step inference restored — no synchronisation
points on the hot path.
docs/isv-slots.md updated per kernel-audit-doc hook.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase E.4.A Task 10 partial: adds the C51 gradient-w.r.t.-input
kernel needed to chain the C51 head's loss gradient back into the
Mamba2 temporal encoder.
Kernel signature: alpha_c51_grad_input_kernel reads probs[B, A, K],
m[B, K], actions[B], W[A*K, in_dim] and writes d_input[B, in_dim].
Math: dL/d_input[b,j] = Σ_k (p[b,a_taken,k] − m[b,k]) · W[a_taken*K+k, j]
(only the taken-action column of W contributes; restricted by the
sparse-over-actions C51 CE gradient structure).
Status: kernel + Rust launcher in place. Mamba2 backward NOT yet
wired in the smoke binary because ml_alpha::Mamba2Block::backward
takes d_logit [B, 1] (post-W_out scalar gradient), not
d_h_enriched [B, hidden_dim]. Two paths to complete:
1. Modify ml-alpha to expose backward_from_h_enriched(cache,
d_h_enriched) bypassing W_out.
2. Replicate the post-W_out backward sequence inline.
Both deferred pending backtest validation (T14): if frozen Mamba2
already lifts backtest Sharpe meaningfully, T10 becomes
optimisation rather than prerequisite. Smoke gate already passed
gate 1 (R_mean +3.9 vs C51-flat -1.1) with frozen weights.
docs/isv-slots.md updated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switch alpha_window_push from circular-buffer-with-head_idx layout
to shift+insert layout matching production mamba2_update_history.
Slot 0 = oldest, slot K-1 = newest after each push, matching
Mamba2Block's [B, K, in_dim] input contract directly (no reorder).
Cost: O(K-1) shifts per state_dim feature per push. For K=16,
state_dim=10: 10 threads × ~15 ops each = trivial.
Kernel signature: drops head_idx, adds K. Test updated to verify
chronological shift across 3 pushes into 4-slot buffer.
docs/isv-slots.md updated per kernel-audit-doc hook.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase E.4.A Task 6: tiny CUDA kernel that pushes a state[state_dim]
vector into slot `head_idx` of a circular window buffer
[K, state_dim]. Host tracks head_idx and zeros buffer on episode
reset. This is the GPU-side append primitive that the smoke
binary's per-step inference will call (T7) before Mamba2 over the
buffer (T8).
GPU smoke (alpha_window_push_circular_writes_to_indexed_slot):
writes state_a at slot 0, state_b at slot 2, verifies non-targeted
slots stay zero. PASS.
docs/isv-slots.md: documents kernel as slot-agnostic per
kernel-audit-doc hook requirement.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
C51 distributional Q-network with GPU Thompson selection borrowed
minimally from production (alpha_c51.cu: forward, project, grad,
expected_q, thompson_select kernels; ~260 lines). Uses Huber
negative-tail compression in projection per production
block_bellman_project_f. Action selection 100% GPU via mapped-pinned
i32 output + __threadfence_system + host volatile read (matches
gpu_training_guard MappedBuffer pattern).
Backtest result (2D sweep, 500 episodes per cell, 30 cells):
cost=0 C51 +10.41 vs linear-Q -15.72 (+26pt, BEATS Phase 1d.4
no-RL baseline +4.4 by 6pt)
cost=0.125 C51 -13.81 vs -29.17 (+15pt closes half-tick gap)
Win rate at cost=0 best τ: linear-Q 0.008 → C51 0.552.
Calibration hypothesis vindicated; documented in
memory/pearl_c51_thompson_closed_phase_e3_gap.md.
Also in this commit (Phase E.3 follow-up cleanup):
- --pruned-actions falsified (2.4× worse Sharpe). Documented in
memory/pearl_action_pruning_falsified.md.
- --real-spread falsified for ES futures (76% of bars at 1-tick floor).
- SnapshotRow bid_l/ask_l extended from [f32; 3] to [f32; 10].
L4-L10 synthesized in this commit; real MBP-10 peek lands in E.4.A T5.
- docs/isv-slots.md updated per kernel-audit-doc hook requirement.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase E.2 Task 16. Engagement-rate self-correcting controller per
pearl_engagement_rate_self_correction. Single-block, single-thread
kernel; runs once per rollout-end boundary.
ISV slots driven:
543 STACKER_THRESHOLD_INDEX clamp [0, 0.5] P-controller on rate
545 TRADE_RATE_OBSERVED_EMA_INDEX Pearl A+D floored Wiener-α
546 STACKER_KELLY_ATTENUATION_INX clamp [0.1, 1.0] P-controller on Sharpe
Reads ISV[544] TRADE_RATE_TARGET_INDEX (TrainingPersist anchor, set once
at training start).
Control law:
observed = trade_count / max(decisions, 1)
ISV[545] ← Pearl_A+D_floored(observed, prev, x_lag)
[α* floor = 0.4 per pearl_wiener_alpha_floor_for_nonstationary;
controller co-adapts with policy → need responsive EMA]
err_rate = ISV[545] - ISV[544]
ISV[543] ← clamp(0, 0.5, ISV[543] + k_threshold · err_rate)
err_sharpe = rollout_sharpe - target_sharpe
ISV[546] ← clamp(0.1, 1.0, prev_atten + k_atten · err_sharpe)
where prev_atten = 1.0 if ISV[546] == 0.0 (sentinel-start)
else ISV[546]
Wiener-α is INLINE (not via canonical apply_pearls_ad_kernel chain)
because the EMA is part of the control loop, not a separate diagnostic
slot. Lower latency, fewer kernels per step.
Floor at 0.1 on Kelly attenuation per
pearl_blend_formulas_must_have_permanent_floor — can't be 0, would
zero out position sizing permanently.
Pub launcher `launch_stacker_threshold_controller` in alpha_kernels.rs
with full safety asserts. Slot indices passed as i32 args (decouple
slot numbering from kernel).
GPU smoke test `stacker_threshold_controller_smoke_matches_hand_
computation` verifies 2-iteration sequence:
Iter 1 (Pearl A): observed=0.30 → ISV[545]=0.30; ISV[543]: 0.05 → 0.052
Iter 2 (Pearl D): observed=0.05 → ISV[545]=0.175 (α* hit floor 0.5)
ISV[543]: 0.052 → 0.05275
Both within 1e-5 tolerance. Anchor slot 544 unchanged.
`cargo test -p ml --lib alpha_kernels`: 6 pass (compile witness + 5 GPU
smokes including this one) on RTX 3050 Ti in 2.18s.
Audit doc docs/isv-slots.md updated per Invariant 7.
Three stabilizers applied to the H=600 DQN smoke after initial run showed
unstable training (early_mvmt=2268× at lr=1e-6, NaN at lr=1e-4):
1. Reward normalization (--reward-scale, default 1000)
Rewards divided by scale BEFORE the Munchausen target. TD error
drops from ~1000 (raw reward magnitude at H=600) into O(1) target /
gradient / weight-update scale. Action selection + rollout-R
reporting use ORIGINAL rewards (so rvr math stays correct against
the Task 7c baseline).
2. Target network (--target-update-every, default 10 episodes)
Separate w_target_dev / b_target_dev buffers. Q_next(s') forward
uses target weights; SGD updates online only. Hard-update copies
online → target every K episodes. Breaks the V_soft(s') chase-its-
own-tail divergence of online-only Munchausen.
3. Gradient clipping (--grad-clip, default 1.0)
New `alpha_clip_inplace_kernel` in alpha_linear_q.cu (element-wise
clamp). Applied to dW and db after grad, before SGD. Safety net.
Diagnostic fix: weight_norm was direction-insensitive — orthogonal
rotations don't change ||W||_F, so early_mvmt read ≈0 even when training.
Switched to weight_distance_from_init = ||W_now − W_init||_F +
||b_now − b_init||_F (captures rotation). q_early = q_init + distance
so kernel's |q_early − q_init| / |q_init| ratio = distance / ||W_init||_F.
With lr bumped back up to 1e-4 (default for the stabilized config),
verified at horizon=100, n_episodes=200:
Q_SPREAD_EMA = 23.64 (≥ 0.05) PASS
ACTION_ENTROPY_EMA = 1.86 (≥ 1.0986) PASS
RETURN_VS_RANDOM_EMA = +0.586 (≥ 0.0) PASS
EARLY_Q_MOVEMENT_EMA = 0.0212 (≥ 0.01) PASS
Overall: PASS (H=6000 scale-up VIABLE)
early_mvmt grew monotonically (0.005 → 0.021) across the 200-episode
run — direction-sensitive diagnostic confirms genuine policy learning.
Audit doc docs/isv-slots.md updated per Invariant 7.
Next: H=600 / 1000-episode run on full data; if PASS holds, Task 13
(H=6000 scale-up) unlocks.
Phase E.1 Task 12a. Three new CUDA kernels for the H=600 DQN smoke
(Task 12 proper) that lands in a follow-up commit:
alpha_linear_q_forward_kernel Q = X · W^T + b
alpha_linear_q_grad_kernel dW, db sparse MSE-TD over taken actions
alpha_linear_q_sgd_step_kernel element-wise params -= lr · grad
Architecture: single linear layer, no hidden layer. The Phase E state
vector has meaningful direct features (alpha_logit, spread_bps, position,
ofi_sum_5, …) so linear Q can capture real relations like Q[Buy] ∝
alpha_logit. If linear can't pass the kill-criteria gate, no architecture
upgrade will save it — and the smoke proceeds with NoisyNet escalation
per the plan.
Sparse gradient: only the taken action contributes (standard DQN TD
loss). No atomicAdd needed — one thread per (i, j) loops over the batch
and adds only when actions[b] == i.
GPU contract:
- No host branches inside any kernel (graph-capture compatible)
- No atomicAdd (per feedback_no_atomicadd)
- All compute on GPU (forward, grad, weight update)
- Tiny launch overhead — fits per-step (batch=64 forward = 576 threads,
1 block; grad = 99 threads, 1 block)
Three pub(crate) Rust launchers in alpha_kernels.rs match the
launch_apply_pearls pattern. Cubin embedded via include_bytes!.
Smoke test `linear_q_forward_grad_sgd_round_trip_matches_hand_math`
exercises all three kernels end-to-end on a small (batch=2, state_dim=2,
n_actions=3) case with full hand-math:
Forward: Q = [[2.1, 3.2, 0.3], [4.1, 5.2, 0.3]] ✓
Grad: dW = [[-1.8, -2.7], [0.8, 1.0], [0, 0]]
db = [-0.9, 0.2, 0] ✓
SGD: W' = [[1.18, 0.27], [-0.08, 0.90], [0, 0]]
b' = [0.19, 0.18, 0.30] ✓
All within 1e-4 tolerance. `cargo test -p ml --lib alpha_kernels`:
5/5 pass on RTX 3050 Ti in 2.04s (compile witness + 4 GPU smokes).
Audit doc docs/isv-slots.md updated per Invariant 7.
End-to-end test for the kernel COMPOSITION that the H=600 DQN smoke
(Task 12 proper) will use at each rollout boundary:
t+0 alpha_kill_criteria_compute_kernel → scratch[0..4]
t+1 apply_pearls_ad_kernel(n_slots=4) → ISV[539..543]
The two prior alpha_kernels smokes (munchausen + kill_criteria) validated
kernels in isolation. This smoke validates the COMPOSITION on the same
stream — failures here are different (stream-ordering, Pearls index base,
Wiener offset base, scratch visibility) and would silently break Task 12.
Two iterations with stationary synthetic inputs:
Iter 1 (Pearl A bootstrap)
prev_x_mean=0 AND x_lag=0 → ISV[539..542] populated with raw scratch
observations = [0.2041, 0.8980, 1.0670, 0.1] within 0.01 tolerance.
Anchor slots 547/548 remain at Task 7c values (-5185, 4953).
Iter 2 (Pearl D stationary)
dx_mean = dx_step = 0 → α* = 0 → ISV unchanged from iter 1 within
1e-4. Stationary signal stays at the bootstrap value.
Test would catch:
- Producer's scratch write not visible to applicator (stream-ordering)
- Wrong Pearls isv_idx_base / wiener_offset_base
- Pearl A sentinel detection broken (formula yields 0 at t=0)
- Wiener state corruption (iter 2 drifts from iter 1)
Reuses launch_apply_pearls from sp4_wiener_ema.rs (pub(crate)). Helper
fn run_chained_iter factors the producer→applicator sequence so the two
iterations are byte-identical apart from the Wiener state's evolution.
`cargo test -p ml --lib alpha_kernels`: 4 pass (compile witness + 2 prior
GPU smokes + this chained smoke) on RTX 3050 Ti in 1.89s total. Audit doc
docs/isv-slots.md updated per Invariant 7.
Remaining Task 12 work (full H=600 DQN trainer integration with this
pipeline at rollout boundaries) is queued for a dedicated session.
Adds the second of the two Phase E.1 kernel smoke tests in
alpha_kernels.rs (companion to the munchausen_target smoke from
91d1a52b9). End-to-end exercises the alpha_kill_criteria_compute_kernel
launcher with synthetic inputs covering all 4 outputs:
q_values = [[1, 2, 3], [5, 5, 5]] → q_spread ≈ 0.2041
action_counts = [10, 30, 60] → action_entropy ≈ 0.8980
rollout_R=100, isv[547]=-5185,
isv[548]=4953 → return_vs_random ≈ 1.0670
q_init=50, q_early=55 → early_movement = 0.1000
ISV buffer is sized to 552 floats with slots 547/548 populated using the
committed Task 7c baseline values — this exercises the production
slot-indexing path through the kernel's
`isv[random_baseline_mean_slot]` / `isv[random_baseline_std_slot]` reads,
not just isolated kernel arithmetic.
Does NOT chain apply_pearls_ad_kernel afterward — the smoothing path is
canonical SP4 applicator territory already covered elsewhere. This test
isolates the kill-criteria producer arithmetic.
Tolerance 0.01 on all four observations; passes on RTX 3050 Ti in <2s
including kernel JIT.
`cargo test -p ml --lib alpha_kernels`: 3 pass (compile witness + both
GPU smokes). Audit doc docs/isv-slots.md updated per Invariant 7.
The kill-criteria producer, Munchausen target kernel, Rust launchers,
fit/baseline binaries, and their output JSON artifacts are *durable
infrastructure* of the alpha trading system (live across Phase E/F/G/...),
not milestone-scoped to Phase E specifically. Aligns with the earlier
`phase_e_isv_slots.rs` → `alpha_isv_slots.rs` rename rationale.
What was renamed:
Code files:
crates/ml/src/cuda_pipeline/phase_e_kill_criteria.cu → alpha_kill_criteria.cu
crates/ml/src/cuda_pipeline/phase_e_munchausen_target.cu → alpha_munchausen_target.cu
crates/ml/src/cuda_pipeline/phase_e_kernels.rs → alpha_kernels.rs
crates/ml/examples/phase_e_fit_fill_model.rs → alpha_fit_fill_model.rs
crates/ml/examples/phase_e_random_baseline.rs → alpha_random_baseline.rs
Artifacts:
config/ml/phase_e_fill_coeffs.json → alpha_fill_coeffs.json
config/ml/phase_e_random_baseline.json → alpha_random_baseline.json
Kernel function names:
phase_e_kill_criteria_compute_kernel → alpha_kill_criteria_compute_kernel
phase_e_munchausen_target_kernel → alpha_munchausen_target_kernel
Rust launcher names:
launch_phase_e_kill_criteria → launch_alpha_kill_criteria
launch_phase_e_munchausen_target → launch_alpha_munchausen_target
Static cubin names:
PHASE_E_MUNCHAUSEN_TARGET_CUBIN → ALPHA_MUNCHAUSEN_TARGET_CUBIN
Historical milestone tags in doc-comments ("Phase E.1 Task N (2026-05-15)")
are RETAINED — they record WHEN the work landed and what plan it
implemented, which doesn't change with the system-scoped rename.
Plus: ADDS the alpha_munchausen_target GPU smoke test in alpha_kernels.rs.
End-to-end validates the launcher + kernel against hand-computed expected
values: batch=2 with one terminal sample; expected targets [29.8, 1.1];
got match within 0.05 tolerance on RTX 3050 Ti. PROVES the Task 9/10
kernels actually run on GPU.
All affected references updated in:
- build.rs (kernel compile list)
- mod.rs (module registration)
- state_reset_registry.rs (4 RegistryEntry descriptions for slots 539-542)
- alpha_isv_slots.rs (slot table comment)
- docs/isv-slots.md (audit-doc cross-references)
Verified:
cargo test -p ml --lib alpha_kernels: 2/2 pass (including GPU smoke)
cargo test -p ml --lib state_reset_registry: 10/10 pass
cargo build -p ml --release --example alpha_fit_fill_model --example alpha_random_baseline: clean
Phase E.1 Task 11. Two pub(crate) launchers expose the kill-criteria
producer (Task 9) and Munchausen target augmentation (Task 10) for use
by future trainer integration:
launch_phase_e_kill_criteria(stream, kernel, q_values_dev, action_counts_dev,
scalar_inputs_dev, isv_dev, batch, n_actions,
scratch_out_dev)
→ kicks the kill_criteria producer; caller chains apply_pearls_ad_kernel
(n_slots=4, isv_idx_base=ALPHA_ISV_BLOCK_LO=539) to smooth into slots
539..542 via Pearl A bootstrap + Pearl D Wiener-α.
launch_phase_e_munchausen_target(stream, kernel, q_next_dev, q_current_dev,
actions_dev, rewards_dev, dones_dev,
gamma, alpha_m, tau, log_clip_min,
target_out_dev, batch, n_actions)
→ one-thread-per-sample target augmentation; α_m/τ/log_clip_min are
scalar args so a downstream ISV-driven controller can tune them.
Plan deviation: Task 11 plan-spec said "replace hardcoded n_step=32,
gamma=0.999 literals" but grep across gpu_dqn_trainer.rs found ZERO such
literals — the trainer already reads gamma via read_isv_signal_at(
GAMMA_DIR_EFF_INDEX) and epsilon via read_isv_signal_at(AUX_TRUNK_EPS_
INDEX). The actual E.1 deliverable was Rust launchers for the new
cubins, which this commit lands.
Both launchers follow the launch_apply_pearls pattern in
sp4_wiener_ema.rs — pre-loaded CudaFunction as parameter, u64 device
pointers, debug_assert! guards.
Audit doc docs/isv-slots.md updated per Invariant 7.
Tested via `cargo test -p ml --lib phase_e_kernels` — 1 compile-witness
passes. Real GPU integration test in Task 12.
Phase E.1 Task 10. Standalone target-augmentation kernel:
m = α_m · max(τ · log π(a|s), log_clip_min)
V_soft(s') = max(Q_next) + τ · log Σ exp((Q_next − max) / τ)
target = r + m + γ · V_soft(s') (terminal: r + m)
π(a|s) ∝ exp(Q_online(s, a) / τ) — softmax policy from the online net.
Munchausen bonus is implicit KL regularisation between successive policies;
soft-V replaces the hard max bootstrap with a τ-weighted softmax average.
Both softmaxes are computed via log-sum-exp with the max-trick. This is
essential at τ ≈ 0.03 where raw exp(Q/τ) would overflow f32 for any
Q-spread > 25 nats. The kernel is one-thread-per-batch-sample, no
atomicAdd, no host branches.
α_m, τ, log_clip_min are kernel args (not hard-coded), so a Phase E.2+
controller can ISV-drive them. Typical Vieillard values: α_m=0.9, τ=0.03,
log_clip_min=-1.0.
Does NOT touch any ISV slot — pure target augmentation.
Cubin: target/release/build/ml-*/out/phase_e_munchausen_target.cubin (12.8 KB).
Launcher integration is Task 11 (consumes target_out where the C51/MSE
loss kernels currently consume `r + γ · max_a' Q_target`). Audit doc
docs/isv-slots.md updated per Invariant 7.
Phase E.0 Task 9. Single-block, single-thread CUDA producer that writes 4
raw scalar observations into a contiguous scratch_out[0..4] block:
scratch_out[0] → ISV[539] Q_SPREAD_EMA (kill: ≥ 0.05)
scratch_out[1] → ISV[540] ACTION_ENTROPY_EMA (kill: ≥ 0.5·ln(9) ≈ 1.10)
scratch_out[2] → ISV[541] RETURN_VS_RANDOM_EMA (kill: ≥ 0, i.e. ≥ random)
scratch_out[3] → ISV[542] EARLY_Q_MOVEMENT_EMA (kill: ≥ 0.01, learned)
Downstream `apply_pearls_ad_kernel` (n_slots=4) chained on the same stream
applies Pearl A first-observation bootstrap + Pearl D Wiener-α smoothing —
composes with the canonical val_sharpe_delta_compute_kernel pattern rather
than reimplementing Wiener math (per feedback_no_cpu_compute_strict — one
Wiener implementation in the codebase, the GPU one).
Inputs:
q_values[batch, n_actions] most-recent Q forward output (device)
action_counts[n_actions] empirical action histogram (device)
scalar_inputs[3] [rollout_R_mean, q_init_norm, q_early_norm]
via mapped-pinned (host writes between rollouts)
isv reads slots 547 + 548 (random baseline
mean/std — populated once by Task 7c,
TrainingPersist)
No atomicAdd (per feedback_no_atomicadd), no host branches
(per pearl_no_host_branches_in_captured_graph), no CPU compute
(per feedback_cpu_is_read_only). __threadfence_system() before exit for
the chained Pearls applicator's visibility guarantee.
Cubin produced: target/release/build/ml-*/out/phase_e_kill_criteria.cubin
(16.8 KB).
Launcher integration is Task 11 (separate commit so this task can stand
alone for cubin validation). Audit doc docs/isv-slots.md updated per
Invariant 7.
Per feedback_registry_entries_need_dispatch_arms — both must land in the
same commit; the test `every_fold_and_soft_reset_entry_has_dispatch_arm`
walks training_loop.rs::reset_named_state source and asserts coverage.
- 10 RegistryEntry rows appended after SP22 block (full producer/consumer
rationale per existing dense-description pattern)
- 7 dispatch arms in reset_named_state (the 3 TrainingPersist anchors
— slots 544/547/548 — are not dispatched per the existing convention)
- All sentinels 0.0 per pearl_first_observation_bootstrap
All 10 registry tests pass. Audit doc docs/isv-slots.md updated per
Invariant-7.
12 contiguous slots reserved for durable alpha-system infrastructure:
- 539-542: diagnostics (Q-spread, action entropy, return-vs-random,
early-Q-movement EMAs) — read by Phase E kill criteria
- 543-546: stacker-threshold controller (threshold, target, observed-rate,
Kelly attenuation) — engagement-rate self-correction
- 547-548: random-uniform baseline (mean, std) — anchor for kill criterion 541
- 549-550: reserved spare (absorb growth without bumping ISV_TOTAL_DIM)
Named `alpha_isv_slots` rather than `phase_e_isv_slots` because these slots
are intended to outlive any individual Phase E/F/G milestone — the SP4..SP22
naming was iteration-scoped, but this block is system-scoped infrastructure.
ISV_TOTAL_DIM bumped to 551. All indices validated in 4 unit tests
(bounds, membership, uniqueness, total-dim coverage). Audit doc
docs/isv-slots.md updated per Invariant-7.
Per Class A audit-fix Batch 4-A (deferred from P1-wiring/P1-producer due to
audit-doc errors). Fixes 2 of 4 deferred items; Batch 4-B handles plan_threshold
floor + MIN_HOLD_TEMPERATURE in a separate commit.
Item 1: DD saturation floor (the upper end of the DD ramp at trade_physics.cuh:154
in apply_margin_cap, NOT line 548 as the audit doc claimed — that line is a
magnitude action constant; the actual saturation floor lives in apply_margin_cap)
- NEW slot DD_SATURATION_FLOOR_ADAPTIVE_INDEX=458
- Producer dd_saturation_floor_update_kernel.cu — p75(per-env DD_MAX) × 1.5
via Welford `mean + Z_75 × sigma` estimator with `max(p75, mean)` robustness
guard, mirrors P0-A REWARD_POS_CAP producer pattern (Pearl-A bootstrap +
Welford α=0.01)
- Cold-start fallback: 0.25f (DD_SATURATION_FLOOR_DEFAULT in state_layout.cuh)
- Bounds: [0.10, 0.50] (Category-1 dimensional safety)
- Distinct from SP15_DD_THRESHOLD_INDEX=421 (the SP15 quadratic DD-penalty
*trigger* threshold, a *lower* bound; this slot is the *upper* end of the
linear position-size scaling ramp dd_scale = max(0.05, 1.0 − dd_frac/floor))
- Threaded `isv_signals_ptr` into `apply_margin_cap` with NULL-tolerant
cold-start fallback to DD_SATURATION_FLOOR_DEFAULT
- 4 oracle tests (Pearl-A bootstrap, no-DD guard, bounds clamp, Welford EMA)
Item 2: Legacy compute_drawdown_penalty path → Case A (DELETED)
- Decision rationale: SP15's quadratic asymmetric DD penalty
(compute_sp15_final_reward_kernel.cu:154 via sp15_dd_penalty helper) runs
unconditionally as a post-modifier on the SP11-composed reward with
ISV-driven λ_dd (slot 420) and DD threshold (slot 421). Layering the legacy
linear-ramp penalty inside the SP11 composer on top of the SP15 quadratic
creates double-counting of DD shaping — exactly the code-smell the Class A
audit was designed to eliminate. Per `feedback_no_legacy_aliases.md` and
`feedback_no_partial_refactor.md`.
- Atomic deletion across:
- `compute_drawdown_penalty` device function (trade_physics.cuh)
- Single call site at experience_kernels.cu:3822
- `dd_threshold` and `w_dd` kernel arguments
- `w_dd` Rust config field (gpu_experience_collector.rs +
trainers/dqn/config.rs DQNHyperparameters)
- `w_dd` profile section + dispatch (training_profile.rs RewardSection,
OptimizableParameterRanges, FixedRewardParameters, ParamLookup
dispatch, profile→hyperparam mapping, test assertion)
- `w_dd *= rki` risk-intensity multiplier (config.rs)
- `w_dd` TOML keys (dqn-hyperopt.toml × 2, dqn-localdev.toml,
dqn-production.toml, dqn-smoketest.toml)
- Stale doc comments on hyperopt/adapters/dqn.rs + config.rs
risk_intensity field
- `config.dd_threshold` SURVIVES (still consumed by `launch_sp15_dd_state`
as the dd_budget for DD_PCT scaling). Documented in field comment.
ISV_TOTAL_DIM: 458 → 459 (Item 1 adds 1 slot; Item 2 is pure deletion)
Cumulative WR-plateau fix series (this is commit 7):
- Class C bug 1 + P0-B (8f218cab2)
- P0-C (316db416b)
- P0-A (394de7d43)
- P1 wiring (c4b6d6ef2) — 1 of 4 wireable
- P0-A downstream (657972a4b)
- P1 producer (87d597d5d)
- audit-fix 4-A (this commit)
Verification: 16 sp14_oracle_tests pass (incl. 4 new), 36 sp15_phase1_oracle_tests
pass, 12 sp14_isv_slots layout tests pass, 4 state_reset_registry tests pass
(every-FoldReset-arm-has-dispatch contract holds), workspace cargo check clean.
Per feedback_isv_for_adaptive_bounds + feedback_no_partial_refactor +
feedback_no_legacy_aliases.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the Phase 1.3.b deferred per-env redesign per
feedback_no_partial_refactor. Path A (env-0-canonical, commit 132609724)
made every downstream consumer read env-0's DD context; production envs
each have their own DD trajectory, so when env-3 was at 30% DD and
env-0 was at ATH, the model learning from env-3's transitions saw
dd_pct=0 and silently skipped the recovery shaping. Path B threads
each env's actual DD context through the reward shaping atomically:
(1) dd_state_kernel reshape — grid [n_envs, 1, 1], one thread per env,
writes 6 scalars per env to a new per-env tile dd_state_per_env
[n_envs * 6]. No more ISV scalar writes.
(2) NEW dd_state_reduce_kernel — single-block tree-reduce (no
atomicAdd; BLOCK=256 with strided initial pass for n_envs up to
32768 on H100). Mean-aggregates per-env tile → 6 scalar ISV slots
[401..407) for HEALTH_DIAG diagnostic. Max-aggregates DD_PERSISTENCE
→ new slot DD_PERSISTENCE_MAX_INDEX=443 for plasticity injection
trigger (one shared advantage-head ⇒ global firing ⇒ max-aggregate
is the only correct rule). ISV_TOTAL_DIM 443→444; SP15_SLOT_END
443→444; SP15_SLOT_COUNT 46→47.
(3) compute_sp15_final_reward_kernel migration — per-(i,t) per-env DD
lookup via env_id = (idx % (N*L)) / L. Helpers sp15_dd_asymmetric_reward
and sp15_dd_penalty migrated to take dd_pct + dd_current as scalar
parameters (the kernel reads from the per-env tile, threads scalars
in). On-policy + CF threads at the same (i,t) read the SAME tile
entry (one DD trajectory per env, shared across slot kinds).
(4) plasticity_injection_kernel migration — persistence read switched
from ISV[404] (mean) to ISV[443] (max). One set of advantage
weights ⇒ ANY env exceeding the threshold should arm the gate.
(5) Per-env tile owned by GpuExperienceCollector (not the trainer) —
the collector knows alloc_episodes (= n_envs); the trainer's
batch_size is a different quantity. Reset to zero via the
sp15_dd_state_per_env registry-arm dispatch.
(6) Per-step launch order: dd_state → dd_state_reduce →
alpha_split_producer → final_reward, all on the same stream
(CUDA serialises producer→consumer without explicit event sync).
(7) HEALTH_DIAG semantic shift (documented breaking change): slots
401-406 now report cross-env mean, not env-0 value. For n_envs=1
smoke configs the mean equals env-0's value (bit-stable migration).
(8) Layout fingerprint break: added markers DD_PERSISTENCE_MAX=443;
ISV_TOTAL_DIM=444; DD_STATE_PER_ENV=sp15_phase_1_3_b_followup.
Pre-followup checkpoints will not load (greenfield OK per spec Q1).
(9) 6 oracle tests migrated + 1 NEW behavioral test
`dd_state_per_env_diverge_independently` — two-env config (env-0
in recovery, env-1 deepening) verifies independent trajectories
+ mean-aggregate + max-aggregate semantics.
Phase 1.3.b-followup-B (separate split): dd_trajectory_decreasing_kernel
+ per_insert_pa migration is NOT in scope. The current Wave 4.3 reads
ISV[439] at insert-batch time (epoch end) — applied uniformly to ALL
inserted transitions (a known pre-existing limitation). Proper fix
requires per-(env, t) trajectory buffer [N*L] + env_id-aware lookup
in per_insert_pa via env_id = (j % (N*L)) / L. That's a different
contract change; splitting preserves no-partial-refactor within each
migration.
Atomic per feedback_no_partial_refactor: all 5 consumers of single-env-
canonical DD slots (final_reward kernel + plasticity kernel +
HEALTH_DIAG diagnostic + 6 oracle tests + new behavioral test) migrate
to per-env tile lookup in this commit; the new DD_PERSISTENCE_MAX
ISV slot lands with its sole consumer (plasticity).
Verified: SQLX_OFFLINE=true cargo check -p ml --features cuda clean;
cargo check -p ml --features cuda --tests clean;
CUDA_COMPUTE_CAP=86 cargo test -p ml --test sp15_phase1_oracle_tests
--features cuda -- --ignored: 17/17 oracle tests green (2 dd_state
incl. new per-env behavioral + 5 plasticity + 3 dd_trajectory + 6
final_reward + 1 per_sampler); cargo test -p ml --features cuda --lib:
947 pass / 12 fail HOLDS the 483cef454 baseline (no new regressions).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per `feedback_isv_for_adaptive_bounds`, the hardcoded
`warmup_gate = (fold_step_counter / WARMUP_STEPS_FALLBACK).min(1.0)`
ramp violated the rule: adaptive bounds in ISV, never hardcoded
constants. The variance-driven k_aux/k_q sigmoid steepness already
provides warmup behavior intrinsically:
- High variance (cold-start, EMAs still moving) → k → K_MIN → flat
sigmoid → gate ≈ 0.5 regardless of input. That IS the warmup.
- Low variance (settled) → k → K_BASE → sharp sigmoid → gates
respond correctly to driver signals.
Adding a separate hardcoded step-counter multiplier on top was
double-counting + tuning-driven (the 1000-step threshold had no
principled basis). Removed entirely.
Removed (per `feedback_no_partial_refactor`, all atomically):
- `WARMUP_STEPS_FALLBACK` constant in `sp14_isv_slots.rs`
- `warmup_gate: f32` parameter in `alpha_grad_compute_kernel.cu`
- `gate1 * gate2 * warmup_gate` → `gate1 * gate2` in kernel
- `warmup_gate` arg from `launch_sp14_alpha_grad_compute`
- `fold_step_counter: usize` field on the trainer struct
- `fold_step_counter = 0` reset in `reset_for_fold`
- `fold_step_counter` init in trainer constructor
- `let warmup_gate: f32 = 1.0;` and `.arg(&warmup_gate)` from B.4
oracle tests (4 launches: 2 in alpha_grad_schmitt_hysteresis,
20 in alpha_grad_adaptive_beta loop)
Build: clean, 18 warnings (pre-existing baseline).
Tests: cargo test --no-run on sp14_oracle_tests succeeds.
Net result: EGF gate's warmup behavior now lives entirely in the
variance-driven k_aux/k_q sigmoid steepness controller (ISV slots
388/var_aux, 389/var_q). No hardcoded step counter. Honors
`feedback_isv_for_adaptive_bounds` and `pearl_controller_anchors_isv_driven`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ROOT CAUSE of L1+L2 from Smoke A2-B: the ISV bus was sized for top
of SP13 (ISV_TOTAL_DIM=383) but B.1 allocated SP14 slots at 383-395.
Every SP14 read/write was OUT-OF-BOUNDS memory access. That's why:
- gate1 (slot 391) read as 0 always (OOB zero-init memory)
- post_open_min (slot 394) accumulated garbage values 9.5 → 28 → 46
- α_smoothed/α_raw values appeared to work but were undefined behavior
SP4/SP5 had a regression test (`all_sp4/5_slots_fit_within_isv_total_dim`)
that catches this exact failure mode at unit-test time. SP14 was missing
it — that gap let the bug ship across all 16 commits without being caught.
Changes:
- ISV_TOTAL_DIM: 383 → 396 (covers SP14 slots 383-395)
- layout_fingerprint_seed: extended with SP14 slot names + new
ISV_TOTAL_DIM=396 marker (forces fingerprint hash bump per
Invariant 8 — old checkpoints invalidated correctly)
- sp14_isv_slots.rs: 2 regression tests (mirror SP4/SP5 patterns)
Both tests pass. After this fix, SP14 EGF kernels will read/write
the correct slots; gate1 should actually flip open when aux_dir_acc
crosses target+0.03; gradient_hack_detect post_open_min stays bounded
in [0, 1] as designed.
NOT yet addressed (separate follow-up):
- warmup_gate hardcoded WARMUP_STEPS_FALLBACK=1000 violates
feedback_isv_for_adaptive_bounds. Should be ISV-signal-driven OR
removed entirely (k_aux/k_q already provide variance-driven warmup).
Redesign post-re-smoke once bus-size fix is verified.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new HEALTH_DIAG[{epoch}]: pearl_egf_diag line immediately after
the aux_moe block in the per-epoch metrics section of training_loop.rs.
Reads all 13 SP14 ISV slots [383..396) — α_smoothed, α_raw, β, k_aux,
k_q, var_aux, var_q, var_α, q_dis_short, q_dis_long, gate1 state,
post_open_min, lockout — via the established read_isv_signal_at pattern,
giving forensic visibility into EGF pearl state each epoch.
gate1/gate2 sigmoid outputs are intentionally omitted: recomputing them
host-side would violate feedback_no_cpu_compute_strict; the sigmoid
inputs are sufficient for a reader to infer the output values.
docs/isv-slots.md updated (Invariant 7): records B.12 HEALTH_DIAG wire-up.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each EGF pearl EMA / state slot resets to its Pearl-A sentinel at fold
boundary, mirroring sp13_aux_dir_acc_short_ema / long_ema entries.
Atomic refactor (feedback_no_partial_refactor): both halves land
together — registry entry + reset_named_state dispatch arm.
Reset slots (11 total, sentinel in parens):
- Q_DISAGREEMENT_SHORT/LONG_EMA (slots 383, 384) → 0.5
- K_AUX_ADAPTIVE (385) → K_BASE_AUX = 20.0
- K_Q_ADAPTIVE (386) → K_BASE_Q = 15.0
- BETA_RATE_LIMITER_ADAPTIVE (387) → BETA_BASE = 0.5
- AUX_DIR_ACC_VARIANCE_EMA, Q_DISAGREEMENT_VARIANCE_EMA,
ALPHA_GRAD_RAW_VARIANCE_EMA (388, 389, 390) → 0.0
(initial k = k_base, β = β_base via ISV-driven controllers)
- GATE1_OPEN_STATE (391) → 0.0 (closed)
- ALPHA_GRAD_SMOOTHED (393) → 0.0
- AUX_DIR_ACC_POST_OPEN_MIN (394) → 1.0 (no min observed)
ALPHA_GRAD_RAW (slot 392, recomputed every step from variance EMAs)
and GRADIENT_HACK_LOCKOUT_REMAINING (slot 395, decays at epoch
boundary) are NOT in the fold-reset registry; both naturally
re-initialise without explicit reset.
Also corrects the isv-slots.md SP14 table: slots 392 and 395 were
incorrectly marked FoldReset in the B.1 entry; corrected to reflect
their actual reset semantics (NOT reset / epoch-boundary decay).
Producer + consumer wiring lands in subsequent tasks (B.3-B.12);
this commit is additive infrastructure only — no behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Allocates ISV slots [383..396) for the Aux→Q Wire + Earned Gradient
Flow pearl (Layer B of SP14). Mirrors sp13_isv_slots.rs pattern.
The plan originally documented [381..394), but Phase 0 verification
found SP13 closeout added HOLD_RATE_TARGET_INDEX=381 and
HOLD_RATE_OBSERVED_EMA_INDEX=382 after the plan was written, so the
range shifts by +2.
Slots fall into 4 functional groups:
- Q-disagreement EMAs (short, long; K=4↔K=2 mapping with Hold/Flat masked)
- Adaptive controllers (k_aux, k_q, β; variance-driven)
- Welford variance EMAs (3, one per adaptive scalar)
- Schmitt state + α_grad outputs + circuit breaker
Plus 14 structural constants for numerical-stability anchors:
K_BASE_*, K_MIN, VARIANCE_REF_*, BETA_BASE, BETA_MAX, SCHMITT_BAND,
WARMUP_STEPS_FALLBACK, LOCKOUT_*, Q_DISAGREEMENT_BASELINE.
Per feedback_isv_for_adaptive_bounds: adaptive bounds (k_*, β,
post_open_min, lockout) live in ISV; numerical anchors live as
structural constants. Per pearl_first_observation_bootstrap: all
EMAs reset to sentinels and Pearl-A bootstraps on first observation.
Producer + consumer wiring lands in subsequent tasks (B.2-B.12);
this commit is additive infrastructure only — no behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Linear magnitude ratios in reward_component_mag_ratio_compute_kernel
amplified popart's intrinsic O(100) magnitude over the other 5
components' O(0.1-2) magnitudes, causing controller to saturate
w_pop toward MAX_WEIGHT regardless of actual signal quality.
Replaced with z-score: z[c] = mag[c] / max(sqrt(var[c]), EPS_DIV).
6 new ISV slots [361..367) for per-component variance EMAs computed
via Welford's online algorithm in extended popart_component_ema_kernel
and reward_component_ema_kernel.
Atomic per feedback_no_partial_refactor: slot allocation +
state-reset registry + 2 producer kernels + canary signature +
launcher Pearls A+D + tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per spec §4 amendment at 52c0b7521 on main: B1b smoke surfaced that
SP11 mag-ratio canary was reading slot 63 (REWARD_POPART_EMA_INDEX)
which is overloaded — pre-SP11 PopArt's normalization input (total
reward mag EMA) was the same value as popart-component magnitude
because composition was inline accumulation. B1b decomposition exposed
the overload; controller emitted w_pop ≈ 2.0 based on contaminated
ratio → 10× sharpe drop in smoke.
Resolution:
- Allocate ISV slot 360 = POPART_COMPONENT_MAG_EMA_INDEX
- Add popart_component_per_sample mapped-pinned buffer + write site
in experience_env_step at the r_popart assignment
- New popart_component_ema_kernel.cu writes slot 360 (single-block
block-tree-reduce per feedback_no_atomicadd)
- mag-ratio canary kernel signature changes from single
popart_ema_base_slot to (popart_specific_slot, cf_others_base_slot)
pair so it reads non-contiguous slot 360 + slots 64..68
- Reset registry: sp11_popart_component_mag_ema entry + dispatch arm
- Slot 63 (PopArt's input) UNCHANGED — pre-SP11 invariant preserved
ISV total: 360 → 361. SP5_SLOT_END = 361. SP5_PRODUCER_COUNT = 187.
cargo check + build clean; SP11 GPU oracle tests pass (6/6 including
updated mag_ratio test with 2 slot-index args); sp5_isv_slots layout
tests pass (10/10 with 185 unique slots / 187 linear span); state
reset registry tests pass (4/4 with new sp11_popart_component_mag_ema
entry + dispatch arm). Local multi_fold_convergence smoke gated on
data volume (175k bars on local fxcache vs 10-month walk-forward
requirement); validation deferred to L40S Argo run on PVC data per
the spec's pass criterion.
Per spec §3.5.3 amended at 7ddaf9c51 on main: experience_env_step
reward composition decomposed from 8+ inline accumulation sites into
explicit per-component locals (r_popart, r_cf, r_trail, r_micro,
r_opp_cost, r_bonus), then composed as Σ w_i × r_i with controller
weights from ISV[340..346).
Trail reward extraction (§3.5.4): trail-fire P&L now flows through
r_trail (forced-exit signal) instead of r_popart (voluntary-exit
signal). REWARD_TRAIL_WEIGHT_INDEX has real signal — controller can
weight forced-exit vs voluntary-exit P&L differently. rc[2] (the
prior structural-placeholder slot) now carries trail magnitude.
Universal post-composition modifiers (§3.4.4): drawdown / capital-
floor / inventory / churn / conviction-scale / cf-flip apply AFTER
the weighted Σ, unweighted. They are risk constraints and structural
operators, NOT learning components — agent cannot weigh them away.
Mean=1 normalization (B0, §3.4.3): weights normalize to mean=1 so per-
bar `w_active × r_active` ≈ pre-SP11 absolute scale on average.
Sentinel-defense: experience_env_step runs at start of epoch, SP11
controller runs at end (training_loop.rs ~3475). At fold 0 epoch 0
step 0 the controller has not yet emitted, so ISV[340..346) hold
sentinel 0. Defense: fmaxf(w_raw, 0.01) — same Invariant-1 hard floor
the controller enforces post-renorm. Cold-start scale = 1% of
pre-SP11; Pearl A bootstrap on first emit replaces sentinel.
cf_reward path: out_rewards[cf_off] now writes controller-weighted
cf reward (w_cf × r_cf with sentinel-defense). Loss-kernel
cf_weight=0.3f at mse:318/c51:789 (structural Q-blend, NOT reward
weight) UNTOUCHED per §3.5 amendment.
Mutual exclusivity preserved (popart / trail / micro / opp_cost):
exactly one path fires per bar; others stay 0. The cascade scalar
`reward` mirrors per-component locals so C.4/D.4b bonus blocks that
read in-progress trade reward (Q-cap pattern from
pearl_one_unbounded_signal_per_reward) keep bit-identical compounding.
After cascade, `reward` is overwritten with r_weighted; post-
composition modifiers operate on r_weighted as before.
This is the production-flip commit. Trainer is now on the SP11
controller end-to-end (modulo replay-time curiosity which lands in
B1c after Layer C audit per §3.5.5).
Verification:
- cargo check + build clean (1m32s release).
- 6/6 SP11 GPU oracle tests pass (none exercise env_step directly).
- 14/14 contract tests pass (sp5_isv_slots=10, state_reset_registry=4).
- Local smoke (RTX 3050 Ti, 20-epoch magnitude_distribution) verifies
HEALTH_DIAG sp11_reward weights drift epoch-over-epoch:
epoch 0: w_pop=1.000 w_cf=1.000 w_tr=1.000 ... (uniform sentinel-defense floor → mean=1)
epoch 3: w_pop=1.991 w_cf=2.036 w_tr=0.493 ... (controller redistributes)
epoch 9: w_pop=1.823 w_cf=1.887 w_tr=0.572 ... (mean ≈ 1.0 preserved, Σ ≈ 6)
EVAL_DIST bit-identical to B1a baseline (eq=0.803 eh=0.197 ef=0.000)
— pre-existing magnitude eval-collapse pathology
(project_magnitude_eval_collapse_kelly_capped) unchanged by B1b.
Audit doc updated (Invariant 7): docs/isv-slots.md SP11 section now
reflects Layer B status with B0/B1a/B1b/B1c rollout timeline.
reward_subsystem_controller_kernel: 5 canaries → 10 outputs, true Z-score
(delta_ema/sqrt(var_ema)), sigmoid blending, weight renormalization to Σ=1,
saboteur post-clamp, curiosity permanent floor (0.2 × bound). Pearls A+D
chained on outputs per spec §3.4.1.
novelty_simhash_kernel: 42×16 random projection → 16-bit SimHash code,
1M-slot bucket count table for novelty signal `1/sqrt(1+count)`. Race-
tolerated update per feedback_no_atomicadd (under-counts bias novelty
UPWARD — safe direction).
novelty_simhash_proj_init_kernel: Philox-seeded GPU init for the
projection matrix (CPU is read-only per feedback_no_cpu_forwards).
HEALTH_DIAG `sp11_reward` line emits 10 outputs + improvement_z each
epoch. Reset registry: novelty hash table reset arm wired (closes the
A0 deferral); projection matrix is frozen at trainer init for run
lifetime, not reset.
All 20 SP11 slots populate every step. No consumer reads them yet —
training behavior unchanged from A1. 3 new GPU oracle tests pass on
RTX 3050 Ti (controller midpoint, weight renorm, saboteur clamp).
Spec: docs/superpowers/specs/2026-05-04-sp11-reward-as-controlled-subsystem.md §3.4 §3.5.2
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds val_sharpe_delta + saboteur_engagement + reward_component_mag_ratio
GPU producers for the SP11 reward-as-controlled-subsystem chain. Each is
a single-block producer chained with apply_pearls_ad_kernel for Pearls
A+D smoothing per pearl_first_observation_bootstrap.md +
pearl_wiener_optimal_adaptive_alpha.md. All three write to slots in
[350..360) which no consumer reads yet — Layer A is additive; consumer
migration lands atomically in Layer B.
A1.1 — val_sharpe_delta_compute_kernel.cu
Two-pass: writes raw delta + (delta - prev_delta_ema)^2 to scratch.
Chained Pearls A+D (n_slots=2) → ISV[VAL_SHARPE_DELTA_EMA_INDEX=350,
VAL_SHARPE_VAR_EMA_INDEX=351]. Host writes val_sharpe to mapped-pinned
history[1]; rotation handled in training_loop.rs at val emit boundary
(a literal already-computed value — no host-side compute, no htod_copy).
A1.2 — saboteur_engagement_compute_kernel.cu
Per-bar |Δreward| > 0.01 × ISV[PNL_REWARD_MAGNITUDE_EMA_INDEX] check
with block tree-reduce (no atomicAdd per feedback_no_atomicadd). The
per-bar Δreward signal is produced by experience_env_step's saboteur
perturbation site as `traded × |reward| × max(|eff_spread − 1|,
|eff_slip − 1|)` — a structural proxy for the cost-differential the
saboteur imposed on bars where the model traded. Single kernel-side
emit (no parallel reward computation), per spec §3.3.1.
Chained Pearls A+D → ISV[SABOTEUR_ENGAGEMENT_RATE_INDEX=358].
A1.3 — reward_component_mag_ratio_compute_kernel.cu
Reads ISV[REWARD_POPART_EMA_INDEX..+6) (the SP4 reward-component
magnitude EMAs), normalises to ratios, and mirrors popart magnitude
into scratch[6] as a side-output. ONE non-pointer parameter
(popart_ema_base_slot) — no _unused param per feedback_no_stubs.
Two chained Pearls A+D launches:
n_slots=6 → ISV[REWARD_COMPONENT_MAG_RATIO_BASE..+6)
n_slots=1 → ISV[PNL_REWARD_MAGNITUDE_EMA_INDEX=359]
(slots non-contiguous: 352..358 then 359.)
Wire-up (per feedback_wire_everything_up):
- 3 cubin entries appended to crates/ml/build.rs
- 3 kernel handles + val_sharpe_history_pinned (MappedF32Buffer[2]) +
saboteur_delta_reward dev-ptr cache fields on GpuDqnTrainer
- 3 launchers (launch_sp11_*) + 1 setter (set_sp11_saboteur_delta_reward_buf)
- saboteur_delta_reward_per_sample buffer field on GpuExperienceCollector
- experience_env_step kernel signature extended with the new buffer arg;
every call site in the same commit per feedback_no_partial_refactor
- training_loop.rs init wires collector→trainer setter; val emit boundary
invokes launch_sp11_val_sharpe_delta_compute; per-epoch metrics block
invokes launch_sp11_mag_ratio_compute then
launch_sp11_saboteur_engagement_compute (mag_ratio first so the
signal-relative threshold base is populated before the saboteur reader)
- SP5_SCRATCH_TOTAL grown 266 → 276 (10 new scratch slots: 2+1+7)
- docs/isv-slots.md SP11 section updated to reflect A1 producers
3 GPU oracle tests in crates/ml/tests/sp11_producer_unit_tests.rs
pass on RTX 3050 Ti via MappedF32Buffer fixtures (zero htod_copy /
dtoh_sync_copy / alloc_zeros — feedback_no_htod_htoh_only_mapped_pinned
compliant).
Note on Step 8a path: the plan offered two routes for the saboteur
Δreward producer — in-kernel diff emission OR a small dedicated
reader-of-existing-buffers. The existing reward path emits ONE reward
(not both with/without), so the dedicated-reader alternative was
infeasible. The in-kernel emission landed as a small write site at the
END of experience_env_step (after total_reward_per_sample is finalised),
threading saboteur_eff_spread/saboteur_eff_slip from the perturbation
site forward to the END via stack vars. Single new kernel parameter,
single new GPU-only buffer, single existing call site updated.
Spec: docs/superpowers/specs/2026-05-04-sp11-reward-as-controlled-subsystem.md §5
Plan: docs/superpowers/plans/2026-05-04-sp11-reward-as-controlled-subsystem.md (Task A1)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-quality review on bf3a32d63 found two stale references that need
SP11 numbers:
- training_loop.rs:6672 + state_reset_registry.rs:891 — sp5_wiener_state
comments referenced the post-SP4/post-SP8 buffer sizes (543, 681);
post-SP11 is (71 + SP5_PRODUCER_COUNT) × 3 = 771 floats. Replaced the
literal sizes with formula form citing SP5_PRODUCER_COUNT directly so
this drifts less in the future.
- docs/isv-slots.md header — "Current ISV_TOTAL_DIM" said 171 (post-SP4
Task A1) while actual is 360. Updated header; SP11 section already
appended at the end of the file.
No logic changes. Cargo check + sp5_isv_slots / state_reset_registry
tests still pass.
Pure infrastructure. No producer kernels, no consumer reads. Existing
training paths trace identically because no consumer reads slots [340..360)
yet. Layout-fingerprint bumped to ISV_TOTAL_DIM=360.
Spec: docs/superpowers/specs/2026-05-04-sp11-reward-as-controlled-subsystem.md
Brings in worktree-agent-acd65ada (commit 89fadec24): per-branch IQN
τ schedules via 4 forward passes per step, each with one branch's τ
slab swapped in via mem::swap. ÷4 budget normalization at every
per-branch IQN call preserves total gradient magnitude.
CRITICAL FIX: this merge replaces the SP5 Layer B Pearl 5 implementation
that violated feedback_no_cpu_compute_strict. The old refresh_taus_from_isv
called upload_f32_via_pinned every step inside the CUDA Graph capture
region, triggering CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED and the
'parent graph capture failed (continuing ungraphed)' fallback observed
in smoke-test-qtn7c at SP5 HEAD.
SP6 Pearl 5 uses pre-uploaded τ slabs + mem::swap pointer arithmetic.
Conflict resolution in fused_training.rs:
- Pearl 5 worktree branched from pre-Pearl-2 HEAD; renamed
iqn_budget → iqn_trunk in 2 sites (parallel + sequential paths) to
match Pearl 2's compute_adaptive_budgets() new return signature.
- Removed redundant single-call apply_iqn_trunk_gradient(iqn_trunk)
from post-join block — Pearl 5's per-branch loop above already
applies iqn_budget_per_branch = iqn_trunk/4 to the IQN trunk gradient
4 times, achieving the same total magnitude with per-branch τ.
cargo check + cargo test --lib (sp4 sp5 state_reset_registry: 13/13)
both clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings in worktree-agent-a4d8a879 (commit ed3fa066b): per-branch σ via
[4]-element mapped-pinned device buffer. add_advantage_noise kernel
indexes σ by branch derived from action_idx % total_actions; Q-value
layout is branch-major contiguous so per-branch σ derivation requires
no forward-pass restructuring.
3 ExperienceCollectorConfig constructors updated.
Resolves Pearl 3 averaging from SP5 Layer B which collapsed 4 per-branch
σ values into a single scalar via training_loop.rs:1747.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SP6 sub-project 1 (Pearl 2): converts the 4 SAXPY launchers from a single
scalar budget (mean of 4 ISV branch slots) to per-branch differentiated
scaling via the correction-factor pattern.
Problem: SP5 Layer B read ISV[190..210) per-branch budget slots but
collapsed them to a scalar via sum/4.0, then passed the single scalar to
apply_c51_budget_scale / apply_cql_saxpy / apply_iqn_trunk_gradient.
Branch HEAD parameters received the same budget as trunk, defeating
per-branch differentiation.
Fix: compute_adaptive_budgets() now returns ([f32;4], [f32;4], [f32;4],
[f32;4], f32, f32, f32, f32) — four per-branch arrays + four trunk-mean
scalars. The trunk mean (D3 decision) is used for the full-buffer trunk/value
SAXPY call (preserving SP5 Layer B behavior for shared params). Branch HEAD
parameter slices receive a correction sub-launch:
correction = branch_budget[b] / trunk_mean (skip if |correction-1| <= 1e-6)
After both launches, branch HEAD slice is effectively scaled by
branch_budget[b], trunk/value is scaled by trunk_mean. No double-scaling.
New helpers added to GpuDqnTrainer:
apply_c51_budget_scale_branch(branch_idx, correction): scale_f32_ungraphed
on branch-slice [f32 elements], offset via padded_byte_offset.
apply_cql_saxpy_branch(branch_idx, correction): saxpy_f32_aux on
branch-slice of both grad_buf and cql_grad_scratch.
IQN trunk gradient: uses iqn_trunk (mean of 4 branch IQN budgets) — IQN
backward flows through trunk only; per-branch IQN routing is beyond SP6 scope.
HEALTH_DIAG: three new per-epoch info! lines emit per-branch c51/iqn/cql
budget arrays (dir/mag/ord/urg) after the intent_dist line.
State: per-branch budget arrays cached on GpuDqnTrainer
(last_*_budget_per_branch: [f32;4]) and on DqnTrainer
(last_*_budget_per_branch: Option<[f32;4]>) for diagnostics.
docs/isv-slots.md: updated Pearl 2 slot rows to reflect SP6 consumer wiring.
Verification: cargo check + release build clean (13 warnings, pre-existing).
13 sp5+sp4+state_reset_registry lib tests pass. sp5_producer_unit_tests
--no-run clean. Sanity grep for old sum/4.0 averaging pattern: empty.
Files changed (6): gpu_dqn_trainer.rs, fused_training.rs, constructor.rs,
mod.rs, training_loop.rs, docs/isv-slots.md. No Pearl 3 or Pearl 5 files touched.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GpuIqnHead gains 12 new CudaSlice<f32> buffers (online_taus_branch[4],
target_taus_branch[4], cos_features_branch[4]) allocated at construction time
via alloc_f32. Each slab is [B,N] for taus and [D,N] for cos_features — same
sizes as the existing main buffers.
refresh_taus_for_branch(branch_idx, tau5): uploads one branch's 5-quantile
τ schedule from ISV[IQN_TAU_BASE + b*5 .. +5] to per-branch slabs with cold-start
floor (FIXED_TAUS[q] when ISV slot is zero). No cross-branch averaging.
activate_branch_taus(b) / deactivate_branch_taus(b): symmetric mem::swap helpers
install/restore one branch's slab into self.online_taus/target_taus/cos_features
for a per-branch IQN forward pass. activate→deactivate(b) is its own inverse.
fused_training.rs:
- Tau refresh block calls refresh_taus_for_branch(b, tau5) for all 4 branches,
then refresh_taus_from_isv for the averaged main buffer (CVaR backward compat).
- grad_decomp_snapshot_iqn() moved BEFORE the parallel/sequential fork so the
snapshot is taken before any of the 4 per-branch apply_iqn_trunk_gradient calls.
- Parallel path: 4 sequential IQN passes on iqn_stream; after each pass, event
sync to main stream, apply_iqn_trunk_gradient(iqn_budget/4), re-fork so next
pass starts after main has consumed d_h_s2_buf. iqn_done_event recorded after
all 4 passes.
- Sequential path: 4 sequential IQN passes on main stream; apply_iqn_trunk_gradient
(iqn_budget/4) inline after each pass while d_h_s2_buf holds that branch's result.
- Post-join: single apply_iqn_trunk_gradient removed (now inline); target_ema_update
and PER loss cast remain.
÷4 normalization: both apply_iqn_trunk_gradient call sites use iqn_budget_per_branch
= iqn_budget / 4.0_f32 so 4 × (budget/4) = budget total — matching SP5 Layer B
gradient magnitude contract exactly.
docs/isv-slots.md: add SP6 Pearl 5 consumer wiring section under the SP5 table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace ExperienceCollectorConfig.noise_sigma: f32 with
noise_sigma_per_branch: [f32; 4] (branch order: dir/mag/ord/urg).
add_advantage_noise kernel (experience_kernels.cu) now takes
const float* noise_sigma[4] + b0/b1/b2/b3 branch-size params.
Each thread derives its branch_idx from action offset using cumulative
branch size offsets; applies that branch's sigma. Sigma=0 fast-exits
with no PRNG work.
GpuExperienceCollector gains noise_sigma_dev: MappedF32Buffer[4]
(mapped-pinned, zero HtoD copy per feedback_no_htod). CPU writes the
4 sigma values via write_from_slice before each kernel launch; kernel
reads via dev_ptr.
training_loop.rs reads ISV[NOISY_SIGMA_BASE..+4] = ISV[210..214)
directly — one slot per branch — instead of averaging all 4 into a
scalar. Cold-start floor 0.01 is Invariant 1 (numerical stability).
Falls back to [hyperparams.noise_sigma; 4] when fused_ctx unavailable.
Default::default() supplies [0.1; 4]. mod.rs test (line 895) uses
Default::default() unchanged — no explicit field to update.
docs/isv-slots.md updated to reflect SP6 Pearl 3 consumer wired.
Files changed: 4 (experience_kernels.cu, gpu_experience_collector.rs,
training_loop.rs, docs/isv-slots.md). No Pearl 2 or Pearl 5 files touched.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Address two code-quality review items on commit 6dcaf1a1c:
1. Convert file-level + Pearl-section comments from `//` to rustdoc
(`//!` module-level + `///` on first constant of each section).
Matches sp4_isv_slots.rs style; SP5 module now `cargo doc`-discoverable
as peer of SP4.
2. Replace spot-check assertions in slot_layout_no_overlaps_and_total_correct
with a HashSet enumerating every slot reachable through every accessor.
Asserts exactly 110 unique slots, min=174, max=285, the 2-slot carve-out
gap (278, 279) absent, and the set equals {174..278} ∪ {280..286}.
Test now actually verifies the no-overlaps invariant its name promised.
Also adds SP5 section to docs/isv-slots.md (Invariant 7 audit-doc update
required by pre-commit hook for cuda_pipeline component changes).
No constant values, accessor signatures, or fingerprint string changed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 important + 1 optional findings from code quality review:
1. layout_fingerprint_seed() now lists all 40 SP4 slots and bumps
`ISV_TOTAL_DIM=131` -> `ISV_TOTAL_DIM=171`. Without this, the fail-fast
checkpoint-load guard would not detect the SP4 layout extension —
a binary built against new code would falsely compare-equal to old
checkpoints' fingerprints.
2. Added `SP4_BRANCH_COUNT=4` constant and `debug_assert!(branch <
SP4_BRANCH_COUNT)` in `atom_pos_bound`. Test extended to verify
atom_pos_bound max does not alias into WEIGHT_BOUND family.
3. Refreshed stale content in docs/isv-slots.md: ISV_TOTAL_DIM 96->171,
fingerprint location [37..39)/[47..49) -> [115..117), table entries
[94]/[95] -> [115]/[116].
4. Added `debug_assert!(group < SP4_PARAM_GROUP_COUNT)` to weight_bound,
adam_m_bound, adam_v_bound, wd_rate accessors (symmetric with the
atom_pos_bound change).
cargo check clean, cargo test slot_layout_is_contiguous_and_total_40 passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per pearl_cold_path_no_exception_to_gpu_drives.md and explicit user
direction ("remove the legacy paths!"), the two Plan-1-era host-side
DtoH+CPU-loop helpers are GONE — not just routed around. Replaced
with GPU kernel reductions writing to ISV slots.
Deleted (160 lines):
- GpuDqnTrainer::per_branch_vsn_mean() — DtoH params slice + host abs+mean loop
- GpuDqnTrainer::per_branch_target_drift() — DtoH (target+online) slices + host RMS loop
- FusedTrainingCtx::per_branch_vsn_mean() and per_branch_target_drift() wrappers
Added (GPU-only producers, ~150 lines):
- target_drift_kernel.cu: 2-block reduction RMS(target − online) for mag/dir
branches. 256-thread smem tree-reduce per block; thread 0 EMA-updates
ISV slot via pinned device-mapped (no DtoH).
- ISV slots [92] TARGET_DRIFT_MAG_EMA_INDEX, [93] TARGET_DRIFT_DIR_EMA_INDEX
- Fingerprint shifted [90,91] → [94,95]; ISV_TOTAL_DIM 92 → 96
- Trainer accessors: branch_param_slice_indices(), target_params_buf_device_ptr()
- Collector launcher launch_target_drift_ema_inplace()
- Constructor cold-start writes for the 2 new slots
HEALTH_DIAG site refactor:
- vsn_mag/vsn_dir read from ISV[VSN_MAG_EMA_INDEX=87], ISV[88] (Task 5
GPU-driven kernel produced these, replacing the legacy VSN scalars)
- drift_mag/drift_dir read from ISV[TARGET_DRIFT_MAG_EMA_INDEX=92], ISV[93]
- All 4 reads via FusedTrainingCtx::read_isv_signal_at — pinned device-mapped
so host reads are coherent with GPU kernel writes without explicit DtoH
isv-slots.md: rows for [87..89] updated to reflect GPU-only producers
(replaces stale text claiming host-DtoH); 2 new rows for [92, 93];
fingerprint shifted to [94, 95]; ISV_TOTAL_DIM bumped 92 → 96.
Smoke fold-2 best Sharpe 100.45 at ep5; per-fold best_val_metric
3.75/10.09/20.21 (avg 11.35) — within Plan 3 T5 baseline (95-117 range).
Pearl validation: this commit demonstrates the cold-path-no-exception
rule applied retroactively. The legacy methods existed for an entire
plan generation; "cold path is fine" was the rationale. New rule:
if a value is computed (reduction/EMA/RMS), the compute is in a kernel,
period — regardless of frequency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan 4 Task 5 Mode A. Light, ISV-only, no model parameters added,
no checkpoint break.
ISV tail-append:
- [87] VSN_MAG_EMA_INDEX
- [88] VSN_DIR_EMA_INDEX
- [89] MAMBA2_RETENTION_EMA_INDEX
- Fingerprint shifted [85,86] → [90,91]; ISV_TOTAL_DIM 87 → 92
Producer (attention_focus_ema_kernel.cu):
- Single kernel, 3-block multi-reduction, fully GPU-driven
- Block 0 reduces magnitude-branch VSN-weight slice of params buffer
- Block 1 reduces direction-branch VSN-weight slice
- Block 2 reduces mamba2_h_enriched buffer
- Each block: 256-thread smem tree-reduce → mean |x| → EMA-update
ISV slot via pinned device-mapped (no explicit DtoH)
- Adaptive α matches Plan 3 Task 3 convention
GPU-only correction (per user direction "no dtoh, pinned memory"):
First-pass agent implementation used host-DtoH + CPU loop for the
Mamba2 retention scalar (mirroring the pre-existing
per_branch_vsn_mean() pattern). User flagged this as a violation
of spec §4.C.6 GPU-drives-CPU-reads even on cold path. Rewrote
the kernel to do all 3 reductions on-device via smem block-reduce,
reading params/mamba2 buffers via raw_ptr() and writing to pinned
ISV slots directly. Removed mamba2_retention_mean() from
GpuDqnTrainer + FusedTrainingCtx (was the host-DtoH culprit).
Read-only AttentionMonitor mirrors PlanThresholdMonitor pattern.
StateResetRegistry: all 3 slots FoldReset.
Smoke fold-2 best Sharpe 95.09, avg best_val_metric 10.98 — within
Plan 3 baseline range. ISV slots populate non-zero from epoch 2.
Pearl: cold-path is not an exception to GPU-drives-CPU-reads. If
the producer is computing a value (not just reading a CPU-side
constant), the compute belongs on GPU regardless of frequency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>