Adds the GPU oracle test scaffold deferred from commit 17cfbb250.
Refactored 3 reward composition functions into device-inline functions
in trade_physics.cuh for testability without behavior drift:
- compute_asymmetric_capped_pnl
- compute_min_hold_penalty
- compute_lump_sum_opp_cost
experience_kernels.cu's segment_complete branch now calls these helpers
in place of the inline math; the lump-sum opp_cost helper preserves the
production multiplication order via parens so the refactor is bit-
equivalent under f32 rounding to the inline computation it replaces.
The min-hold helper subsumes the original `if (hold_time < target)`
early-exit (returns 0 when at/past target — capped_pnl - shaping_scale
* 0 == capped_pnl).
New test kernel sp12_reward_math_test_kernel.cu exposes the device
functions for GPU oracle testing. The wrapper is single-thread / single-
block (the math is pure register arithmetic) and writes outputs to
mapped-pinned buffers with __threadfence_system() for host visibility,
matching the thompson_test_kernel / sp4_histogram_p99_test_kernel
pattern. Cubin registered in build.rs alongside the other test kernels.
New test module crates/ml/tests/sp12_reward_math_tests.rs covers all
three changes:
Asymmetric cap (5 tests):
- clips above pos_cap (+20 -> +5)
- clips below neg_cap (-20 -> -10)
- passes through zero
- exact at pos_cap (+5 -> +5)
- exact at neg_cap (-10 -> -10)
Min-hold soft penalty (5 tests):
- at target -> zero penalty
- at hold=0,target=30,T=10,max=3 -> 2.25 (factor 30/40 = 0.75)
- mid-deficit hold=15 -> 1.8 (factor 15/25 = 0.6)
- past target -> no penalty (early-exit branch)
- temperature smooths transition (T=10 -> 2.25 vs T=20 -> 1.8)
Lump-sum opp_cost (4 tests):
- exiting + position + hold_time -> -0.01
- zero position -> zero cost
- zero hold_time -> zero cost
- negative position -> uses |position| (matches +0.5 magnitude)
Per feedback_no_cpu_test_fallbacks: GPU oracle pattern (synthetic
inputs through real device functions, verified against analytical
expected outputs). No CPU reference impl. Per
feedback_no_htod_htoh_only_mapped_pinned: every CPU<->GPU buffer is
a MappedF32Buffer; zero htod_copy / dtoh_sync_copy. Tests gated with
#[ignore = "requires GPU"] to match the existing sp4/sp5/sp11
producer-test convention.
Verified on RTX 3050 Ti (sm_86):
SQLX_OFFLINE=true CUDA_COMPUTE_CAP=86 \
cargo test -p ml --test sp12_reward_math_tests --features cuda \
-- --ignored --nocapture
-> 14 passed; 0 failed (3.32s).
Build: SQLX_OFFLINE=true cargo check -p ml --lib --features cuda clean.
Audit doc updated in docs/dqn-wire-up-audit.md (Invariant 7).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>