Commit Graph

4566 Commits

Author SHA1 Message Date
jgrusewski
76d58406bd merge: SP6 Pearl 3 — NoisyLinear per-branch σ array
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>
2026-05-02 09:10:51 +02:00
jgrusewski
becd488320 merge: SP6 Pearl 2 — SAXPY per-branch loss budgets
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>
2026-05-02 09:10:41 +02:00
jgrusewski
82ca733255 fix(sp5): Layer A bug — add 21 reset_named_state dispatch arms
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>
2026-05-02 08:54:48 +02:00
jgrusewski
f62860ea82 feat(sp6): Pearl 2 — SAXPY per-branch loss budgets (correction-factor pattern)
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>
2026-05-02 02:08:54 +02:00
jgrusewski
ed3fa066b9 feat(sp6): Pearl 3 — NoisyLinear per-branch σ array
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>
2026-05-02 02:05:42 +02:00
jgrusewski
6b01b70c3e docs(sp6): implementation plan — 3-sub-project parallel refactor (Pearls 2, 3, 5)
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>
2026-05-02 01:47:07 +02:00
jgrusewski
ca6e0007da docs(sp6): brainstorm spec — per-branch consumer infrastructure refactor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 01:36:48 +02:00
jgrusewski
3ad5e011b9 fix(sp5): Layer B fix-up — close 3 review findings before Layer C
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>
2026-05-02 01:27:40 +02:00
jgrusewski
99367b9c60 feat(sp5): Layer B — atomic consumer migration to ISV-driven adaptive signals
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>
2026-05-02 01:11:30 +02:00
jgrusewski
19e5cde91e docs(sp5): Layer A close-out — replace stale scratch-buf comment
Code-quality review of A8 caught that gpu_dqn_trainer.rs:15154 still
read `// to SP5_SCRATCH_TOTAL (103) = 71 + 16 (q_branch_stats) + 16
(pearl_1_atom)` — pre-existing from A1 that wasn't updated as the
constant grew through A2-A8 (103 → 111 → 131 → 171 → 199 → 203 → 207).
The runtime allocation is correct (uses the constant, not the literal),
so this was readability-only.

Replaced the 2-line stale comment with the complete scratch-buffer
layout map covering all SP5 Layer A producer outputs:

  [0..71)    SP4 producers
  [71..87)   q_branch_stats        (Pearl 1 / A1)
  [87..103)  pearl_1_atom_update   (Pearl 1 / A1)
  [103..107) pearl_3_sigma σ       (Pearl 3 / A2)
  [107..111) pearl_3_sigma SF      (Pearl 3 / A2)
  [111..131) pearl_2_budget        (Pearl 2 / A3)
  [131..147) pearl_4 cosine + l2   (auxiliary, A4)
  [147..171) pearl_4 β1/β2/ε       (Pearl 4 / A4)
  [171..199) pearl_5 skew/kurt+τ   (Pearl 5 / A5)
  Pearl 6 (A6) writes directly to ISV — no scratch.
  [199..203) pearl_8 trail_dist    (Pearl 8 / A7)
  [203..207) pearl_1-ext num_atoms (Pearl 1-ext / A8)

Closes the last residual A1-vintage comment debt; gives Layer B
implementers a self-contained map of the scratch contract.

Comment-only change. cargo check clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 00:26:43 +02:00
jgrusewski
2e84fd35d4 feat(sp5): Task A8 — Pearl 1-ext per-branch num_atoms — Layer A complete
Final SP5 Layer A producer. Per-branch C51 num_atoms derived from
per-branch ATOM_V_HALF (Pearl 1, Task A1). Threshold cascade:
  v_half < 0.1  → 64 atoms (narrow Q, high resolution)
  v_half < 1.0  → 32 atoms (moderate)
  v_half ≥ 1.0  → 16 atoms (wide Q, modest resolution)

4 ISV slots [ATOM_NUM_ATOMS_BASE=274..278). Pearls A+D smooths the
discrete output during transitions; Layer B's atoms_update consumer
rounds to nearest valid count.

producer_step_scratch_buf grew 203 → 207. wiener_state_buf already
at 543 (sized at A1 for entire SP5 block).

StateResetRegistry: 1 new FoldReset entry (sp5_atom_num_atoms).

atoms_update consumer migration deferred to Layer B.

LAYER A COMPLETE. 8 producers + 3 auxiliary kernels (q_branch_stats,
grad_cosine_sim, q_skew_kurtosis) populating 110 ISV slots [174..286)
with 4 cross-fold-persistent slots carved out (Kelly).

Refs: SP5 spec 6e6e0fa11 line 1020

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 00:19:36 +02:00
jgrusewski
b4e5fc9891 fix(sp5): Task A7 — close two minor review findings
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>
2026-05-02 00:06:40 +02:00
jgrusewski
fd1426a398 feat(sp5): Task A7 — Pearl 8 per-direction trail-stop distance
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>
2026-05-01 23:59:22 +02:00
jgrusewski
f40ccc16a7 fix(sp5): Task A6 — close two minor review findings
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>
2026-05-01 23:31:37 +02:00
jgrusewski
5b4cdec4ff feat(sp5): Task A6 — Pearl 6 cross-fold-persistent Kelly cap signals
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>
2026-05-01 23:22:06 +02:00
jgrusewski
1aaa1cb0bc docs(sp5): Task A5 — correct pearl_5_iqn_tau header comment direction
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>
2026-05-01 22:59:46 +02:00
jgrusewski
b1ef312a40 feat(sp5): Task A5 — Pearl 5 per-branch IQN τ schedule GPU producer (Layer A)
Two new CUDA kernels land as SP5 Layer A additive producers feeding ISV[250..270)
with per-branch IQN quantile-τ schedules derived from Q-distribution skew.

q_skew_kurtosis_update: single-block 4-thread, reads save_q_online[B×13],
two-pass central moments → skew clamped [-3,+3] + ex_kurt clamped [-3,+30];
writes scratch[171..179). EPS_DIV=1e-12 (Invariant 1 anchor). No atomicAdd.

pearl_5_iqn_tau_update: single-block 4-thread, shifts symmetric default τ
{0.05,0.25,0.5,0.75,0.95} by skew×SKEW_SHIFT=0.05, clamps to [0.01,0.99];
writes scratch[179..199). SKEW_SHIFT and envelope are Invariant 1 anchors.

launch_sp5_pearl_5_iqn_tau fires both kernels + 20 apply_pearls_ad calls
(ALPHA_META=1e-3) → ISV[IQN_TAU_BASE=250..270). SP5_SCRATCH_TOTAL 171→199.

StateResetRegistry +1 FoldReset entry (sp5_iqn_tau, ISV[250..270)).
training_loop.rs wired after Pearl 4 with tracing::warn on error.
Two GPU-only unit tests (10+11): zero-skew symmetric default + left-skew
floor clamp. Module docstring updated A1-A5.

No consumer migration — Layer A additive only per spec.
cargo check -p ml --offline clean (11 pre-existing warnings, none new).
cargo test --no-run clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 22:50:06 +02:00
jgrusewski
d4d12abab2 docs(sp5): close two minor review findings before A5
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>
2026-05-01 22:35:18 +02:00
jgrusewski
4da6b34d78 test(sp5): Task A4 — add grad_cosine_sim_update unit test
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>
2026-05-01 22:30:02 +02:00
jgrusewski
67dd414cb3 feat(sp5): Task A4 — Pearl 4 per-group Adam β1/β2/ε
Per-param-group Adam hyperparameters from per-group gradient
direction-stability EMA + L2 norm. 8 SP4 param groups × 3 hparams = 24
ISV slots [226..250).

Two-kernel chain:
  grad_cosine_sim_update — per-group cosine_sim of curr vs prev grads
                          + L2 norm; writes back grad_curr → grad_prev
                          for next step's comparison.
  pearl_4_adam_hparams_update — β1/β2/ε from cosine + l2_norm.

Structural envelopes (Invariant 1 anchors per spec line 88):
  β1 ∈ [0.85, 0.95]; β2 ∈ [0.99, 0.9995]; ε ∈ [1e-10, 1e-6]

ALPHA_META migration (Option A — atomic shared-contract migration per
feedback_no_partial_refactor): apply_pearls_ad_kernel.cu and the
launch_apply_pearls Rust wrapper now take alpha_meta as a parameter.
All 45 existing call sites (SP4 + SP5 producers) pass the prior default
1.0e-3 explicitly via crate::cuda_pipeline::sp4_wiener_ema::ALPHA_META.
Pearl 4's apply_pearls calls pass 5.0e-4 (half the default per spec
line 89) to limit β change rate.

Theoretical caveat: adaptive β breaks Adam's constant-β convergence
proof. Mitigations: structural envelopes + halved ALPHA_META + Pearls
A+D smoothing. Fall-back path defined: revert + ε-only adaptive
variant if Layer C destabilization observed.

New mapped-pinned buffer: grad_prev_buf_per_group [TOTAL_PARAMS] for
cosine-sim previous-step direction storage. Mapped-pinned i32 mirror
of grad_buf layout.

producer_step_scratch_buf grew 131 → 171 (40 new outputs: 8 cosine_sim
+ 8 l2_norm + 24 β1/β2/ε). wiener_state_buf already at 543 (A1 sized
for entire SP5 block).

StateResetRegistry: 4 new FoldReset entries: sp5_adam_beta1,
sp5_adam_beta2, sp5_adam_eps, sp5_grad_prev_buf. All sentinel 0 →
bootstrap to envelope midpoint via Pearls A+D + cosine_sim=0 fall-back
on first step of new fold.

Adam-launcher consumer migration deferred to Layer B.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 22:18:10 +02:00
jgrusewski
71a0275f54 test(sp5): Task A3 — assert budget sum-to-1 invariant
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>
2026-05-01 21:46:40 +02:00
jgrusewski
4b2093a627 feat(sp5): Task A3 — Pearl 2 per-branch loss budget
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>
2026-05-01 21:38:43 +02:00
jgrusewski
ab3e17f4a2 feat(sp5): Task A2 — Pearl 3 per-branch NoisyNet sigma
Per-branch sigma scales with per-branch Q magnitude (v_half from Pearl 1's
ATOM_V_HALF, populated in A1). SIGMA_FRACTION adapts via entropy-deficit
controller targeting 70% of max action entropy (target_entropy =
log(n_actions[b]) * 0.7).

8 ISV slots (NOISY_SIGMA[210..214), SIGMA_FRACTION[214..218)). Reuses
BRANCH_ENTROPY (218..222) from Task A1's q_branch_stats_kernel.

producer_step_scratch_buf grew 103 -> 111 (8 new outputs). wiener_state_buf
already at 543 (A1 sized for entire SP5 block).

NoisyLinear consumer migration deferred to Layer B.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 21:16:11 +02:00
jgrusewski
3b4ce05465 fix(sp5): Task A1 — pearl_1 clip_rate denominator must be per-branch
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>
2026-05-01 21:04:12 +02:00
jgrusewski
db0b603127 fix(sp5): Task A1 — MappedF32Buffer.len is a public field, not a method
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>
2026-05-01 20:55:18 +02:00
jgrusewski
0b0f3b21e1 feat(sp5): Task A1 — Pearl 1 atom-span + Q-stats GPU producers (Layer A)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 20:46:50 +02:00
jgrusewski
f91098e456 refactor(sp5): Task A0 quality fix-up — rustdoc + HashSet overlap test
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>
2026-05-01 20:20:53 +02:00
jgrusewski
6dcaf1a1c9 feat(sp5): Task A0 — define 110 SP5 ISV slot constants
Foundation commit for SP5 per-branch + per-group adaptation layer.
Allocates slot ranges 174..286 with 6 cross-fold-persistent Kelly
slots at 280..286 (NOT in fold-reset registry per spec). Intentional
2-slot gap at 278..280 makes the cross-fold carve-out structurally
visible.

Slot layout:
  174..226  Per-branch (Pearls 1, 2, 3 + Q-var shared signal)
  226..250  Per-group Adam β1/β2/ε (Pearl 4)
  250..270  Per-branch IQN τ schedule (Pearl 5)
  270..274  Per-direction trail distance (Pearl 8)
  274..278  Per-branch num_atoms (Pearl 1-ext)
  280..286  Cross-fold-persistent Kelly (Pearl 6)

LAYOUT_FINGERPRINT_SEED bumped — existing checkpoints fail-fast.
ISV_TOTAL_DIM 173 → 286.

No producer/consumer wired yet. Layer A commits A1-A8 populate slots
in per-pearl commits in dependency order:
  Pearl 1 → Pearl 3 → Pearl 2 → Pearl 4 → Pearl 5 → Pearl 6 → Pearl 8 → Pearl 1-ext

Refs: docs/superpowers/specs/2026-05-01-sp5-magnitude-differentiation-and-eval-collapse-design.md (HEAD 6e6e0fa11)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:10:31 +02:00
jgrusewski
11979d7c08 docs(sp5): comprehensive implementation plan — 4 layers, ~21 tasks
SP5 implementation plan covering:
  Layer A: 8 per-pearl commits (Tasks A0-A8)
    A0: ISV slot constants foundation
    A1: Pearl 1 per-branch atom span + Q-stats source
    A2: Pearl 3 per-branch NoisyNet σ
    A3: Pearl 2 per-branch loss budget (after Pearls 1+3)
    A4: Pearl 4 per-group Adam β/β/ε
    A5: Pearl 5 per-branch IQN τ schedule
    A6: Pearl 6 cross-fold-persistent Kelly
    A7: Pearl 8 per-direction trail distance
    A8: Pearl 1-ext per-branch num_atoms

  Layer B: 1 atomic commit (Task B1) — 11 consumer migrations

  Layer C: validation + cleanup (Tasks C1-C6)
    C1: Local GPU unit tests
    C2: L40S 5-epoch smoke
    C3: L40S 3-seed × 50-epoch full validation
    C4: Pearl 7 investigation (post-validation)
    C5: Audit doc + 8 memory pearls
    C6: Layer C commit

  Layer D: separate atomic commit (Tasks D1-D4)
    D1: PnL aggregation kernel
    D2: Health composition kernel
    D3: Training metrics EMA kernel
    D4: Layer D atomic commit

Total: 21 numbered tasks, ~110 ISV slots, 9-11 producer kernels,
11 consumer migrations.

Plan follows SP4's high-fidelity-for-Task-A1 + differential-pattern-
for-A2-A8 structure. Each task has concrete file paths, code blocks,
verification commands, exact commit messages.

Self-review:
  - Spec coverage: all 9 pearls + 4 layers covered
  - No TBD/TODO placeholders in step bodies
  - Type/slot consistency verified across tasks

Refs: docs/superpowers/specs/2026-05-01-sp5-magnitude-differentiation-and-eval-collapse-design.md (HEAD 6e6e0fa11)
2026-05-01 20:01:27 +02:00
jgrusewski
6e6e0fa114 docs(sp5): remove duplicate Layer A close-out section (superseded by Layer D) 2026-05-01 19:54:02 +02:00
jgrusewski
af8fe57d1c docs(sp5): apply critical review fixes — Layer D, Kelly carve-out, Pearl 4 mitigations, acceptance loosened
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.
2026-05-01 19:50:49 +02:00
jgrusewski
3361944456 docs(sp5): resolve open questions Q1=c, Q2=a, Q3=a
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.
2026-05-01 19:39:59 +02:00
jgrusewski
5f1c1eec51 docs(sp5): comprehensive per-branch + per-group adaptation spec — close all hardcoded-multiplier deferrals
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.
2026-05-01 19:32:39 +02:00
jgrusewski
a60e7b092f Merge plan-c-phase-2-thompson — SP4 signal-driven magnitude control + Plan C Phase 2 Thompson direction branch
119 commits, 89 files changed (+24,734 / -1,415).

Two coordinated workstreams:

1. Plan C Phase 2 (#233): direction-branch Thompson sampling resumed
   to resolve the ff00af68a UCB-asymmetry regression. Selector now
   matches distributional Q-target (Thompson at training, argmax at
   eval), restoring the symmetry pearl. Exit gate train_active_frac >
   0.40 met by smoke-test-gmvf8 (F0=0.75, all 3 folds Succeeded).

2. SP4 Layer A + Layer B + close-out: ISV-driven adaptive bounds for
   the magnitude branch replace SP1-era 1e6×ISV multipliers and
   hard-coded clamps. 42 ISV slots wired through 5 producer kernels
   with sentinel-detect → Pearl-A first-observation + Pearl-D
   Wiener-optimal meta-rate adaptation. Pearl B fused per-param-group
   oracle covers all 8 param groups including Curiosity sub-buffers.
   Pearl C engagement counter integrated into 5 Adam kernels with
   rate-deficit gating. StateResetRegistry extended for fold-boundary
   correctness.

Subsequent close-out:
- GPU-only Pearls A+D refactor (eliminated host-side compute path
  that broke CUDA Graph capture).
- Layer B per-group Adam split (resolved Pearl C engagement deferral).
- #260 SP1-era 1e6 × ISV multiplier elimination via dedicated
  bw_d_h_s2_p99 + q_dir_grad_p99 producers.
- feedback_no_cpu_compute_strict sweep: 5 GPU EMA migrations
  (grad_norm fast/slow + iqn_loss + utilization + adaptive_clip +
  calibrate_homeostatic_targets) + 5 documented exceptions for
  host-aggregated PnL/eval-result inputs.
- Intent-side magnitude distribution diagnostic (#212).

Validation:
- 4 L40S smokes Succeeded: smoke-test-tkkx6, 5nrz7, g2mvm, gmvf8
- Local RTX 3050 Ti: 16/16 SP4 GPU producer tests + 11/11 SP4 lib tests
- cargo check --workspace clean (no new warnings)

Zero open deferrals on SP4-touched paths. Zero hardcoded magnitude
multipliers in cuda_pipeline consumer code. Zero host-side EMA/
reduction violations on production hot paths.

Memory pearls landed: pearl_first_observation_bootstrap (Pearl A),
pearl_fused_per_group_statistics_oracle (Pearl B),
pearl_engagement_rate_self_correction (Pearl C),
pearl_wiener_optimal_adaptive_alpha (Pearl D),
pearl_l1_lambda_grad_direction_entropy_deficit (signal-driven L1),
pearl_sp4_close_out (canonical reference for future migrations),
feedback_no_cpu_compute_strict (hard rule against host-side compute).

Closes #84, #175, #212, #217, #233, #240.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 17:17:35 +02:00
jgrusewski
d6eca73e52 feat(dqn): #212 — intent-side magnitude distribution HEALTH_DIAG metric
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.
2026-05-01 17:11:19 +02:00
jgrusewski
6d0ac7beb3 fix(sp4): GPU-port calibrate_homeostatic_targets per feedback_no_cpu_compute_strict
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>
2026-05-01 16:14:22 +02:00
jgrusewski
a385c1d2be chore(sp4): delete compute_adaptive_tau orphan helper per feedback_wire_everything_up
`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>
2026-05-01 16:05:39 +02:00
jgrusewski
6a6b58aec5 docs(sp4): comprehensive feedback_no_cpu_compute_strict sweep audit grid
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>
2026-05-01 15:12:33 +02:00
jgrusewski
1112abc2a4 fix(sp4): migrate winsorized adaptive grad-clip update to GPU per feedback_no_cpu_compute_strict
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>
2026-05-01 15:09:57 +02:00
jgrusewski
ca63158606 fix(sp4): migrate atom utilization EMA to GPU per feedback_no_cpu_compute_strict
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>
2026-05-01 15:00:05 +02:00
jgrusewski
605a8f5268 fix(sp4): migrate IQN readiness gauge update to GPU per feedback_no_cpu_compute_strict
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>
2026-05-01 14:49:47 +02:00
jgrusewski
24accea774 fix(sp4): migrate fast/slow grad-norm EMA update to GPU per feedback_no_cpu_compute_strict
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>
2026-05-01 14:07:10 +02:00
jgrusewski
45da3eac69 refactor(sp4): #260 follow-ups — symbolic doc anchors, dedicated q_dir_grad table, p99 helper
3 IMPORTANT items from #260 code-quality review (commit 88ae74ca7), addressed
post-smoke-validation (smoke-test-tkkx6 Succeeded):

1. Stale numeric line-references in 8+ doc-comments replaced with symbolic
   code anchors per feedback_trust_code_not_docs. Pre-existing stale
   `ISV_TOTAL_DIM = 60` comment also corrected.

2. q_dir_grad launcher allocated dedicated `q_dir_grad_subbuf_table_buf` +
   `q_dir_grad_subbuf_counts_buf` (2 entries each) instead of reusing the
   shared `oracle_subbuf_table_buf`. Eliminates implicit "must-run-before-
   oracle" temporal coupling; removes `K_MAX=4` local redefinition.

3. `launch_sp4_p99_producer_single_buf` helper extracted on GpuDqnTrainer.
   Collapses ~30-line boilerplate x 3 call sites (target_q, h_s2,
   bw_d_h_s2) into single-line calls. Multi-sub-buffer launchers
   (q_dir_grad, param_group_oracle) and shape-distinct producers
   (grad_norm 1-thread, atom_pos 4-iter+batched-Pearls) keep their
   bespoke shape.

Build clean, 11 SP4 lib tests pass, 16 SP4 GPU tests pass on RTX 3050 Ti.

Refs: #260 code-quality review, smoke-test-tkkx6 (commit 88ae74ca7).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:22:01 +02:00
jgrusewski
88ae74ca73 fix(sp4): #260 — eliminate SP1-era 1e6 × isv multipliers via ISV-driven producers
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>
2026-05-01 12:41:29 +02:00
jgrusewski
1c150d1901 fix(sp4): Layer B fix-up — restore Adam coverage for tensors [33..163)
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>
2026-05-01 11:47:42 +02:00
jgrusewski
58ffb3a48e feat(sp4): Layer B — atomic consumer migration to ISV-driven bounds
Single coordinated commit per `feedback_no_partial_refactor`. All
SP3-era hardcoded magnitude multipliers (10×, 100×, 1e3×, 1e6×) and
ε floors (.max(1.0)) replaced by per-slot ISV reads with consumer-side
EPS_CLAMP_FLOOR=1.0 numerical safety.

Mechanism mapping:
- Mech 1 target_q clamp: 10 × Q_ABS_REF.max(1.0) → ISV[TARGET_Q_BOUND]
- Mech 2 atom-position clamps (3 sites × 4 branches): 10 × Q_ABS_REF
  → ISV[ATOM_POS_BOUND[branch]]
- Mech 5 fused diagnostic: per-slot ISV reads in
  `dqn_nan_check_fused_f32_kernel` (kernel takes `isv_signals*` instead
  of `q_abs_ref_eff` / `h_s2_rms_ema_eff` host args)
- Mech 6 adaptive_clip upper_bound: 100 × slow_ema × Q_ABS_REF
  → ISV[GRAD_CLIP_BOUND]
- Mech 9 post-Adam weight_clamp (5 Adam kernels): 100 × Q_ABS_REF
  → ISV[WEIGHT_BOUND[group]]
- Mech 10 h_s2 clamp: 100 × H_S2_RMS_EMA → ISV[H_S2_BOUND]
- AdamW weight_decay (5 kernels): config field → ISV[WD_RATE[group]]
- L1 lambda (trunk only): 1e-3 → ISV[L1_LAMBDA_TRUNK_INDEX]

DQN main Adam split into 3 per-group sub-launches (DqnTrunk / DqnValue /
DqnBranches) per `feedback_no_quickfixes`. Overrides the plan's
"max/min-of-3 single-launch shortcut" recommendation. Each sub-launch
reads its own WEIGHT_BOUND[group], WD_RATE[group], and (trunk only)
L1_LAMBDA_TRUNK_INDEX. Pearl C engagement-counter deferral from
A14/A15 resolved in this same commit — per-group split means each
sub-launch writes per-block counts at its own offset, and
`pearl_c_post_adam_engagement_check` is invoked per group from
fused_training.rs (DqnTrunk/DqnValue/DqnBranches separate calls).

`weight_decay` field removed from:
- DQNHyperparameters (crates/ml/src/trainers/dqn/config.rs)
- GpuDqnTrainConfig (crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs)
- GpuIqnConfig (crates/ml/src/cuda_pipeline/gpu_iqn_head.rs)
- GpuIqlConfig (crates/ml/src/cuda_pipeline/gpu_iql_trainer.rs)
- TrialOverrides + PSO search-space (crates/ml/src/training_profile.rs)
- apply_family_scaling (`weight_decay *= li` line removed)

Aux trainers outside SP4 8-group taxonomy (DT, ofi_embed, denoise,
sel/recursive_conf) keep `weight_clamp_max_abs = 0.0` disable —
mirrors the existing DT pattern. They have no individual ISV producer,
so they don't read SP4 bounds.

Files-touched (17): atoms_update_kernel.cu, iql_value_kernel.cu,
experience_kernels.cu, dqn_utility_kernels.cu, gpu_dqn_trainer.rs,
gpu_iqn_head.rs, gpu_iql_trainer.rs, gpu_attention.rs, gpu_tlob.rs,
fused_training.rs, training_loop.rs, constructor.rs, config.rs,
generalization.rs (smoke), training_profile.rs, train_baseline_rl.rs,
dqn-wire-up-audit.md.

Verification (local, RTX 3050 Ti):
- `cargo check -p ml --offline`: clean.
- `git grep -nE "10\.0_f32 \* q_abs_ref|10\.0f \* q_abs_ref|100\.0_f32
  \* q_abs_ref|100\.0f \* q_abs_ref|1e6_f32 \* q_abs_ref|1e3_f32 \*
  q_abs_ref|100\.0_f32 \* h_s2|100\.0f \* h_s2_rms" crates/ml/src/`:
  ZERO matches.
- `git grep -nE "weight_decay:\s*f64|l1_lambda:" crates/ml/src/trainers/dqn/`:
  ZERO matches.
- `git grep -n "self.config.weight_decay" crates/ml/src/`: only TFT
  remains (separate trainer outside SP4 scope).
- `git grep -n "q_abs_ref_eff|h_s2_rms_ema_eff"
  crates/ml/src/cuda_pipeline/dqn_utility_kernels.cu`: ZERO matches.
- 8 SP4 lib tests pass (sp4_wiener_ema, sp4_isv_slots,
  state_reset_registry).
- 14 SP4 producer GPU tests pass on RTX 3050 Ti (no behavior change at
  producer level — consumer-side migration only).
- `cargo test -p ml --lib --offline`: 928 passed, 14 failed (all 14
  pre-existing on HEAD `1389d1c81`; no new failures).

Validation deferred to Layer C smoke. Expected: F0/F1/F2 all complete
5 epochs; F1 trains past step 1000; F0 ≥ 37.5; F2 ≥ 55; slot 49 quiet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:10:41 +02:00
jgrusewski
1389d1c810 refactor(sp4): GPU-only Pearls A+D — eliminate all host-side compute paths
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>
2026-05-01 09:57:57 +02:00
jgrusewski
0a1fae77d9 docs(sp4): A18 — Layer A close-out audit
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).
2026-05-01 09:05:11 +02:00
jgrusewski
4c231fa812 refactor(sp4): A13 code-quality pass — DRY helper, named constant, doc fix, param cleanup
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>
2026-05-01 08:42:40 +02:00
jgrusewski
c5add566db fix(sp4): A13 spec gap — fold-reset retrofit ISV slots to Pearl A sentinel (0.0)
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>
2026-05-01 08:11:18 +02:00