8a25b330fcc2a0b197eccec242e1cfcffb8ccfdf
2518 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8a25b330fc |
fix(sp9): targets as Invariant-1 anchors + divergence sentinel handling
Two targeted fixes to the SP9 Kelly cold-start warmup floor (Fix 37,
commit `48a8b9ee7`) for quirks observed in smoke-test-wrwkz on a
5-epoch L40S smoke. Atomic per `feedback_no_partial_refactor`.
**Quirk 1 — EMA target saturation:** ep1 HEALTH_DIAG showed
`stat_count_tgt=419 div_tgt=105002 temp_tgt=1.0 conf [stat=1.000
bhv=0.333 tmp=1.000] floor=0.0`. Pearl A sentinel-bootstrap on the
3 EMA target updaters (`kelly_*_target_ema_kernel.cu`) caused the
first observation to replace the sentinel directly, so
`target = first_obs`, `current/target = 1.0`, `confidence = 1.0`,
`floor = base × (1 − 1.0) = 0` — defeating the cold-start mechanism.
The principled fix per `feedback_isv_for_adaptive_bounds.md`:
"sufficient" is defined in absolute terms via Invariant-1 numerical
anchors. The 3 target ISV slots become constructor-written constants
(written ONCE in trainer constructor, identical pattern to existing
`config.cql_alpha → ISV[CQL_ALPHA_INDEX=48]` from Plan 1 Task 12).
- ISV[KELLY_SAMPLE_COUNT_TARGET_INDEX=333] = 100.0 (trades)
- ISV[KELLY_DIVERGENCE_TARGET_INDEX=334] = 2.0 (ratio)
- ISV[KELLY_TEMPORAL_TARGET_INDEX=335] = 5.0 (epochs)
**Quirk 2 — divergence ratio explosion:** ep1 showed
`divergence=70005`. Algebraically `intent_f / max(eval_f, 1e-6) =
0.07 / 1e-6 = 70 000` because `ISV[EVAL_DIST_F_INDEX=338]` was still
at Pearl A sentinel 0 before the first val window populated it.
Algebraically the warmup-floor kernel still derived
`behavioral_conf = 0` (correct semantic), but the synthetic 70 000
in HEALTH_DIAG masked the real signal flow.
Fix: explicit sentinel detection in
`intent_eval_divergence_compute_kernel.cu`:
if (eval_f < SENTINEL_THRESHOLD=1e-5)
divergence = SENTINEL_DIVERGENCE=1e6 // forces behavioral_conf=0
else
divergence = intent_f / eval_f
Same `behavioral_conf = 0` outcome but with explicit provenance —
HEALTH_DIAG now reads `divergence=1e6` until eval_f matures.
**Atomic structure:**
- DELETE 3 EMA target updater kernels (`.cu` files)
- DELETE 3 entries from `crates/ml/build.rs::kernels_with_common`
- DELETE 3 cubin static byte arrays + 3 struct fields + 3 cubin
loaders + 3 fields in trainer construction tuple in
`gpu_dqn_trainer.rs`
- DELETE 3 launches in `launch_sp9_kelly_warmup_floor` (chain
shrinks 5 → 2: q_var_mag_ema + main warmup-floor)
- DELETE 3 scratch slots; SP5_SCRATCH_TOTAL 268 → 265;
SCRATCH_SP9_EVAL_DIST_BASE slides 265 → 262
- ADD constructor-write of 3 Invariant-1 anchors (100.0, 2.0, 5.0)
immediately before layout-fingerprint write
- UPDATE 3 dispatch arms in `reset_named_state` to rewrite the
Invariant-1 anchors at fold boundary (NOT sentinel 0) — these
are constants, not stateful EMAs
- UPDATE 3 registry descriptions to reflect Invariant-1 anchor
semantic + smoke-test-wrwkz evidence
- UPDATE `intent_eval_divergence_compute_kernel.cu` with
sentinel-detect branch
- ISV slot layout UNCHANGED (3 target slots @ ISV[333..336)
remain); Wiener-buffer linear span (`SP5_PRODUCER_COUNT=165`)
UNCHANGED — 3 wiener triples for deleted producers become
reserved-unused like Pearl 6's [525..543) carve-out
- audit doc Fix 37.1 entry with full provenance + smoke evidence
**Verification:**
- `SQLX_OFFLINE=true cargo check -p ml` — clean (only pre-existing
18 warnings; no new errors or warnings).
- `SQLX_OFFLINE=true cargo test -p ml --lib state_reset` — 4/4 pass
including `every_fold_and_soft_reset_entry_has_dispatch_arm`.
- `SQLX_OFFLINE=true cargo test -p ml --lib sp5_isv_slots` — 9/9
pass including `sp9_slots_contiguous_above_sp8_block`.
**Expected smoke signature post-fix:**
- floor: starts ≈ base_floor × 1.0, decays as confidence accumulates
- divergence: 1e6 until first val window, small (≤ 5) afterward
- conf: stat=count/100, bhv=0 until eval_dist matures, tmp=ep/5
- combined_conf reaches 1.0 only when at least one axis genuinely
matures (typically temporal at epoch 5 in fold 0)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
48a8b9ee7c |
fix(sp9): Kelly cold-start ISV-driven warmup floor (Fix 37)
Per pearl_cold_start_exit_signal_or.md + pearl_controller_anchors_isv_driven.md:
the Kelly cap's `warmup_floor` and its release condition were both
regime-encoded constants. Single-axis statistical cold-start exit
(`total_trades >= 10` in trade_physics.cuh::kelly_position_cap) created
a chicken-and-egg deadlock: cap closed → no trades → no Kelly samples →
cap stays closed. T10 wsnc6 ep1-3 evidence (post-Fix 36):
intent_dist_f rising to 0.47 while eval_dist_f pinned to 0.00 with
kelly_f=0 across all epochs — train-vs-eval divergence as direct
symptom of the cold-start deadlock.
SP9 lifts the floor and its release condition fully onto ISV. The exit
condition is OR'd across statistical / behavioral / temporal axes per
the pearl — any single axis firing exits cold-start.
Atomic commit per feedback_no_partial_refactor:
- 9 new ISV slots @ [330..339):
- KELLY_WARMUP_FLOOR_INDEX (330) — adaptive floor consumed by
unified_env_step_core
- Q_VAR_MAG_EMA_INDEX (331) — historical EMA baseline for the
self-relative `base_floor` ratio (eliminates 0.5 hardcoded
half-Kelly)
- INTENT_EVAL_DIVERGENCE_INDEX (332) — behavioral-axis numerator
- 3 EMA targets (sample_count / divergence / temporal)
- 3 EVAL_DIST_{Q,H,F}_INDEX — replaces DtoH
read_eval_intent_magnitude_distribution()
- ISV_TOTAL_DIM 330 → 339; SP5_PRODUCER_COUNT linear span 156 → 165
- 9 new scratch slots @ [259..268); SP5_SCRATCH_TOTAL 259 → 268
- 7 new producer kernels (single-block cold-path, chained through
apply_pearls_ad_kernel for Pearl A bootstrap + Pearl D Wiener-α):
- eval_intent_dist_compute_kernel.cu — GPU reduction of
intent_mag_buf, replaces DtoH path per
feedback_no_cpu_compute_strict.md
- intent_eval_divergence_compute_kernel.cu
- q_var_mag_ema_compute_kernel.cu
- kelly_sample_count_target_ema_kernel.cu
- kelly_divergence_target_ema_kernel.cu
- kelly_temporal_target_ema_kernel.cu
- kelly_warmup_floor_compute_kernel.cu — main producer combining
all 8 ISV inputs
- consumer migration in trade_physics.cuh::unified_env_step_core:
health_safety_sp9 = fmaxf(health_safety_sp5,
isv[SP9_KELLY_WARMUP_FLOOR_INDEX])
threading through apply_kelly_cap's health_floor parameter; zero
sentinel → no-op via fmaxf so existing path's behavior is preserved
- DELETE read_eval_intent_magnitude_distribution() in
gpu_backtest_evaluator.rs (was DtoH read_all + host loop) per
feedback_no_htod_htoh_only_mapped_pinned.md; replaced by
intent_mag_dev_ptr_and_n() GPU accessor; metrics.rs:908 consumer
migrated from DtoH to launch + ISV mapped-pinned read
- 9 new state-reset registry FoldReset entries + 9 dispatch arms in
reset_named_state (Pearl A bootstrap on first observation)
- HEALTH_DIAG sp9_kelly_warmup line emits floor + divergence + 3
confidence axes + 3 EMA targets per feedback_no_hiding
- audit doc Fix 37 entry with full provenance, files touched, pearls
applied, verification
Constants: only KELLY_FLOOR_MIN_RATIO=0.25, KELLY_FLOOR_MAX_RATIO=1.0,
EPS_DIV=1e-6 — Invariant 1 numerical-stability anchors per
feedback_isv_for_adaptive_bounds.md. All thresholds, targets, and
the floor itself are signal-driven via ISV.
Pre-existing 18 cargo warnings unchanged. State-reset contract test
`every_fold_and_soft_reset_entry_has_dispatch_arm` passes for all 9
new entries; SP5 ISV slot layout test
`sp9_slots_contiguous_above_sp8_block` passes;
`SQLX_OFFLINE=true cargo check -p ml` clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
e0dbae3c99 |
fix(sp7): ISV-driven MAX_BUDGET via GPU train_active_frac canary (Fix 36)
Per pearl_controller_anchors_isv_driven.md: Fix 35 (CQL formula direction
flip) is necessary but insufficient — `const float MAX_BUDGET = 1.0f;` at
line 113 of loss_balance_controller_kernel.cu is regime-encoded the same
way the original CQL target_ratio formula was. Under healthy training
the cap is fine; under val-Flat-collapse it lets the controller saturate
budgets to 1.0 while the model is regressing into Flat.
The pearl prescribes the structural fix: pick the canary that fires
under the failure mode → train_active_frac (Long+Short / total during
training rollout). Lift it onto ISV with Pearls A+D smoothing; route it
through a producer kernel that derives per-(head, branch) cap via linear
interpolation FLOOR + active_frac × (CEIL - FLOOR); read the cap from
ISV in the controller kernel (no more hardcoded 1.0).
Atomic commit per feedback_no_partial_refactor:
- new kernel: train_active_frac_compute_kernel.cu — single-thread
reduction over monitoring_summary[5..17), writes scratch[250]; chained
apply_pearls_ad → ISV[TRAIN_ACTIVE_FRAC_INDEX=321]
- new kernel: loss_balance_max_budget_compute_kernel.cu — 8 threads
(2 heads × 4 branches), reads ISV[TRAIN_ACTIVE_FRAC_INDEX], writes
scratch[251..259); chained apply_pearls_ad ×8 →
ISV[LB_MAX_BUDGET_{CQL,C51}_BASE..+4)
- consumer: loss_balance_controller_kernel.cu — replace
`const float MAX_BUDGET = 1.0f;` with per-(head, branch) ISV reads,
defensive Pearl A bootstrap clamp
- 9 new ISV slots @ [321..330); ISV_TOTAL_DIM 321 → 330;
SP5_PRODUCER_COUNT linear span 147 → 156
- 9 new scratch slots @ [250..259); SP5_SCRATCH_TOTAL 250 → 259
- delete CPU train_active_frac compute (training_loop.rs:3392-3396) per
feedback_no_cpu_compute_strict; delete host field
`last_train_active_frac: f32`; replace with accessor reading from ISV
- 3 new state-reset registry entries (FoldReset; Pearl A bootstrap on
first observation) + dispatch arms in reset_named_state
- audit doc: Fix 36 entry with full provenance, files touched, pearls
applied, verification
Pre-existing 18 cargo warnings unchanged. State-reset contract test
`every_fold_and_soft_reset_entry_has_dispatch_arm` passes for all 3 new
entries; SP5 ISV slot layout test
`sp8_max_budget_slots_contiguous_and_above_activation_block` passes;
`SQLX_OFFLINE=true cargo check -p ml` clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
65c3083dec |
fix(sp7): flip CQL target_ratio direction — death spiral on magnitude
`target_ratio[CQL][b] = ANCHOR_CQL_RATIO * (1 - flatness[b])` had the sign inverted: it pushed CQL HIGH when Q was flat (the collapse state) and LOW when Q had variance. Per pearl_2_budget_kernel.cu, `flatness = var_q / σ²`, so flatness HIGH means Q has variance — exactly when overconfidence-risk-driven CQL pressure should kick in. Symptom (T10-v3 train-multi-seed-x7sl2 logs): Once IQN Fix 34 woke IQN-mag/ord/urg branches and produced real Q-targets, the controller's inverted formula sent cql_budget to MAX_BUDGET=1.0 saturation on every branch, the conservative pull kept Q flat, the model collapsed to single-Flat-action eval (val_active_frac=0, dir_entropy=0, trade_count=1, sharpe=0). Fix is one operational character: `(1 - flatness)` → `flatness`. C51 formula was already correctly aligned (`flatness * ANCHOR_C51_RATIO`) and unchanged. Per pearl_controller_anchors_isv_driven.md: the inverted formula was masked for months because the IQN-dead regime (Fix 34) suppressed cql_raw on mag/ord/urg, never letting the controller engage with the inverted target. Fixing the IQN regime exposed the dormant formula bug. ANCHOR_CQL_RATIO=2.0 and MAX_BUDGET=1.0 remain hardcoded; Fix 36 will make those ISV-driven via a GPU train_active_frac canary signal per the pearl's "pick the canary that fires under the pathology" rule. |
||
|
|
9b5296b2f0 |
fix(iqn): set_cached_target_h_s2 per branch — fixes silent 3/4 IQN branch dropout
execute_training_pipeline consumes cached_target_h_s2_ptr via .take(), so
the SP6 Pearl 5 per-branch loop (4 sequential iqn calls per step) only
worked for branch 0. Branches 1, 2, 3 saw cached_ptr=None, returned a
"non-fatal" Err logged as a warning, and silently skipped apply_iqn_
trunk_gradient — only the direction branch's IQN quantile signal ever
reached the trunk, for months.
Surfaced in T10-retry train-multi-seed-ksjcm logs as repeated:
WARN: IQN parallel branch 1 step failed (non-fatal):
WARN: IQN parallel branch 2 step failed (non-fatal):
WARN: IQN parallel branch 3 step failed (non-fatal):
cached_target_h_s2_ptr is None.
Pre-existing since SP6 Pearl 5 (P4.T3-era multi-quantile IQN per-branch
τ schedules) — masked by the non-fatal warning classification.
Likely explains:
- cql_mag persistence at SP7 smoke (cql_mag=0.07 vs cql_dir=1.0):
CQL was the only learning signal mag had because IQN-mag was dead.
- SP7 c51 1000:1 controller suppression on mag/ord/urg may stabilize
at non-collapse values once IQN signal returns to those branches.
Fix is local: re-set cached ptr inside both 4-branch loops (parallel arm
near line 2123, sequential arm near line 2308). The ptr is the same
value (target_h_s2 is per-step, not per-branch), but the .take()
contract requires set-per-call. Defensive single-shot semantic preserved
— if a future bug skips the set, IQN errors loud rather than reusing
stale ptr.
Per feedback_no_partial_refactor: SP6 changed the call pattern 1→4 per
step but didn't migrate the cache contract. Per feedback_no_hiding: the
non-fatal classification hid the structural bug; escalation to hard
error deferred to next commit after post-fix T10 validates the warnings
disappear.
Audit doc updated with Fix 34 entry.
|
||
|
|
e3d0829680 |
fix(build): cargo:rerun-if-env-changed=CUDA_COMPUTE_CAP for cubin arch
Without this trigger, cargo treated CUDA_COMPUTE_CAP-driven cubin
compilation as cached output of a non-tracked env var. The cargo-target
PVC is shared across H100 (sm_90) and L40S (sm_89) training jobs, so
swapping --gpu-pool between archs left stale cubins from the previous
build cached for any future sm_X variation.
Symptom (T10 train-multi-seed-rn559 today):
Failed to create DQN: rmsnorm cubin load: DriverError(
CUDA_ERROR_NO_BINARY_FOR_GPU,
"no kernel image is available for execution on the device")
The training pod scheduled on L40S (sm_89), the binary cache served
sm_90 cubins from a prior H100 build, and the rmsnorm cubin had no
sm_89 entrypoint.
Fix is one line: tell cargo to invalidate when CUDA_COMPUTE_CAP changes.
First post-fix build will rebuild all cubins (cargo conservatively
reruns when a new rerun-if-env-changed trigger is added without prior
state). Subsequent builds rebuild only on actual env changes.
Generalises the same lesson as the recent SP7 host-branch-in-captured-graph
fix: env-var-conditional code that doesn't declare its dependencies
freezes at first observation regardless of runtime input.
|
||
|
|
07f5ed5d74 |
fix(sp7): GPU dispatch for cql/c51 budget — eliminate capture-time host-branch freeze
The SP7 controller wrote real budgets to ISV[BUDGET_CQL_BASE/C51_BASE] and
the activation flag correctly transitioned 0→1, but downstream SAXPY ops
still saw exactly 0.02/0.05 every step. Root cause: compute_adaptive_budgets
ran host-side Rust with `if cql_active >= 0.5 { real } else { bootstrap }`,
captured into the aux_child CUDA Graph at step 0 of each fold (when
cql_active is FoldReset sentinel 0.0). The bootstrap branch resolves to
literal 0.02/0.05 and freezes into kernel arg buffers; ~1000 graph replays
per fold use frozen scalars regardless of runtime ISV state.
Same disease as SP4 host-side EMA elimination 2026-05-01. Fix:
- New consume_lb_budget_kernel.cu (8 threads, 1 block): reads ISV slots
for activation flag + controller budget per (head, branch); applies
if/else dispatch on-device; writes trunk_mean + per-branch correction
to mapped-pinned scratch buf [10] = (cql_trunk + cql_corr×4 + c51_trunk
+ c51_corr×4). Same cubin houses dqn_{scale,saxpy}_f32_dev_ptr_kernel
variants reading alpha from a device pointer (no kernel-arg freeze).
- Buffer + launcher + cubin loading + 4 dev-ptr accessor methods
in gpu_dqn_trainer.rs.
- apply_c51_budget_scale / apply_cql_saxpy + per-branch variants take
alpha_dev_ptr: u64 instead of scalar f32. The "skip when ≈ 1.0"
optimization is dropped — the value lives on-device. Existing scalar
dqn_{saxpy,scale}_f32_kernel are NOT modified — they remain reused
by distill, VSN dW dilution, etc.
- compute_adaptive_budgets refactored to launch the dispatch kernel
inline (captured along with consumers; replay-time fresh values
every step) and stop returning host-resolved CQL/C51 scalars.
IQN/ENS keep their host-side resolution (out-of-scope per change
scope — no activation-flag dispatch).
- HEALTH_DIAG reads from lb_budget_effective_buf via new accessor
methods on FusedTrainingCtx; dead caches on GpuDqnTrainer
(last_{cql,c51}_budget_eff / _per_branch) deleted.
- Audit doc Fix 33. Memory pearl out-of-tree.
Bootstrap byte-equivalence preserved: kernel-arg constants
cql_bootstrap=0.02 / c51_bootstrap=0.05 remain byte-identical to the
prior host-side CQL_BOOTSTRAP_BUDGET / C51_BOOTSTRAP_BUDGET literals
and to loss_balance_controller_kernel.cu's COLD_START_FLOOR_* anchors.
Files: consume_lb_budget_kernel.cu (new), build.rs, gpu_dqn_trainer.rs,
fused_training.rs, training_loop.rs, dqn-wire-up-audit.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
14af4854fb |
fix(merge): resolve sp5→main merge fixups — DevicePtrMut import + adam_step 7-arg signature
After sp5 merge with -X theirs, two compile errors needed manual fix: - gpu_her.rs lost the DevicePtrMut trait import - gpu_tlob.rs Fix 20 regression test used the 4-arg adam_step signature before sp5's Pearl 4 added β1/β2/ε params. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
aef2003db2 |
Merge: sp5-magnitude-differentiation → main (SP4+SP5+SP6+SP7 + observability + ISV bus fix)
Conflicts auto-resolved with -X theirs strategy. sp5 has more evolved MappedF32Buffer API + more recent work; main's via-pinned cleanup landed in parallel as a lateral migration. Taking sp5's side preserves the cleaner consolidated upload pattern. |
||
|
|
ad4a2de12a |
Merge: fix(tlob) align dW layout + fuse Q/K/V SGEMMs
# Conflicts: # crates/ml/src/cuda_pipeline/gpu_tlob.rs |
||
|
|
32ccf963dc | Merge: refactor delete via_pinned helpers, migrate 22 callers | ||
|
|
6c3a91c878 |
fix(sp7): wire raw CQL norm kernel reading cql_grad_scratch directly
The SP7 controller's CQL reference signal was reading cql_sx (post-SAXPY, budget-scaled) which created a self-perpetuating deadlock at small budget values: cql_sx_norm = budget × raw_grad → small budget → small cql_sx → controller can't update → budget stays small. The earlier offset 6 → 3 attempt failed because grad_decomp_launch_cql was never effectively populating slot 3 — the snapshot pattern measures ‖grad_buf − snapshot‖, but apply_cql_gradient writes to cql_grad_scratch (separate buffer), not grad_buf, so the snapshot delta is always 0. This commit adds a real producer kernel cql_raw_norm_compute that reads cql_grad_scratch directly and computes ‖raw_cql‖ over mag/dir/trunk slices. Wired to fire AFTER apply_cql_gradient and BEFORE apply_cql_saxpy, populating grad_decomp_result_pinned[3..6] with the raw norm independent of cql_budget. SP7 launcher updated to read from offset 3 (now: real raw CQL norm, not the never-populated cql snapshot delta). HEALTH_DIAG label renamed cql_sx → cql_raw to reflect the new contract; component index in the cached grad_component_norms_* arrays switched 2 → 1. The historical grad_decomp_launch_cql() call is removed — keeping it would overwrite the slot with 0 after cql_raw_norm_compute fires. The paired grad_decomp_snapshot_cql snapshot is left in place to scope the diff to Path A; buffer cleanup (grad_snapshot_cql allocation + grad_decomp_launch_cql definition) belongs in a follow-up commit per feedback_no_partial_refactor. Files: cql_raw_norm_kernel.cu (new, 97 LOC), build.rs, gpu_dqn_trainer.rs (struct field + cubin static + load + launcher + SP7 read offset 6→3), loss_balance_controller_kernel.cu (docstring + arg comment), fused_training.rs (4 launch_cql_raw_norm call sites, 1 dead grad_decomp_launch_cql call removed), training_loop.rs (HEALTH_DIAG label + index), audit doc Fix 31 SP7 Path A entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6cf6f26ab9 |
diag(sp7): emit grad_decomp_pinned + lb_active_per_branch HEALTH_DIAG
The SP7 smoke at
|
||
|
|
237b3dbfb0 |
fix(sp7): bump ISV_TOTAL_DIM 294→321 to match SP5_SLOT_END
SP7 T1 added 24 ISV slots (LB_DIFF_VAR_CQL_BASE=297 ... LB_C51_ACTIVE through 321) without bumping ISV_TOTAL_DIM, leaving SP7's wiener-state and activation slots out-of-bounds of the allocated pinned buffer (294 * 4 = 1176 bytes; SP7 slots write to bytes 1188..1284). GPU direct-pointer writes silently corrupted memory in the next page; CPU read_isv_signal_at returned garbage in release builds. This explains the SP7 smoke's confusing zero-budget-everywhere observation: the activation flag was reading OOB memory, the consumer saw inconsistent values, and the controller's actual ISV state was never visible. Bumped ISV_TOTAL_DIM to 321 (max valid index 320, covering SP5_SLOT_END-1). Made pub(crate) so the new contract test can reference it from sp5_isv_slots.rs. Updated layout_fingerprint_seed()'s slot entries to include the previously missing D3 and SP7 slots (TRAINING_SHARPE_EMA=294 through LB_C51_ACTIVE_BASE=317) and updated ISV_TOTAL_DIM= literal to 321 in lockstep per feedback_no_partial_refactor. Added contract test all_sp5_slots_fit_within_isv_total_dim to permanently gate this class of bug at cargo test time. The test would have caught SP7 T1 instantly; future slot allocations cannot regress this. Files: gpu_dqn_trainer.rs (ISV_TOTAL_DIM + comment + fingerprint), sp5_isv_slots.rs (test), docs/dqn-wire-up-audit.md (Fix 31 sub-bullet). Cargo check workspace clean. Cargo test ml --lib 935 passed (934 baseline + 1 new contract test); 16 pre-existing GPU-hardware failures unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
bbf15ad690 |
diag(sp7): HEALTH_DIAG emit Q_VAR_PER_BRANCH signal
The SP7 controller's flatness gate reads per-branch Q-variance from ISV[Q_VAR_PER_BRANCH_BASE=222..226) but that signal was not visible in HEALTH_DIAG. The existing "var_q" in the main HEALTH_DIAG line is realized step-return variance per magnitude bin from gpu_experience_collector — a trade-outcome metric, semantically distinct from per-branch Q-output variance. Added one emit line immediately after cql_budget_per_branch: HEALTH_DIAG[E]: q_var_per_branch [dir=X.XXXX mag=X.XXXX ord=X.XXXX urg=X.XXXX] Reads ISV[222..226) via read_isv_signal_at — the same slots written by q_branch_stats_kernel.cu (scratch slot 2 per branch) and routed into ISV by apply_pearls_ad_kernel in launch_sp5_pearl_1_atom. No new ISV slots, no kernel change, no StateResetRegistry entry. Class 2 signal (mag_concat_scale / q_rms): Option A infeasible — q_rms is a per-sample register variable in mag_concat_qdir with no existing ISV slot; h_s2_rms_ema at ISV[96] is the only available proxy. Option B (new ISV slot) blocked pending explicit controller OK. See audit doc for full stop-and-report rationale. Files touched: crates/ml/src/trainers/dqn/trainer/training_loop.rs (+28 LOC) docs/dqn-wire-up-audit.md (+13 LOC) [ISV slot decision: Option A reused existing Q_VAR_PER_BRANCH_BASE=222..226] Cargo check workspace clean. State-reset contract test passes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
e09757419c |
fix(tlob): align backward dW_Q/K/V layout with forward W_Q/K/V (Fix 20)
Forward W_Q SGEMM stored col-major [K, M] (lda=K) while backward dW_Q wrote col-major [M, K] (ldc=M). When M ≠ K (TLOB: M=16, K=32), Adam's element-wise update applied gradients computed at position (m, k) to weights stored at position (k, m) — silent learning corruption at every flat index ≠ 0 (511 of 512 W_Q slots updated using wrong-position gradients, matched in W_K/V; W_O is square so unaffected). Standardised backward dW_Q/K/V SGEMM output to col-major [K, M] (ldc=K) matching the forward layout (Strategy A from the audit brainstorm — the forward layout is the definitive weight storage; Adam's flat layout follows forward's allocation). The fix flips the cuBLAS strided-batched operands: backward now computes `dW^T = ofi @ d_proj^T` instead of `dW = d_proj @ ofi^T`. Same gradient values, just re-laid-out so flat indexing matches `params`. No new kernel; no kernel-internal layout change (the SDP forward/backward kernels still read `proj_qkv_buf` / `d_proj_qkv_buf` as [M, B] col-major — those buffers are untouched). The QKV-fusion `cublasSgemmStridedBatched(batch=3)` semantics are preserved: ofi is the new shared operand (strideA=0), d_proj is the per-batch operand (strideB=M·B), strideC=M·K=512 unchanged. Phase-1 reproduction (`tlob_dw_layout_alignment_repro`, #[ignore = "requires GPU"]) ran the broken and fixed cuBLAS dispatches side-by-side on identical sentinel inputs (`d_proj[m=0,b=0]=1`, `ofi[k=1,b=0]=1`, all else 0); broken `[M, K]` placed the `1.0` gradient at flat 16, fixed `[K, M]` placed it at flat 1 — O(1) cross-layout delta exactly matching the audit prediction. Pre-fix Adam would have updated `W_Q[m=0, k=16]` (the forward layout's flat-16 slot) using the gradient computed for `W_Q[m=0, k=1]` — the silent corruption. Phase-3 regression (`tlob_dw_layout_alignment_regression_full_chain`, #[ignore = "requires GPU"]) exercises the full forward → backward → Adam → forward chain with random Xavier-init weights (W_O seeded to break the production-zero-init that would collapse the gradient chain to all-zero in a synthetic test). Asserts (1) GPU dW_Q matches a CPU reference computed in the post-fix [K, M] layout within TF32 tolerance, and (2) the second forward Q matches the analytical [K, M] interpretation of the post-Adam W_Q — locks in cross-step layout agreement and would fail if any future refactor accidentally re-permutes `params` between Adam and the next forward. Existing inline `tlob_sgemm_parity_with_cpu_reference` still passes (its CPU dW_Q/K/V reference was updated in lockstep to the [K, M] layout per `feedback_no_partial_refactor`; pre-fix the GPU produced [M, K] and the new CPU reference would diverge element-wise — a clean no-skip parity check that locks the layout convention end-to-end). `tlob_qkv_fusion_equivalence` unchanged (the fix only touches the backward call, forward QKV fusion is bit-identical pre/post). Local verification (RTX 3050 Ti, batch=256 for fusion test): tlob_dw_layout_alignment_repro: PASS tlob_dw_layout_alignment_regression_full_chain: PASS tlob_qkv_fusion_equivalence: PASS (3.79× speedup retained) tlob_sgemm_parity_with_cpu_reference: PASS Fix 20 in docs/dqn-gpu-hot-path-audit.md updated FIXED with verdict + strategy + test list. Forward SGEMM call site got an inline comment block documenting the [K, M] convention and pointing at the `tlob_dw_layout_alignment_*` regression coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9ba08ff609 |
chore(ml): gate ZN.FUT data-loader tests + migrate test HtoD/DtoH to mapped-pinned
ZN.FUT tests in crates/ml/src/data_loader.rs were failing because no
valid ZN.FUT DBN data is available locally; gated with #[ignore].
ml-asset-selection's universe definition and backtesting's
zn_futures() slippage profile remain untouched — those are production
references to ZN as a candidate symbol, distinct from data availability.
Migrated 4 deprecated cudarc memcpy_stod/memcpy_dtov sites in the
test function test_eval_action_select_eval_argmax_picks_best in
crates/ml/src/cuda_pipeline/mod.rs to mapped-pinned per
feedback_no_htod_htoh_only_mapped_pinned:
- 3x memcpy_stod (f32 input uploads) → MappedF32Buffer::new +
write_from_slice + dev_ptr as raw u64 kernel arg; kernel reads
directly from mapped-pinned pages, no DtoD copy needed
- 1x memcpy_dtov (i32 output readback) → MappedI32Buffer::new +
dev_ptr as kernel arg + read_all() after stream sync
The cudarc deprecation suggested clone_htod/clone_dtoh as replacements
but those still perform HtoD/DtoH copies — violating the strict rule.
Mapped-pinned with direct dev_ptr kernel args is the correct pattern
(matches distributional_q_tests.rs).
Note: DqnGpuData/PpoGpuData upload paths also in mod.rs still use
clone_to_device_f32_via_pinned; migrating those requires changing
CudaSlice<f32> struct fields to MappedF32Buffer which is blocked until
gpu_dqn_trainer.rs consumers are also updated (separate scope).
Workspace cargo check warnings: 15 → 15 (test-only deprecated calls
not visible to cargo check; ZN gate adds 3 to ignored count).
cargo test -p ml --lib failures: 16 → 13 (3 ZN tests now ignored).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
cb6a89714b |
sp7(controller): activation flag + Welford-α to escape bootstrap trap
Fixes the SP7 controller dormancy discovered in smoke-test-8556k:
per-branch budgets stuck at exactly bootstrap constants because the
kernel's cold_start_basis numerically equaled the consumer's bootstrap
fallback, AND the Wiener-α was clamped at ALPHA_FLOOR=1e-4 from step 1.
Architectural change:
- 8 new ISV slots LB_{CQL,C51}_ACTIVE_BASE per (head × branch).
Activation flag is monotonic per fold, FoldReset on boundary.
- Kernel only sets active=1 when grads are populated AND on subsequent
active-state computation; cold-start branch leaves active=0 (fresh
branch) or holds prior budget steady (transient grad gate).
- Consumer dispatches on activation: bootstrap when active<0.5,
controller verbatim when active>=0.5. No more spurious bootstrap
when controller writes legitimate small values.
- Welford-α hybrid (max of 1/max(1,epoch_idx_in_fold) and Wiener-α)
gives full update on first active step, falls off as 1/N until
Wiener takes over with meaningful variance estimates. EPOCH_IDX_INDEX
is the existing per-fold-reset counter (no new tuned constants).
State reset registry: 2 new sp7_lb_*_active FoldReset entries +
matching dispatch arms in reset_named_state. Contract test
(every_fold_and_soft_reset_entry_has_dispatch_arm) gates compile.
GPU unit test sp7_loss_balance_controller_activation_flag_transitions
exercises 3 transitions (cold start → both flags 0; active → both
flags 1 with controller-computed budget != bootstrap; transient grad-
gate → flags hold at 1, prior budget held verbatim). Passes on local
RTX 3050 Ti.
Audit doc: Fix 31 sub-bullet describing the activation-flag fix.
Memory pearl out-of-tree (controller will dispatch separately).
Touched:
crates/ml/src/cuda_pipeline/sp5_isv_slots.rs
crates/ml/src/cuda_pipeline/loss_balance_controller_kernel.cu
crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs
crates/ml/src/trainers/dqn/fused_training.rs
crates/ml/src/trainers/dqn/state_reset_registry.rs
crates/ml/src/trainers/dqn/trainer/training_loop.rs
crates/ml/tests/sp5_producer_unit_tests.rs
docs/dqn-wire-up-audit.md
Cargo check workspace clean. Cargo test ml --lib clean (incl. contract
test + 6 sp5_isv_slots tests). 16 pre-existing failures unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
dad76e1c11 |
perf(tlob): fuse Q/K/V SGEMMs into cublasSgemmStridedBatched (batch=3)
Replaces three back-to-back `cublasSgemm_v2` calls (one per Q/K/V projection, M=16 K=32 N=B at TF32) with a single `cublasSgemmStridedBatched(batch=3)` launch in both the forward and the dW_Q/K/V backward paths. Cuts cuBLAS heuristic-lookup + kernel-launch overhead 3× on the TLOB hotspot identified by task #218 nsys profiling. Strategy chosen: strided batched (Strategy 2 from the worktree brief), NOT the originally-recommended concatenated-W approach. Reason: the concat-W path requires the SDP kernel to read with stride-3M (col-major [3M, B], ldc=3M), forcing a kernel signature change and breaking bit-equivalence with the prior 3-SGEMM path. Strided batched keeps the per-projection [M, B] memory layout intact, so the SDP forward + backward kernels are byte-identical pre/post fusion (only the buffer layout is fused: 3 contiguous M·B-float chunks at offsets 0, M·B, 2·M·B inside `proj_qkv_buf` and `d_proj_qkv_buf`). Param flat layout `[W_Q | W_K | W_V | W_O]` is unchanged (strideA=M·K reads the existing weights in order), so the checkpoint/save/load contract is unaffected (TLOB has no on-disk checkpoint; weights are Xavier-init random). Numerical equivalence + microbenchmark (RTX 3050 Ti, batch=256, TF32): - max abs diff Q=3.77e-4, K=3.41e-4, V=4.29e-4 → within 2e-3 TF32 tolerance (matches inline parity test's TOL_GEMM) - per-call latency over 200 iters: fused 1× SgemmStridedBatched batch=3: 5–6 µs ref 3× cublasSgemm_v2 back-to-back: 19–22 µs → ~3.5–3.8× speedup on the QKV-projection portion alone (forward; backward dW_Q/K/V fusion has the same shape and the same gain). Tolerance rationale documented inline (`TOL_FUSION = 2e-3`): the shared classic-cuBLAS handle is bound to `CUBLAS_TF32_TENSOR_OP_MATH` (`shared_cublas_handle::create_handles_and_workspace`); the strided-batched dispatch can pick a different internal algo than back-to-back single calls and the K=32 reduction amplifies TF32 rounding to a few × 1e-4. Both paths are mathematically equivalent within TF32 precision; sub-1e-5 bit-equivalence is not achievable on a TF32 handle and is not what the fusion is supposed to provide. Layout/stride/offset bugs would show up as O(1) deltas, which the 2e-3 threshold catches trivially. Tests: - `cuda_pipeline::gpu_tlob::tests::tlob_sgemm_parity_with_cpu_reference` (existing inline parity vs CPU SGEMM reference): still PASSES — the fused path produces the same Q/output/dW values to within 2e-3 of the hand-rolled CPU reference. - `cuda_pipeline::gpu_tlob::tests::tlob_qkv_fusion_equivalence` (NEW, `#[ignore = "requires GPU"]`): runs both the new fused path and a private 3-SGEMM reference helper on identical inputs, asserts max abs diff ≤ TOL_FUSION, and prints a fused-vs-3-call latency microbenchmark over 200 iters. Reverts the fusion if it ever stops helping. Audit doc updated: `docs/dqn-gpu-hot-path-audit.md` Fix 20 records the strategy, bench numbers, and a pre-existing forward/backward W_Q-vs-dW_Q lda/ldc transposition observation surfaced during analysis (orthogonal to QKV fusion; flagged for a separate audit pass — the fusion preserves the existing per-projection layouts byte-for-byte). via_pinned migration (overlap with `wt/via-pinned-cleanup`): The repo's pre-commit `check_no_dtod_via_pinned` guard rejects ANY staged .rs file containing `upload_f32_via_pinned` or `clone_to_device_*_via_pinned`. Three pre-existing call sites in gpu_tlob.rs (line ~235 production param upload + 2 inline-test uploads) plus one new site I added in the equivalence test would have blocked this commit. Per the worktree brief I was instructed to leave the existing line ~235 alone for the parallel `wt/via-pinned-cleanup` worktree (commit |
||
|
|
072c1d3f93 |
refactor(mapped_pinned): delete via_pinned helpers, migrate 22 callers to MappedF32/I32/U32Buffer
The *_via_pinned helpers (clone_to_device_f32_via_pinned, upload_f32_via_pinned, clone_to_device_i32_via_pinned, upload_i32_via_pinned, upload_u32_via_pinned) were temporary scaffolding during the SP4 mapped-pinned migration. The canonical replacement is MappedF32Buffer / MappedI32Buffer / MappedU32Buffer in the same module — direct allocation + write_from_slice + device_ptr with no additional indirection. Each caller now inlines the staging + DtoD pattern directly: - Allocate MappedXxxBuffer (cuMemHostAlloc DEVICEMAP) - Write data via write_from_slice (no memcpy, mapped pinned coherence) - alloc_zeros::<T> CudaSlice for device-resident destination - memcpy_dtod_async from staging.dev_ptr to dst - stream.synchronize() before staging drops This commit migrates all callers atomically (per feedback_no_partial_refactor) and removes the now-unused helpers in the same commit. grep -rn 'via_pinned' returns 0 matches after this lands. The two local file-private helpers in gpu_experience_collector.rs (upload_host_to_cuda_f32_via_pinned / upload_host_to_cuda_i32_via_pinned) were already correctly using MappedF32Buffer directly; they were renamed to drop the _via_pinned suffix. Touched: gpu_attention.rs, gpu_backtest_evaluator.rs, gpu_dqn_trainer.rs, gpu_experience_collector.rs, gpu_her.rs, gpu_iql_trainer.rs, gpu_iqn_head.rs, gpu_ppo_collector.rs, gpu_tlob.rs, gpu_walk_forward.rs, gpu_weights.rs, mapped_pinned.rs, mod.rs, hyperopt/adapters/mamba2.rs, hyperopt/adapters/ppo.rs, trainers/dqn/trainer/training_loop.rs, trainers/ppo.rs, docs/dqn-wire-up-audit.md (18 files, +930/-363 LOC). Cargo check workspace clean. Cargo test ml --lib: 925 passed, 17 failed (all 17 are pre-existing GPU/data infrastructure failures unrelated to this change). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
07ac5ceea5 |
test(state_reset): contract test — every RegistryEntry has dispatch arm
Surfaced the SP7 T7 dispatch-arm bug at runtime (unknown-name panic at fold boundary). This test enumerates RegistryEntry names from StateResetRegistry::new() and asserts each has a match arm in reset_named_state, catching the bug at `cargo test -p ml --lib` instead of mid-training. Source-introspection design — no production-code change. Test fails fast if a future contributor adds a registry entry without the corresponding dispatch arm. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
023c62da28 |
test(magnitude_distribution): assert on EVAL_INTENT (pre-Kelly-cap), not EVAL_DIST
Pre-existing test bug: line 164 asserted `ef >= 0.05` (post-Kelly-cap eval_dist), which gates on Kelly cold-start warmup completing within the smoke's training horizon — not on whether the Q-head learned to prefer Full magnitude. On the local-laptop smoke (1 quarter MBP-10), Kelly warmup never completes, pinning eval_dist[Full] = 0 even when Q(Full) > Q(Half) clearly. Per `project_magnitude_eval_collapse_kelly_capped`, the diagnostic split landed in #212: intent_dist measures policy learning, eval_dist measures policy + Kelly-cap. Tests asserting on Q-learning success must use intent_dist; the test was never updated. Smoke now PASSES with intent_full=0.057 (intent_full=0.866 in the prior re-run — both above the 0.05 threshold; Q(Full) is clearly preferred when Kelly cap doesn't suppress it). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6e479c55c7 |
sp7(state): T2 bug-fix — add 4 missing reset_named_state dispatch arms
T2 (commit
|
||
|
|
148aa1f464 |
test(integration): migrate CudaSlice→MappedF32Buffer in tests blocked since Bug-1 close-out
Three compile errors in crates/ml/tests/smoke_test_real_data.rs at lines 568, 644, and 715 — all three were calls to `collect_experiences_gpu(&market_buf, &target_buf, ...)` where `market_buf` and `target_buf` were `CudaSlice<f32>` allocated via `stream.alloc_zeros + memcpy_htod`. The production API changed in |
||
|
|
2de9a58922 |
sp7(wire): T7 polish — fix 2 stale docstrings around compute_adaptive_budgets
Two clarity-only edits surfaced by code review: - M1: compute_adaptive_budgets header docstring claimed floors of "0.05 C51, 0.05 IQN, 0.02 CQL, 0.02 Ens" — stale post-T7 (IQN is now 0.11, others are sentinel-aware bootstrap). Rewritten to reflect the SP7 contract: IQN keeps BASE_IQN=0.11 structural floor; C51/CQL/ENS use sentinel-aware bootstrap. - M2: last_ens_budget_eff docstring misattributed ENS to "SP5 Pearl 2 (flatness-modulated)" — Pearl 2 stopped writing BUDGET_ENS_BASE in T6. Rewritten to state ENS has no active controller driver in SP7; bootstrap-only. Comment-only — zero runtime effect. cargo check clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
55b8457fdf |
sp7(wire): launch loss-balance controller + sentinel-aware consumer (atomic)
Atomic per feedback_no_partial_refactor — launch site + consumer floor
change + stale doc are three halves of the same contract:
(a) launch_loss_balance_controller wired into the per-step pipeline
after launch_sp5_pearl_2_budget (FLATNESS_BASE populated) and after
backward (grad_decomp pinned slots populated).
(b) compute_adaptive_budgets replaces hard floors (0.02/0.05) with
sentinel-aware bootstrap. The controller may now drive budgets near
zero when the structural target says so. IQN keeps BASE_IQN=0.11
structural floor (reference, can't be 0).
(c) All 4 stale "B4/G5" budget-eff docstrings rewritten to reflect
actual ownership: last_iqn_budget_eff (Pearl 2 flatness),
last_cql_budget_eff (SP7 controller), last_c51_budget_eff (SP7
controller), last_ens_budget_eff (Pearl 2 flatness +
sentinel-aware bootstrap). The previous claims (e.g.,
"0.10×(1−regime)×health") were never implemented; per
feedback_trust_code_not_docs, code wins over docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d704ffd1bc |
docs(dqn): T6 polish — fix 4 stale Pearl 2 prose references in gpu_dqn_trainer.rs
Four comment/docstring sites still described the pre-SP7 Pearl 2 as writing "20 floats / 5 outputs / c51/cql/ens" after the T6 kernel- signature shrink. Updated to reflect the current 6-arg kernel writing 8 floats (IQN + flatness only) into scratch[115..119, 127..131). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
805a7429f4 |
sp7(pearl_2): contract change — drop CQL/C51/ENS args (atomic)
Kernel signature reduced from 9 to 6 args; pearl_2_budget_update now writes only budget_iqn[4] and flatness[4]. Launcher migrated atomically: arg list shrinks, apply_pearls smoothing loop shrinks from 5 slot-blocks to 2. SCRATCH_PEARL_2_C51, SCRATCH_PEARL_2_CQL, SCRATCH_PEARL_2_ENS deleted as orphan constants per feedback_wire_everything_up. The corresponding producer scratch slots (111..115, 119..127) become reserved-for-future inside the buffer. CQL/C51/ENS budget ownership now lives in the SP7 loss-balance controller (loaded in T5; wired in T7). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
40871595e8 |
sp7(trainer): T5 polish — rename cql_dev → cql_sx_dev + base_wiener annotation
Two clarity-only edits surfaced by code review: - Rename `cql_dev` → `cql_sx_dev` in `launch_loss_balance_controller`. The launcher uses `grad_decomp_result_dev_ptr + 6 * f32_size` which is the cql_sx (post-budget) slot, not cql (pre-budget at offset 3). The variable name now reflects that. - Add `// 213` inline annotation on `base_wiener_offset` to match the pattern used by all four sibling launchers (lines 11062, 11194, 11303). No semantic impact — pointer arithmetic and parameter order unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ab6fbcb668 |
sp7(trainer): load loss-balance controller kernel + launcher fn
LOSS_BALANCE_CONTROLLER_CUBIN static, kernel slot in GpuDqnTrainer, 6 SCRATCH_LB_* index constants (218..242), pub(crate) launch_loss_balance_controller that runs the producer + 24 apply_pearls_ad smoothing launches. SP5_SCRATCH_TOTAL bumped 218→242 with updated docblock and allocation comment to match the 24 new slots. Component pointers into grad_decomp_result_pinned: IQN at offset 0, CQL_SX at offset 6, C51 at offset 9 (3-float layout per launch_grad_decomp docstring). Producer-only — call site in training_loop.rs lands at T7 atomically with the consumer floor change and stale-doc deletion per feedback_no_partial_refactor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8e4d5cb45b |
sp7(build): compile loss_balance_controller_kernel cubin
Adds the kernel to the build.rs manifest after pearl_1_ext_num_atoms_kernel and before the SP5 Layer D producers (matching the chronological SP5/SP7 producer ordering). nvcc-compiled cubin lands under $OUT_DIR/loss_balance_controller_kernel.cubin and is loaded by gpu_dqn_trainer.rs in T5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
21059320e2 |
sp7(cuda): T3 polish — comment-only clarifications
Three comment-only edits surfaced by code review: - M1: Annotate `tid >= 8` guard with `// 8 = 2 heads × 4 branches`. - M2: Header pseudocode `actual_ratio = h_norm / iqn_norm` clarified — the implementation correctly omits the inline `fmaxf` because the outer guard already enforces `iqn_n >= EPS_DIV`. Comment now states the invariant explicitly. - M3: Header "No atomicAdd" line now cites `feedback_no_atomicadd` for consistency with sibling pearl kernels. Comment-only — zero runtime effect. cargo check clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
63226f17cb |
sp7(cuda): loss-balance controller kernel (producer-only)
Single-block 8-thread kernel: 2 heads (CQL, C51) × 4 branches. Reads 3-float views into grad_decomp_result_pinned for IQN/CQL_SX/C51, plus FLATNESS_BASE for the per-branch target modulator. Writes new budgets + raw Wiener observations (diff² and sample²) to producer scratch. Per-branch flatness-modulated target ratio (CQL → ANCHOR×(1-flatness), C51 → ANCHOR×flatness). Wiener-optimal α from per-branch EMA state slots, clamped to [1e-4, 0.5]. Cold-start seeds at consumer-side bootstrap (0.02 CQL, 0.05 C51) on first observation; sentinel-aware. Producer-only commit; build.rs entry, kernel slot, launch site land in subsequent tasks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
60804788eb |
sp7(state): T2 polish — descriptions reflect current state vs future T6/T7
Rewrite the three sp5_budget_c51/cql/ens registry descriptions to be
accurate at HEAD
|
||
|
|
aa28540175 |
sp7(state): register loss-balance controller state slots + fix stale doc
4 new ResetEntries × 4 slots each cover ISV[297..313). Sentinel 0 triggers Pearl A first-observation bootstrap on fold boundary, matching the existing Pearl 2/3/4/5/6/8 pattern. Also: corrected the stale sp5_budget_cql description claiming a non-existent "regime_stability allocator" (per feedback_trust_code_not_docs); clarified sp5_budget_c51 / sp5_budget_ens to reflect SP7 ownership. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6e7690d82d |
sp7(isv): T1 polish — alignment + audit-doc stub softening
Minor item 1: Remove extra column-alignment spaces from the 4 SP7 constants (LB_DIFF_VAR_CQL_BASE, LB_SAMPLE_VAR_CQL_BASE, LB_DIFF_VAR_C51_BASE, LB_SAMPLE_VAR_C51_BASE) so they match the no-alignment style of surrounding SP5 constants (BUDGET_C51_BASE etc.). Constant values (297, 301, 305, 309) and inline comments are unchanged. Minor item 2: Soften the Fix 31 T7 stub in dqn-wire-up-audit.md from "sentinel-aware consumer" to "sentinel-aware bootstrap with bootstrap constants matching the kernel's cold-start basis (defined in T7)" so the audit entry does not forward-reference specific constant names before they land. Cargo check: clean (zero warnings, zero errors). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9822b2eea7 |
sp7(isv): refresh SP5_PRODUCER_COUNT docstring for T1 slot growth
Update stale (118) → 137 unique-slot count and [174..294) = 120 → [174..313) = 139 range in the SP5_PRODUCER_COUNT docstring. Update inline comment unique-slot count 121 → 137 with SP7 T1 breakdown entry. Append SP7 Task 1 history paragraph matching the established Layer D D3 paragraph style. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a92abedbae |
sp7(isv): allocate 16 ISV slots for loss-balance controller state
LB_DIFF_VAR_CQL_BASE=297, LB_SAMPLE_VAR_CQL_BASE=301, LB_DIFF_VAR_C51_BASE=305, LB_SAMPLE_VAR_C51_BASE=309. Bumps SP5_SLOT_END 297 → 313 and SP5_PRODUCER_COUNT 123 → 139. Helper fns mirror the existing budget_*/flatness/q_var_per_branch pattern. Layout fingerprint extended. slot_layout_no_overlaps_and_total_correct asserts the new total. Audit doc Fix 31 stub added; will be extended commit-by-commit through Tasks 2-9 of the SP7 plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2fb7d7f57c |
fix(data): Fix 30 Stale-B — backtest_plan_kernel raw_close from prices_buf (post-MappedF32Buffer migration)
Closes Fix 29 audit row #13 — the last ⚠ Stale row from the Bug-1 contract drift triage. Pre-fix, `backtest_plan_state_isv` extracted raw_close as `features[bar*feat_dim + 0]`. Post Bug-1 (commit `5a5dd0fed`) `features[..+0]` is z-normed log-return, NOT raw_close. The resulting `equity = cash + position*raw_close` and `unrealized = position*(raw_close - entry_price)` formulas mixed z-normed-log-return as a dollar price, corrupting val plan_isv slots [PNL_VS_TARGET] (slot 1) and [PNL_VS_STOP] (slot 2). Other plan_isv slots (progress, conviction, drift, regime, remaining) don't depend on raw_close and were correct pre-fix. Resolution: route raw_close from the upload-once `prices` buffer (layout `[n*max_len*4]` raw OHLC). Close is at column index 3 — the same index `backtest_env_kernel.cu` already reads from for portfolio mark-to-market (line 64 of that kernel; OHLC layout is canonical across the val backtest path). Reading from `prices` aligns the val plan_isv path with env_step's source-of-truth, eliminating the mixed-units pathology end-to-end. Sites fixed: - crates/ml/src/cuda_pipeline/backtest_plan_kernel.cu:74-100 Kernel signature: `const float* features` and `int feat_dim` parameters dropped, replaced by `const float* prices` (the [n*max_len*4] raw OHLC buffer). Raw_close read becomes `prices[(w*max_len + current_step)*4 + 3]`. Multi-line comment block documents the Bug-1 origin and the env_step parity reference. The `bool have_close` / `prices != nullptr` guard semantics preserved — callers without OHLC data fall back to `unrealized = 0` exactly as pre-fix. - crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs:~1956 Single launcher (`evaluate_dqn_graphed` chunk loop, the only invocation site of `plan_state_isv_kernel`) updated to pass `&self.prices_buf.dev_ptr` instead of `&self.features_buf.dev_ptr` and to drop the now-unused `feat_dim_i32` local + `.arg()` call. Inline comment explains the Bug-1 origin. - docs/dqn-wire-up-audit.md Stale-B row appended to Fix 30's table. The standalone Stale-B DEFERRED paragraph at the bottom replaced by the commit summary + the Fix 30 closure note (all 4 ⚠ Stale and 1 ❓ Ambiguous rows from Fix 29's deferred follow-ups now resolved). Migration scope per `feedback_no_partial_refactor`: kernel signature changed → every consumer migrates in the same commit. Single launcher; verified via `grep -rn backtest_plan_state_isv` (only the kernel definition + the gpu_backtest_evaluator launcher + load site appear). Verification: - SQLX_OFFLINE=true cargo check -p ml --offline (43.68s) clean. - SQLX_OFFLINE=true cargo build -p ml --release --offline --features cuda (1m 30s) clean; cubin recompiled via nvcc. - Pre-commit DtoD-via-pinned guard passes (the prereq commit `4d966e62f` migrated `gpu_backtest_evaluator.rs`'s buffers to MappedF32Buffer, eliminating the 5 `_via_pinned` callers that had blocked any prior staging of this file). Refs Fix 29 row #13. `feedback_no_partial_refactor` (single launcher migrated alongside kernel signature change in one commit), `feedback_no_functionality_removal` (PNL_VS_TARGET / PNL_VS_STOP slots preserved — only their data source corrected; the audit's "drop the slots" alternative explicitly rejected), `feedback_no_hiding` (no fallback to z-normed reads remaining; kernel either gets real raw_close or falls through to `have_close=false` with `unrealized=0`, identical to pre-fix smoke-test semantics where prices==NULL), `feedback_no_cpu_compute_strict` n/a (zero new host-side compute), `feedback_no_htod_htoh_only_mapped_pinned` already satisfied (`prices_buf` is `MappedF32Buffer` post the prereq migration), `feedback_trust_code_not_docs` (the kernel comment said "raw_close from features buffer" for months — accurate-when-written pre-Bug-1, stale-after-Bug-1; verify-against-code disambiguates). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4d966e62f4 |
refactor(cuda): migrate gpu_backtest_evaluator buffers to MappedF32Buffer (Fix 30 Stale-B prereq)
Prerequisite for the deferred Fix 30 Stale-B kernel-side fix
(`backtest_plan_kernel.cu` raw_close source). The DtoD-via-pinned
pre-commit guard (`scripts/pre-commit-hook.sh::check_no_dtod_via_pinned`,
commit `5275932f4`) blocks any commit that stages `gpu_backtest_evaluator.rs`
against the file's 5 pre-existing `clone_to_device_*_via_pinned` callers
that landed before the guard. Stale-B has to stage this file (to thread
`prices_buf` into the `backtest_plan_state_isv` launcher), so the
migration must land first as its own atomic commit per
`feedback_no_partial_refactor`.
Migration scope. Single file, four buffer fields:
- prices_buf CudaSlice<f32> → MappedF32Buffer [n*max_len*4]
- features_buf CudaSlice<f32> → MappedF32Buffer [n*max_len*feat_dim]
- portfolio_buf CudaSlice<f32> → MappedF32Buffer [n*8] (kernel-mutated)
- window_lens_buf CudaSlice<i32> → MappedI32Buffer [n]
Init sites (lines ~651-664 post-edit). 4 `clone_to_device_*_via_pinned`
calls replaced with `MappedF32Buffer::new(host.len())` +
`write_from_slice(host)`. No memcpy_dtod_async + stream.synchronize()
pair at construction — mapped-pinned coherence makes the kernel see
host writes after the next stream-sync barrier.
Reset site (`reset_evaluation_state`, line ~1485 post-edit). In-place
`self.portfolio_buf.write_from_slice(&portfolio_init)` replaces the
prior buffer-replacement
`self.portfolio_buf = clone_to_device_f32_via_pinned(...)`. No alloc
churn per epoch; the device pointer stays stable across resets which
matches MappedF32Buffer's intended use.
Consumer sites (17 kernel arg passes). `arg(&self.X_buf)` →
`arg(&self.X_buf.dev_ptr)` so the launcher receives the device pointer
the kernel expects. Sites: launch_gather (×2), launch_gather_chunk,
launch_env_step, env_batch_kernel chunked path, plan_state_isv kernel,
metrics_kernel. CUdeviceptr (u64) is passed by reference exactly as
metrics_dev_ptr already does at the metrics launch site (~line 2497).
Kernel-mutated MappedF32Buffer precedent. portfolio_buf is mutated by
the env_step kernel every step. The same file's `plan_diag_buf` (a
MappedF32Buffer) is also kernel-written via dev_ptr (lines ~1230-1256)
and host-read via host_ptr — direct precedent. The IQN τ migration
(commit `facbf76eb`) confirms cuMemHostAlloc DEVICEMAP for kernel
reads. The mapped_pinned.rs module docstring states explicitly:
"Kernels write through dev_ptr (with __threadfence_system())".
What this change touches:
- crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs
Field types (lines 299-321), init block (lines 641-665), reset
(lines 1462-1471), 17 kernel arg passes across 6 launchers.
- docs/dqn-wire-up-audit.md
Fix 30 Stale-B paragraph extended with the prereq commit summary.
Stale-B itself remains DEFERRED (kernel-side fix lands next).
Verification:
- SQLX_OFFLINE=true cargo check -p ml --offline (44.38s) clean.
- SQLX_OFFLINE=true cargo build -p ml --release --offline
--features cuda (53.78s) clean.
- grep -n "clone_to_device_.*_via_pinned\|upload_.*_via_pinned"
crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs returns
zero hits.
- grep -n "self\.\(prices\|features\|window_lens\|portfolio\)_buf"
shows every kernel-arg site followed by `.dev_ptr`. The only
non-`.dev_ptr` references are the field declarations, the
constructor moves into `Self { ... }`, and the
`write_from_slice` calls in `reset_evaluation_state`.
- Pre-commit DtoD-via-pinned guard now passes on staging this file.
Eliminates the last 5 `_via_pinned` callers in
`gpu_backtest_evaluator.rs`. Per
`feedback_no_htod_htoh_only_mapped_pinned`, `feedback_no_partial_refactor`
(every consumer of a field-type change migrates in one commit),
`feedback_no_hiding` (no `--no-verify`; the migration IS the fix the
guard is asking for).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
c4aa71b9df |
cleanup(cuda): Fix 30 Ambiguous-A — delete orphan phantom_liquidity_gbm
Closes Fix 29 audit row #18. The synthetic-data overlay kernel `phantom_liquidity_gbm` in `dqn_utility_kernels.cu:1050-1096` was flagged ❓ Ambiguous because its writer wrote `log_ret` to `market_features[bar*market_dim + 0]` and `+3` — the WRITER's downstream consumer contract was unverifiable post Bug 1 (commit `5a5dd0fed`) which redefined feature index 0 from raw log-return to z-normed log-return. Investigation result: zero callers in the entire codebase. The audit grep `grep -rn "phantom_liquidity_gbm" crates/ml/src/ services/ crates/ bin/` returns ONLY the kernel definition itself — no `load_function`, no `launch_builder`, no Rust-side launcher. The kernel has been compiled but never invoked since at least early 2026. Resolution: delete the kernel definition and replace with an explanatory comment block. Per `feedback_no_hiding` an orphan kernel with an ambiguous post-Bug-1 contract is exactly the failure mode the rule warns against — a future re-wirer would have written synthetic z-normed log-returns into a slot the production pipeline expects raw log-returns at, re-introducing the very Bug-1 drift the audit was triaging. Per `feedback_wire_everything_up` a kernel that compiles but is unconsumed is either wired in the same commit or deleted; this kernel was never wired since its writer existed. The replacement comment block (23 lines) documents the design intent (GBM-based synthetic-data augmentation) so any future re-introduction has the spec available, plus the re-introduction conditions: caller wiring must land in the same commit, and the writer/consumer contract for `market_features[..+0]` (z-normed vs raw log-return) must be explicit at the kernel boundary. What this change touches: - crates/ml/src/cuda_pipeline/dqn_utility_kernels.cu:1037-1096 47-line kernel definition + ══ comment header deleted; 23-line DELETED notice replaces it. Net -24 lines. - docs/dqn-wire-up-audit.md Fix 30 Ambiguous-A row appended with the dead-code-deleted resolution. Stale-B deferral note also added (separate paragraph) documenting the pre-existing `_via_pinned` guard block requires a dedicated `gpu_backtest_evaluator.rs` migration commit before the Stale-B kernel-side fix can land. Verification: - SQLX_OFFLINE=true cargo build -p ml --release --offline --features cuda (1m 30s) clean; cubin recompiled via nvcc. - Pre-commit guards pass. - Final grep for `phantom_liquidity_gbm` returns only the comment block in the kernel file. Refs Fix 29 row #18 deferred follow-up. `feedback_no_hiding` (delete orphans, do not leave them as ambiguous landmines), `feedback_wire_everything_up` (every module/feature/kernel built must be wired to a production path or removed), `feedback_no_functionality_removal` does NOT apply: a kernel with zero callers is not a functional feature, and the audit doc retains the design intent so any future re-introduction can rebuild the mechanism with the correct post-Bug-1 contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7e54dedd22 |
fix(data): Fix 30 Stale-C — scripted_policy raw_close from fxcache targets
Closes Fix 29 audit row #12. Pre-fix, `scripted_policy_select` read `state[MARKET_START]` as `close_now` and computed `recent_ret = (close_now - prev_close) / prev_close`. Post Bug-1 (commit `5a5dd0fed`) `state[MARKET_START]` is z-normed log-return at the WRITER, not raw_close, so the formula mixed two unrelated signals (z-normed return vs dollar price). The seed-phase MOMENTUM / MEAN_REV / VWAP_DEV branches degenerated to noise-floor below the ±0.0001f cutoffs, leaving 60% of seed-phase episodes (the non-UNIFORM 20%+20%+20%) effectively in DIR_HOLD instead of expressing the intended scripted-policy diversity. Resolution: route raw_close from the same fxcache target buffer the env_step kernel reads from. Kernel signature gains `const float* targets`, `const int* episode_starts`, `int t`, `int total_bars` parameters. Per-thread `bar_idx = episode_starts[i] + t` (matching `experience_kernels.cu:1769`'s indexing convention), then `close_now = targets[bar_idx*6 + TARGET_RAW_CLOSE]`. Bounds-clamp to `[0, total_bars-1]` mirrors env_step's out-of-bounds early-return. Sites fixed: - crates/ml/src/cuda_pipeline/scripted_policy_kernel.cu Kernel signature extended; close_now read source switched; `FXCACHE_TARGET_STRIDE` / `FXCACHE_TARGET_RAW_CLOSE` mirrored as #defines (kernels can't import Rust constants; co-locating the literals in a comment-block keeps the cross-language contract visible at the call site for any future TARGET_DIM bump). The previous `bar_idx` parameter renamed `t`; the LCG seed now mixes per-thread `bar_idx = episode_starts[i] + t` instead of the per-step `t` — stronger entropy across episodes, no behavioural regression (UNIFORM policy still produces 4-direction uniform). - crates/ml/src/cuda_pipeline/gpu_experience_collector.rs:~3735 Single launcher updated to pass `&targets_buf.dev_ptr`, `&self.episode_starts_buf`, `t as i32`, and `total_bars` (already in scope from line 3301). Inline comment explains Bug-1 origin. Migration scope per `feedback_no_partial_refactor`: single launcher; no other call sites. Verified via `grep -rn scripted_policy_select crates/ml/src/`. NO new `PS_*` slot or `PORTFOLIO_STRIDE` bump required — the targets buffer is the canonical raw_close source and routing it directly avoids cascading through the ~12 PORTFOLIO_STRIDE consumers (kelly_cap_update_kernel, trade_stats_kernel, gpu_experience_collector, ml-core mirror, etc.). This is the "route raw_close directly" branch of the audit's contract decision; the alternative "add an extra state slot" branch was explicitly rejected as cascade-heavy for a seed-phase-only signal. Verification: - SQLX_OFFLINE=true cargo check -p ml --offline (43.87s) clean. - SQLX_OFFLINE=true cargo build -p ml --release --offline --features cuda (1m 30s) clean; cubin recompiled via nvcc. - No host-side compute added; all reads on GPU. - `targets_buf` is mapped pinned by upstream caller (per `feedback_no_htod_htoh_only_mapped_pinned`). Refs Fix 29 row #12. `feedback_no_partial_refactor` (single launcher migrated in same commit), `feedback_no_functionality_removal` (`recent_ret` signal preserved — only its data source is fixed; the CUSUM-substitution alternative path explicitly rejected because the recent_ret signal IS the scripted-policy contract, not an implementation accident), `feedback_no_hiding` (no fallback to z-normed-log-return reads; kernel either gets real raw_close or clamps to total_bars-1 boundary), `feedback_no_cpu_compute_strict` n/a (zero new host-side compute), `feedback_trust_code_not_docs` (the kernel comment said `state[MARKET_START + 0] = close_now` for months — accurate-when-written, stale-after-Bug-1; verify-against-code disambiguates). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
73e3ea87a4 |
fix(data): Fix 30 Stale-A — host-side target[0] → TARGET_RAW_CLOSE swap
Closes Fix 29 audit rows #14 and #15 (Bug-1 contract drift in val/HPO close-price extraction). Both sites read `target[0]` thinking it is raw_close, but post Bug-1 (commit `5a5dd0fed`) `target[0]` is preproc_close (z-normed log-return). The `fv[3]` fallback path is unreachable on every production code path because `set_val_data_from_slices` (in `dqn/trainer/mod.rs:1704`) always yields `Vec<f64>` of length 6 from `[f64; 6]` slices post `TARGET_DIM=6` bump (commit `063fd2716`), so `target.len() >= 2` is an always-true guard. Per `feedback_no_hiding` the dead fallback is removed in the same edit rather than left as a silent wrong-units path. Sites fixed: - crates/ml/src/trainers/dqn/trainer/metrics.rs:576 (val window_prices for GpuBacktestEvaluator) - crates/ml/src/hyperopt/adapters/dqn.rs:2492 (HPO adapter val_close_prices for window-aggregated backtest) Both now read `target[TARGET_RAW_CLOSE]` (col 2). Both import the named constant from `crate::fxcache` so a future column rename moves the call site with the writer (Fix 27 prevention pattern). Verification: - SQLX_OFFLINE=true cargo check -p ml --offline (8.03s) clean. - No GPU code changed; cubin not affected. Affects val Sharpe annualization + window equity curves on training metrics; affects HPO val score on every trial. Pre-fix would yield "prices" of magnitude ~stddev(log_return) (~7e-5 for ES 1-min) feeding into PnL math that expects dollar prices, producing degenerate backtest output. Post-fix prices are real raw_close values. References Fix 27 Bug B (host-side Welford) — same kind of bug surfaced at val/HPO consumer rather than training Welford. References feedback_no_hiding (delete unreachable fallbacks rather than leaving silent wrong-units fall-through), feedback_no_partial_refactor (both consumers of the same (fv, target) tuple convention migrate together), feedback_trust_code_not_docs (the `target.len() >= 2` guard read as defensive but was masking a contract drift). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ab378327ec |
fix(data): vol_normalizer double-normalization removed (post-Bug-1)
The kernel block at experience_kernels.cu:698-704 divided market_features[0..3] by vol_normalizer at runtime — designed for the pre-Bug-1 pipeline where features arrived as RAW log returns (~±0.001). After Bug 1 fix (commit |
||
|
|
2683d46378 |
diag(dqn): DIAG_AUX_LABEL one-shot — pin source of label_scale=5481 leak
Production training run train-multi-seed-bn42w showed label_scale=5481
(raw_close magnitude) at epoch 4 vs smoke's ~25 at the same commit
|
||
|
|
29b1d34c6d |
fix(data): target stride/column drift — host + GPU consumer cleanup
Two latent bugs converged in the cancelled 50-epoch run (train-multi-seed-p5qzw at |
||
|
|
2e9e276a0c |
fix(sp5): Layer D Task D4 — atomic 3-kernel wiring of D1+D2+D3
Wires the 3 Layer D producer kernels into the production hot path AND removes the host-side EMA arithmetic for sharpe/max_dd/low_dd_ratio in the same commit per feedback_no_partial_refactor + feedback_no_cpu_compute_strict. Sites migrated (host → GPU kernel + ISV slot): training_sharpe_ema → launch_training_metrics_ema → ISV[294] max_dd_ema → launch_training_metrics_ema → ISV[295] low_dd_ratio → launch_training_metrics_ema → ISV[296] LearningHealth.compose → launch_health_composition → ISV[290..294) trade-PnL aggregation → launch_sp5_pnl_aggregation → ISV[286..290) Behavior preserved within float precision: every EMA β decay constant, warmup/clamp wrapper, and downstream consumer formula migrates verbatim. The kernels reproduce the host computations bit-for-bit (verified by the GPU-gated unit tests landed in D1/D2/D3 at |
||
|
|
66f7e64d13 |
fix(sp5): D1 rewrite — match production per-bar semantics, fix D4 blocker
The original D1 kernel (commit
|
||
|
|
f42b5fff8d |
feat(sp5): Layer D Task D3 — Training metrics EMA kernel (additive)
Third of 3 Layer D producer kernels. Replaces host-side training_sharpe_ema, max_dd_ema, low_dd_ratio updates (host-side EMAs in training_loop.rs) with a fused GPU kernel chained through apply_pearls_ad_kernel. Per feedback_no_cpu_compute_strict. Note: agent investigated training_loop.rs and found the third metric is low_dd_ratio (not gamma_blend as the original plan brief named). The authored kernel reproduces the actual host-side EMA triplet present in the codebase. Additive only — no consumer wiring, no behavior change. The kernel + launcher are loaded into GpuDqnTrainer and the slots are reserved on the ISV bus, but the host-side updates continue to run unchanged. D4 (atomic Layer D commit) wires this and D1+D2 to call sites in the same atomic refactor per feedback_no_partial_refactor. Also note: training_loop.rs gains two state-reset-registry dispatch arms (sp5_health_composition for D2 + sp5_training_metrics_ema for D3) — registry plumbing required by the new entries, NOT consumer wiring of the kernels themselves; same pattern as SP5 Layer A bug-fix #281. What this lands: - training_metrics_ema_kernel.cu (single-block 3-thread fused EMA) - Rust launcher launch_training_metrics_ema() - 3 new ISV slots (TRAINING_SHARPE_EMA_INDEX..LOW_DD_RATIO_INDEX, 294..297) - ISV_TOTAL_DIM 294 → 297, SP5_PRODUCER_COUNT 120 → 123 (linear span) - LAYOUT_FINGERPRINT_SEED bump (auto via slot string) - StateResetRegistry entries for D2 (sp5_health_composition) + D3 (sp5_training_metrics_ema) with reset_named_state dispatch arms - build.rs cubin registration - GPU-gated unit test in sp5_producer_unit_tests.rs (analytical EMA) - SP5 contiguity slot test training_metrics_ema_slots_contiguous_and_above_health_block - Audit doc append Formula fidelity: kernel reproduces the host-side EMA update for sharpe/max_dd/low_dd_ratio bit-for-bit within float precision. β decay constants and any warmup/clamp logic migrate as Invariant 1 anchors with no algorithmic change. Verified by unit test asserting kernel output matches an analytical EMA sequence within 1e-6 rel-err. Tests: cargo check + cargo build clean; ISV slot + state_reset_registry unit tests pass (8/8 incl. new contiguity check); GPU correctness test fires on next L40S smoke. Refs: SP5 plan §D Task D3, builds on D1 ( |
||
|
|
e49756ac90 |
feat(sp5): Layer D Task D2 — Health composition kernel (additive)
Second of 3 Layer D producer kernels. Replaces multi-step host arithmetic
in LearningHealth composition (q_gap_ema + q_var_ema + grad_norm_ema →
composed health score) with a fused GPU kernel chained through
apply_pearls_ad_kernel. Per feedback_no_cpu_compute_strict.
Additive only — no consumer wiring, no behavior change. The kernel +
launcher are loaded into GpuDqnTrainer and the slots are reserved on the
ISV bus, but the host-side composition continues to run unchanged. D4
(atomic Layer D commit) wires this and D1+D3 to call sites in the same
atomic refactor per feedback_no_partial_refactor.
What this lands:
- health_composition_kernel.cu (single-block fused composition)
- Rust launcher launch_health_composition()
- 4 new ISV slots (HEALTH_SCORE_INDEX..GRAD_NORM_NORM_INDEX, slots 290..294)
- ISV_TOTAL_DIM 290 → 294, SP5_PRODUCER_COUNT 116 → 120 (linear span)
- LAYOUT_FINGERPRINT_SEED bump (auto via slot string)
- StateResetRegistry entries (sentinel 0.0; per-fold reset enabled)
- build.rs cubin registration
- GPU-gated unit test in sp5_producer_unit_tests.rs (formula fidelity)
- Audit doc append
Formula fidelity: kernel reproduces the host-side LearningHealth::compose
(or equivalent) computation bit-for-bit within float precision. Migration
is structural only — no algorithmic change. Verified by unit test
asserting kernel output matches a Rust copy of the host formula within
1e-6 rel-err.
Tests: cargo check + cargo build clean; ISV slot + state_reset_registry
unit tests pass; GPU correctness test fires on next L40S smoke.
Refs: SP5 plan §D Task D2, builds on D1 (
|