From c4b6d6ef29745583ddd2259030ea0bfae32b4b02 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Fri, 8 May 2026 08:55:57 +0200 Subject: [PATCH] fix(class-a-p1-wiring): var_floor q_gap-only adaptive (1 of 4 wireable) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Class A audit P1 wiring batch — only 1 of 4 items was wireable as spec'd. The other 3 are deferred per the spec's "DEFER not BLOCK" rule because their target ISV slots either don't exist or are unit-incompatible with the consumer site. Items: 1. trade_physics.cuh:548 (DD floor_dd 0.25f) — DEFERRED. Slot 421 (DD_THRESHOLD_INDEX) holds the DD trigger threshold (~0.05), NOT the penalty saturation floor (~0.25). Different parameters; substituting would break the ramp formula `(dd-thr)/(floor-thr)`. Needs a new slot for the saturation floor. 2. gpu_experience_collector.rs:343-344 (dd_threshold + w_dd config) — DEFERRED. No W_DD slot exists in any sp*_isv_slots.rs. The legacy compute_drawdown_penalty path (experience_kernels.cu:3769) uses config scalars; the SP15 reward axis (compute_sp15_final_reward_kernel) already reads slot 421 directly. Wiring needs a new W_DD slot. 3. plan_threshold_update_kernel.cu:44 (plan threshold floor) — DEFERRED. Unit mismatch: ema and plan_threshold are probabilities ∈[0,1]; Q_DIR_ABS_REF (slot 21) is a Q-value magnitude EMA (5–50). Scaling a probability by a Q-magnitude is dimensionally meaningless. 4. experience_kernels.cu:2471 (var_floor formula) — DONE. Drop the hardcoded 0.25f lower bound. Was `fminf(fmaxf(q_gap*0.5f, 0.25f), 1.0f)`; now `fminf(q_gap*0.5f, 1.0f)`. NULL-q_gaps fallback uses ε=1e-6f for numerical safety (vs prior 0.25f). var_scale itself remains (0,1] from `1/(1+sqrt(var_q))` so the multiplicative chain stays bounded. Cumulative WR-plateau fix series: - Class C bug 1 + P0-B (8f218cab2) - P0-C (316db416b) - P0-A (394de7d43) - P1 wiring (this commit, partial — Items 1/2/3 deferred to producer batch) Per feedback_isv_for_adaptive_bounds.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/cuda_pipeline/experience_kernels.cu | 12 +++++-- docs/dqn-wire-up-audit.md | 32 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/crates/ml/src/cuda_pipeline/experience_kernels.cu b/crates/ml/src/cuda_pipeline/experience_kernels.cu index cf20d36f2..35d50baaf 100644 --- a/crates/ml/src/cuda_pipeline/experience_kernels.cu +++ b/crates/ml/src/cuda_pipeline/experience_kernels.cu @@ -2467,10 +2467,16 @@ extern "C" __global__ void experience_env_step( /* Use max variance across the two exposure branches (direction + magnitude) */ float var_q = fmaxf(var_taken, var_mag); float var_scale = 1.0f / (1.0f + sqrtf(var_q)); - /* Permanent floor — same q_gap-derived conviction clamped at line 1628. */ + /* Permanent floor — q_gap-derived conviction (no hardcoded 0.25f). + * Class A P1 wiring (2026-05-08): per `feedback_isv_for_adaptive_bounds.md`, + * the floor is now entirely q_gap-driven — the prior 0.25f lower bound + * was a structural floor that signal-driven controllers couldn't + * escape. When q_gaps is NULL the var_floor mechanism short-circuits + * to a tiny ε (1e-6f) for numerical safety; var_scale itself remains + * (0,1] from `1/(1+sqrt(var_q))`. */ float var_floor = (q_gaps != NULL) - ? fminf(fmaxf(q_gaps[i] * 0.5f, 0.25f), 1.0f) - : 0.25f; + ? fminf(q_gaps[i] * 0.5f, 1.0f) + : 1e-6f; var_scale = fmaxf(var_scale, var_floor); /* Task 0.3: persist the per-sample Kelly scale for host-side epoch-mean * reduction. Host filters on `> 0.0f` to distinguish measured samples diff --git a/docs/dqn-wire-up-audit.md b/docs/dqn-wire-up-audit.md index 010c01f96..eac14f7c5 100644 --- a/docs/dqn-wire-up-audit.md +++ b/docs/dqn-wire-up-audit.md @@ -7910,3 +7910,35 @@ No additional consumers found via `grep -rnE "REWARD_POS_CAP|REWARD_NEG_CAP" cra - Class A P0-B (`8f218cab2`): Kelly warmup floor wiring — keeps Kelly cap from going to 0 cold-start. - Class A P0-C (`316db416b`): MIN_HOLD_TARGET adaptive from AVG_WIN_HOLD_TIME — patience-matched holds. - Class A P0-A (this commit): REWARD_POS/NEG_CAP adaptive from realized return distribution — restores upper-tail alpha to the controller's view. + +## Class A P1 wiring batch: 4 hardcoded → ISV (2026-05-08) + +Class A P1 audit identified four hardcoded constants that the Class A audit flagged as creating "structural floors/ceilings" the surrounding ISV-driven controllers couldn't escape. All four were intended as wiring-only fixes (existing ISV producers), but on inspection three of the four require slot allocations or have unit-mismatch issues — only Item 4 was wireable. Per the spec's "DEFER not BLOCK" rule, Items 1-3 are deferred to a producer batch and Item 4 lands here. + +### Items + +#### Item 1 — DEFERRED: trade_physics.cuh:548 `floor_dd = 0.25f` +- **Site**: `crates/ml/src/cuda_pipeline/trade_physics.cuh:548` in `compute_drawdown_penalty`. +- **Audit's intent**: replace the hardcoded `floor_dd = 0.25f` (DD-penalty saturation point, where penalty reaches `-5*w_dd`) with `isv[SP15_DD_THRESHOLD_INDEX=421]`. +- **Why deferred**: slot 421 holds the DD **trigger threshold** (~0.05 — the lower bound where the penalty starts), not the DD **saturation floor** (~0.25 — the upper bound where the penalty saturates). The two parameters have different semantics and must satisfy `floor_dd > dd_threshold` (otherwise the formula `(dd - dd_threshold) / (floor_dd - dd_threshold)` divides by ≤ 0). Reading slot 421 for `floor_dd` would make `floor_dd ≈ dd_threshold`, breaking the ramp. There is no existing ISV producer for the saturation floor — needs new slot allocation in a follow-up producer batch. + +#### Item 2 — DEFERRED: gpu_experience_collector.rs:343-344 `dd_threshold=0.02 + w_dd=1.0` +- **Site**: `crates/ml/src/cuda_pipeline/gpu_experience_collector.rs:343-344` in `ExperienceCollectorConfig::default`. +- **Audit's intent**: read `dd_threshold` and `w_dd` from ISV slots. +- **Why deferred**: per the spec's "STOP this item if slots don't exist" rule. `grep -nE "W_DD|w_dd" crates/ml/src/cuda_pipeline/sp*_isv_slots.rs` returns no W_DD slot. SP15 slot 421 (`DD_THRESHOLD_INDEX`) exists for the threshold but its value flows through the SP15 reward axis path (`compute_sp15_final_reward_kernel`), not through the legacy `compute_drawdown_penalty` → `experience_kernels.cu:3769` path. Wiring would require either a new slot for `w_dd` or a contract migration to retire the legacy DD-penalty path entirely. Defer to producer batch. + +#### Item 3 — DEFERRED: plan_threshold_update_kernel.cu:44 unit mismatch +- **Site**: `crates/ml/src/cuda_pipeline/plan_threshold_update_kernel.cu:44`. +- **Audit's intent**: scale the `fmaxf(0.1f, 0.5f * ema)` floor/multiplier by `isv[Q_DIR_ABS_REF_INDEX=21]`. +- **Why deferred**: unit mismatch. `ema` (and `plan_threshold` itself) is a **probability ∈ [0,1]** (running mean of per-sample readiness ∈ [0,1]). `Q_DIR_ABS_REF` is a **Q-value magnitude EMA** (typically 5–50). Multiplying a probability by a Q-magnitude produces a dimensionally meaningless quantity that would break the consumer site's `> threshold` comparisons (consumers compare against `readiness ∈ [0,1]`). The 0.1 / 0.5 hardcoded constants here are dimensionless probability fractions, not signal-bound coefficients — they are correctly literal and should not be scaled. Slot 21 is wrong for this site; the proper fix is either (a) leave as-is (the constants are unit-correct), or (b) introduce a probability-unit ISV scalar in a follow-up. + +#### Item 4 — DONE: experience_kernels.cu:2471 `var_floor` hardcoded 0.25f lower bound +- **Site**: `crates/ml/src/cuda_pipeline/experience_kernels.cu:2471`. +- **Change**: drop the hardcoded `0.25f` lower bound from the `var_floor` formula. Was `fminf(fmaxf(q_gaps[i] * 0.5f, 0.25f), 1.0f)`; now `fminf(q_gaps[i] * 0.5f, 1.0f)`. When `q_gaps == NULL`, the var_floor mechanism short-circuits to a tiny ε (1e-6f) for numerical safety — var_scale remains (0, 1] from `1/(1+sqrt(var_q))` so the multiplicative chain doesn't go to zero. +- **Rationale**: per audit, the 0.25f floor was a structural anchor that prevented `var_floor` from being entirely signal-driven. Per `feedback_isv_for_adaptive_bounds.md`, the floor must come from an ISV signal — `q_gaps` (already a per-sample conviction signal upstream) is the right driver, and the 0.25f baseline silenced its low-conviction portion of the range. +- **Verification**: cargo check + sp14_oracle_tests + sp15_phase1_oracle_tests pass. + +### Concerns surfaced + +1. The audit description for Item 1 conflates `floor_dd` (saturation point) and `dd_threshold` (trigger point) — these are distinct parameters in `compute_drawdown_penalty`. Future producer batch must add a separate slot for the saturation floor, not reuse slot 421. +2. The legacy `compute_drawdown_penalty` (called from `experience_kernels.cu:3769`) and the SP15 `sp15_dd_penalty` (called from `compute_sp15_final_reward_kernel.cu:154`) coexist with separate parameter flows. The SP15 path is fully ISV-driven (slots 420, 421); the legacy path takes config scalars. A future cleanup should retire the legacy path or migrate it to ISV.