Root cause: Wave 4.1b (s1_input_dim 102→103, eb9515e41) introduced
`bn_tanh_concat_dd_kernel` which reads `isv[DD_PCT_INDEX=406]` from the
collector's `isv_signals_dev_ptr`. Wave 4.1b's atomic refactor docstring
claimed "guarded by the captured-graph wiring in training_loop.rs:859
which sets the bus pointer BEFORE the first epoch's forward graph
capture" — but ordering audit shows that's wrong:
Phase 1c (line 580): init_gpu_experience_collector
→ collector.isv_signals_dev_ptr = 0 (NULL)
Phase 2 (line 730): collect_gpu_experiences_slices
→ bn_tanh_concat_dd_kernel reads isv[406]
= NULL+1624 = 0x658 → ILLEGAL_ADDRESS
Phase 4 (line 859+): set_isv_signals_ptr (TOO LATE)
The error surfaces as "load sp15_dd_state cubin: ILLEGAL_ADDRESS" via
sticky-cascade (the OOB happened in the prior kernel; the next CUDA
call — dd_state cubin load — surfaces the cascade).
Reproduced locally via compute-sanitizer on
test_no_hang_single_epoch (RTX 3050 Ti). Sanitizer pinpointed
"Invalid __global__ read of size 4 bytes at bn_tanh_concat_dd_kernel
+0x4d0 ... Access at 0x658 is out of bounds" — exactly slot 406 * 4.
Fix: move the ISV / SP15 control pointer wiring (set_isv_signals_ptr,
set_sp15_alpha_warm_count_ptr, set_sp15_plasticity_target, PER
buffer's set_isv_signals_ptr + set_sp15_dd_trajectory_per_env_ptr +
set_sp15_per_env_dims) into init_gpu_experience_collector BEFORE the
collector is moved into self.gpu_experience_collector. This guarantees
the FIRST collect_experiences_gpu call (epoch 0, Phase 2) sees
non-NULL pointers — required because cudarc's graph capture bakes the
arg values into the captured graph at capture time, so subsequent
re-wiring has no effect on the captured replay path.
The per-epoch re-wiring at lines 855-957 of the outer epoch loop is
now redundant (idempotent setters re-applying the same stable
pointers) but kept in place per feedback_no_partial_refactor for
explicit per-epoch refresh semantics — these underlying pointers are
stable for the trainer's lifetime so re-setting is a no-op, and
removing the per-epoch call would scatter the wire-up logic across a
non-obvious pre-init / per-epoch split.
Verification:
* 30/30 SP15 phase 1 oracle tests pass under compute-sanitizer
memcheck with 0 errors (unchanged baseline)
* test_no_hang_single_epoch under sanitizer: SP15 dd_state OOB is
GONE; the first OOB now surfaces at compute_expected_q+0x2480
(denoise_target_q_buf stride 12 vs total_actions=13 — a SEPARATE
pre-existing bug previously masked by the SP15 OOB; reported but
out of scope for this commit per single-atomic-fix discipline).
* compute-sanitizer "Access at 0x658" matches the
bn_tanh_concat_dd_kernel isv[406] read path; production training
on L40S/H100 should now reach the next-layer issue.
Refs: SP15 Wave 4.1b (eb9515e41), feedback_no_partial_refactor,
feedback_wire_everything_up, pearl_no_host_branches_in_captured_graph.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>