plan(sp18 v2): Phase 0 Task 0.1 — D-leg per-action reward decomposition diagnostic
Adds the SP18 v2 Phase 0 D-leg observability scaffold per the plan's
"Phase 0 — diagnostic emit (NO functional change)" task:
- New kernel `reward_decomp_diag_kernel.cu`: block tree-reduce
(4 blocks × 256 threads, one block per direction-axis bin) reading
`reward_components_per_sample [N×6]` + `actions_out [N]` and emitting
5 per-bin stats (mean r_micro / mean r_opp_cost / mean r_popart /
mean |reward| / fire_rate) into a 20-float row-major output. Bin
order: Hold(0)→Long(1)→Short(2)→Flat(3); col order: micro→opp→
popart→abs→fire. Empty-bin guard emits 0.0 (NOT NaN) per the
consumer-side KILL CRITERION arithmetic contract.
- Cubin manifest entry in `crates/ml/build.rs` + `REWARD_DECOMP_DIAG_
CUBIN` re-export in `gpu_dqn_trainer.rs`.
- 20-float `MappedF32Buffer sp18_reward_decomp_diag_buf` field on
`GpuDqnTrainer` + accessor pair (`sp18_reward_decomp_diag_dev_ptr`
for the writer-side launcher; `read_sp18_reward_decomp_diag` for the
HEALTH_DIAG reader). Buffer is constructor-zeroed so cold-start
HEALTH_DIAG emits a deterministic zero block.
- `sp18_reward_decomp_diag_kernel` field on `GpuExperienceCollector` +
cubin load on the collector's stream + `launch_sp18_reward_decomp_
diag(n, b1, b2, b3, out_dev_ptr)` launcher. Wired in
`training_loop.rs` at the per-step boundary, BEFORE
`launch_reward_component_ema_inplace` (which `memset_zeros` the
source buffer after consuming it) per `pearl_canary_input_freshness_
launch_order`.
- New per-epoch HEALTH_DIAG line emit at the existing per-epoch
boundary (after the SP17 dueling line):
HEALTH_DIAG[N]: reward_decomp [hold(micro=X opp=Y popart=Z abs=W
fire=F) long(...) short(...) flat(...)]
Reads the mapped-pinned 20-float diag buffer directly via the
collector→trainer host_ptr — no DtoH copy.
- New `crates/ml/tests/sp18_hold_reward_oracle_tests.rs`:
* `reward_decomp_per_action_cpu_oracle` (CPU oracle pinning the
per-bin reduction math against a 4-sample synthetic batch).
* `reward_decomp_per_action_gpu_oracle` (GPU oracle, ignored unless
`--ignored`; asserts kernel matches CPU oracle bit-for-bit within
1e-6 f32 budget).
* `reward_decomp_empty_bin_emits_zero_not_nan` (empty-bin contract
guard).
Pure observability — no production-path consumer in this commit. No
reward changes, no Bellman target changes, no kernel modifications to
the action-selection or training paths. Per
`feedback_no_partial_refactor` the kernel + cubin manifest + buffer +
launcher + production wire-up + HEALTH_DIAG emit + GPU oracle test all
land atomically.
Verification:
SQLX_OFFLINE=true CUDA_COMPUTE_CAP=86 cargo check --workspace clean.
CPU oracle test passes; GPU oracle + empty-bin guard both pass on
RTX 3050 Ti (2.13s).
Plan: docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md
§ Phase 0 Task 0.1.
Audit: docs/dqn-wire-up-audit.md § "SP18 v2 Phase 0 Task 0.1".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -114,6 +114,15 @@ static ATOMS_UPDATE_CUBIN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/ato
|
||||
static Q_QUANTILE_CUBIN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/q_quantile_kernel.cubin"));
|
||||
/// Plan 3 Task 1 C.2: per-component |reward| EMA into ISV[63..69).
|
||||
pub(crate) static REWARD_COMPONENT_EMA_CUBIN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/reward_component_ema_kernel.cubin"));
|
||||
/// SP18 v2 Phase 0 Task 0.1 (2026-05-08): D-leg per-action reward
|
||||
/// decomposition diagnostic. Reads `reward_components_per_sample`
|
||||
/// + `actions_out` from the collector, bins per-direction
|
||||
/// (DIR_HOLD / DIR_LONG / DIR_SHORT / DIR_FLAT), emits 5 stats per bin
|
||||
/// (mean r_micro / r_opp_cost / r_popart / |reward| / fire_rate) into
|
||||
/// a 20-float mapped-pinned `reward_decomp_diag_buf`. Block tree-
|
||||
/// reduce — 4 blocks × 256 threads. Pure observability — no
|
||||
/// production-path consumer in this commit. Plan: SP18 Phase 0 Task 0.1.
|
||||
pub(crate) static REWARD_DECOMP_DIAG_CUBIN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/reward_decomp_diag_kernel.cubin"));
|
||||
/// Plan 3 Task 3 B.2: Flat→Positioned transition rate EMA into ISV[71].
|
||||
pub(crate) static TRADE_RATE_EMA_CUBIN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trade_rate_ema_kernel.cubin"));
|
||||
/// Plan 3 Task 4 B.4: per-batch readiness EMA + derived plan_threshold.
|
||||
@@ -6803,6 +6812,25 @@ pub struct GpuDqnTrainer {
|
||||
/// host writes — kernel-owned). Lifetime matches the trainer; reset
|
||||
/// once at construct, written-then-read inside the kernel each launch.
|
||||
sp17_advantage_clip_scratch: super::mapped_pinned::MappedF32Buffer,
|
||||
|
||||
// ── SP18 v2 Phase 0 Task 0.1 (2026-05-08): D-leg per-action reward
|
||||
// decomposition diagnostic buffer ──────────────────────────────────
|
||||
/// 20-float mapped-pinned diagnostic buffer for the SP18 D-leg
|
||||
/// per-action reward decomposition. Layout (row-major, 4×5):
|
||||
///
|
||||
/// row 0 = Hold, row 1 = Long, row 2 = Short, row 3 = Flat
|
||||
/// col 0 = mean(r_micro), col 1 = mean(r_opp_cost),
|
||||
/// col 2 = mean(r_popart), col 3 = mean(|reward|),
|
||||
/// col 4 = fire_rate
|
||||
///
|
||||
/// Written by the collector's
|
||||
/// `launch_sp18_reward_decomp_diag(...)` kernel each per-step
|
||||
/// boundary, BEFORE `launch_reward_component_ema_inplace` zeros the
|
||||
/// source buffer. Consumed exclusively by the per-epoch HEALTH_DIAG
|
||||
/// emit at `training_loop.rs`. Pure observability — no production-
|
||||
/// path consumer in this commit. Plan: SP18 Phase 0 Task 0.1.
|
||||
pub(crate) sp18_reward_decomp_diag_buf: super::mapped_pinned::MappedF32Buffer,
|
||||
|
||||
// SP14 Layer C Phase C.1 (2026-05-08): α-machinery struct fields deleted
|
||||
// atomically with the kernel files (alpha_grad_compute_kernel,
|
||||
// gradient_hack_detect_kernel, sp14_scale_wire_col_kernel) per
|
||||
@@ -9407,6 +9435,48 @@ impl GpuDqnTrainer {
|
||||
Ok(self.read_isv_signal_at(ADVANTAGE_CLIP_BOUND_INDEX))
|
||||
}
|
||||
|
||||
/// SP18 v2 Phase 0 Task 0.1 (2026-05-08) — D-leg per-action reward
|
||||
/// decomposition diagnostic device pointer.
|
||||
///
|
||||
/// Returns the mapped-pinned device pointer of the 20-float
|
||||
/// `sp18_reward_decomp_diag_buf` so the collector's
|
||||
/// `launch_sp18_reward_decomp_diag` can write into it. The host-side
|
||||
/// HEALTH_DIAG emit reads via `read_sp18_reward_decomp_diag()` after
|
||||
/// stream sync. Pure observability — caller is expected to be the
|
||||
/// per-step training loop wiring point.
|
||||
pub fn sp18_reward_decomp_diag_dev_ptr(&self) -> u64 {
|
||||
self.sp18_reward_decomp_diag_buf.dev_ptr
|
||||
}
|
||||
|
||||
/// SP18 v2 Phase 0 Task 0.1 (2026-05-08) — D-leg per-action reward
|
||||
/// decomposition diagnostic readback.
|
||||
///
|
||||
/// Returns the 20-float diagnostic block as a `[f32; 20]`, row-major
|
||||
/// 4 bins × 5 stats:
|
||||
///
|
||||
/// [Hold_micro, Hold_opp, Hold_popart, Hold_abs, Hold_fire,
|
||||
/// Long_micro, Long_opp, Long_popart, Long_abs, Long_fire,
|
||||
/// Short_micro, Short_opp, Short_popart, Short_abs, Short_fire,
|
||||
/// Flat_micro, Flat_opp, Flat_popart, Flat_abs, Flat_fire]
|
||||
///
|
||||
/// Reads the mapped-pinned host_ptr directly — caller MUST have
|
||||
/// stream-synced after the collector's launch (e.g. via the natural
|
||||
/// per-step boundary that already runs `cuStreamSynchronize` between
|
||||
/// collection and training). No HtoD/DtoH copy.
|
||||
///
|
||||
/// On cold-start (before any kernel launch) returns the
|
||||
/// constructor-initialised zero block.
|
||||
pub fn read_sp18_reward_decomp_diag(&self) -> [f32; 20] {
|
||||
let v = self.sp18_reward_decomp_diag_buf.read_all();
|
||||
debug_assert_eq!(
|
||||
v.len(), 20,
|
||||
"sp18_reward_decomp_diag_buf size invariant: 4 bins × 5 stats"
|
||||
);
|
||||
let mut out = [0.0_f32; 20];
|
||||
for (i, x) in v.iter().take(20).enumerate() { out[i] = *x; }
|
||||
out
|
||||
}
|
||||
|
||||
/// Update per-branch liquid tau modulation — GPU kernel (RK4/Euler adaptive ODE).
|
||||
///
|
||||
/// Reads per_branch_q_gaps (pinned device-mapped) and qlstm_context from device.
|
||||
@@ -20568,6 +20638,27 @@ impl GpuDqnTrainer {
|
||||
"sp17_advantage_clip_scratch alloc ({sp17_clip_scratch_len} f32): {e}"
|
||||
)))?;
|
||||
|
||||
// SP18 v2 Phase 0 Task 0.1 (2026-05-08): D-leg per-action reward
|
||||
// decomposition diagnostic buffer. 4 bins × 5 stats = 20 floats
|
||||
// mapped-pinned. The collector's
|
||||
// `launch_sp18_reward_decomp_diag` writes per-step; the host-side
|
||||
// HEALTH_DIAG emit reads via the mapped host_ptr after stream
|
||||
// sync. Pure observability — no production-path consumer.
|
||||
const SP18_REWARD_DECOMP_DIAG_LEN: usize = 4 * 5;
|
||||
let sp18_reward_decomp_diag_buf = unsafe {
|
||||
super::mapped_pinned::MappedF32Buffer::new(SP18_REWARD_DECOMP_DIAG_LEN)
|
||||
}
|
||||
.map_err(|e| MLError::ModelError(format!(
|
||||
"sp18_reward_decomp_diag_buf alloc ({SP18_REWARD_DECOMP_DIAG_LEN} f32): {e}"
|
||||
)))?;
|
||||
// Initialise to 0.0 so the first HEALTH_DIAG read on cold-start
|
||||
// (before the collector has fired) is a deterministic zero block
|
||||
// rather than indeterminate page contents. Pearl-A first-
|
||||
// observation bootstrap fires inside the kernel itself when
|
||||
// n_samples > 0; this just zero-fills before the kernel ever runs.
|
||||
sp18_reward_decomp_diag_buf
|
||||
.write_from_slice(&vec![0.0_f32; SP18_REWARD_DECOMP_DIAG_LEN]);
|
||||
|
||||
// SP14 Layer C Phase C.1 (2026-05-08): α-machinery cubin loads
|
||||
// (alpha_grad_compute, gradient_hack_detect, sp14_scale_wire_col)
|
||||
// deleted atomically with the kernel files. Coupling A
|
||||
@@ -24810,6 +24901,11 @@ impl GpuDqnTrainer {
|
||||
sp17_v_share_kernel,
|
||||
sp17_advantage_clip_bound_kernel,
|
||||
sp17_advantage_clip_scratch,
|
||||
// SP18 v2 Phase 0 Task 0.1 (2026-05-08): D-leg per-action
|
||||
// reward decomposition diagnostic buffer. Mapped-pinned 20
|
||||
// floats (4 bins × 5 stats). Written by collector kernel,
|
||||
// read host-side at HEALTH_DIAG cadence.
|
||||
sp18_reward_decomp_diag_buf,
|
||||
sp14_dir_concat_qaux_kernel,
|
||||
sp14_dir_qaux_concat_scratch,
|
||||
// SP14 backward dX scratch — column-SH2 wire is dropped at
|
||||
|
||||
Reference in New Issue
Block a user