0275a25d9f838adeedc005d5dcc38c52ba34093e
4722 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0275a25d9f |
Merge SP11/12/13/14 implementation chain into main
SP11 reward-as-controlled-subsystem: B1b z-score normalization for
magnitude-asymmetric weight ratios; popart-component magnitude slot.
SP12 v3: per-trade event-driven reward (asymmetric pos/neg cap +
min-hold target + zero per-bar dense shaping).
SP13 Layer B: K=1→2 softmax CE aux head + i32 replay buffer ring +
GpuBatchPtrs plumbing + scale-free MSE bridge for ISV[117].
SP14 Layer A+B: 3 stability fixes (C51 inv_a_std floor lift, set_aux_weight
clamp, stagnation warmup gate) + Earned Gradient Flow pearl wired through
alpha_grad_compute / q_disagreement_update / gradient_hack_detect /
dir_concat_qaux / scale_wire_col kernels + ISV_TOTAL_DIM bus fix
(383→396) + warmup_gate delete (variance-driven k_aux/k_q handles
warmup intrinsically).
Smoke A2-B PASSED. 8-epoch train-dd4xl L40S validation revealed:
- Walk-forward test_start..test_end slice generated but never consumed
(val IS the selection set; no sealed test).
- Downward-spiral pathology: trades 131k→64k, active_frac 0.48→0.17,
sharpe_ann 79→44 across 8 epochs.
SP15 (trader-discipline-and-recovery) addresses both: honest cost-aware
metrics on Q1-Q7/Q8/Q9 split, behavioral test suite on dev RTX 3050 Ti,
DD-state foundational input, recovery dynamics inc. plasticity injection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
# docs/superpowers/specs/2026-05-04-sp11-reward-as-controlled-subsystem.md
|
||
|
|
c0fc28e455 |
fix(sp14): delete warmup_gate — let variance-driven k_aux/k_q handle warmup (ISV-driven)
Per `feedback_isv_for_adaptive_bounds`, the hardcoded `warmup_gate = (fold_step_counter / WARMUP_STEPS_FALLBACK).min(1.0)` ramp violated the rule: adaptive bounds in ISV, never hardcoded constants. The variance-driven k_aux/k_q sigmoid steepness already provides warmup behavior intrinsically: - High variance (cold-start, EMAs still moving) → k → K_MIN → flat sigmoid → gate ≈ 0.5 regardless of input. That IS the warmup. - Low variance (settled) → k → K_BASE → sharp sigmoid → gates respond correctly to driver signals. Adding a separate hardcoded step-counter multiplier on top was double-counting + tuning-driven (the 1000-step threshold had no principled basis). Removed entirely. Removed (per `feedback_no_partial_refactor`, all atomically): - `WARMUP_STEPS_FALLBACK` constant in `sp14_isv_slots.rs` - `warmup_gate: f32` parameter in `alpha_grad_compute_kernel.cu` - `gate1 * gate2 * warmup_gate` → `gate1 * gate2` in kernel - `warmup_gate` arg from `launch_sp14_alpha_grad_compute` - `fold_step_counter: usize` field on the trainer struct - `fold_step_counter = 0` reset in `reset_for_fold` - `fold_step_counter` init in trainer constructor - `let warmup_gate: f32 = 1.0;` and `.arg(&warmup_gate)` from B.4 oracle tests (4 launches: 2 in alpha_grad_schmitt_hysteresis, 20 in alpha_grad_adaptive_beta loop) Build: clean, 18 warnings (pre-existing baseline). Tests: cargo test --no-run on sp14_oracle_tests succeeds. Net result: EGF gate's warmup behavior now lives entirely in the variance-driven k_aux/k_q sigmoid steepness controller (ISV slots 388/var_aux, 389/var_q). No hardcoded step counter. Honors `feedback_isv_for_adaptive_bounds` and `pearl_controller_anchors_isv_driven`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
60ad42676e |
fix(sp14): bump ISV_TOTAL_DIM 383 → 396 to cover SP14 EGF slots
ROOT CAUSE of L1+L2 from Smoke A2-B: the ISV bus was sized for top of SP13 (ISV_TOTAL_DIM=383) but B.1 allocated SP14 slots at 383-395. Every SP14 read/write was OUT-OF-BOUNDS memory access. That's why: - gate1 (slot 391) read as 0 always (OOB zero-init memory) - post_open_min (slot 394) accumulated garbage values 9.5 → 28 → 46 - α_smoothed/α_raw values appeared to work but were undefined behavior SP4/SP5 had a regression test (`all_sp4/5_slots_fit_within_isv_total_dim`) that catches this exact failure mode at unit-test time. SP14 was missing it — that gap let the bug ship across all 16 commits without being caught. Changes: - ISV_TOTAL_DIM: 383 → 396 (covers SP14 slots 383-395) - layout_fingerprint_seed: extended with SP14 slot names + new ISV_TOTAL_DIM=396 marker (forces fingerprint hash bump per Invariant 8 — old checkpoints invalidated correctly) - sp14_isv_slots.rs: 2 regression tests (mirror SP4/SP5 patterns) Both tests pass. After this fix, SP14 EGF kernels will read/write the correct slots; gate1 should actually flip open when aux_dir_acc crosses target+0.03; gradient_hack_detect post_open_min stays bounded in [0, 1] as designed. NOT yet addressed (separate follow-up): - warmup_gate hardcoded WARMUP_STEPS_FALLBACK=1000 violates feedback_isv_for_adaptive_bounds. Should be ISV-signal-driven OR removed entirely (k_aux/k_q already provide variance-driven warmup). Redesign post-re-smoke once bus-size fix is verified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
976ab4bf1a |
docs(sp14): Smoke A2-B PASSED — Layer A+B chain validated, 2 EGF bugs flagged
Workflow smoke-test-z2kt7 on commit
|
||
|
|
26343cd573 |
docs(sp14): Layer A+B close-out — full chain summary pre-Smoke-A2-B
Adds a Layer A+B close-out section to dqn-wire-up-audit.md summarizing
all 15 commits in the SP14 chain (3 Layer A stability fixes + 12 Layer B
EGF pearl tasks).
Includes:
- Commit-by-commit table with SHA + task ID + summary
- Architectural summary post-Layer B (forward + backward + α_grad pipeline)
- 8 explicit kill criteria for Smoke A2-B (per spec B.7)
- B.13 skip rationale (per-kernel oracle tests cover unit behavior;
B.14 smoke is the real integration test)
Layer B effective end:
|
||
|
|
e41dbb7d8a |
diag(sp14 B.12): per-epoch pearl_egf_diag HEALTH_DIAG emit
Adds a new HEALTH_DIAG[{epoch}]: pearl_egf_diag line immediately after
the aux_moe block in the per-epoch metrics section of training_loop.rs.
Reads all 13 SP14 ISV slots [383..396) — α_smoothed, α_raw, β, k_aux,
k_q, var_aux, var_q, var_α, q_dis_short, q_dis_long, gate1 state,
post_open_min, lockout — via the established read_isv_signal_at pattern,
giving forensic visibility into EGF pearl state each epoch.
gate1/gate2 sigmoid outputs are intentionally omitted: recomputing them
host-side would violate feedback_no_cpu_compute_strict; the sigmoid
inputs are sufficient for a reader to infer the output values.
docs/isv-slots.md updated (Invariant 7): records B.12 HEALTH_DIAG wire-up.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
857722e774 |
feat(sp14): B.11 — orchestrator wire-up for 3 EGF producer kernels + var_aux gap closure
Per-step launches (in graph capture order): 1. Forward (existing) 2. Action select (existing) → q_dir_logits available 3. launch_sp14_q_disagreement_update → ISV[383, 384, 389] 4. launch_sp14_alpha_grad_compute → ISV[385..395] (consumes q_disagreement) 5. Backward (existing) — wire-col scale at B.10 reads ISV[393] Per-epoch launch (end of epoch): 6. launch_sp14_gradient_hack_detect → circuit breaker α_short=0.3, α_long=0.05, α_var=0.05 per spec; warmup_gate derived from steps_in_fold / WARMUP_STEPS_FALLBACK. Var_aux producer gap closed (option C from B.4): alpha_grad_compute_kernel now also writes ISV[VAR_AUX_INDEX=388] via Welford EMA against (aux_dir_acc_short - aux_dir_acc_long). Adaptive k_aux is now functional (was degenerate at K_BASE_AUX=20.0 constant pre-B.11). Closes the "adaptive_k_aux currently degenerate" concern flagged in B.4 commit. After this commit, the EGF pearl is FULLY ACTIVE end-to-end: - Forward: aux signal feeds direction Q-head input (B.8/B.9) - Backward: wire-col gradient gated by α_grad_smoothed (B.10) - Producers: α_grad computed every step from real driver signals (B.11) - Pre-B.11 force-closed gate (sentinel 0.0) → post-B.11 responsive gate Build clean: 18 warnings pre-existing baseline, 0 new. Tests: 4/4 P0b aux_w tests pass (no regression). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
dc3f948ee9 |
feat(sp14): B.10 — backward wire gradient gating by ALPHA_GRAD_SMOOTHED
Critical safety mechanism that completes the EGF pearl: scales the wire
column of `dL/dx_concat [B, SH2 + 1]` (the gradient flowing FROM the
direction Q-head's first FC SGEMM TO `aux_softmax_diff`) by
`ISV[ALPHA_GRAD_SMOOTHED_INDEX = 393]`, computed by B.4's
`alpha_grad_compute_kernel` and orchestrated per-step in B.11.
`dL/dW[wire_col]` (Q-head's own weight gradient for the appended column)
is NOT scaled — the dW SGEMM `dY^T × x_concat` and the dX SGEMM
`dY × W^T` are independent, so scaling `dx[:, SH2]` AFTER both have
completed leaves dW unaffected. Q-head learns to USE the wire freely;
only the gradient PROPAGATING BACK to aux is gated.
Pre-B.11 (no producer wired) `ISV[393]` holds sentinel `0.0` →
wire force-closed (gradient zeroed) — the conservative safety state.
Post-B.11, B.4 writes the live gate output ∈ [0, 1] each step.
Closes the latent K-mismatch B.8/B.9 left in backward
============================================================
B.8 grew `w_b0fc` to `[adv_h, SH2 + 1]` end-to-end (Adam m/v +
spectral-norm vector + smoke fixtures); B.9 closed the forward dispatch
K-mismatch. The backward dW/dX SGEMMs for `d == 0` still used `K = SH2`
against the new `LDA = SH2 + 1` weight tensor — silently dropping the
last column of dW and zeroing the wire-col gradient. B.10 closes that
gap atomically with the wire-col scale per `feedback_no_partial_refactor`:
* `backward_branch_dw` for `d == 0` now uses `(dir_qaux_concat_ptr, SH2 + 1)`
instead of `(save_h_s2, SH2)` — matching the forward consumer
pattern from B.9.
* `backward_branch_dx` for `d == 0` now writes to
`d_dir_qaux_concat [B, SH2 + 1]` with `K = SH2 + 1` instead of
`scratch_d_h_s2 [B, SH2]` with `K = SH2`. Mirrors the magnitude
branch's wider-buffer pattern.
New artifacts
=============
* `sp14_scale_wire_col_kernel.cu`: one thread per batch row, scales
`dx_concat[b, SH2]` by `isv[393]` IN-PLACE. NaN-safe per the
`dqn_scale_f32_kernel` precedent (explicit `α==0 ⇒ 0` branch).
Pure per-thread map, no atomicAdd, no shared memory.
* `sp14_d_dir_qaux_concat: CudaSlice<f32>` `[B, SH2 + 1]` trainer-
struct field. Dx SGEMM destination; the wire-col scale acts on
this buffer; the strided accumulator copies the first SH2 columns
into `bw_d_h_s2` after the scale.
* `launch_sp14_scale_wire_col` launcher reads `self.isv_signals_dev_ptr`
and the new buffer's raw_ptr.
* `backward_full` signature grows two trailing `u64` args
(`dir_qaux_concat_ptr`, `d_dir_qaux_concat_ptr`); both
`backward_full` call sites (CQL aux + main online) wired
atomically per `feedback_no_partial_refactor`.
Post-call orchestration at trainer level
========================================
1. `launch_sp14_dir_concat_qaux(save_h_s2)` rebuilds the ONLINE
concat in `sp14_dir_qaux_concat_scratch` (the forward pass had
overwritten it with the TARGET concat at line ~25817). Same
one-step-lag semantic preserved — `aux_nb_softmax_buf` is
unchanged between forward and backward.
2. `cuMemsetD32Async` zero of `d_h_s2` — pre-B.10 the direction
branch (d==0) wrote it with beta=0; post-B.10 the dir-Q dX
lives in `d_dir_qaux_concat` and is gated + accumulated AFTER
`backward_full` returns, so the value-FC dx accumulator inside
`backward_full` (beta=1) needs an explicit zero baseline.
3. `backward_full` runs: dir branch → `d_dir_qaux_concat`,
mag/ord/urg branches → their concat dX buffers, value-FC →
`d_h_s2` (beta=1, on top of zeroed buffer).
4. `launch_sp14_scale_wire_col` gates col SH2 of `d_dir_qaux_concat`.
5. `accumulate_d_h_s2_from_concat` (beta=1) copies first SH2 cols
of `d_dir_qaux_concat` into `d_h_s2`. Wire col stays in
`d_dir_qaux_concat[:, SH2]`, untouched by this accumulator (its
destination range is [0, SH2)). Pre-B.11 the wire is already
zeroed by the sentinel-α gate; the orchestrator that propagates
the gated wire-col gradient back to the aux head's softmax CE
backward chain lives in B.11.
6. mag/ord/urg accumulators continue with beta=1 (comments updated).
Wire status
===========
* Forward dispatch: unchanged (B.9-complete).
* Backward dispatch: GATED on both call sites (CQL aux + main online).
* dW unchanged: the `dW = dY^T × x_concat` SGEMM writes
`grad_buf[goff_w_b0fc..]` BEFORE the scale-wire-col launches;
the scale operates ONLY on `d_dir_qaux_concat` (the dx buffer)
AFTER both dW and dX SGEMMs complete.
* Target net unaffected: Polyak EMA-only, no backward.
* CudaSlice wrapper path: passes `0u64` for both new args, falls
back to the legacy K=SH2 path. Consistent with the forward
wrapper's diagnostic-only residual.
Verified
========
* `SQLX_OFFLINE=true cargo check -p ml` clean, 18 warnings (baseline)
* `cargo test -p ml --test sp14_oracle_tests` 2 passed, 6 ignored (GPU)
* Audit doc `docs/dqn-wire-up-audit.md` updated per Invariant 7.
After this commit, the EGF pearl is architecturally complete; the
orchestration of when/how the alpha_grad gates fire happens in B.11
(producer chain orchestrator).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ecf4757c0d |
feat(sp14): B.9 — wire forward concat into direction Q-head SGEMM
Closes the latent SGEMM K-mismatch left by B.8 (
|
||
|
|
6715ab4ea1 |
feat(sp14): B.8 — direction Q-head input dim SH2 → SH2+1 + fingerprint
Forward wire (weight-side only — dispatch consumer lands in B.9): direction Q-head's first FC weight tensor `w_b0fc` (param-table index 17) input dim grows by 1 to accept aux_softmax_diff. The (rows, cols) shape table at the trainer-init Xavier site is updated in lock-step with `compute_param_sizes()`. Output dim (adv_h) and the bias `b_b0fc` are unchanged — bias is per-output, not per-input. `layout_fingerprint_seed()` entry renamed `PARAM_W_B0FC` → `PARAM_W_B0FC_AUX1`, forcing the FNV-1a hash to bump per Invariant 8 (old checkpoints fail-fast at load via `check_layout_fingerprint` instead of silently aliasing onto the new architecture). Per `feedback_no_legacy_aliases`, no `_DEPRECATED` shim — straight in-place rename. New weight column zero-initialised in trainer construction (mirrors the OFI column zeroing for w_b2fc/w_b3fc): the model starts ignoring the new input and learns to use it through gradient descent. Xavier- init on this column would inject day-0 noise the trunk would have to denoise — let the EGF gate decide when the aux signal is trustworthy. Atomic-migration consumers (`feedback_no_partial_refactor`) updated: - `gpu_dqn_trainer.rs` — Xavier (rows, cols) table at index 17 grows to (adv_h, SH2+1); spectral-norm descriptor entry [4] for W_a1 grows in_dim from sh2 to sh2+1; spec_v_a1 power-iteration vector grows from sh2 to sh2+1 floats. - `dqn/smoke_tests/gradient_budget.rs` — both `alloc_dueling` fixtures' slot 8 grow `cfg.adv_h * cfg.shared_h2` → `cfg.adv_h * (cfg.shared_h2 + 1)`. - `docs/dqn-wire-up-audit.md` — new SP14 Layer B B.8 entry per Invariant 7. Note: forward GEMM dispatch still uses `K = shared_h2` until B.9 lands the concat-then-SGEMM consumer (per plan §2358). Until then the new column reads as ignored padding; this is safe because (a) it's zero- initialised, (b) GPU-only smoke tests are skipped on this CPU CI, (c) the fingerprint bump invalidates any pre-SP14 checkpoint that would attempt to load. Test: `layout_fingerprint_bumps_after_sp14_wire` (CPU-only, in `sp14_oracle_tests.rs`) hashes the pre-B.8 seed verbatim with the single difference `PARAM_W_B0FC` (vs post-B.8 `PARAM_W_B0FC_AUX1`) and asserts `LAYOUT_FINGERPRINT_CURRENT` differs — any silent revert of the rename trips this test. Mirrors the `fingerprint_bumped_from_ pre_b1_1a` pattern from `sp13_layer_b_oracle_tests.rs`. Verified: - `SQLX_OFFLINE=true cargo check -p ml` clean, 18 warnings (baseline) - `cargo test -p ml --test sp14_oracle_tests` 2 passed, 6 ignored (GPU) - `cargo test -p ml --test sp13_layer_b_oracle_tests fingerprint_bumped_from_pre_b1_1a` still passes (sister test) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9843de5e3d |
feat(sp14): B.7 — trainer struct fields for EGF kernels + concat scratch
Adds 4 CudaFunction handles + 1 scratch buffer to GpuDqnTrainer: - sp14_q_disagreement_update_kernel - sp14_alpha_grad_compute_kernel - sp14_gradient_hack_detect_kernel - sp14_dir_concat_qaux_kernel - sp14_dir_qaux_concat_scratch: CudaSlice<f32> [B * (SH2 + 1)] All loaded from precompiled cubins in trainer construction, mirroring the SP13 aux_pred_to_isv_tanh / aux_sign_label kernel-loading pattern. Per feedback_no_partial_refactor: handles + scratch are held but not yet wired in. Subsequent tasks (B.9+) launch them. Build: cargo check --workspace clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4527d8c852 |
feat(sp14): B.6 — dir_concat_qaux_kernel (pre-SGEMM forward-wire concat)
Mirrors mag_concat_qdir precedent (experience_kernels.cu:4560). Concats [h_s2 ; aux_softmax_diff] into scratch buffer [B, SH2+1] for the direction Q-head's first FC SGEMM. aux_softmax_diff = softmax[b, 1] - softmax[b, 0] in [-1, +1] computed inline; structurally bounded by softmax components per pearl_bounded_modifier_outputs_require_structural_activation. Pure per-thread map; no reduction; no atomicAdd. Does not read or write ISV slots — purely data-movement. Launch order constraint: aux head forward MUST complete before this concat reads aux_nb_softmax_buf (enforced by orchestrator in B.10/B.11). Test: dir_concat_qaux_correct verifies row-wise contiguous concat + correct softmax diff values for both 'down' (-0.8) and 'up' (+0.8) synthetic aux predictions. B.3+B.4+B.5 regression: 5 GPU tests unchanged (6 total pass). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
82fe6cea66 |
feat(sp14): B.5 — gradient_hack_detect_kernel (anti-mesa-opt circuit breaker)
Detects suspected gradient hacking: when gate1 is open AND aux_dir_acc post-open-minimum drops > LOCKOUT_TRIGGER_DROP (0.05) below the Schmitt open-threshold (target + SCHMITT_BAND = target + 0.03) AND q_disagreement rises > LOCKOUT_TRIGGER_DIS_RISE (0.10) above the analytic random-alignment baseline 0.5, simultaneously. Action: force gate1_open_state = 0 (ISV[391]); set lockout_remaining = 2.0 epochs (LOCKOUT_EPOCHS). During lockout, gate1 stays force-closed regardless of alpha_grad_compute_kernel output. Tracks AUX_DIR_ACC_POST_OPEN_MIN (ISV[394]): running minimum of aux_dir_acc since gate1 last opened; resets to 1.0 sentinel when gate closes naturally or when circuit breaker fires. Slot indices shifted +2 from original plan (SP13 closeout added HOLD_RATE_TARGET=381 + HOLD_RATE_OBSERVED_EMA=382): Q_DIS_SHORT=383, GATE1=391, POST_OPEN_MIN=394, LOCKOUT=395. Matches sp14_isv_slots.rs. Single-thread state-machine kernel (threadIdx.x==0 guard); runs at end of each epoch after alpha_grad_compute_kernel. No atomicAdd per feedback_no_atomicadd.md. 1 oracle test: gradient_hack_circuit_breaker_fires verifies trigger conditions (aux_drop=0.08 > 0.05, q_rise=0.15 > 0.10) cause lockout=2.0 and gate1 force-close=0.0. B.3+B.4 regression: 4 GPU tests unchanged (5 total GPU pass, 1 host pass). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
49cdf90ecc |
feat(sp14): B.4 — alpha_grad_compute_kernel (EGF heart)
Single-thread state-machine kernel that is the heart of the Earned Gradient Flow pearl. Reads driver signals from the global ISV bus, runs Schmitt-trigger Gate 1, computes adaptive k_aux/k_q/β, evaluates two sigmoids, multiplies with a host-supplied warmup gate, applies a β-rate-limiter, and writes 7 outputs back to ISV. Per-step pipeline: 1. Read aux_dir_acc (slot 373), q_disagreement (slot 383), Welford variance EMAs (388, 389, 390), persistent Schmitt state (391), alpha_smoothed_prev (393). 2. Compute adaptive k_aux = K_BASE_AUX/(1 + var_aux/VARIANCE_REF_AUX) and k_q analogously (B.2.5; floor at K_MIN = 1.0). 3. Run Schmitt-trigger Gate 1 state update (open at target+0.03, close at target-0.03; intentional discontinuity at transition is smoothed by the β rate-limiter downstream). 4. Evaluate Gate 1 sigmoid (aux competence, distance from threshold) and Gate 2 sigmoid (Q-aux disagreement vs analytic 0.5 baseline). 5. alpha_grad_raw = gate1 × gate2 × warmup_gate (structurally bounded to [0, 1] per pearl_bounded_modifier_outputs_require_structural_ activation; no runtime clamp). 6. Update Welford variance of alpha_grad_raw → adaptive β (B.2.8; floor BETA_BASE = 0.5, ceiling BETA_MAX = 0.95). 7. alpha_grad_smoothed = β × prev + (1-β) × raw (rate-limited). 8. Write back 7 outputs: k_aux (385), k_q (386), β (387), var_alpha (390), gate1_state (391), alpha_raw (392), alpha_smoothed (393). Sigmoid arguments clipped to [-30, 30] before __expf for fp32 overflow guard (precision-neutral; sigmoid saturates bit-equal at those bounds). Per pearl_bounded_modifier_outputs_require_structural_activation: sigmoid composition produces structurally-bounded [0, 1] output. KNOWN LIMITATION: as of B.4 landing, NO upstream kernel writes ISV[388] (AUX_DIR_ACC_VARIANCE_EMA). The grep at status-report time finds only the sp14_isv_slots.rs declaration. Effect: var_aux stays at sentinel 0.0 forever, so k_aux is degenerate-but-non-fatal at K_BASE_AUX (constant). Gate 1 still works, the sigmoid just doesn't soften under noisy aux_dir_acc. To be resolved in B.11 producer- chain orchestrator OR a separate fix-up task that adds a Welford- variance update next to the existing AUX_DIR_ACC_SHORT_EMA producer. var_q (389) IS written by q_disagreement_update_kernel (B.3), so adaptive k_q is fully functional from B.4 onward. Slot indices hardcoded inside the kernel via const int locals — must match crates/ml/src/cuda_pipeline/sp14_isv_slots.rs (and 372/373 from sp13_isv_slots.rs). The plan originally documented 381/383/ 384/385/386/387/388/389/390/391 for SP14 slots; the actual values are +2 because SP13 closeout added HOLD_RATE_TARGET=381 + HOLD_RATE_OBSERVED_EMA=382 after the plan was written. Tests (RTX 3050 Ti pass; B.3's 2 tests still pass — no regression): - alpha_grad_schmitt_hysteresis: 4-step trajectory verifies the closed→open→open→closed transition. Closed at aux=0.55 (below open=0.58); opens at aux=0.60; stays open at aux=0.54 (in hysteresis band [close=0.52, open=0.58]); finally closes at aux=0.50 (below close=0.52). - alpha_grad_adaptive_beta: 20-oscillation regime verifies β grows above β_base=0.5 and remains bounded by β_max=0.95. docs/dqn-wire-up-audit.md updated per Invariant 7 with full B.4 behaviour contract, per-step pipeline, single-thread launch convention, sigmoid clip rationale, Schmitt discontinuity note, and the var_aux Known Limitation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d3a35cc6e9 |
feat(sp14): B.3 — q_disagreement_update_kernel with K=4↔K=2 mapping
First of four producer kernels in the Earned Gradient Flow pearl chain.
Per-step computes the K=4↔K=2 mapped argmax mismatch between the Q-head's
4-way direction action (DIR_SHORT/DIR_HOLD/DIR_LONG/DIR_FLAT) and the
aux head's 2-way next-bar prediction (down/up), updates fast + slow
EMAs of the disagreement rate, and updates a Welford-style variance
EMA on the same signal in a single launch.
Mapping (per state_layout.cuh):
DIR_SHORT (=0) → aux down (=0) contributes
DIR_HOLD (=1) → masked no contribution
DIR_LONG (=2) → aux up (=1) contributes
DIR_FLAT (=3) → masked no contribution
Hold and Flat are masked because they represent "no NEW directional
commitment" (Hold = keep prior position; Flat = close all positions).
Penalising the aux head for not matching them would conflate
position-management actions with directional predictions.
Block tree-reduce on numerator + count separately, then divide and
update EMAs in a single thread (tid == 0). No atomicAdd per
feedback_no_atomicadd.md. Pure GPU compute per
feedback_no_cpu_compute_strict.md.
Pearl-A first-observation: when ISV[Q_DISAGREEMENT_SHORT_EMA=383] AND
ISV[Q_DISAGREEMENT_LONG_EMA=384] both equal sentinel 0.5f AND batch
has at least one valid contribution (total_cnt > 0), both EMAs are
replaced directly with the first observation per
pearl_first_observation_bootstrap.md. The 0.5f exact-match is safe
because 0.5 is exactly representable in IEEE 754 single precision
(mantissa = 1.0, exponent = -1). The Welford variance EMA at slot
389 drives the adaptive k_q sigmoid steepness consumed by the
alpha_grad_compute_kernel in B.4.
Slot indices are hardcoded inside the kernel via #define — must match
crates/ml/src/cuda_pipeline/sp14_isv_slots.rs (currently 383, 384, 389).
The kernel header documents the coupling explicitly.
Launch contract:
grid_dim = (1, 1, 1)
block_dim = (256, 1, 1)
shared_mem_bytes = 2 * 256 * sizeof(float) = 2048
A shared_mem_bytes = 0 launch reads garbage and corrupts the EMA — the
kernel header documents the launcher requirement; oracle tests pass
2048 explicitly.
Files:
- crates/ml/src/cuda_pipeline/q_disagreement_update_kernel.cu (NEW)
- crates/ml/build.rs — register cubin in kernels_with_common
- crates/ml/tests/sp14_oracle_tests.rs — append #[cfg(feature="cuda")]
mod gpu with cubin handle + 2 GPU oracle tests
- docs/dqn-wire-up-audit.md — SP14 B.3 section (Invariant 7)
Tests (both pass on RTX 3050 Ti):
- q_disagreement_k4_k2_mapping: 8-row batch with 2 agreements,
2 disagreements, 4 masked Hold/Flat → first-obs Pearl-A replaces
both EMAs with batch_mean = 0.5 (asserted within 1e-4)
- q_disagreement_all_hold_no_contribution: all-Hold edge case;
total_cnt = 0 so batch_mean = 0/1 = 0; sentinel-bootstrap guard
(total_cnt > 0) keeps EMA from collapsing to 0; current kernel
blends to 0.35, test bound [0.0, 0.5] tolerant of either current
blend or future skip-update refinement, asserts is_finite()
Wire-up status: producer kernel exists and is exercised only by the
oracle tests. The Rust launcher and graph-capture integration land in
B.7+ alongside the consumer (alpha_grad_compute in B.4 reads slots
383/384/389). This is one producer kernel of four (B.3 q_disagreement,
B.4 alpha_grad_compute, B.5 gradient_hack_detect, B.6 dir_concat_qaux);
known-orphan for the duration of the producer chain per
feedback_wire_everything_up.md (the same-commit wire-up rule is
relaxed for atomic chained-producer-consumer landings, with each
orphan documented in the audit doc; this orphan is acknowledged in
docs/dqn-wire-up-audit.md SP14 B.3 section).
Build + test verification:
- SQLX_OFFLINE=true CUDA_COMPUTE_CAP=86 cargo check -p ml --features cuda
→ clean (only the 18 pre-existing warnings)
- SQLX_OFFLINE=true cargo test -p ml --test sp14_oracle_tests
set_aux_weight → host-only A.2 still passes (no shared dependency)
- SQLX_OFFLINE=true CUDA_COMPUTE_CAP=86 cargo test -p ml
--test sp14_oracle_tests --features cuda q_disagreement
-- --ignored --nocapture → 2 PASS
Slot indices reflect the SP13-closeout +2 shift documented in
sp14_isv_slots.rs (the original plan's 381/382/387 became 383/384/389
because HOLD_RATE_TARGET=381 and HOLD_RATE_OBSERVED_EMA=382 landed
between when the plan was written and B.1 was implemented).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
84de278dfe |
feat(sp14): B.2 — register 11 SP14 ISV slots for fold-boundary reset
Each EGF pearl EMA / state slot resets to its Pearl-A sentinel at fold
boundary, mirroring sp13_aux_dir_acc_short_ema / long_ema entries.
Atomic refactor (feedback_no_partial_refactor): both halves land
together — registry entry + reset_named_state dispatch arm.
Reset slots (11 total, sentinel in parens):
- Q_DISAGREEMENT_SHORT/LONG_EMA (slots 383, 384) → 0.5
- K_AUX_ADAPTIVE (385) → K_BASE_AUX = 20.0
- K_Q_ADAPTIVE (386) → K_BASE_Q = 15.0
- BETA_RATE_LIMITER_ADAPTIVE (387) → BETA_BASE = 0.5
- AUX_DIR_ACC_VARIANCE_EMA, Q_DISAGREEMENT_VARIANCE_EMA,
ALPHA_GRAD_RAW_VARIANCE_EMA (388, 389, 390) → 0.0
(initial k = k_base, β = β_base via ISV-driven controllers)
- GATE1_OPEN_STATE (391) → 0.0 (closed)
- ALPHA_GRAD_SMOOTHED (393) → 0.0
- AUX_DIR_ACC_POST_OPEN_MIN (394) → 1.0 (no min observed)
ALPHA_GRAD_RAW (slot 392, recomputed every step from variance EMAs)
and GRADIENT_HACK_LOCKOUT_REMAINING (slot 395, decays at epoch
boundary) are NOT in the fold-reset registry; both naturally
re-initialise without explicit reset.
Also corrects the isv-slots.md SP14 table: slots 392 and 395 were
incorrectly marked FoldReset in the B.1 entry; corrected to reflect
their actual reset semantics (NOT reset / epoch-boundary decay).
Producer + consumer wiring lands in subsequent tasks (B.3-B.12);
this commit is additive infrastructure only — no behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d63cb7992e |
feat(sp14): B.1 — sp14_isv_slots.rs with 13 new ISV slot constants
Allocates ISV slots [383..396) for the Aux→Q Wire + Earned Gradient Flow pearl (Layer B of SP14). Mirrors sp13_isv_slots.rs pattern. The plan originally documented [381..394), but Phase 0 verification found SP13 closeout added HOLD_RATE_TARGET_INDEX=381 and HOLD_RATE_OBSERVED_EMA_INDEX=382 after the plan was written, so the range shifts by +2. Slots fall into 4 functional groups: - Q-disagreement EMAs (short, long; K=4↔K=2 mapping with Hold/Flat masked) - Adaptive controllers (k_aux, k_q, β; variance-driven) - Welford variance EMAs (3, one per adaptive scalar) - Schmitt state + α_grad outputs + circuit breaker Plus 14 structural constants for numerical-stability anchors: K_BASE_*, K_MIN, VARIANCE_REF_*, BETA_BASE, BETA_MAX, SCHMITT_BAND, WARMUP_STEPS_FALLBACK, LOCKOUT_*, Q_DISAGREEMENT_BASELINE. Per feedback_isv_for_adaptive_bounds: adaptive bounds (k_*, β, post_open_min, lockout) live in ISV; numerical anchors live as structural constants. Per pearl_first_observation_bootstrap: all EMAs reset to sentinels and Pearl-A bootstraps on first observation. Producer + consumer wiring lands in subsequent tasks (B.2-B.12); this commit is additive infrastructure only — no behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f75786fc5a |
fix(sp14): A.1 — C51 inv_a_std floor lift (1e-6 → 1e-3)
c51_grad_kernel.cu line 275: lift floor from 1e-6 to 1e-3 in \`inv_a_std = 1.0f / (a_std + 1e-3f)\`, capping the magnitude-branch gradient amplifier at 1000 instead of ~1e6 in the degenerate case. Why: Smoke A produced 1109 GRAD_CLIP_OUTLIER events with C51 grad reaching 9.5e6 — the SP7 budget controller saturated at the EPS_DIV floor instead of rebalancing proportionally. Phase-0 verification against the actual kernel found the amplifier is NOT the spec's claimed −log(p)/p divide (which does not exist; the kernel uses the CE-stable expf(lp) - proj form at line 81). The actual amplifier is inv_a_std = 1/(a_std + 1e-6) at line 275, gated by \`if (d == 1) grad_val *= inv_a_std\` at line 282. When magnitude advantage logits collapse near-uniform (Smoke A: var_q=9e-10), a_std → ~1e-9, so inv_a_std → ~1e6. Per feedback_isv_for_adaptive_bounds, this is a numerical-stability anchor (Invariant 1: prevent division-by-near-zero amplification), not a behavioural bound. ISV-driven bounds govern behavior; the existing 1e-12f floor on a_std at line 274 is also a structural anchor — same class of fix. Validation gate: Smoke A2-A GRAD_CLIP_OUTLIER count <100 in fold 2 (was 1109 pre-fix). The 3-order-of-magnitude reduction in worst- case amplification should bring C51 grad spikes back under SP7 budget controller authority. Audit doc: Fix 40 added (parallel to Fix 39 for A.2 and Fix 41 for A.3); the stale "A.1 deferred" note was removed in the A.3 commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1420383212 |
fix(sp14): A.3 — stagnation warmup gate at fold boundary
compute_aux_w_p0b's stagnation term was firing inappropriately at fold reset because Pearl-A first-observation bootstrap forces both EMAs equal: short_ema = sentinel 0.5 → first observation X long_ema = sentinel 0.5 → first observation X (same update) improvement = max(0, short - long) = 0 stagnation = (1 - 0/max(deficit, 0.005)) = 1.0 aux_w *= (1 - 0.7) = 0.3 → spurious decay on a non-stagnation Fix: add epochs_in_fold parameter; skip stagnation when < 1. Wait one full epoch for the α=0.3 vs α=0.05 short/long EMA timescale split to produce real improvement signal. Atomic refactor (feedback_no_partial_refactor): all 5 callers migrated — 1 production site (training_loop.rs:4257) plus 4 existing unit tests at trainers/dqn/trainer/tests.rs. Test: aux_w_stagnation_warmup_gate_epoch_0 verifies: - Epoch 0: stagnation = 0, aux_w = base × deficit_amp = 0.625 - Epoch 1: stagnation = 1.0, aux_w = floor 0.15 Combined with A.2 (clamp lift), Fold 2's aux_w should now hit 0.625 in epoch 0 (the controller's intended post-deficit-amp value) instead of being collapsed to 0.164. Audit doc: Fix 41 added; stale "A.1 deferred" note from a prior killed agent was also removed (A.1 lands in the next commit, not deferred per user direction). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
731cae4c80 |
fix(sp14): A.2 — lift set_aux_weight clamp to SP13 P0b range [0.15, 1.5]
The pre-fix clamp at gpu_dqn_trainer.rs:14722 was [0.05, 0.3] — the SP11-era cap. P0b's controller computes aux_w in [0.15, 1.5] (base 0.5 × [0.3, 3.0]) but the setter silently chopped everything above 0.3, masking the deficit-amplification term entirely. Smoke A trace confirmed: Fold 0/1: raw aux_w = 0.66-0.80 → clamped to 0.30 (deficit invisible) Fold 2: raw aux_w = 0.164 (stagnation; below clamp) → 45% deficit Post-fix: deficit-amp term `(1 + 5 × deficit)` actually expresses through to the trainer. Fold 2 stagnation will get the designed floor 0.15 instead of being capped at 0.3 — but the upper range 1.5 also opens, so deficit-amp can pull aux_w up when accuracy is below target. Constants imported from sp13_isv_slots.rs (AUX_W_BASE=0.5, AUX_W_HARD_FLOOR_RATIO=0.3, AUX_W_HARD_CEIL_RATIO=3.0). No new slots; existing constants exposed as the clamp bounds. Test: set_aux_weight_clamp_range verifies constants resolve correctly. A.1 (C51 atom-probability floor) deferred — Phase 0 verification found the spec's stated `−log(p)/p · ∂p/∂z` divide does NOT exist in c51_grad_kernel.cu at HEAD 037c24116; actual kernel uses the numerically-stable `expf(lp) - proj`. The 1109 GRAD_CLIP_OUTLIER events in Smoke A are real but their mechanism is different. Will be re-spec'd as a separate task post-SP14. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
037c24116d |
plan(sp14): implementation plan for spec v3
Two-sub-project plan: - Layer A (4 tasks, ~50 LOC): C51 atom-floor, set_aux_weight clamp lift, stagnation warmup gate, smoke validation - Layer B (15 tasks, ~1180 LOC): 13 ISV slots, 4 kernels (q_disagreement, alpha_grad, gradient_hack_detect, dir_concat_qaux), forward wire, backward gradient gating, orchestrator wire-up, HEALTH_DIAG, tests, smoke Each task has bite-sized TDD steps (write failing test, run fail, implement, run pass, commit) per the writing-plans skill conventions. Phase 0 verification tasks (A.0, B.0) anchor against current code at HEAD 40e737a18+ before edits. 19 total tasks across 2 layers. Each layer commits independently as an atomic feature; intermediate per-task commits during Layer B keep the wire safety-protected (forward concat lands before backward gating in B.9 → B.10 sequence). 8 explicit kill criteria for Smoke A2-B per spec B.7. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
40e737a181 |
docs(sp14): spec v3 — K=4 direction + ISV-adaptive β rate limiter
Two further user-flagged corrections from second-critical review:
1. Direction Q-head emits K=4 actions, NOT K=3.
Authoritative source: state_layout.cuh:123-126
#define DIR_SHORT 0 // open/maintain short
#define DIR_HOLD 1 // keep current position (no-op)
#define DIR_LONG 2 // open/maintain long
#define DIR_FLAT 3 // close all to zero
The "default: 3 — Short/Flat/Long" comment at gpu_dqn_trainer.rs:2438
is stale pre-SP13. Production callsites all set branch_0_size: 4.
SP13 added DIR_HOLD as a separate fourth direction action (Hold-pricing).
The config default comment was never updated when DIR_HOLD landed.
This is exactly the feedback_trust_code_not_docs failure mode — a
single stale comment would have silently corrupted the q_disagreement
signal (Hold/Flat being indices 1/3 instead of just Flat=1).
Updates:
- B.1 action-space context: 4 actions with Hold AND Flat both
non-committal (Hold = keep position, Flat = exit to zero)
- B.2.3 q_disagreement mapping: K=4↔K=2 with both Hold and Flat
masked from disagreement signal (no new directional commitment
to evaluate); only Short and Long picks contribute to disagreement
- Edge case handling for all-Hold/all-Flat batches
2. Adaptive β rate limiter (was structural β=0.9).
Per feedback_isv_for_adaptive_bounds, β should be signal-driven
not hardcoded. v3 derives β from variance of α_grad_raw, mirroring
the k_aux/k_q variance-driven steepness pattern in B.2.5.
Formula:
β = clip(β_base + variance_alpha_raw / variance_ref_alpha,
[β_base, β_max])
β_base = 0.5 (light smoothing baseline; ~2-step half-life)
β_max = 0.95 (heavy smoothing; ~20-step half-life)
Stable α_grad_raw → β = β_base (preserves directional intent)
Volatile α_grad_raw → β → β_max (dampens jitter)
Adds 2 ISV slots:
ALPHA_GRAD_RAW_VARIANCE_EMA_INDEX (Welford variance)
BETA_RATE_LIMITER_ADAPTIVE_INDEX (current β value)
ISV slot count: 11 → 13 (net +2 for variance + adaptive β).
LOC estimate: ~1150 → ~1180 (negligible delta; 3 Welford
variances now in alpha_grad_compute_kernel instead of 2).
HEALTH_DIAG pearl_egf_diag emit updated to expose all three
adaptive scalars (α, β, k_aux/k_q) plus all three driving
variances (var_alpha, var_aux, var_q) for full observability.
Verified against current code at HEAD
|
||
|
|
d243a6f080 |
docs(sp14): spec v2 — critical review corrections
8 corrections from code-anchored critical review at HEAD
|
||
|
|
eaf4adcb98 |
docs(sp14): spec — aux→Q wire + Earned Gradient Flow pearl
Designs the SP14 chain on top of SP13 Layer B (HEAD
|
||
|
|
6657e56265 |
feat(sp13): B1.1b — producer kernel + replay direct path + experience collector
Final piece of the SP13 Layer B chain. B1.1a flipped the aux head from K=1
MSE regression to K=2 softmax CE classification but aux_nb_label_buf was
zero-init — model was training on "all bars are class 0 (down)". B1.1b
lands the producer kernel that fills i32 -1/0/1 labels from the 30-bar
price trajectory, the replay direct-path 8th gather that carries those
labels into the trainer, and the experience collector hoist that ensures
bar_indices_pinned is always populated (producer + hindsight relabel both
consume it). Aux head finally trains on real classification signal.
Recovery commit: this completes a B1.1b agent dispatch that crashed
mid-edit. The implementer had landed ~95% of the cascade (kernel file,
build.rs, replay buffer signature + direct path, fused_training getter,
trainer accessor, both training_loop.rs callers, kernel field + cubin
loader on the experience collector) before being killed. The missing
pieces (experience collector launch + bar_indices_pinned hoist + 6
producer tests + audit doc) were completed manually post-crash and
verified end-to-end.
Three contracts (atomic single commit per feedback_no_partial_refactor):
1. NEW aux_sign_label_kernel.cu producer — pure per-thread O(1) map
reading targets[bar*6+2] (raw_close column) at bar and bar+lookahead,
writing -1 (skip if bar+lookahead >= total_bars), 0 (down/flat under
strict greater-than tie-break), or 1 (up). Replaces B0 alloc_zeros.
2. Replay direct-path 8th gather — set_trainer_buffers gains 8th arg
trainer_aux_sign_labels_ptr; direct branch in sample_proportional
adds gather_i32_scalar into the trainer ptr; fallback gather wrapped
in if !direct_to_trainer (avoids wasted DtoD). Direct-mode
GpuBatchPtrs return points aux_sign_labels_ptr at trainer ptr.
3. Experience collector bar_indices_pinned cpu-fill hoist — moved out
of if hindsight_fraction > 0.0 so producer + hindsight share it.
Files (9 total):
- crates/ml/src/cuda_pipeline/aux_sign_label_kernel.cu (NEW)
- crates/ml/build.rs (cubin registration)
- crates/ml/src/cuda_pipeline/gpu_experience_collector.rs (kernel
field + cubin loader + struct init + hoist + producer launch)
- crates/ml-dqn/src/gpu_replay_buffer.rs (8th arg + direct gather +
fallback skip + GpuBatchPtrs return)
- crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs (aux_nb_label_buf_ptr
accessor mirrors 6 existing trainer-buf accessors)
- crates/ml/src/trainers/dqn/fused_training.rs
(trainer_aux_sign_labels_buf_ptr getter)
- crates/ml/src/trainers/dqn/trainer/training_loop.rs (both
set_trainer_buffers callers updated)
- crates/ml/tests/sp13_layer_b_oracle_tests.rs (6 NEW producer tests)
- docs/dqn-wire-up-audit.md (B1.1b section)
Hard rules upheld:
- feedback_no_partial_refactor: every consumer of the 3 contracts
migrates atomically
- feedback_no_atomicadd: producer is pure map; no reductions
- feedback_cpu_is_read_only: producer GPU-only; only host work is
pre-existing bar_indices_pinned cpu-fill (hoisted unchanged)
- feedback_no_stubs: kernel output flows through real chain — ring
buffer → direct gather → aux_nb_label_buf → CE consumer
- feedback_no_legacy_aliases: 8-arg setter gets
#[allow(clippy::too_many_arguments)] not an alias shim
- feedback_no_htod_htoh_only_mapped_pinned: targets_buf and
bar_indices_pinned both pre-existing mapped-pinned
Build + test:
- cargo check --workspace clean (only pre-existing warnings)
- cargo check --workspace --tests clean
- 17 tests in sp13_layer_b_oracle_tests.rs:
- 2 CPU-only (fingerprint bump + HEALTH_DIAG snap stable) pass
- 15 GPU on RTX 3050 Ti pass (9 B1.1a + 6 new B1.1b producer):
aux_sign_label_monotone_up_all_ones
aux_sign_label_monotone_down_all_zeros
aux_sign_label_flat_all_zeros_strict_gt
aux_sign_label_last_30_bars_skip
aux_sign_label_boundary_first_valid_last_skip
aux_sign_label_multi_episode_per_episode_skip
Producer tests cover every edge case in the kernel:
- Monotone trajectories (label=1 / label=0 across all valid bars)
- Flat tie-break (strict greater-than means flat → 0)
- Skip sentinel for last lookahead bars
- First/last bar boundary (bar=0 valid, bar=L-1 skip)
- Multi-episode global skip semantics
Next: Smoke A — L40S 5-epoch validation of full SP13 stack
(P0a + P0b + B0 + B0.1 + B1.0 + B1.1a + B1.1b). Expected: aux head
trains on real K=2 softmax CE labels; aux_dir_acc_short_ema rises above
0.5 within first epoch (vs B1.1a degraded baseline at 0.5);
HEALTH_DIAG aux_b1_diag emits per-epoch with n_down/n_up/n_skip/mask_frac.
If aux_dir_acc_short_ema > 0.55 by epoch 5, B1.1b is validated and the
chain merges to main.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
7d10ea8b3e |
feat(sp13): B1.1a — K=1→2 + softmax CE kernel rewrites + struct flips
Flips the aux next-bar head from K=1 MSE regression to K=2 softmax
cross-entropy classification. Kernel ABIs, struct fields, partial-buf
shapes, and per-step ISV producers all migrate atomically; the producer
that fills `aux_sign_labels` with real -1/0/1 from the price trajectory
lands separately in B1.1b.
Why split B1.1a from B1.1b: the original B1 brief was decomposed (B1.0
+ B1.1) after six full-B1 dispatches confirmed agent-session capacity
is the bottleneck, not cascade understanding. B1.1 itself is now further
split into B1.1a (kernel/struct/test cascade — this commit) and B1.1b
(producer kernel + replay direct path + experience collector hoist +
remaining tests + Smoke A). B1.1a is contract-consistent atomic
kernel-side; B1.1b lands the producer + smoke.
Why labels stay zero-init: B1.1a is producer-less by design. The
`aux_nb_label_buf: CudaSlice<i32>` is `alloc_zeros<i32>` so every
sample receives label 0 ("down"). The model converges on "predict
class 0 (down) everywhere" until B1.1b lands the producer kernel that
fills real -1/0/1 from the 30-bar price trajectory. This degraded
training behavior is intentional and known — the cascade is internally
consistent (every consumer of K-flip / softmax tile / CE / i32 label
migrates atomically per `feedback_no_partial_refactor`); the labels are
placeholder. Local unit tests (CE correctness, dir_acc correctness,
isv_tanh correctness, fingerprint bump) validate B1.1a in isolation;
no L40S smoke runs between B1.1a and B1.1b.
Four contracts (atomic in this commit):
1. K_NB flip 1 → 2: AUX_NEXT_BAR_K constant, compute_param_sizes
([121]/[122] grow), fingerprint seed rename
(PARAM_AUX_NB_W2/B2 → PARAM_AUX_NB_W2_K2/B2_K2 — bumps the hash),
forward + backward kernels, partial-buf allocs (nb_w2 [B,H]→[B,K,H],
nb_b2 [B]→[B,K]), saxpy spec table, max_aux_tensor_len,
aux_nb_pred_buf renamed to aux_nb_logits_buf per
feedback_no_legacy_aliases.
2. Softmax tile: aux_next_bar_forward writes [B, K] softmax via
in-kernel stable softmax (max-shift form, K=2 single-thread fanout
mirrors regime kernel); 4 consumers read the tile (loss, backward,
dir-acc, isv-tanh). NEW field aux_nb_softmax_buf [B, K].
3. MSE → CE: aux_next_bar_loss_reduce reads softmax + i32 labels,
masks -1, divides by B_valid (mean-over-valid-rows), writes loss +
B_valid scalar. aux_next_bar_backward reads B_valid so loss + grad
share the same divisor — derivatives of the same scalar function.
NEW field aux_nb_valid_count_buf [1]. All-skip batch produces
loss = 0 (no NaN; fmaxf(valid, 1) divisor) and zero gradients.
Numerical floor 1e-30 prevents -log(0) = +inf in extreme-logit path.
4. i32 label dtype: aux_nb_label_buf flipped f32 → i32; -1 mask
sentinel handled across loss + backward + dir-acc + isv-tanh.
The strided_gather of next_states[:, 0] retired entirely.
Cascade (atomic per feedback_no_partial_refactor):
- aux_heads_kernel.cu: aux_next_bar_forward gains K + softmax tile
output (via in-kernel stable softmax); aux_next_bar_loss_reduce
ABI flipped (softmax + i32 labels, mean-over-valid CE,
valid_count_out); aux_next_bar_backward ABI flipped (softmax +
i32 labels + valid_count, K-fanout d_logits, masked rows zero
across the K-vector)
- aux_dir_acc_reduce_kernel.cu: read softmax + i32 labels, argmax
over K, output grew 3 → 6 floats (added n_down/n_up/n_skip);
shmem 4 → 6 int arrays
- aux_pred_to_isv_tanh_kernel.cu: read softmax tile, compute
mean(softmax[:, 1] - softmax[:, 0]); tanh transcend retired
(structural [-1, +1] bound via softmax components per
pearl_bounded_modifier_outputs_require_structural_activation)
- gpu_aux_heads.rs: AUX_NEXT_BAR_K 1 → 2; forward_next_bar gains K +
logits_out + softmax_out args; next_bar_loss_reduce gains K +
valid_count_out; backward_next_bar gains softmax_in + labels_i32_in
+ valid_count_in + K
- gpu_dqn_trainer.rs: compute_param_sizes ([121]/[122]); fingerprint
seed rename (W2/B2 → W2_K2/B2_K2); struct fields (logits, softmax,
i32 label, valid_count); aux_dir_acc_buf 3 → 6 floats; partial-buf
allocs grow; max_aux_tensor_len extended; saxpy spec table updated;
orchestrator launchers (launch_aux_dir_acc_reduce,
launch_aux_pred_to_isv_tanh, launch_sp13_aux_dir_metrics) gain K
arg; strided_gather block deleted entirely
- training_loop.rs: aux_b1_diag HEALTH_DIAG line reads
aux_dir_acc_buf [3..6] for n_down / n_up / n_skip + mask_frac;
doc comment update for the per-step aux dir-metrics block
- tests/sp13_phase0_oracle_tests.rs: 6 dir_acc + 3 isv_tanh tests
rewritten in-place to new ABI (no shadow tests, per
feedback_no_legacy_aliases)
- tests/sp13_layer_b_oracle_tests.rs (NEW): 11 B1.1a tests — 5 CE
loss/backward (single-row, batch-mixed, all-skip-NaN, backward
single-row, backward batch-mixed); 2 dir_acc (handcrafted argmax,
all-skip NaN-safe); 2 isv_tanh (bounded fuzz, mean handcrafted);
2 layout regression (fingerprint bump, HEALTH_DIAG snap stable)
- docs/dqn-wire-up-audit.md: B1.1a section
Hard rules upheld:
- feedback_no_partial_refactor: every consumer of K-flip / softmax tile
/ CE / i32 labels migrates atomically — kernels + orchestrators +
struct + diag + existing oracle tests
- feedback_no_atomicadd: block tree-reduce only; CE loss reduce uses
2 parallel partial-reduction strips; CE backward uses existing
per-sample partial → final aux_param_grad_reduce pattern
- feedback_cpu_is_read_only: aux_nb_label_buf is GPU-resident
CudaSlice<i32>; HEALTH_DIAG aux_b1_diag reads via mapped-pinned
aux_dir_acc_buf (no DtoH)
- feedback_no_stubs: every new buffer + kernel arg wired through to
a real consumer; CE forward / loss / backward chain executes
end-to-end against placeholder labels (degraded behavior, not stub)
- feedback_no_legacy_aliases: aux_nb_pred_buf renamed in-place
(no shim); PARAM_AUX_NB_W2/B2 renamed to _W2_K2/_B2_K2 in seed
(no _DEPRECATED alias); 9 existing oracle tests rewritten in-place
- feedback_no_cpu_test_fallbacks: 9 GPU tests gated #[ignore]; 2
layout-regression tests are CPU-only (pub const + size_of)
- feedback_no_htod_htoh_only_mapped_pinned: every CPU↔GPU buffer
in tests + production is MappedF32Buffer / MappedI32Buffer
- feedback_isv_for_adaptive_bounds: no hardcoded thresholds added
(1e-30 numerical floor on log is stability epsilon, not tunable)
- feedback_trust_code_not_docs: 8/8 Phase 0 anchors verified at
HEAD
|
||
|
|
75e94858c5 |
feat(sp13): B1.0 — ISV[117] retirement + scale-free MSE bridge
Retires ISV[117]=AUX_LABEL_SCALE_EMA_INDEX together with its producer
kernel (aux_label_scale_ema_update), launch site, backward pass-through,
StateResetRegistry entry, HEALTH_DIAG snapshot field, and unit test.
Why: labels at the data layer are z-normalised, so the
mean(|label|) EMA tracked by ISV[117] sits at ~1.0 empirically.
Dividing by max(scale, 1e-6) before the residual `(pred - label)`
reduces to `(pred - label)` within rounding. The divisor was a
defensive scaffold from when the data layer carried mixed-scale
labels (1e-3 log returns vs 5000 raw prices); z-normalisation made
that scaffold redundant.
This is a numerical bridge, NOT the final fix. B1.1 lands on top:
- Aux head 1→2 dim (next-bar regression → 2-class direction logit)
- MSE → CE loss flip
- aux_dir_acc reads softmax over the 2 logits
- aux_pred_to_isv_tanh rewrite as logit-diff
- Producer kernel that fills aux_sign_labels with real -1/0/1 from
the 30-bar price trajectory (B0 plumbing currently zero-init)
- dqn_param_layout fingerprint bump (head dim changes)
- aux_b1_diag HEALTH_DIAG metric
- 17+ GPU oracle unit tests
Cascade (atomic per feedback_no_partial_refactor):
- aux_heads_kernel.cu: aux_next_bar_loss_reduce + aux_next_bar_backward
drop `isv` + `isv_label_scale_index` params; residual is (pred - label)
- aux_heads_loss_ema_kernel.cu: aux_label_scale_ema_update kernel deleted
- gpu_aux_heads.rs: kernel field/loader + launch_label_scale_ema +
isv_* args from next_bar_loss_reduce / backward_next_bar all dropped
- gpu_dqn_trainer.rs: Step 2b producer launch + ISV slot uses dropped;
AUX_LABEL_SCALE_EMA=117 line retained in fingerprint seed
(no fingerprint bump in B1.0; B1.1 will bump on head-dim flip)
- gpu_health_diag.rs + health_diag.rs: aux_label_scale snapshot field
dropped; aux block 4→3 floats, downstream offsets shift down by 1,
WORD_TOTAL 150→149, snapshot_size_is_stable test 150*4 → 149*4
- health_diag_kernel.cu: WORD_AUX_LABEL_SCALE removed, downstream
offsets shift, static_assert(WORD_TOTAL == 149)
- state_reset_registry.rs: isv_aux_label_scale_ema FoldReset dropped
- training_loop.rs: reset_named_state arm + HEALTH_DIAG read +
aux line label_scale field all dropped
- sp4_producer_unit_tests.rs: load_aux_label_scale_ema_kernel helper +
sp4_aux_label_scale_ema_writes_step_obs_via_pearl_a_then_converges_pearl_d
test dropped
Hard rules upheld:
- feedback_no_partial_refactor: every consumer of ISV[117] migrates
atomically — kernel + Rust orchestrator + producer launch + backward
+ HEALTH_DIAG + reset registry + unit test all in this commit
- feedback_no_stubs: not a stub — divisor is removed at every site,
not aliased through a 1.0_const shim
- feedback_no_legacy_aliases: no legacy AUX_LABEL_SCALE_EMA_INDEX → 1.0
alias function
- feedback_no_hiding: doc comments forward to B1.1 explicitly; no
underscore suppression or #[allow(dead_code)]
Build: cargo check --workspace --tests clean.
Tests: snapshot_size_is_stable passes at 149*4=596 bytes.
cargo test -p ml --lib + cargo test -p ml-dqn --lib compile.
Net delta: 10 files, −288 LOC.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
6a869ad366 |
fix(sp13): B0 cascade gap — 5 unaudited insert_batch call sites
The B0 audit (commit
|
||
|
|
62ab8ed850 |
feat(sp13): B0 — replay buffer i32 ring + GpuBatchPtrs plumbing
Pure plumbing: threads a new i32 column (aux_sign_labels) through every
layer of the replay path so B1 can wire the aux head's CrossEntropy
classification target without touching any aggregator or batch-shape
contract on its own. No consumer reads the column yet — all labels are
zero-initialized; smoke between B0 and B1 should be bit-identical to
parent
|
||
|
|
0ad5b6fa42 |
chore(sp13): script bug fix + Layer B implementation notes
While P0b smoke (train-sw4ws on
|
||
|
|
bdc5cb8bb2 |
feat(sp13): P0b — aux_w deficit+stagnation controller + Hold cost lift
P0a smoke (train-67gqb on
|
||
|
|
f934ea1719 |
feat(sp13): P0a atomic — Hold-pricing + dir_acc instrumentation (additive)
Tests user's hypothesis (Hold being FREE is the bug, not Hold itself) by
pricing Hold via ISV-driven adaptive controller targeting 20% Hold-rate.
11 new SP13 ISV slots [372..383). 5 new GPU kernels:
- aux_dir_acc_reduce_kernel.cu (correct/pos_pred/pos_label/valid → 3 scalars)
- hold_rate_observer_kernel.cu (packed batch_actions decode, count(Hold)/B)
- apply_fixed_alpha_ema_kernel.cu (preserves short/long timescale split that
Wiener-optimal apply_pearls_ad_kernel would collapse)
- aux_pred_to_isv_tanh_kernel.cu (mean(tanh(aux_pred)) → ISV[375])
- 3 reward-composition sites in experience_kernels.cu subtract isv[HOLD_COST]
on Hold actions (segment_complete pre-asymmetric-cap, positioned-non-event
per-bar, flat per-bar)
Host-side controller in training_loop.rs:
excess = max(0, observed - target)
hold_cost = HOLD_COST_BASE × (1 + 5 × excess), clamped [0.5×, 5.0×base]
Per-step observer + EMA chain in gpu_experience_collector.rs after
experience_action_select. Per-epoch HEALTH_DIAG emit:
aux_dir_acc target/short/long/pred_tanh
hold_pricing observed_rate/target/cost
4-way action space stays (ExposureLevel::Hold preserved). Replay buffer /
fxcache compatibility preserved. SP11 (11/11) + SP12 (14/14) tests no
regression. SP13 P0a oracle tests: 14/14 on RTX 3050 Ti.
Spec/plan: docs/superpowers/{specs,plans}/2026-05-04-sp13-redefine-success-for-predictive-skill.md (v3)
Audit: docs/dqn-wire-up-audit.md (SP13 P0a section appended)
v2 → v3 reframe: P0a.T3 v2 implementer's audit found DirectionAction enum
doesn't exist (codebase uses 8-variant fused ExposureLevel cascading through
77 files). v3 reframes from "eliminate Hold" (250 LOC + 32-test cascade) to
"price Hold" (additive, no contract change, no cross-crate cascade).
Tension with pearl_event_driven_reward_density_alignment acknowledged in spec
— per-bar Hold cost is exposure-NEGATIVE (pulls policy AWAY from Hold-default,
inverse of the pearl's failure mode), models real economic carry, ISV-bounded
by controller. Faithful reward modeling, not artificial shaping.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ba83fcd1f5 |
docs(sp13): v3 spec + plan — Hold-pricing replaces Hold-elimination
P0a.T3 v2 implementer's audit revealed `DirectionAction` enum doesn't exist; the codebase uses an 8-variant fused `ExposureLevel` (ShortSmall/Half/Full, Hold, LongSmall/Half/Full, Flat) with cross-crate consumers across 77 files and 32+ test files pinning the 8-variant invariant. Atomic Hold elimination would cascade massively. User insight (2026-05-04): Hold being FREE is the bug, not Hold itself. MFT trading legitimately needs multi-bar holds; we want the model to use them deliberately, not as a CQL-bias lazy default. Holding isn't free in the real world — broker fees, margin interest, opportunity cost. v3 reframes as Hold-pricing: - 4-way action space stays; ExposureLevel::Hold stays; no cross-crate cascade - 3 new ISV slots (380-382): HOLD_COST_INDEX, HOLD_RATE_TARGET_INDEX, HOLD_RATE_OBSERVED_EMA_INDEX - Hold-rate observer: small GPU kernel + Pearls A+D smoothing - Hold-cost controller: 5-line deficit-driven formula (excess > target → cost rises 1×→5× base; observed ≤ target → relax) - Per-bar reward subtraction at action == DIR_HOLD site - 2 GPU oracle tests for the controller P0a.T3 cuts from ~250 LOC + 32-test cascade → ~120 LOC additive. T1+T2 already-staged work unchanged. T4/T5/Layer B/C/D structure preserved. Tension with pearl_event_driven_reward_density_alignment acknowledged in spec — per-bar Hold cost is exposure-NEGATIVE (away from Hold), models real economic carry, ISV-bounded by controller. Inverse of the pearl's failure mode. Faithful reward modeling, not artificial shaping. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
0ca45ef61d |
docs(sp13): v2 spec + plan after critical review
Spec v2 supersedes v1 with five major fixes from the critical review: - Drop alpha-vs-benchmark reward (mathematically tautological — Long trades had alpha = -costs always against always-long benchmark) - Split Phase 0 into 0a (Hold-only, clean test of user hypothesis) + 0b (aux amplification probe, only if 0a partial), avoiding the v1 confounded experiment - Bound direction-skill bonus relative to |alpha| (cap_ratio × |alpha|) to prevent reward gaming on small-alpha correct-direction losers; spec adds 8-quadrant worked-example matrix verifying the no-negative-EV invariant - Replace 5-epoch absolute-threshold gate with 10-epoch trajectory criterion to avoid false-negatives from aux head underconvergence - Aux_w controller adds dual-EMA stagnation detector (decays toward base when no improvement) — prevents permanent destabilization of Q-head in data-limited case Plan v2 mirrors spec changes: - Phase 0a (Hold elimination + dir_acc instrumentation, ~300 LOC, 1 atomic commit) - Phase 0b (aux_w controller replacement, conditional, ~80 LOC) - Layer B (aux head regression -> binary classification, ~120 LOC) - Layer C (skill bonus + luck discount with calibrated bounds, ~150 LOC) - Layer D (30-epoch validation + 3 new pearls) ISV slot allocation [372..380): drops slot 371 (BENCHMARK_PNL_CUMULATIVE), adds 374 (AUX_DIR_ACC_LONG_EMA — stagnation), 379 (SKILL_BONUS_CAP_RATIO). Plan integrates Explore-agent touch-list (50-70 sites) with corrected enum ordering: Short=0, Long=1, Flat=2 (preserves codebase Short-first convention, avoids 30+ stale-comment churn). Adds direction-bias signal handling at experience_kernels.cu:5159 (Hold's 0.5 softening gate vanishes -> [1, 1, 0]). Three new pearls planned for Layer D close-out: - pearl_redefine_success_for_predictive_skill - pearl_skill_bonus_must_be_alpha_bounded (calibration lesson) - pearl_reward_quadrant_audit_required (meta-pearl) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a1681abc46 |
Revert "exp(sp13): aux_w=1.0 override + directional accuracy metric for data investigation"
This reverts commit
|
||
|
|
d2a27a0042 |
exp(sp13): aux_w=1.0 override + directional accuracy metric for data investigation
One-shot diagnostic to answer the SP13 root question: does the data have
predictable directional signal at the bar level?
Wires the existing `aux_next_bar_loss_reduce` kernel (which already had a
4-strip shmem reduction emitting `[dir_acc, pos_pred_frac, pos_label_frac]`
into a 3-float output) end-to-end:
- `gpu_aux_heads.rs`: launcher takes `dir_acc_out_ptr`, allocates 4×AUX_BLOCK
shmem to back the four parallel reductions.
- `gpu_dqn_trainer.rs`: adds `aux_nb_dir_acc_buf` (3 f32 device buffer),
threads it through the loss-reduce launch, exposes `read_aux_dir_acc()`
accessor for once-per-epoch DtoH readback.
- `training_loop.rs`: pins `aux_w = 1.0` (instead of the ISV-driven 0.05–0.3
clamp) so the supervised aux head dominates the loss; emits a new
`HEALTH_DIAG[ep]: aux_dir_acc accuracy=… pos_pred_frac=… pos_label_frac=…`
line per epoch.
Verdict thresholds:
* dir_acc > 55% by ep 5 ⇒ data has signal, DQN failing to use it
* dir_acc ≈ 50% throughout ⇒ data lacks signal at this timescale
* dir_acc 60–70% ⇒ strong signal we're not using
EXPERIMENT BRANCH — revert this commit after the investigation reads back the
5-epoch table from smoke logs. All five touch points are tagged
"SP13 data-investigation" / "EXPERIMENT" for clean revert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
1c645264e6 |
test(sp12): GPU oracle tests for the 3 reward math changes
Adds the GPU oracle test scaffold deferred from commit
|
||
|
|
17cfbb2503 |
fix(sp12): per-trade event-driven reward composition
Three architectural changes in one atomic commit per spec |
||
|
|
ecab584c32 |
spec(sp12): per-trade event-driven reward composition (v3)
Three architectural changes in one unified design to push the model from
HFT noise extraction (62% trade rate, sharpe-gaming) toward MFT alpha-hunting:
1. Asymmetric bounded cap (-10/+5) — restores loss aversion erased by
SP11 symmetric cap. 2:1 ratio matches Kahneman/Tversky prospect theory.
Anchor: pearl_audit_unboundedness_for_implicit_asymmetry.
2. Min-hold soft penalty with temperature curriculum — patience requirement
at exit. Soft factor = deficit/(deficit+T), T anneals 50→5 over 50 epochs.
Forces commitment without paralysis in early training.
3. Zero per-bar shaping (gate micro/opp_cost on events) — eliminates
continuous-reward gradient that pulls toward continuous exposure.
Anchor: pearl_event_driven_reward_density_alignment.
Combined: reward fires only on trade events with prospect-theory loss
aversion + commitment requirement. Pure per-trade event-driven Q-learning
properly aligned with per-trade P&L objective.
~50 LOC across 3-4 files. No new ISV slots in Phase 1 (constants only).
Cost ~€1.30 (€0.30 smoke + €1.00 30-epoch validation).
Empirical motivation: train-multi-seed-pmbwn 50-epoch on commit
|
||
|
|
6a259942e9 |
docs(sp11): close-out — pearl_symmetric_clamp_audit + audit doc
Documents the SP11 sharpe-degradation root cause investigation: - |
||
|
|
b92dcc3dfc |
chore(sp11): remove reward-chain diagnostic instrumentation
Instrumentation from |
||
|
|
348f6078b8 |
fix(sp11): plan_isv symmetric clamp — 4 sites mirror reward-cap bug
Implementer of
|
||
|
|
35db310893 |
fix(sp11): symmetric reward cap — losses were unbounded
experience_kernels.cu:2788:
float capped_pnl = fminf(base_reward, 10.0f);
^^^^^^^^^^^^^ caps profits, NOT losses
Diagnostic instrumentation in smoke-test-k9drh on commit
|
||
|
|
774d7552a0 |
diag(sp11): instrument reward chain to find the 5000x inflater
Smoke smoke-test-gwfn8 on
|
||
|
|
7a19c51522 |
docs(sp11): B1b smoke-recovery close-out — audit + 3 memory pearls
Layer C close-out for SP11 B1b smoke-recovery work. Two atomic commits land the fix: - |
||
|
|
fd24b53833 |
fix(sp11): B1b launch-order — reward_component_ema before mag-ratio canary
smoke-test-4rbv9 on
|
||
|
|
b3b4d02789 |
fix(sp11): B1b smoke-recovery — z-score normalization for mag-ratio canary
Linear magnitude ratios in reward_component_mag_ratio_compute_kernel amplified popart's intrinsic O(100) magnitude over the other 5 components' O(0.1-2) magnitudes, causing controller to saturate w_pop toward MAX_WEIGHT regardless of actual signal quality. Replaced with z-score: z[c] = mag[c] / max(sqrt(var[c]), EPS_DIV). 6 new ISV slots [361..367) for per-component variance EMAs computed via Welford's online algorithm in extended popart_component_ema_kernel and reward_component_ema_kernel. Atomic per feedback_no_partial_refactor: slot allocation + state-reset registry + 2 producer kernels + canary signature + launcher Pearls A+D + tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
85069ba75e |
spec(sp11): amend B1b smoke-recovery — z-score normalization for mag-ratio canary
smoke-test-6wd2c on commit |
||
|
|
61b2fa962b |
fix(sp11): B1b bug 3 — cf-component feedback loop in mag-ratio canary
Deep audit on |
||
|
|
b435d25bec |
fix(sp11): B1b bug-hunt fix-up — stale rc[] init + cf_flip ordering
Bug-hunt review on
|