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>
Brings in worktree-agent-a284432c (commit f62860ea8): per-branch SAXPY
sub-launches via correction-factor pattern. Trunk gets mean budget,
branch HEAD weights get branch[b]/trunk_mean correction multiplier.
Resolves Pearl 2 averaging from SP5 Layer B (compute_adaptive_budgets)
which collapsed 4 per-branch budgets into a single scalar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
L40S smoke at SP5 Layer B HEAD (3ad5e011b) failed in 9.4s at first
fold-reset boundary with:
ERROR: fold-reset 'sp5_atom_v_center': StateResetRegistry reset
dispatch: unknown name 'sp5_atom_v_center'.
Every SP5 Layer A task added RegistryEntry blocks but none added the
matching dispatch arms in reset_named_state. Runtime validator catches
this on first fold boundary, crashing all 3 folds before training runs
(0/3 checkpoints saved). Local unit tests verified slot layouts but
never exercised the fold-reset dispatch path — gap masked through
Layer A close-out.
Adds 21 dispatch arms covering all SP5 Layer A FoldReset entries:
Pearl 1: sp5_atom_v_center / v_half / headroom / clip_rate
shared: sp5_branch_entropy / q_var_per_branch
wiener: sp5_wiener_state (no-op — sp4 arm covers full buffer)
Pearl 3: sp5_noisy_sigma / sigma_fraction
Pearl 2: sp5_budget_c51 / iqn / cql / ens / flatness
Pearl 4: sp5_adam_beta1 / beta2 / eps + sp5_grad_prev_buf
Pearl 5: sp5_iqn_tau
Pearl 8: sp5_trail_dist_per_dir
Pearl 1-ext: sp5_atom_num_atoms
Pearl 6 (slots 280..286) intentionally absent — those slots are
cross-fold-persistent (the whole point of A6 per
project_magnitude_eval_collapse_kelly_capped).
Each ISV-range arm zeros its slot range to fire Pearl A's first-
observation sentinel on the new fold's first producer launch.
sp5_grad_prev_buf calls a new reset_sp5_grad_prev_buf bulk-zero
method on GpuDqnTrainer (mirrors reset_sp4_clamp_engage_counters).
sp5_wiener_state is no-op since reset_sp4_wiener_state already
bulk-zeros the full wiener_state_buf [0..543) which includes the SP5
producer block at offsets [213..543).
cargo check + cargo test --lib (sp4+sp5+state_reset_registry: 13/13
pass) both clean. Smoke re-deployment pending.
Co-Authored-By: Claude Opus 4.7 (1M context) <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>
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>
Three independent sub-projects (one per Pearl), each an atomic commit,
each running in its own git worktree (sp6-pearl-2/3/5). Pearl 2 expands
compute_adaptive_budgets() to per-branch [f32;4] arrays + trunk-mean
scalars, dispatches branch-correction SAXPY sub-launches. Pearl 3
replaces noise_sigma: f32 with noise_sigma_per_branch: [f32;4] in
ExperienceCollectorConfig and extends add_advantage_noise to per-branch
lookup. Pearl 5 adds 4-branch tau buffer triplets to GpuIqnHead, runs
4 IQN forward passes per step (Approach B), each contributing
iqn_trunk/4 to prevent 4x gradient accumulation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comprehensive review of Layer B (commit 99367b9c6) caught one critical
and two important findings. All three fix in one atomic commit.
Critical: IQL Adam groups 4+5 (IqlHigh, IqlLow) missed the Pearl 4
migration. gpu_iql_trainer.rs::train_value_step still read
self.config.beta1=0.9, self.config.beta2=0.999 at runtime. ISV[230]
(adam_beta1(4)) and ISV[231] (adam_beta1(5)) were populated by the
Layer A producer but unconsumed — partial refactor of the 8-group
Pearl 4 contract. Fix mirrors the Curiosity pattern: 3 new
iql_beta1/beta2/epsilon parameters threaded through the IQL Adam call,
2 ISV reads at each of the 2 train_value_step call sites in
fused_training.rs.
Important: consumer clamp envelopes were wider than the SP4 producer
range (0.5..0.9999 vs producer's 0.85..0.95 for β1, etc.). At
cold-start with ISV=0, this produced β1=0.5 (more aggressive momentum
than the SP4-anchored 0.85 floor). Tightened all Pearl 4 consumer
clamps to match the producer envelope: β1∈[0.85,0.95], β2∈[0.99,0.9995],
ε∈[1e-10,1e-6].
Important: gpu_curiosity_trainer.rs:66-68 left dead ADAM_BETA1/BETA2/
EPS constants after the Pearl 4 migration. Removed.
8/8 Pearl 4 groups now ISV-driven (DqnTrunk, Value, Branches, IQN,
IqlHigh, IqlLow, Attn, Curiosity). cargo check + cargo build + lib
tests all clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SP5 Layer B: all consumers of ISV slots 174..286 migrated in one atomic
commit per feedback_no_partial_refactor.
Pearl 1 (C51 atom span, done prior session):
atoms_update_kernel.cu: reads v_center/v_half from ISV[174+b]/ISV[178+b]
Pearl 1-ext (per-branch num_atoms):
atoms_update_kernel.cu: reads ISV[274+b], rounds to valid {16,32,64},
uses eff_na for softmax/cumsum; output stride still global num_atoms
Pearl 2 (per-branch loss budgets):
fused_training.rs: compute_adaptive_budgets() reads ISV[190..194/198/202]
averaged over 4 branches; Invariant-1 floors 0.05/0.05/0.02/0.02
Pearl 3 (per-branch NoisyNet sigma):
training_loop.rs: ExperienceCollectorConfig.noise_sigma reads
mean(ISV[210..214]) with 0.01 floor; falls back to hyperparams.noise_sigma
when fused_ctx absent
Pearl 4 (per-group Adam beta1/beta2/epsilon):
gpu_dqn_trainer.rs: per-group reads inside launch_adam_update loop via
ISV[226+g], ISV[234+g], ISV[242+g]
gpu_iqn_head.rs: iqn_beta1/beta2/epsilon threaded into
execute_training_pipeline + train_iqn_step_gpu
gpu_attention.rs: attn_beta1/beta2/epsilon threaded into adam_step
gpu_tlob.rs: same as attention (shares ParamGroup::Attn=6)
fused_training.rs: 4 call sites read ISV[226+g..242+g] for IQN+Attn+TLOB
gpu_curiosity_trainer.rs: cur_beta1/beta2/epsilon params added to
launch_adam_step + train_on_collector_buffers
gpu_experience_collector.rs: train_curiosity_gpu threads cur_beta1/2/eps
training_loop.rs: reads ISV[233/241/249] for Curiosity=7 at call site
Pearl 5 (per-branch IQN tau schedule):
gpu_iqn_head.rs: new refresh_taus_from_isv() averages 4 branches per
quantile, applies FIXED_TAUS cold-start floor, re-uploads online_taus/
target_taus/cos_features in-place via upload_f32_via_pinned
fused_training.rs: reads ISV[250..270) before IQN prepare_buffers
Pearl 6 (Kelly cap) + Pearl 8 (trail distance) done in prior session:
trade_physics.cuh, experience_kernels.cu, backtest_env_kernel.cu
Audit doc: dqn-wire-up-audit.md SP5 Layer B entry added.
All 930 passing lib tests still pass; 14 pre-existing failures unchanged.
Compile: SQLX_OFFLINE=true cargo check -p ml --offline clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Combined spec-quality review caught two minor issues in the Pearl 8
commit. Both are mechanical fixes; no behavior change.
1. training_loop.rs:3640 used `let _ = std::mem::ManuallyDrop::new(guard);`
to keep the cudarc StreamGuard alive past the inner let-binding
block — the pattern leaks the guard's borrow bookkeeping for the
lifetime of the function rather than dropping it after the kernel
launch. Replaced with the idiomatic
let (feat_dev_ptr, _guard) = features_buf.device_ptr(stream_ref);
at the same scope as the launch, matching the existing convention
for SP4 producer wire-ups elsewhere in this file. The
_guard binding lets cudarc's borrow drop naturally at end-of-block
after the kernel launch returns.
2. The audit doc's description of test 14 incorrectly claimed
atr_norm was chosen so atr_abs=10.0 and Short/Long=20.0. The
actual test uses atr_norm=0.5 → log_atr=1.0 → atr_abs=e^1≈2.7183
→ Short/Long≈5.4366. Updated the audit doc to match the actual
test values.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-direction trail-stop distance derived from the current bar's ATR.
Short[270] and Long[272] receive 2× denormalized ATR (industry-standard
2×ATR trail); Hold[271] and Flat[273] receive EPS_CLAMP_FLOOR=1.0
(no trail-stop fires for those directions; floor prevents zero).
4 ISV slots [TRAIL_DIST_PER_DIR_BASE=270..274). ATR is read from
features[bar_idx × market_dim + 9] (ATR_NORM column) and denormalized
via the canonical fxcache scheme:
atr_abs = max(0.01, exp(atr_norm × 16 − 7))
Constants 16, 7, 0.01 are Invariant 1 anchors from the existing
fxcache normalization in experience_kernels.cu:2701.
Layer A simplification: Short and Long share the same current-bar ATR
value. The spec implied direction-conditional ATR via per-trade-event
aggregation, but that requires infrastructure not yet in place. The
4-slot ISV layout preserves the consumer contract so a future Layer C
extension can populate truly direction-specific values without
breaking Layer B's check_trailing_stop reads.
producer_step_scratch_buf grew 199 → 203 (1 new constant
SCRATCH_PEARL_8_TRAIL=199). wiener_state_buf stays at 543 (sized at
A1 for entire SP5 block).
StateResetRegistry: 1 new FoldReset entry (sp5_trail_dist_per_dir).
Pearl A sentinel 0 → bootstrap on fold boundary's first launch.
check_trailing_stop consumer migration deferred to Layer B.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Combined spec/quality review caught two minor issues in the Pearl 6
commit. Both are mechanical fixes; no behavior change.
1. Test 12 (pearl_6_kelly_within_fold_ewma_blend) was missing an
explicit assertion for slot 282 (TRADE_VAR_SMOOTH_IDX). The test
setup initialized tvar_i32 and the launcher passed it through the
kernel's parameter slot, but no assert! ever fired against the
resulting ISV value. With n_envs=1, the kernel's
`(kelly_count > 1) ? variance : 0.0f` branch returns 0 (no
cross-env variance possible with 1 env), so EWMA blend yields
0.99 × 0.5 + 0.01 × 0.0 = 0.495. Added the missing assertion to
close the within-fold coverage gap for s==2.
2. pearl_6_kelly_kernel.cu:136 doc comment said the slot computes
"standard deviation of per-env Kelly fractions" but the code
actually computes `ksum_sq / kelly_count` — i.e. the variance
(second moment), not the standard deviation. The slot name
TRADE_VAR_SMOOTH_INDEX correctly indicates variance; the comment
was wrong. Updated comment to match: 'variance of per-env Kelly
fractions' with explicit note that this is the second moment,
NOT std-dev (no sqrtf applied).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `pearl_6_kelly_kernel.cu` (single-block 6-thread kernel reading
portfolio_state directly) to populate ISV[280..286) with Bayesian-prior
Kelly fraction, conviction identity, trade variance, cumulative sample
count (max-semantics), win-rate and loss-rate EMAs. EWMA α=0.01 is an
Invariant 1 structural anchor for cross-fold inertia.
Key design departure from A1-A5: ISV[280..286) are intentionally EXEMPT
from the StateResetRegistry and from apply_pearls/Pearls A+D. portfolio_state
has WindowReset lifecycle (resets at EVERY window boundary, not just fold),
making cross-fold persistence essential. The max()-semantics for sample_count
(s==3) ensure the cumulative count never decreases across any boundary.
Wiener offsets [525..543) that naive formula would produce are intentionally
unused; in-kernel EWMA replaces external wiener bootstrap.
Verification: cargo check -p ml --offline clean (11 pre-existing warnings,
no new). sp5_producer_unit_tests --no-run clean. Two GPU tests (12, 13)
validate EWMA blend and cross-fold persistence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code-quality review caught that the kernel header described the τ
shift as 'toward the dense tail' but the actual math
(`t = default + skew × SKEW_SHIFT`) shifts τ in the SAME direction
as the skew sign — i.e. toward the LONG (sparse) tail, not the dense
region.
For a left-skewed distribution (long left tail, mode + dense mass on
the right), skew < 0 → τ shifts down toward 0 → IQN samples more of
the LEFT tail (sparse). The intent is risk-aware quantile coverage:
when there's tail mass that the symmetric 5-tuple under-represents,
lean the grid toward that tail. This matches risk-aware IQN where
financial risk modeling cares about downside coverage.
The math is correct and faithful to the plan's formula; only the
prose description was reversed. Replaced the one-liner with a 7-line
explanation that names the actual semantic clearly ('leaning the
quantile grid INTO the asymmetry direction').
Comment-only change. Cubin rebuild clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two minor code-review nits accumulated across A1-A4 reviews; closing
them in a single docs/comment-only commit before Layer A continues.
1. tests/sp5_producer_unit_tests.rs module docstring (A4 review):
the file header still claimed it covered only A1's two kernels
even though A2/A3/A4 had each appended their tests. Updated header
to enumerate all 9 tests across A1-A4 (+ 1 grad_cosine_sim test
landed in 4da6b34d7) so a reader cold to the file can locate each
producer's coverage without scanning the body.
2. gpu_dqn_trainer.rs:2854 stale field comment `[B, TOTAL_ACTIONS(11)]`
(A1 review): comment was left at 11 since the pre-4-direction
layout but the actual slot count is 13 (4 dir + 3 mag + 3 ord + 3
urg). Updated to `[B, TOTAL_ACTIONS(13)]` with the per-branch
breakdown inline so the field declaration matches the SP5 producer
docstrings (e.g. q_branch_stats_kernel comments at line 277, 282).
Comment-only changes; no behavior change. cargo check + cargo test
--no-run both clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-quality review caught that pearl_4_adam_hparams_kernel had direct
GPU tests but the auxiliary grad_cosine_sim_kernel had none. Tests 7
and 8 launched the hparams kernel with pre-baked cosine inputs,
never exercising the per-group reduction (dot + 2× L2 norm sums) or
the writeback (grad_curr → grad_prev for next step's comparison).
The writeback is load-bearing for cross-step cosine evolution — if
broken, every subsequent step's cosine_sim is computed against a stale
or mis-aligned previous gradient.
Adds Test 9 `grad_cosine_sim_per_group_dot_norm_and_writeback` with
8 analytically-known synthetic group cases:
group 0: curr=prev=e1 → cos=+1, |c|=1
group 1: curr=e1, prev=e2 → cos= 0 (orthogonal)
group 2: curr=e1, prev=-e1 → cos=-1 (antiparallel; raw)
group 3: curr=prev=(3,4,0,0) → cos=+1, |c|=5
group 4: curr=0, prev=e1 → cos= 0, |c|=0 (cold-start curr)
group 5: curr=e1, prev=0 → cos= 0, |c|=1 (Pearl A sentinel)
group 6,7: same as group 0
NO CPU oracle — expected values are unit-vector cosines from the
kernel formula. Writeback verified by reading grad_prev_buf after
the kernel runs and asserting bit-identity with grad_curr_buf for
all 32 elements.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-quality review caught that the two Pearl 2 unit tests verified each
output (c51, iqn, cql, ens) against its analytical expected value within
1% relative tolerance, but did NOT assert the structural invariant
`c51 + iqn + cql + ens ≈ 1.0` that the kernel maintains by construction
(`ens = max(0, 1 - iqn - c51 - cql)`).
A coefficient typo (e.g. BASE_IQN=0.111 instead of 0.11) would produce
individually-plausible per-component values that all still pass the
relative checks while quietly summing to 0.97 or 1.04. The sum check
catches that class of regression.
Adds `assert!((c51+iqn+cql+ens - 1.0).abs() < 1e-4)` inside both
per-branch loops in the flat-regime and sharp-regime tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-branch C51/IQN/CQL/Ens loss budgets driven by per-branch flatness =
var(Q[b]) / (σ[b]² + EPS_DIV). IQN dominates when flat, C51 yields
proportionally. CQL stays gated by existing regime_stability allocator.
Reads Q_VAR_PER_BRANCH (222..226 from Pearl 1's q_branch_stats) AND
NOISY_SIGMA (210..214 from Pearl 3 — must run AFTER both).
20 ISV slots (BUDGET_C51[190..194), BUDGET_IQN[194..198),
BUDGET_CQL[198..202), BUDGET_ENS[202..206), FLATNESS[206..210)).
StateResetRegistry: 5 new FoldReset entries.
producer_step_scratch_buf grew 111 → 131 (20 new outputs).
wiener_state_buf already at 543 (A1 sized for entire SP5 block).
Loss budget consumer migration deferred to Layer B.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code-quality review caught: pearl_1_atom_kernel.cu computed
`clip_rate = atoms_clip_count[b] / (batch_size × 13)` using the
total-actions=13 sum across all branches. The headroom controller's
TARGET_CLIP_RATE=0.01 is calibrated against the per-branch clip event
rate. Using a denominator 3.25–4.3× larger than the per-branch
denominator made the controller systematically under-responsive — at
actual 1% per-branch clipping it would read ~0.23–0.31% and contract
headroom toward the 2.0 floor instead of holding the target.
Currently masked: `atoms_clip_count` is zero in Layer A (Layer B's
atoms_update_kernel migration is what populates it). Without this fix,
Layer B would inherit a silently-wrong formula that converges to the
floor rather than the target rate.
Fix: plumb `action_counts[4]` ({4, 3, 3, 3}) through the kernel
signature and use `batch_size × action_counts[b]` as the per-branch
denominator. Matches q_branch_stats_kernel's existing parameter pattern.
Test #2 launcher updated to allocate action_counts_buf and pass its
dev_ptr through the new kernel parameter slot. Audit doc entry added
per Invariant 7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Test sp5_producer_unit_tests.rs:212 called `scratch_buf.len()` but `len`
is declared as `pub len: usize` on MappedF32Buffer (mapped_pinned.rs:210),
not a method. `cargo test --no-run` failed with E0599; library + cubin
build was unaffected.
One-character fix: `scratch_buf.len()` → `scratch_buf.len`.
Co-Authored-By: Claude Opus 4.7 (1M context) <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>
Critical self-review surfaced 15 issues; user directed fixes:
- "a no cpu path": KEEP host-EMA close-out (rule compliance) — but split
into separate Layer D atomic commit (was mis-scoped as Layer A)
- Pearl 4 kept: 3 concrete risks documented (constant-β proof break,
β2 memory reset destabilization, ε numerical envelope), structural
envelope bounds added, ALPHA_META halved, ε-only fall-back path defined
- Pearl 6 Kelly cross-fold persistence carve-out: separate slot range
280..286, NOT in SP4 fold-reset registry, Invariant 1 architectural exception
- Commit ordering Pearls 1 → 3 → 2 → 4 → 5 → 6 → 8 → 1-ext (resolves
Pearl 2 circular dependency on 1+3)
- Acceptance criteria: correctness gates (must pass) + performance gates
(loosened to "not catastrophically negative", within 2σ of pre-SP5)
- Pearl 7 timing: explicitly Layer C step 4, post-Layer-B + 3-seed validation
- Pearl 8 enumeration: 4 slots (TRAIL_DIST_PER_DIR per direction)
- Pearl 9 collapsed: 0 slots (Thompson achieved via Pearl 1's atom adaptation)
- Total slot count corrected: 110 (was 120-128 inconsistent)
Layer structure: A (additive, 8 commits) → B (atomic, 11 consumers) → C
(validation + Pearl 7 investigation) → D (host-EMA close-out, separate
atomic commit). Layer D split off from A's "close-out" because PnL
aggregation pipeline migration is its own architectural concern.
User final review pending before invoking writing-plans skill.
Per user direction:
Q1 (Layer A granularity): per-pearl commits (~9 commits, decision c)
Q2 (Pearl 7 timing): investigate-only in SP5; fix in follow-up
if Bin(2,0.5) persists post-Pearls-1-3 (decision a)
Q3 (Pearl 4 Adam β): include as designed; accept theoretical risk
with Pearls A+D + EPS_CLAMP_FLOOR mitigation;
Layer C smoke monitors for destabilization;
fall-back to ε-only if observed (decision a)
Spec section updated:
- Layer A description: per-pearl commit structure
- Pearl 7 framed as investigation-only with conditional follow-up
- Pearl 4 documents theoretical caveat + mitigation + fallback
User final review pending before invoking writing-plans skill.
SP5 design covers every known adaptive-parameter deferral in the DQN
training loop in a single coherent project. After SP5: zero hardcoded
multipliers, every adaptive value ISV-driven via Pearls A+D.
9 pearls + 1 sweep close-out + 1 validation milestone:
1-3. Per-branch atom span / loss budget / NoisyNet σ (52 slots)
4. Per-group Adam β1/β2/ε ISV-driven (24 slots)
5. Per-branch IQN τ schedule (20 slots)
6. Kelly cap signal-driven floors (6 slots)
7. dist_q/h/f Bin(2,0.5) audit + action_select fix (0-8 slots)
8. Trail stop signal-driven thresholds (6-8 slots)
9. Thompson direction-branch temperature (4 slots)
1-ext. Per-branch C51 num_atoms (4 slots)
Layer A close-out: 5 host-EMA host→GPU migrations
Validation: 3-seed × 50-epoch acceptance gate
Total: 120-128 new ISV slots, ~5000-7500 LOC, 11-13 producer kernels,
~12 consumer migrations.
Layer A (additive infrastructure, ~15 commits) → Layer B (atomic
consumer migration, single coordinated commit) → Layer C (validation +
cleanup). Mirrors SP4's layer pattern.
Triggering data: train-multi-seed-cv2mw 50-epoch L40S baseline
(terminated F0 ep10) revealed magnitude head Q-flatness, eval collapse,
and frozen action distributions. Plus all SP4 close-out + sweep
deferrals folded in per user direction "no deferrals — make a single
plan based on ALL findings".
Spec at:
docs/superpowers/specs/2026-05-01-sp5-magnitude-differentiation-and-eval-collapse-design.md
User review pending before invoking writing-plans skill.
Adds intent_dist_q/h/f alongside eval_dist_q/h/f to separate policy-
learning quality from Kelly-enforcement reality. Per memory pearl
project_magnitude_eval_collapse_kelly_capped.md: EVAL_DIST Quarter
dominance is a downstream artefact of Kelly cap × warmup_floor ×
safety_multiplier math, NOT a policy-learning failure. The intent
metric exposes the policy's pre-Kelly-cap chosen mag bucket so
operators can distinguish "policy isn't learning Full" from "Kelly
cap is suppressing Full" — the latter being an operationally-correct
state during cold-start positions.
Pure observability: no Kelly-math tweaks, no new tuned constants, no
reward-bias mechanisms (all explicitly forbidden by the memory pearl).
The intent_mag bucket comes straight from the factored action's
mag_idx (0/1/2) which already maps 1:1 to Quarter/Half/Full buckets.
The pre-cap mag_idx is already captured by the action-select kernel
into intent_mag_buf and exposed via the trainer's pre-existing
last_eval_intent_magnitude_dist host field — this commit only wires
that signal into HEALTH_DIAG.
Wired through (one coordinated commit per feedback_no_partial_refactor):
- health_diag.rs: HealthDiagSnapshot gains intent_dist_q/h/f fields
adjacent to eval_dist_*; size assertion bumped 147 → 150 fields.
- health_diag_kernel.cu: 3 new WORD_INTENT_DIST_* slots at [77..80);
every downstream WORD_* shifted by +3 in lockstep; WORD_TOTAL +
static_assert bumped 147 → 150. (The slots are reserved identically
to WORD_EVAL_DIST_* — both currently inert; HEALTH_DIAG GPU port
Phases 2/3 will populate them in lockstep with their sibling slots.)
- training_loop.rs: new adjacent tracing::info!() emitting
"intent_dist [iq=... ih=... if=...]" from the existing host-side
last_eval_intent_magnitude_dist field, immediately after the big
HEALTH_DIAG line containing eval_dist [eq=... eh=... ef=...].
- docs/dqn-wire-up-audit.md: new top-of-file entry per Invariant 7.
Behavior: zero change. Only adds observability.
Verification:
- cargo check -p ml --offline: clean.
- cargo test -p ml --lib --offline -- health_diag: 3/3 pass.
- cargo test -p ml --test sp4_producer_unit_tests --release
--ignored: 16/16 GPU tests pass.
Closes#212.
Per-step host-side EMA loop at gpu_dqn_trainer.rs:4671 over 6 mapped-
pinned homeostatic-target slots was a feedback_no_cpu_compute_strict
violation discovered during the sweep audit (commit 6a6b58aec) but
deferred for scope. Sweep audit grid site #9.
Migrated:
- New calibrate_homeostatic_kernel.cu — single-block, six threads
(one thread per homeostatic slot). Reads host-passed `readiness`
scalar (already-clamped IQN gauge from `iqn_readiness` shadow field,
bit-for-bit match of the deleted host clamp), observations from
`homeostatic_obs_dev_ptr`, applies adaptive α
`0.3 × (1 - readiness) + 0.01 × readiness` to targets[k] for k=1..5;
thread 0 forces the Q-mean invariant `targets[0] = 0.0` exactly as
the deleted host post-loop assignment did. __threadfence_system()
after writes for PCIe-visibility to homeostatic_kernel's dev_ptr reads.
- build.rs cubin registration and trainer-struct wiring (cubin static,
field, struct constructor, cubin load) mirror the C2/C3/C4 pattern
from the prior sweep commits.
- Host-side `for k in 0..HOMEOSTATIC_N_OBS` loop in
`calibrate_homeostatic_targets` replaced with a single
`launch_calibrate_homeostatic` kernel launch; chained on the
trainer's stream so it remains graph-capture-compatible.
Preserved:
- Same α formula, same Q-mean=0 invariant, same call ordering.
- Mapped-pinned target buffer retained — homeostatic_kernel still
reads via homeostatic_targets_dev_ptr unchanged.
- No cold-start sentinel: constructor pre-initialises targets to
`[0.0, 0.85, 0.1, 0.0, 1.0, 0.5]` (gpu_dqn_trainer.rs:14583-14588)
so the first EMA call blends defaults with the first observation,
same algebraic shape the deleted host loop relied on.
- State-reset registry unchanged — deleted host loop had no fold reset
(per-call EMA only); GPU port preserves identical per-call semantics.
cargo check clean. SP4 + state_reset_registry lib tests pass (11/11).
16/16 SP4 producer GPU tests pass on RTX 3050 Ti. No behavior change —
pure architectural fix.
Refs: feedback_no_cpu_compute_strict sweep audit grid site #9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`compute_adaptive_tau` (gpu_dqn_trainer.rs:7045) had zero production
callers — its q_div_ema field's doc-comment explicitly flagged it
"NOTE: only consumed by the orphan helper compute_adaptive_tau (zero..)".
Pre-SP3 attempt at adaptive-tau control, superseded by the SP3/SP4 ISV-
driven tau controller (`tau_kernel.cu` + ISV[TAU_INDEX]).
Removed:
- compute_adaptive_tau method (CPU EMA + ratio-scaled tau formula)
- q_divergence_readback method (only caller was compute_adaptive_tau)
- q_div_ema field + initialiser + fold-reset assignment
- q_divergence_pinned + q_divergence_dev_ptr struct fields, alloc, free
- q_divergence_dev_ptr memsets at C51 launch sites (2)
- q_divergence_dev_ptr arg in launch_c51_loss kernel call
- c51_loss_batched kernel parameter `q_divergence` (never written by
kernel body — comment "removed from hot path — zero atomicAdd"
confirmed buffer was inert; deleting parameter keeps kernel ABI clean
per feedback_no_partial_refactor)
- Stale doc-comments referencing q_divergence in c51 reduction kernel
- readback_pinned [12]=q_divergence layout doc (slot was never read)
cargo check clean. SP4 + state_reset_registry lib tests pass (11/11).
16/16 SP4 producer GPU tests pass on RTX 3050 Ti. No behavior change —
dead code only.
Refs: feedback_no_cpu_compute_strict sweep audit (commit 6a6b58aec)
flagged this as orphan; feedback_wire_everything_up mandates deletion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final commit of the SP4 Layer C close-out sweep (commits 24accea77, 605a8f526,
ca6315860, 1112abc2a). Adds the comprehensive audit grid to dqn-wire-up-audit.md
covering all 10 host-side EMA/compute sites identified during the sweep:
- 4 migrations completed (C1 redesigned + C2 iqn_loss_ema + C3 utilization_ema +
C4 adaptive_clip full chain).
- 5 documented exceptions: host-aggregated PnL/eval-result inputs
(training_sharpe_ema, max_dd_ema, gamma blend) and multi-step host-normalised
composition (HealthEmaTrackers + LearningHealth) where migration crosses the
brief's "architectural redesign" stop-condition (would require porting whole
compute_epoch_financials or learning_health composition to GPU).
- 1 orphan helper flagged for feedback_wire_everything_up (compute_adaptive_tau
q_div_ema — zero production callers).
- 1 NEW VIOLATION discovered during the audit (calibrate_homeostatic_targets
per-step host EMA loop over 6 mapped-pinned slots) deferred for separate
Layer C task.
- 1 misclassification noted (collector::set_utilization_ema is a setter, not
EMA arithmetic).
Sweep verification post-grep:
git grep -nE "= EMA_BETA \*|= \(1\.0 - EMA_BETA\)|= \(1\.0 - \w*ALPHA\) \*
self\.\w*_ema|= \w*_BETA \* self\.\w*_ema" crates/ml/src/cuda_pipeline/
crates/ml/src/trainers/dqn/
→ 1 match remaining (orphan compute_adaptive_tau q_div_ema, documented).
Going forward: zero-tolerance for new host-side EMA/reduction/mean in
SP4-touched code paths per feedback_no_cpu_compute_strict.md (saved 2026-05-01,
sweep date appended to memory entry).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layer C close-out C4 — the most architecturally substantive site of the
feedback_no_cpu_compute_strict sweep. GpuDqnTrainer::update_adaptive_clip
was running a 6-step host-side compute chain on GPU-produced inputs:
winsor (1) + cold-start sentinel + EMA (3) + scalar reduction (4) + ISV
upper-bound clamp (5) + mapped-pinned write (6). Per
feedback_no_partial_refactor the chain must migrate coherently — splitting
EMA-only into a kernel and leaving the surrounding scalar reductions on
host would be a partial migration violating the rule.
Migration: new update_adaptive_clip_kernel.cu (single-thread, single-block).
Takes host-passed observed_grad_norm (already a mapped-pinned readback)
+ 6 fixed structural constants (legacy values preserved per
feedback_no_quickfixes) + 4 mapped-pinned dev_ptrs + ISV[GRAD_CLIP_BOUND_INDEX].
Writes the full output chain (adaptive_clip_pinned, grad_norm_ema_pinned,
outlier_diag_pinned).
Storage migration:
- grad_norm_ema migrated from host-resident f32 to mapped-pinned scalar
(matches C1/C2/C3 pattern).
- New outlier_diag_pinned mapped-pinned slot for the GRAD_CLIP_OUTLIER warn
diagnostic. The kernel writes `delta = observed - clamped` and the host
reads it post-launch to format the warn log without running scalar
arithmetic on the host.
All structural constants preserved: EMA_BETA=0.95, CLIP_MULTIPLIER=2.0,
MIN_CLIP=1.0, GRAD_CLIP_OUTLIER_K=100, EPS_CLAMP_FLOOR (SP4). Same cold-start
sentinel `prev_ema <= 0.0 ⇒ assign clamped directly`; same Mech 6 (SP3) +
Layer B (SP4) bound design. Same outlier-warn log format reconstructed from
the mapped-pinned diagnostic slot.
Host-side early-return guard preserved (the pre-existing pattern from
C1 redesigned). grad_norm_emas_step_count counter unchanged (scalar
control-flow metadata, not compute, per the rule's explicit carve-out).
Verification: SP4 lib tests + 16 SP4 GPU producer unit tests pass on RTX 3050 Ti.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layer C close-out C3 — the host-side adaptive-α EMA over result.atom_utilization
(GPU-produced via q_readback_pinned mapped-pinned readback) at the bottom of
GpuDqnTrainer::reduce_current_q_stats violated feedback_no_cpu_compute_strict.
Migration: new update_utilization_ema_kernel.cu (single-thread, single-block,
mirrors C2's update_iqn_readiness_kernel shape). Takes the host-passed
atom_util scalar and updates two mapped-pinned slots in lockstep:
- utilization_ema_pinned (the EMA's new storage)
- homeostatic_obs_pinned[1] (the homeostatic mirror previously written by
update_homeostatic_observables host-side; kernel takes over)
Storage: utilization_ema migrated from host-resident f32 field to mapped-pinned
device-mapped scalar (matches C1/C2 pattern). Constructor init=1.0 preserves
the deleted host code's `if self.utilization_ema > 0.99 { assign obs }`
cold-start sentinel; same adaptive-α formula clamp(|err|/(|err|+0.1), 0.01, 0.30)
and EMA recurrence.
Consumer-chain coherence per feedback_no_partial_refactor:
- `update_homeostatic_observables` slot [1] write removed (kernel takes over).
- `utilization_ema()` accessor reads through pinned host_ptr.
- `adaptive_entropy` host derivation in `launch_c51_grad` reads via accessor.
- collector `set_utilization_ema` callsite uses accessor unchanged.
Discovered during the audit (deferred to separate tasks):
- `compute_adaptive_tau` q_div_ema is host-side EMA on a helper with ZERO
production callers — flagged per feedback_wire_everything_up.
- `calibrate_homeostatic_targets` runs a per-step host-side EMA loop over 6
mapped-pinned slots — NEW violation not in original 9-site list, flagged
for separate Layer C task.
- collector::set_utilization_ema was misclassified in original audit (it's a
setter, not EMA arithmetic).
Verification: SP4 lib tests + 16 SP4 GPU producer unit tests pass on RTX 3050 Ti.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layer C close-out C2 — the host-side EMA arithmetic block in
GpuDqnTrainer::update_iqn_readiness violated feedback_no_cpu_compute_strict
(any compute — EMA, reduction, mean — belongs on GPU regardless of frequency).
The cold-start sentinel + adaptive-α EMA + improvement-gauge formula now run
GPU-side via update_iqn_readiness_kernel (single-thread, single-block, mirrors
update_grad_norm_emas_kernel shape). The kernel takes the host-passed iqn_loss
scalar (from gpu_iqn.read_total_loss() mapped-pinned readback) and updates
three coupled mapped-pinned scalars (iqn_loss_initial_pinned, iqn_loss_ema_pinned,
iqn_readiness_pinned) in-place. __threadfence_system() guarantees PCIe-visibility
to mapped-pinned host_ptr accessors and to other-stream kernel reads via dev_ptrs.
Storage: iqn_loss_initial and iqn_loss_ema migrated from host-resident f32
fields to mapped-pinned device-mapped scalars (matches grad_norm_fast/slow_ema
pattern from C1 redesigned). New accessors iqn_loss_ema_value() and
iqn_loss_initial_value() read through the host_ptrs. iqn_readiness pinned slot
remains the same — c51_loss_kernel CVaR α consumer dev_ptr unchanged.
Cold-start sentinel (prev_initial < 1e-12 ⇒ assign loss directly + readiness=0)
preserves the deleted host-side bootstrap branch exactly. Same adaptive-α formula
clamp(|err|/(|err|+0.01), 0.01, 0.30) and improvement gauge clamped to [0,1].
state_reset_registry entries updated to reflect the new mapped-pinned storage
and producer relocation; reset_iqn_readiness_state writes 0.0 through the
pinned slots so the next kernel launch re-enters the bootstrap branch.
Verification: SP4 lib tests + 16 SP4 GPU producer unit tests pass on RTX 3050 Ti.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layer C close-out C1 redesigned. Original plan claimed
grad_norm_slow_ema_pinned was orphan post-Mech-6 migration; verification
surfaced a SECOND live consumer (fold_warmup_factor_update, commit
4ef1d8ebb) that legitimately reads the slow EMA as cross-fold
steady-state baseline.
The actual defect: the EMA UPDATE at update_adaptive_clip:22720-22737
was host-side `(1-α)*prev + α*obs` arithmetic — exactly the pattern
feedback_no_cpu_compute_strict (saved 2026-05-01) strictly forbids.
Migrated:
- New `update_grad_norm_emas_kernel.cu` — single-thread fused fast+slow
EMA update kernel. Reads `grad_norm_buf[0]`, updates two mapped-pinned
EMA scalars via dev_ptr. `__threadfence_system()` ensures the
`fold_warmup_factor_kernel` consumer sees freshly-written values.
- `launch_update_grad_norm_emas` Rust launcher chained on the
producer's stream — graph-capture-compatible, no host sync.
- update_adaptive_clip's host-side `unsafe { ... }` block replaced
with the GPU launcher call (warn-and-continue on launch failure
mirroring the launch_h_s2_rms_ema / launch_fold_warmup_factor
per-step ISV producer pattern at training_loop.rs:3450/3464).
Preserved:
- grad_norm_slow_ema_pinned mapped-pinned buffer (cross-fold persistent;
legitimate consumer is fold_warmup_factor_update).
- Fixed-α design (FAST_ALPHA=0.1, SLOW_ALPHA=0.001) — Pearls A+D
adaptive α would defeat the cross-fold-baseline semantic the warmup
factor depends on.
- Cold-start sentinel (`prev ≤ 0.0` ⇒ assign obs directly) — same
formula as the deleted host code.
- Host-side update_adaptive_clip early-return guard — kernel only
launches when observed_grad_norm is finite and > 0.
- grad_norm_emas_step_count host counter — scalar control-flow
metadata for warmup-window gating, not compute.
Plan/spec docs updated to remove stale "orphan" claim. State-reset
registry doc-comment + field doc-comments updated to reflect GPU-only
update path. fold_warmup_factor_kernel docstring no longer describes
its grad-norm EMA inputs as host-side-fed.
Build clean, sp4 + state_reset_registry lib tests pass (11/11), 16/16
SP4 producer GPU tests pass on RTX 3050 Ti. No behavior change — pure
architectural fix.
Refs: SP4 Layer C C1 redesigned (was: retire). Original plan
docs/superpowers/plans/2026-04-30-sp4-signal-driven-magnitude-control.md
lines 2184-2221.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two SP1-Phase-C cuBLAS backward sanitisers at gpu_dqn_trainer.rs:7615
(bw_d_h_s2 input) and :20679 (d_value_logits + d_adv_logits) used
hardcoded `1e6 × signal.max(1.0)` formulas — outside SP4 mechanisms
1/2/5/6/9/10 but flagged by feedback_isv_for_adaptive_bounds review as
the last remaining hardcoded multipliers in the cuBLAS-backward
sanitiser consumer path after Layer A/B closed out the rest of SP4.
Migrated to the proper SP4 pattern:
- 2 new SP4 ISV slots [171, 172) extending [131..171) → [131..173):
BW_D_H_S2_BOUND_INDEX=171 (single-buffer p99) and
Q_DIR_GRAD_BOUND_INDEX=172 (multi-sub-buffer p99 over
d_value_logits ∪ d_adv_logits, n_sub=2).
- 2 new producer kernels:
bw_d_h_s2_p99_kernel.cu (mirrors h_s2_p99 single-buffer pattern)
q_dir_grad_p99_kernel.cu (mirrors param_group_oracle multi-sub
convention via shared sp4_histogram_p99_multi<256> template;
reuses oracle_subbuf_table_buf + oracle_subbuf_counts_buf).
- Pearls A+D wired via existing apply_pearls_ad_kernel chained on
same stream (GPU-only, graph-capture-compatible).
- Consumers read ISV[slot].max(EPS_CLAMP_FLOOR) directly.
Buffer growth (single source of truth in gpu_dqn_trainer.rs):
SP4_PRODUCER_COUNT 69 → 71, SP4_WIENER_TOTAL_FLOATS 207 → 213.
ISV_TOTAL_DIM 171 → 173. LAYOUT_FINGERPRINT_SEED extended (existing
checkpoints will fail-fast at load — re-train required).
StateResetRegistry: 2 new FoldReset entries (sp4_bw_d_h_s2_bound,
sp4_q_dir_grad_bound) + 2 matching reset_named_state dispatch arms;
both halves of Pearl A's sentinel contract reset together per
feedback_no_partial_refactor.
Unit tests: 2 new GPU-gated tests in sp4_producer_unit_tests.rs
exercising (single-buffer Pearl-A→Pearl-D convergence, multi-sub-
buffer p99 vs analytical |N(0,1)|). All 16 GPU tests pass on local
RTX 3050 Ti (rel-err 0.00000 / 0.00526).
Behaviour change: bound is now Pearls-smoothed p99 of the actual
gradient distribution, tighter than the pre-existing 1e6 ceiling but
appropriate-scale for observed values. Smoke validation must verify
F0/F1/F2 still converge.
Refs: task #260, baseline commit 1c150d190 (Layer A + B + fix-up +
L40S smoke pass).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layer B's 3-way DQN main Adam split (commit 58ffb3a48) covered only
tensors [0..33) (DqnTrunk + DqnValue + DqnBranches per
param_group_buffers mapping). Tensors [33..163) — bottleneck, VSN
bottleneck, GLU, KAN, regime gate, spacing, multi-horizon value heads,
risk, ISV encoder, VSN per-group, aux heads, MoE — were silently no
longer Adam-updated. Their grads still computed, m/v state still
allocated, but no parameter step applied. Detected by code-quality
reviewer; would have surfaced at L40S smoke as ~70% network freeze
attributable to Sharpe noise.
Fixes:
1. 4th sub-launch added to launch_adam_update: trunk-extras tail
covering tensors [33..163), tagged ParamGroup::DqnTrunk for shared
bounds, with SP4_ENGAGE_OFFSET_DISABLED for Pearl C engagement
(rolls into trunk's accounting since they share WEIGHT_BOUND/
WD_RATE).
2. MAX_BLOCKS_PER_ADAM grown 256 → 4096 to accommodate trunk-extras's
block count (production cfg ~2400 blocks). SP4_ENGAGE_BUF_LEN
auto-updates: 11 × 4096 = 45056 ints. All 4 Curiosity-sub-launch
offset constants auto-derive from MAX_BLOCKS_PER_ADAM.
3. Coverage debug_assert_eq added: trunk + value + branches +
trunk_extras must equal total_params. Prevents future regressions.
4. read_group_adam_bounds(group) -> (clamp, wd) helper introduced on
GpuDqnTrainer; 6 verbose 2-line ISV-read patterns in
fused_training.rs collapsed to one-liners (TLOB + IQL high + IQL
low + IQN parallel + Attn parallel + Attn sequential + IQN
sequential = 7 call sites). The 4-way Adam loop in
launch_adam_update also uses the helper.
5. 9 stale doc-comments referring to "100 × Q_ABS_REF.max(1.0)"
updated to current "ISV[WEIGHT_BOUND[group]].max(EPS_CLAMP_FLOOR)"
contract (curiosity_training_kernel.cu, gpu_curiosity_trainer.rs
×2, iqn_dual_head_kernel.cu ×3, iql_value_kernel.cu,
attention_backward_kernel.cu, dqn_utility_kernels.cu ×2,
gpu_dqn_trainer.rs historical SP3 reference).
6. launch_adam_update + pearl_c_post_adam_engagement_check + audit
doc updated to reflect actual 4-way coverage and trunk-extras's
engagement folding into DqnTrunk.
Per feedback_no_partial_refactor: trunk-extras consumers (= no
dedicated SP4 producer yet) and bound (= reused trunk's
WEIGHT_BOUND/WD_RATE) ship together with the launch fix in this
commit. A future task may introduce dedicated SP4 producers for
sub-trunk regions (VSN, MoE, etc.) once warranted by signal analysis.
Refs: 58ffb3a48 (Layer B atomic consumer migration).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layer A L40S smoke smoke-test-v9kjv revealed CUDA Graph capture failure
at aux_label_scale_ema mid-step host sync inside aux_heads_forward Step 2b
(CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED). Per feedback_no_cpu_compute_strict
and pearl_cold_path_no_exception_to_gpu_drives: CPU compute path is
strictly forbidden — any formula (EMA / reduction / Pearls A+D / adaptive α)
belongs on GPU regardless of frequency.
This commit migrates ALL 11 Pearls A+D applications + 1 inline application
to a single GPU apply_pearls_ad_kernel:
- 5 SP4 producers (target_q, atom_pos×4, param_group_oracle, grad_norm, h_s2)
- 6 A13 retrofits (h_s2_rms, aux_heads_loss, moe_expert_util,
vsn_mask, iqn_quantile, reward_component)
- 1 inline aux_label_scale block in aux_heads_forward Step 2b
Eliminates: stream.synchronize() + host_ptr read_volatile + host arithmetic
+ host_ptr write_volatile pattern from all 11 launchers + 1 inline.
apply_pearls_to_slot host helper deleted. pearls_ad_update kept as
test-only reference implementation (also retained for the post-Adam
pearl_c_post_adam_engagement_check host-side diagnostic which reduces
mapped-pinned i32 counters outside any captured graph). New GPU unit
test asserts kernel output matches reference within fp32 ULP for 5
representative cases plus a multi-slot batch sanity check.
The refactor is graph-capture-compatible by construction: the kernel
runs single-thread in the same stream as the producer kernel; no host
synchronisation needed between producer and applicator.
Build clean (cargo check --workspace), 6 host sp4_wiener_ema tests pass,
13 SP4 GPU tests + 1 new oracle test pass on RTX 3050 Ti. L40S smoke
re-validation deferred to A17 redo.
Refs: smoke-test-v9kjv graph-capture failure (terminated), commit 4c231fa81.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One Invariant 7 entry covering all of SP4 Layer A: producers landed (11),
Pearl A/B/C/D coverage, mapped-pinned discipline, atomic-add discipline,
orphan deletion, Layer B/C deferrals, and acceptance gate status.
This is the stake-in-the-ground for Layer A — every new code path either
wired or accounted for as deliberate Layer B/C scope.
Refs: A1..A15 + spec gap-fix + code-quality refactor (aada419de..4c231fa81 + ea31ebfe3).
Addresses 5 IMPORTANT items from A13 code-quality review:
1. apply_pearls_to_slot helper extracted into sp4_wiener_ema.rs.
Collapses ~30 lines of read_volatile / pearls_ad_update / write_volatile
per-slot block into a single unsafe fn. 12 launchers now consume the
helper (5 SP4 producers A5-A9, 6 A13 retrofits A13.0-A13.5, plus the
inline label-scale block in aux_heads_forward Step 2b — including 2
apply_pearls closures inside multi-slot launchers and the cross-boundary
GpuExperienceCollector consumer). Pearl C rate_deficit site untouched
(different mapped-pinned buffer + Rust ema array, doesn't share the
helper's pointer-based contract).
2. REWARD_COMPONENT_COUNT named constant added next to
REWARD_POPART_EMA_INDEX in gpu_dqn_trainer.rs. Replaces 3 hardcoded `6`
literals across collector launcher (block_dim, Pearls A+D loop) and
training_loop fold-reset range arithmetic. Mirrors MOE_NUM_EXPERTS /
SL_NUM_FEATURE_GROUPS invariant-guard pattern with debug_assert_eq! in
the collector launcher. Kernel literal `6` retained (allows nvcc full
unroll); kernel comment now documents the host-side invariant.
3. SP4_PRODUCER_COUNT, SP4_WIENER_FLOATS_PER_SLOT, SP4_WIENER_TOTAL_FLOATS
promoted from fn-local consts inside `pub fn new` to module-level
`pub const`s in gpu_dqn_trainer.rs, re-exported via cuda_pipeline::mod.
All 13 redeclarations in tests/sp4_producer_unit_tests.rs replaced
with single `use ml::cuda_pipeline::SP4_PRODUCER_COUNT;` import. Future
buffer growth requires single-file edit.
4. _ema_alpha_unused: f32 caller-compat shim removed from 6 retrofitted
launchers (launch_h_s2_rms_ema, launch_aux_heads_loss_ema,
launch_vsn_mask_ema, launch_moe_expert_util_ema, launch_iqn_quantile_ema,
launch_reward_component_ema_inplace) and the FusedTrainingCtx proxy.
All callers in training_loop.rs + fused_training.rs updated to drop
the unused argument per feedback_no_legacy_aliases (no soft-deprecated
wrappers; rename all call sites directly). Doc-comments updated from
"α dropped per SP4 — argument preserved so callers compile unchanged"
to "α derived adaptively from per-slot Pearls A+D Wiener state — see
sp4_wiener_ema::pearls_ad_update".
5. Stale doc reference fixed at gpu_aux_heads.rs:391 — comment referenced
non-existent launch_label_scale_ema_with_pearls function. Now correctly
points at the inline Pearls A+D block in GpuDqnTrainer::aux_heads_forward
Step 2b (which now consumes apply_pearls_to_slot).
Pure refactor — no spec or behaviour change. Same kernel launches, same
Pearls A+D semantics, same ISV slot writes.
cargo check -p ml --offline clean (12 pre-existing warnings, no new);
cargo test -p ml --lib --offline sp4_wiener_ema 7/7 passing
(6 originals + apply_pearls_to_slot_pearl_a_bootstrap_path);
cargo test -p ml --lib --offline state_reset_registry 3/3 passing.
Refs: A13 review (commits aada419de..c5add566d).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A13's per-producer commits (A13.0..A13.5) zeroed the Wiener triples at
fold boundary via the bulk `sp4_wiener_state` reset, but left the
companion ISV-slot dispatch arms writing pre-SP4 cold-start values. With
`prev_x_mean != 0 && state.x_lag == 0`, `pearls_ad_update` enters Pearl D's
formula at step 0 with `prev = stale_cold_start` — defeating Pearl A's
sentinel-detection contract documented at design-spec L262/284/286.
Updated 5 retrofit producer dispatch arms in `training_loop.rs` to write
0.0 at fold boundary (matching the Wiener-state half of the contract):
- isv_h_s2_rms_ema (was 1.0)
- isv_vsn_mask_g{0..5}_ema (was 1/SL_NUM_FEATURE_GROUPS = 1/6 per group)
- isv_aux_label_scale_ema (was 1.0; consumer 1e-6 floor guards
the divide-by-zero window between fold
boundary and first producer fire)
- isv_moe_expert_util_ema (was 1/MOE_EXPERT_UTIL_EMA_COUNT = 1/8 per
expert)
- isv_moe_gate_entropy_ema (was ln(MOE_EXPERT_UTIL_EMA_COUNT) = ln(8))
Already-correct retrofit arms verified writing 0.0 (no change):
- isv_iqn_q_p{05,25,75,95}_ema (A13.4) — already 0.0
- isv_aux_next_bar_mse_ema, isv_aux_regime_ce_ema (A13.1) — already 0.0
- isv_reward_component_emas (A13.5) — already 0.0
Registry descriptions in `state_reset_registry.rs` updated in the same
commit per `feedback_no_partial_refactor.md` (doc + dispatch arm are two
halves of the same contract — both must reset together).
Also fixed stale `141`-float and `2048`-int Wiener-buffer comments to
`207` / `2816` in:
- state_reset_registry.rs (block comment around the SP4 fold-reset
contract block)
- training_loop.rs (sp4_wiener_state bulk-reset comment 141 -> 207)
- gpu_dqn_trainer.rs (wiener_state_buf field doc 47 -> 69 ISV-bound
EMAs; scratch-layout comment 40..47/7 -> 40..69/29)
Verification:
- cargo check -p ml --offline: clean (11 pre-existing warnings)
- cargo test -p ml --lib --offline sp4_wiener_ema: 6/6 passing
- cargo test -p ml --lib --offline state_reset_registry: 3/3 passing
Spec: docs/superpowers/specs/2026-04-30-sp4-signal-driven-magnitude-control-design.md L262/284/286
Plan: docs/superpowers/plans/2026-04-30-sp4-signal-driven-magnitude-control.md L440
Refs: A13.0..A13.5 (aada419de..4f82b74a5)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>