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>
This commit is contained in:
jgrusewski
2026-05-03 12:45:59 +02:00
parent bbf15ad690
commit 237b3dbfb0
3 changed files with 38 additions and 2 deletions

View File

@@ -825,7 +825,7 @@ const ISV_NETWORK_DIM: usize = 23;
/// (shifted 112→116 in Plan 4 Task 6 Commit A).
/// Written by the constructor; checked at checkpoint load. Fail-fast only — no migration
/// path exists. See spec §4.A.2 and `LAYOUT_FINGERPRINT_CURRENT` for structural-hash rationale.
const ISV_TOTAL_DIM: usize = 294; // SP5 + Layer D D1+D2: 173 + 121 (118 SP5 slots @ 174..278/280..294, with 2-slot gap before cross-fold-persistent Kelly block; Layer D D1 PnL outputs at [286..290); Layer D D2 health composition outputs at [290..294))
pub(crate) const ISV_TOTAL_DIM: usize = 321; // SP5 + Layer D D1+D2+D3 + SP7: 173 + 148 (145 SP5 slots @ 174..278/280..321, with 2-slot gap before cross-fold-persistent Kelly block; Layer D D1 PnL outputs at [286..290); Layer D D2 health composition outputs at [290..294); Layer D D3 training metrics EMA at [294..297); SP7 T1 loss-balance Wiener stats at [297..313); SP7 activation-flag fix per-(head,branch) flags at [313..321))
/// Legacy alias preserved for call sites that haven't been audited for the
/// network-vs-total split. New code should pick `ISV_NETWORK_DIM` (for weight
/// tensor sizing) or `ISV_TOTAL_DIM` (for the broadcast bus buffer).
@@ -1699,7 +1699,11 @@ const fn layout_fingerprint_seed() -> &'static [u8] {
KELLY_SAMPLE_COUNT=283;WIN_RATE_SMOOTH=284;LOSS_RATE_SMOOTH=285;\
PNL_TOTAL=286;PNL_MEAN=287;PNL_VAR=288;PNL_MAX_DD=289;\
HEALTH_SCORE=290;Q_GAP_NORM=291;Q_VAR_NORM=292;GRAD_NORM_NORM=293;\
ISV_TOTAL_DIM=294;\
TRAINING_SHARPE_EMA=294;MAX_DD_EMA=295;LOW_DD_RATIO=296;\
LB_DIFF_VAR_CQL_BASE=297;LB_SAMPLE_VAR_CQL_BASE=301;\
LB_DIFF_VAR_C51_BASE=305;LB_SAMPLE_VAR_C51_BASE=309;\
LB_CQL_ACTIVE_BASE=313;LB_C51_ACTIVE_BASE=317;\
ISV_TOTAL_DIM=321;\
PARAM_W_A_H_S1=0;PARAM_B_A_H_S1=1;PARAM_W_B_H_S1=2;PARAM_B_B_H_S1=3;\
PARAM_W_RESIDUAL_H_S1=4;PARAM_GAMMA_H_S1=5;PARAM_BETA_H_S1=6;\
PARAM_W_A_H_S2=7;PARAM_B_A_H_S2=8;PARAM_W_B_H_S2=9;PARAM_B_B_H_S2=10;\

View File

@@ -506,4 +506,15 @@ mod tests {
assert_eq!(SP5_SLOT_END, 321);
assert_eq!(SP5_PRODUCER_COUNT, SP5_SLOT_END - SP5_SLOT_BASE);
}
#[test]
fn all_sp5_slots_fit_within_isv_total_dim() {
use crate::cuda_pipeline::gpu_dqn_trainer::ISV_TOTAL_DIM;
assert!(
SP5_SLOT_END <= ISV_TOTAL_DIM,
"SP5_SLOT_END={} exceeds ISV_TOTAL_DIM={} — bus too small for SP5/SP7 slots; \
bump ISV_TOTAL_DIM in gpu_dqn_trainer.rs (and update layout_fingerprint_seed()).",
SP5_SLOT_END, ISV_TOTAL_DIM,
);
}
}

View File

@@ -3995,6 +3995,27 @@ extended. No producer kernel yet — that arrives in the next commit.
gate (was_active=1, grads back to 0) → flags hold at 1, prior
budget held verbatim. No CPU reference oracle per
`feedback_no_cpu_test_fallbacks.md`. Passes on local RTX 3050 Ti.
- ISV_TOTAL_DIM OOB fix (commit on wt/sp7-observability, 2026-05-03):
**Discovery**: `ISV_TOTAL_DIM=294` but `SP5_SLOT_END=321`; the pinned
ISV buffer was allocated at `294×4=1176 bytes`. SP7 T1 added 24 slots
(ISV[297..321)) and the activation-flag fix added 8 more (ISV[313..321))
without bumping `ISV_TOTAL_DIM`. All SP7 ISV writes/reads from indices
297..320 were out-of-bounds: GPU direct-pointer writes silently
corrupted memory in the next page-aligned region; CPU
`write_isv_signal_at` silently no-oped for `index >= ISV_TOTAL_DIM`;
CPU `read_isv_signal_at` returned garbage in release builds.
**Root cause**: T1 allocated slots in `sp5_isv_slots.rs` but did not
bump `ISV_TOTAL_DIM` in `gpu_dqn_trainer.rs`; the constant lived in a
different file with no compile-time linkage to `SP5_SLOT_END`.
**Fix**: `ISV_TOTAL_DIM` bumped `294→321` and made `pub(crate)`;
`layout_fingerprint_seed()` extended with the missing D3 and SP7 slot
entries (TRAINING_SHARPE_EMA=294 through LB_C51_ACTIVE_BASE=317) and
`ISV_TOTAL_DIM=321` in lockstep per `feedback_no_partial_refactor`.
**Contract test**: `all_sp5_slots_fit_within_isv_total_dim` added to
`sp5_isv_slots.rs` test module; asserts `SP5_SLOT_END <= ISV_TOTAL_DIM`
at `cargo test -p ml --lib`. This test would have caught the SP7 T1
miss instantly; future slot allocations cannot silently regress the bus
size without breaking CI.
- T8 (out-of-tree): memory pearl `pearl_loss_balance_controller.md` +
MEMORY.md index entry. Captures the two-layer (signal-modulated target ×
outcome-driven α) pattern for future controller designs.