feat(sp17-3.1): A_var_ema per-branch producer + HEALTH_DIAG emit

Phase 3 of SP17 dueling-Q identifiability — first of three diagnostic
producers landed atomically with kernel + launcher + Rust wrapper +
HEALTH_DIAG emit + GPU oracle test per `feedback_wire_everything_up`.

Per branch d ∈ {dir, mag, ord, urg}:
  Var_d = (1/(B × n_d × NA)) Σ_{i, a, z} (A[i, a, z] − mean_a A[*, z])²

Block tree-reduce (no atomicAdd, `feedback_no_atomicadd`); 4 blocks ×
256 threads. Pearl-A first-observation bootstrap (sentinel 0.0 →
REPLACE on first launch); steady-state α = WELFORD_ALPHA_MIN=0.4 per
`pearl_wiener_alpha_floor_for_nonstationary` — the structural-control
floor preserves catch-up bandwidth without storing 24 Welford
accumulator slots for a cold-path-cadence diagnostic.

Cold-path emit: single launch per HEALTH_DIAG cadence (epoch boundary)
right after `v_a_means`. New line:

  HEALTH_DIAG[N]: dueling [a_var=(d=X m=Y o=Z u=W)]

The line will be extended with V_share + advantage_clip_bound in
Phase 3.2, then finalised in Phase 3.3.

GPU oracle test on RTX 3050 Ti: synthetic A constructed so each branch
d has a closed-form Var(A_centered); kernel readback matches expected
value within ε=1e-4 (f32 rounding budget for ~8×n×51 accumulator
length).

Plan: docs/superpowers/plans/2026-05-08-sp17-dueling-q-network.md
      Phase 3.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-05-08 22:45:40 +02:00
parent 079a06e485
commit 1e70cd5e59
6 changed files with 649 additions and 0 deletions

View File

@@ -674,6 +674,18 @@ pub(crate) static SP14_Q_DISAGREEMENT_CUBIN: &[u8] =
pub(crate) static SP17_V_A_MEANS_DIAG_CUBIN: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/v_a_means_diag_kernel.cubin"));
/// SP17 Phase 3.1 (2026-05-08): per-branch EMA of the over-actions
/// variance of `A_centered`. 4 blocks × 256 threads (one block per
/// branch). Cold-path (epoch boundary) producer for the HEALTH_DIAG
/// `dueling [a_var=...]` line; signals dueling-Q identifiability health
/// (Var → 0 = `all actions equivalent, V dominates` = pre-SP17 pathology
/// re-emerging). Block tree-reduce per `feedback_no_atomicadd`. Pearl-A
/// first-observation bootstrap on slots 474..478. Loaded from
/// `sp17_a_var_ema_kernel.cubin`. Consumed by
/// `read_a_var_ema_per_branch()`.
pub(crate) static SP17_A_VAR_EMA_CUBIN: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/sp17_a_var_ema_kernel.cubin"));
// SP14 Layer C Phase C.1 (2026-05-08): α-machinery cubin statics deleted
// atomically with the alpha_grad_compute / gradient_hack_detect /
// sp14_scale_wire_col kernel files. SP14_ALPHA_GRAD_CUBIN,
@@ -6712,6 +6724,15 @@ pub struct GpuDqnTrainer {
/// host reads via `read_all()` after a stream sync. Per
/// `feedback_no_htod_htoh_only_mapped_pinned`.
sp17_v_a_means_diag_buf: super::mapped_pinned::MappedF32Buffer,
// ── SP17 Phase 3.1 (2026-05-08): per-branch A_var EMA producer ────────
/// 4-block × 256-thread kernel computing per-branch
/// `Var(A_centered)` over (B × n_branch × NA) and writing the EMA
/// into ISV[A_VAR_EMA_DIR/MAG/ORD/URG_INDEX] (slots 474..478).
/// Pearl-A first-observation bootstrap on sentinel 0.0; α=0.4
/// post-bootstrap per `pearl_wiener_alpha_floor_for_nonstationary`.
/// Loaded from `sp17_a_var_ema_kernel.cubin`. Consumed by
/// `read_a_var_ema_per_branch()`.
sp17_a_var_ema_kernel: CudaFunction,
// 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
@@ -9009,6 +9030,112 @@ impl GpuDqnTrainer {
Ok([host[0], host[1], host[2], host[3], host[4]])
}
/// SP17 Phase 3.1 (2026-05-08) — per-branch A_var EMA producer launch.
///
/// Cold-path (epoch boundary) launcher for `sp17_a_var_ema_kernel`.
/// Reads the same online advantage buffer (`on_b_logits_buf`) that
/// `compute_expected_q` consumes; computes per-branch
/// `Var(A_centered)` over (B × n_branch × NA) where
/// `A_centered[i, a, z] = A[i, a, z] (1/n_d) Σ_a' A[i, a', z]`
/// and EMA-blends into ISV[A_VAR_EMA_*_INDEX] with Pearl-A
/// first-observation bootstrap (sentinel
/// `SENTINEL_A_VAR_EMA=0.0`) + α=`WELFORD_ALPHA_MIN=0.4` per
/// `pearl_wiener_alpha_floor_for_nonstationary`.
///
/// 4 blocks × 256 threads (one block per branch — dir/mag/ord/urg).
/// Block tree-reduce per `feedback_no_atomicadd`. Returns Ok(()) on
/// success; HEALTH_DIAG reads the ISV slots via
/// `read_isv_signal_at(A_VAR_EMA_*_INDEX)` after a stream sync.
///
/// Diagnostic only — does NOT modify any consumer path. Per
/// `feedback_no_htod_htoh_only_mapped_pinned` the ISV bus is already
/// mapped-pinned; this kernel writes via `dev_ptr +
/// __threadfence_system()` and the host reads after sync.
pub fn launch_a_var_ema_update(&self) -> Result<(), MLError> {
use super::sp14_isv_slots::{
A_VAR_EMA_DIR_INDEX, A_VAR_EMA_MAG_INDEX,
A_VAR_EMA_ORD_INDEX, A_VAR_EMA_URG_INDEX,
SENTINEL_A_VAR_EMA, WELFORD_ALPHA_MIN,
};
let b = self.config.batch_size;
let na = self.config.num_atoms;
let b0 = self.config.branch_0_size;
let b1 = self.config.branch_1_size;
let b2 = self.config.branch_2_size;
let b3 = self.config.branch_3_size;
if b == 0 || na == 0 || (b0 + b1 + b2 + b3) == 0 {
return Ok(());
}
let adv_ptr = self.on_b_logits_buf.raw_ptr();
let isv_ptr = self.isv_signals_dev_ptr;
let b_i32 = b as i32;
let na_i32 = na as i32;
let b0_i32 = b0 as i32;
let b1_i32 = b1 as i32;
let b2_i32 = b2 as i32;
let b3_i32 = b3 as i32;
let var_idx_dir = A_VAR_EMA_DIR_INDEX as i32;
let var_idx_mag = A_VAR_EMA_MAG_INDEX as i32;
let var_idx_ord = A_VAR_EMA_ORD_INDEX as i32;
let var_idx_urg = A_VAR_EMA_URG_INDEX as i32;
let alpha: f32 = WELFORD_ALPHA_MIN;
let sentinel: f32 = SENTINEL_A_VAR_EMA;
// 4 blocks × 256 threads. Dynamic shmem = (BLOCK_SIZE + NA) × 4
// bytes for the tree-reduce tile + per-atom mean cache.
let block_dim: u32 = 256;
let shmem_bytes: u32 =
(block_dim + na as u32) * std::mem::size_of::<f32>() as u32;
unsafe {
self.stream
.launch_builder(&self.sp17_a_var_ema_kernel)
.arg(&adv_ptr)
.arg(&isv_ptr)
.arg(&b_i32)
.arg(&na_i32)
.arg(&b0_i32)
.arg(&b1_i32)
.arg(&b2_i32)
.arg(&b3_i32)
.arg(&var_idx_dir)
.arg(&var_idx_mag)
.arg(&var_idx_ord)
.arg(&var_idx_urg)
.arg(&alpha)
.arg(&sentinel)
.launch(LaunchConfig {
grid_dim: (4, 1, 1),
block_dim: (block_dim, 1, 1),
shared_mem_bytes: shmem_bytes,
})
.map_err(|e| MLError::ModelError(format!(
"sp17_a_var_ema_update launch: {e}"
)))?;
}
Ok(())
}
/// SP17 Phase 3.1 (2026-05-08) — per-branch A_var EMA readback.
///
/// Convenience wrapper: launches the producer, syncs, returns
/// `[a_var_dir, a_var_mag, a_var_ord, a_var_urg]` from the ISV bus.
/// Cold-path only (HEALTH_DIAG cadence).
pub fn read_a_var_ema_per_branch(&self) -> Result<[f32; 4], MLError> {
use super::sp14_isv_slots::{
A_VAR_EMA_DIR_INDEX, A_VAR_EMA_MAG_INDEX,
A_VAR_EMA_ORD_INDEX, A_VAR_EMA_URG_INDEX,
};
self.launch_a_var_ema_update()?;
self.stream
.synchronize()
.map_err(|e| MLError::ModelError(format!("sp17_a_var_ema sync: {e}")))?;
Ok([
self.read_isv_signal_at(A_VAR_EMA_DIR_INDEX),
self.read_isv_signal_at(A_VAR_EMA_MAG_INDEX),
self.read_isv_signal_at(A_VAR_EMA_ORD_INDEX),
self.read_isv_signal_at(A_VAR_EMA_URG_INDEX),
])
}
/// 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.
@@ -20117,6 +20244,17 @@ impl GpuDqnTrainer {
}
.map_err(|e| MLError::ModelError(format!("sp17_v_a_means_diag_buf alloc (5 f32): {e}")))?;
// SP17 Phase 3.1 (2026-05-08): per-branch A_var EMA producer.
// Cold-path (epoch boundary) — one launch per HEALTH_DIAG emit.
// 4 blocks × 256 threads; block tree-reduce per
// `feedback_no_atomicadd`. Pearl-A bootstrap on slots 474..478.
let sp17_a_var_ema_module = stream.context()
.load_cubin(SP17_A_VAR_EMA_CUBIN.to_vec())
.map_err(|e| MLError::ModelError(format!("sp17 a_var_ema cubin: {e}")))?;
let sp17_a_var_ema_kernel = sp17_a_var_ema_module
.load_function("sp17_a_var_ema_update")
.map_err(|e| MLError::ModelError(format!("sp17_a_var_ema_update: {e}")))?;
// 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
@@ -24355,6 +24493,7 @@ impl GpuDqnTrainer {
// producer launched at HEALTH_DIAG emit only.
sp17_v_a_means_diag_kernel,
sp17_v_a_means_diag_buf,
sp17_a_var_ema_kernel,
sp14_dir_concat_qaux_kernel,
sp14_dir_qaux_concat_scratch,
// SP14 backward dX scratch — column-SH2 wire is dropped at