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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user