diag(policy-quality): Task 2.0 extension — instrument 5 more grad writers
First Task 2.0 pass (commitd60e5375a) covered IQN/CQL/C51/Ens. Result: IQN+Ens are architecturally zero (don't touch branches); C51+CQL send 30-400× more gradient to magnitude than direction. But Task 0.4's grad_ratio_mag_dir=0.0000 at epoch end — so magnitude gradient gets cancelled somewhere in the aux-graph gap between CQL/C51 and epoch end. This commit instruments the 5 unmeasured writers/scalings: - apply_distillation_gradient - launch_recursive_confidence_backward - compute_predictive_coding_loss - apply_c51_budget_scale (multiplicative; signed delta reveals shrinkage) - apply_cql_saxpy (multiplicative) Schema extended 4 → 9 components. Pinned result slot grew 8 → 18 floats. 9 device scratch buffers (~90 MB total on RTX 3050 Ti — 2.2% of 4 GB, acceptable for diagnostic). HEALTH_DIAG now emits: grad_split_bwd [iqn=… cql=… c51=… ens=…] grad_split_aux [distill=… rec=… pred=… cql_sx=… c51_bs=…] No atomicAdd (block-internal tree reduction). memcpy_dtod_async is not captureable → used graph_safe_copy_f32 (same as first pass). Last 5 epochs (HEALTH_DIAG[15..19]), RTX 3050 Ti, magnitude_distribution smoke test: grad_split_bwd [iqn=0.0000 cql=18.4618 c51=102.2001 ens=0.0000] grad_split_aux [distill=1.2281 rec=0.0000 pred=0.0000 cql_sx=18.4618 c51_bs=102.2001] grad_split_bwd [iqn=0.0000 cql=55.0089 c51=125.1374 ens=0.0000] grad_split_aux [distill=1.2381 rec=0.0000 pred=0.0000 cql_sx=55.0089 c51_bs=125.1374] grad_split_bwd [iqn=0.0000 cql=30.1931 c51=262.0410 ens=0.0000] grad_split_aux [distill=0.7916 rec=0.0000 pred=0.0000 cql_sx=30.1931 c51_bs=262.0410] grad_split_bwd [iqn=0.0000 cql=70.2364 c51=126.6637 ens=0.0000] grad_split_aux [distill=0.7397 rec=0.0000 pred=0.0000 cql_sx=70.2364 c51_bs=126.6637] grad_split_bwd [iqn=0.0000 cql=103.3925 c51=104.4165 ens=0.0000] grad_split_aux [distill=0.8085 rec=0.0000 pred=0.0000 cql_sx=103.3925 c51_bs=104.4165] Keystone findings for Task 2.1: * rec (recursive confidence) + pred (predictive coding) report 0.0000 every epoch — architecturally expected: MSE/predictive gradients flow through bw_d_h_s2 / trunk tensors 0..8 only, they never touch branch tensors 8..16. Same architectural-zero class as IQN+Ens. * distill is the only aux writer that actually touches branches, but at ratios ~0.5–1.2 (nearly balanced mag/dir), so it cannot be the source of Task 0.4's grad_ratio_mag_dir=0 cancellation. * cql_sx ≡ cql and c51_bs ≡ c51 exactly (ratio, not norm). This is expected — multiplicative scalings (saxpy budget / c51 budget scale) uniformly scale BOTH direction and magnitude slices, so the mag/dir RATIO is invariant. These ops change amplitude, not balance. Conclusion: NONE of the 9 instrumented stages zero the magnitude signal. CQL+C51 reach grad_buf with huge mag/dir ratios (30-400×); distill adds a balanced micro-contribution; rec/pred/iqn/ens/scalings all architectur- ally neutral for the mag/dir ratio. Yet Task 0.4's post-step grad_ratio_mag_dir=0 (meaning dir < 1e-9). Re-reading Task 0.4's impl: `per_branch_grad_norms` returns [dir, mag, …] and the ratio is `mag / dir if dir > 1e-9 else 0.0`. So 0.0000 means dir_norm is zero, NOT mag cancelled. Task 0.4's measurement runs at EPOCH boundary after Adam consumes grad_buf — which means the "cancel" is either (a) Adam consuming/zeroing grad_buf pre-readback, (b) the next step's `submit_forward_ops_main` memset firing before Task 0.4's DtoH reads, or (c) something Adam-related that drops dir to zero. Five unmeasured writers verified present (all 5 exist via grep + read — none renamed or deleted since the first pass's report). Writers that DON'T touch branches (rec/pred) are now explicit 0.0000 evidence, not speculation. Feeds Task 2.1's decision tree: H4 is NOT caused by cancellation in the aux-graph gap. Next hypothesis: Adam grad_buf lifecycle / Task 0.4 timing relative to memset. Build clean; magnitude_distribution smoke green (21.90 s local, RTX 3050 Ti). Test result: 1 passed; 0 failed. Push status: deferred — first Task 2.0 pass reported network unreachable (d60e5375alocal-only). Will attempt in same commit cycle; if it fails again, that's consistent with the prior pass's finding. Per plan Task 2.0 extension (from Task 2.0 first-pass escalation report). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -971,31 +971,57 @@ pub struct GpuDqnTrainer {
|
||||
/// training graph capture region.
|
||||
grad_readback_pinned_ptr: usize, // host-visible f32 buffer
|
||||
grad_readback_pinned_capacity: usize, // element count (TOTAL_PARAMS)
|
||||
/// Task 2.0 — per-component magnitude-branch grad decomposition.
|
||||
/// Task 2.0 (+ Task 2.0 extension) — per-component magnitude-branch grad
|
||||
/// decomposition.
|
||||
///
|
||||
/// Four device-side scratch buffers sized to the combined length of
|
||||
/// Nine device-side scratch buffers sized to the combined length of
|
||||
/// branch 0 (direction, tensors 8..12) + branch 1 (magnitude, tensors
|
||||
/// 12..16). BEFORE each component's backward fires (captured in the
|
||||
/// training graph), a `copy_f32` kernel snapshots `grad_buf` over the
|
||||
/// branch 0+1 byte range into these slots. AFTER the component's
|
||||
/// backward, `grad_component_delta_norm` computes `‖current−snapshot‖`
|
||||
/// on direction and magnitude slices and writes two f32 values into
|
||||
/// the pinned result slot.
|
||||
/// 12..16). BEFORE each component's op fires (captured in the training
|
||||
/// graph), a `copy_f32` kernel snapshots `grad_buf` over the branch 0+1
|
||||
/// byte range into these slots. AFTER the component's op,
|
||||
/// `grad_component_delta_norm` computes `‖current−snapshot‖` on direction
|
||||
/// and magnitude slices and writes two f32 values into the pinned result
|
||||
/// slot at the component's 2-float offset.
|
||||
///
|
||||
/// The C51 slot is paired with `grad_zero_ref_buf` (permanent zeros)
|
||||
/// because `grad_buf` is memset-zeroed at the start of every
|
||||
/// `submit_forward_ops_main` — there is no "pre-C51" grad to snapshot.
|
||||
/// Uniform subtraction keeps the kernel interface simple.
|
||||
/// The C51 (post-backward) slot is paired with `grad_zero_ref_buf`
|
||||
/// (permanent zeros) because `grad_buf` is memset-zeroed at the start of
|
||||
/// every `submit_forward_ops_main` — there is no "pre-C51" grad to
|
||||
/// snapshot. Uniform subtraction keeps the kernel interface simple.
|
||||
///
|
||||
/// Task 2.0 extension adds five measurement points in the aux-graph gap
|
||||
/// between CQL and Ens where Task 0.4's `grad_ratio_mag_dir=0` indicates
|
||||
/// the magnitude signal gets cancelled:
|
||||
/// - `grad_snapshot_cql_sx` — before `apply_cql_saxpy`
|
||||
/// - `grad_snapshot_c51_bs` — before `apply_c51_budget_scale`
|
||||
/// - `grad_snapshot_distill` — before `apply_distillation_gradient`
|
||||
/// - `grad_snapshot_rec` — before `launch_recursive_confidence_backward`
|
||||
/// - `grad_snapshot_pred` — before `compute_predictive_coding_loss`
|
||||
///
|
||||
/// Total device memory: 9 × branch01_elems × 4 bytes (~90 MB on a model
|
||||
/// with ~TOTAL_PARAMS ≈ 2.5 M branch01 elems — 2.2 % of the 4 GB
|
||||
/// RTX 3050 Ti frame buffer, acceptable for diagnostic).
|
||||
grad_snapshot_iqn: CudaSlice<f32>, // [branch01_elems] — snapshot before IQN trunk grad
|
||||
grad_snapshot_cql: CudaSlice<f32>, // [branch01_elems] — snapshot before CQL SAXPY
|
||||
grad_snapshot_cql: CudaSlice<f32>, // [branch01_elems] — snapshot before CQL block (gradient+saxpy)
|
||||
grad_snapshot_cql_sx: CudaSlice<f32>, // [branch01_elems] — snapshot before apply_cql_saxpy only
|
||||
grad_zero_ref_buf: CudaSlice<f32>, // [branch01_elems] — permanent zeros; C51 snapshot substitute
|
||||
grad_snapshot_c51_bs: CudaSlice<f32>, // [branch01_elems] — snapshot before apply_c51_budget_scale
|
||||
grad_snapshot_distill: CudaSlice<f32>, // [branch01_elems] — snapshot before apply_distillation_gradient
|
||||
grad_snapshot_rec: CudaSlice<f32>, // [branch01_elems] — snapshot before launch_recursive_confidence_backward
|
||||
grad_snapshot_pred: CudaSlice<f32>, // [branch01_elems] — snapshot before compute_predictive_coding_loss
|
||||
grad_snapshot_ens: CudaSlice<f32>, // [branch01_elems] — snapshot before ensemble diversity
|
||||
/// Pinned device-mapped 8-float result slot written by the reduction
|
||||
/// kernel. Layout (matches plan Task 2.0 Step 4):
|
||||
/// [0]=iqn_mag [1]=iqn_dir
|
||||
/// [2]=cql_mag [3]=cql_dir
|
||||
/// [4]=c51_mag [5]=c51_dir
|
||||
/// [6]=ens_mag [7]=ens_dir
|
||||
/// Pinned device-mapped 18-float result slot written by the reduction
|
||||
/// kernel. Layout (matches plan Task 2.0 extension Step 4, index order
|
||||
/// chosen to match host-cache `[iqn, cql, cql_sx, c51, c51_bs, distill,
|
||||
/// rec, pred, ens]`):
|
||||
/// [ 0]=iqn_mag [ 1]=iqn_dir
|
||||
/// [ 2]=cql_mag [ 3]=cql_dir
|
||||
/// [ 4]=cql_sx_mag [ 5]=cql_sx_dir
|
||||
/// [ 6]=c51_mag [ 7]=c51_dir
|
||||
/// [ 8]=c51_bs_mag [ 9]=c51_bs_dir
|
||||
/// [10]=distill_mag [11]=distill_dir
|
||||
/// [12]=rec_mag [13]=rec_dir
|
||||
/// [14]=pred_mag [15]=pred_dir
|
||||
/// [16]=ens_mag [17]=ens_dir
|
||||
grad_decomp_result_pinned: *mut f32,
|
||||
grad_decomp_result_dev_ptr: u64,
|
||||
/// Cached grad-decomp slice metadata (element indices into `grad_buf`).
|
||||
@@ -1011,9 +1037,9 @@ pub struct GpuDqnTrainer {
|
||||
grad_decomp_kernel: CudaFunction,
|
||||
/// Host-side cached per-component norms populated at epoch boundary
|
||||
/// from the pinned result slot. Index order matches `grad_mag_*_ratio`
|
||||
/// accessors: `[iqn, cql, c51, ens]`.
|
||||
grad_component_norms_mag: [f32; 4],
|
||||
grad_component_norms_dir: [f32; 4],
|
||||
/// accessors: `[iqn, cql, cql_sx, c51, c51_bs, distill, rec, pred, ens]`.
|
||||
grad_component_norms_mag: [f32; 9],
|
||||
grad_component_norms_dir: [f32; 9],
|
||||
params_buf: CudaSlice<f32>, // [TOTAL_PARAMS] f32 master online parameters (Adam operates here)
|
||||
target_params_buf: CudaSlice<f32>, // [TOTAL_PARAMS] f32 master target parameters (EMA operates here)
|
||||
m_buf: CudaSlice<f32>, // [TOTAL_PARAMS] Adam first moment (f32 for precision)
|
||||
@@ -2280,33 +2306,80 @@ impl GpuDqnTrainer {
|
||||
|
||||
/// Task 2.0 — convenience: snapshot `grad_buf` into the component's
|
||||
/// snapshot buffer (captured DtoD via `copy_f32` kernel). Called BEFORE
|
||||
/// the component's backward fires.
|
||||
/// the component's backward / scaling op fires.
|
||||
pub(crate) fn grad_decomp_snapshot_iqn(&self) -> Result<(), MLError> {
|
||||
self.grad_decomp_snapshot(&self.grad_snapshot_iqn, "grad_snapshot_iqn")
|
||||
}
|
||||
pub(crate) fn grad_decomp_snapshot_cql(&self) -> Result<(), MLError> {
|
||||
self.grad_decomp_snapshot(&self.grad_snapshot_cql, "grad_snapshot_cql")
|
||||
}
|
||||
/// Task 2.0 extension — snapshot before `apply_cql_saxpy` (isolates the
|
||||
/// SAXPY step from the CQL-logit-gradient + cuBLAS-backward portion of
|
||||
/// `apply_cql_gradient`, which writes into `cql_grad_scratch` not
|
||||
/// `grad_buf`).
|
||||
pub(crate) fn grad_decomp_snapshot_cql_sx(&self) -> Result<(), MLError> {
|
||||
self.grad_decomp_snapshot(&self.grad_snapshot_cql_sx, "grad_snapshot_cql_sx")
|
||||
}
|
||||
/// Task 2.0 extension — snapshot before `apply_c51_budget_scale`. The
|
||||
/// scale op multiplies `grad_buf` in place by `c51_budget`, so delta =
|
||||
/// current − snapshot exposes the amplitude change the scale applied
|
||||
/// (negative-signed on both dir and mag components when c51_budget < 1).
|
||||
pub(crate) fn grad_decomp_snapshot_c51_bs(&self) -> Result<(), MLError> {
|
||||
self.grad_decomp_snapshot(&self.grad_snapshot_c51_bs, "grad_snapshot_c51_bs")
|
||||
}
|
||||
/// Task 2.0 extension — snapshot before `apply_distillation_gradient`.
|
||||
pub(crate) fn grad_decomp_snapshot_distill(&self) -> Result<(), MLError> {
|
||||
self.grad_decomp_snapshot(&self.grad_snapshot_distill, "grad_snapshot_distill")
|
||||
}
|
||||
/// Task 2.0 extension — snapshot before
|
||||
/// `launch_recursive_confidence_backward`.
|
||||
pub(crate) fn grad_decomp_snapshot_rec(&self) -> Result<(), MLError> {
|
||||
self.grad_decomp_snapshot(&self.grad_snapshot_rec, "grad_snapshot_rec")
|
||||
}
|
||||
/// Task 2.0 extension — snapshot before `compute_predictive_coding_loss`.
|
||||
pub(crate) fn grad_decomp_snapshot_pred(&self) -> Result<(), MLError> {
|
||||
self.grad_decomp_snapshot(&self.grad_snapshot_pred, "grad_snapshot_pred")
|
||||
}
|
||||
pub(crate) fn grad_decomp_snapshot_ens(&self) -> Result<(), MLError> {
|
||||
self.grad_decomp_snapshot(&self.grad_snapshot_ens, "grad_snapshot_ens")
|
||||
}
|
||||
|
||||
/// Task 2.0 — convenience: launch the reduction kernel for each component
|
||||
/// with its pre-configured snapshot buffer and pinned-slot offset.
|
||||
///
|
||||
/// Pinned-slot offsets (2 floats per component — mag then dir):
|
||||
/// iqn = 0, cql = 2, cql_sx = 4,
|
||||
/// c51 = 6, c51_bs = 8, distill = 10,
|
||||
/// rec = 12, pred = 14, ens = 16.
|
||||
pub(crate) fn grad_decomp_launch_iqn(&self) -> Result<(), MLError> {
|
||||
self.launch_grad_decomp(&self.grad_snapshot_iqn, 0, "iqn")
|
||||
}
|
||||
pub(crate) fn grad_decomp_launch_cql(&self) -> Result<(), MLError> {
|
||||
self.launch_grad_decomp(&self.grad_snapshot_cql, 2, "cql")
|
||||
}
|
||||
pub(crate) fn grad_decomp_launch_cql_sx(&self) -> Result<(), MLError> {
|
||||
self.launch_grad_decomp(&self.grad_snapshot_cql_sx, 4, "cql_sx")
|
||||
}
|
||||
/// C51 uses `grad_zero_ref_buf` (permanent zeros) because `grad_buf` is
|
||||
/// zero before `submit_forward_ops_main` — there is no pre-C51 snapshot.
|
||||
/// Kernel computes `‖grad_buf − 0‖` on branch 0+1 slices.
|
||||
pub(crate) fn grad_decomp_launch_c51(&self) -> Result<(), MLError> {
|
||||
self.launch_grad_decomp(&self.grad_zero_ref_buf, 4, "c51")
|
||||
self.launch_grad_decomp(&self.grad_zero_ref_buf, 6, "c51")
|
||||
}
|
||||
pub(crate) fn grad_decomp_launch_c51_bs(&self) -> Result<(), MLError> {
|
||||
self.launch_grad_decomp(&self.grad_snapshot_c51_bs, 8, "c51_bs")
|
||||
}
|
||||
pub(crate) fn grad_decomp_launch_distill(&self) -> Result<(), MLError> {
|
||||
self.launch_grad_decomp(&self.grad_snapshot_distill, 10, "distill")
|
||||
}
|
||||
pub(crate) fn grad_decomp_launch_rec(&self) -> Result<(), MLError> {
|
||||
self.launch_grad_decomp(&self.grad_snapshot_rec, 12, "rec")
|
||||
}
|
||||
pub(crate) fn grad_decomp_launch_pred(&self) -> Result<(), MLError> {
|
||||
self.launch_grad_decomp(&self.grad_snapshot_pred, 14, "pred")
|
||||
}
|
||||
pub(crate) fn grad_decomp_launch_ens(&self) -> Result<(), MLError> {
|
||||
self.launch_grad_decomp(&self.grad_snapshot_ens, 6, "ens")
|
||||
self.launch_grad_decomp(&self.grad_snapshot_ens, 16, "ens")
|
||||
}
|
||||
|
||||
/// Task 2.0 — populate the host-side `grad_component_norms_mag/_dir`
|
||||
@@ -2319,12 +2392,21 @@ impl GpuDqnTrainer {
|
||||
/// the last-step kernel output.
|
||||
///
|
||||
/// Pinned layout (written by `grad_component_delta_norm`, 2 floats per
|
||||
/// component — mag then dir):
|
||||
/// [0]=iqn_mag [1]=iqn_dir [2]=cql_mag [3]=cql_dir
|
||||
/// [4]=c51_mag [5]=c51_dir [6]=ens_mag [7]=ens_dir
|
||||
/// component — mag then dir). Task 2.0 extension grows this from 8 → 18
|
||||
/// floats to cover 9 measurement points:
|
||||
/// [ 0]=iqn_mag [ 1]=iqn_dir
|
||||
/// [ 2]=cql_mag [ 3]=cql_dir
|
||||
/// [ 4]=cql_sx_mag [ 5]=cql_sx_dir
|
||||
/// [ 6]=c51_mag [ 7]=c51_dir
|
||||
/// [ 8]=c51_bs_mag [ 9]=c51_bs_dir
|
||||
/// [10]=distill_mag [11]=distill_dir
|
||||
/// [12]=rec_mag [13]=rec_dir
|
||||
/// [14]=pred_mag [15]=pred_dir
|
||||
/// [16]=ens_mag [17]=ens_dir
|
||||
///
|
||||
/// Host caches use `[iqn, cql, c51, ens]` index order matching the
|
||||
/// `grad_mag_{iqn,cql,c51,ens}_ratio` accessor families.
|
||||
/// Host caches use `[iqn, cql, cql_sx, c51, c51_bs, distill, rec, pred,
|
||||
/// ens]` index order matching the `grad_mag_{iqn,cql,cql_sx,c51,c51_bs,
|
||||
/// distill,rec,pred,ens}_ratio` accessor families.
|
||||
pub fn refresh_grad_component_norms(&mut self) -> Result<(), MLError> {
|
||||
if self.grad_decomp_result_pinned.is_null() {
|
||||
return Err(MLError::ModelError(
|
||||
@@ -2335,9 +2417,9 @@ impl GpuDqnTrainer {
|
||||
MLError::ModelError(format!("grad_component_norms sync: {e}"))
|
||||
})?;
|
||||
let host_slice: &[f32] = unsafe {
|
||||
std::slice::from_raw_parts(self.grad_decomp_result_pinned, 8)
|
||||
std::slice::from_raw_parts(self.grad_decomp_result_pinned, 18)
|
||||
};
|
||||
for comp in 0..4_usize {
|
||||
for comp in 0..9_usize {
|
||||
self.grad_component_norms_mag[comp] = host_slice[2 * comp];
|
||||
self.grad_component_norms_dir[comp] = host_slice[2 * comp + 1];
|
||||
}
|
||||
@@ -2346,11 +2428,11 @@ impl GpuDqnTrainer {
|
||||
|
||||
/// Task 2.0 — accessor for cached per-component magnitude norms
|
||||
/// populated by `refresh_grad_component_norms`. Index order:
|
||||
/// `[iqn, cql, c51, ens]`.
|
||||
pub fn grad_component_norms_mag(&self) -> [f32; 4] { self.grad_component_norms_mag }
|
||||
/// `[iqn, cql, cql_sx, c51, c51_bs, distill, rec, pred, ens]`.
|
||||
pub fn grad_component_norms_mag(&self) -> [f32; 9] { self.grad_component_norms_mag }
|
||||
/// Task 2.0 — accessor for cached per-component direction norms
|
||||
/// populated by `refresh_grad_component_norms`.
|
||||
pub fn grad_component_norms_dir(&self) -> [f32; 4] { self.grad_component_norms_dir }
|
||||
pub fn grad_component_norms_dir(&self) -> [f32; 9] { self.grad_component_norms_dir }
|
||||
|
||||
/// Task 0.6 — per-branch target/online parameter drift (H8 detection signal).
|
||||
///
|
||||
@@ -5996,6 +6078,10 @@ impl GpuDqnTrainer {
|
||||
.map_err(|e| MLError::ModelError(format!("alloc grad_snapshot_iqn: {e}")))?;
|
||||
let grad_snapshot_cql = stream.alloc_zeros::<f32>(grad_decomp_snapshot_len)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc grad_snapshot_cql: {e}")))?;
|
||||
// Task 2.0 extension — isolate `apply_cql_saxpy` (the point where the
|
||||
// CQL scratch is actually added into grad_buf with budget scaling).
|
||||
let grad_snapshot_cql_sx = stream.alloc_zeros::<f32>(grad_decomp_snapshot_len)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc grad_snapshot_cql_sx: {e}")))?;
|
||||
// C51 fires BEFORE the aux phase and `grad_buf` is memset-zeroed at the
|
||||
// start of `submit_forward_ops_main` — no "pre-C51" grad exists to
|
||||
// snapshot. `grad_zero_ref_buf` is initialized to zeros and never
|
||||
@@ -6003,20 +6089,30 @@ impl GpuDqnTrainer {
|
||||
// the C51 path.
|
||||
let grad_zero_ref_buf = stream.alloc_zeros::<f32>(grad_decomp_snapshot_len)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc grad_zero_ref_buf: {e}")))?;
|
||||
// Task 2.0 extension — multiplicative scale + additive-writer snapshots.
|
||||
let grad_snapshot_c51_bs = stream.alloc_zeros::<f32>(grad_decomp_snapshot_len)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc grad_snapshot_c51_bs: {e}")))?;
|
||||
let grad_snapshot_distill = stream.alloc_zeros::<f32>(grad_decomp_snapshot_len)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc grad_snapshot_distill: {e}")))?;
|
||||
let grad_snapshot_rec = stream.alloc_zeros::<f32>(grad_decomp_snapshot_len)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc grad_snapshot_rec: {e}")))?;
|
||||
let grad_snapshot_pred = stream.alloc_zeros::<f32>(grad_decomp_snapshot_len)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc grad_snapshot_pred: {e}")))?;
|
||||
let grad_snapshot_ens = stream.alloc_zeros::<f32>(grad_decomp_snapshot_len)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc grad_snapshot_ens: {e}")))?;
|
||||
|
||||
// Pinned device-mapped 8-float result slot. Device writes via
|
||||
// Pinned device-mapped 18-float result slot (Task 2.0 extension grows
|
||||
// from 8 → 18 floats for 9 measurement components). Device writes via
|
||||
// `grad_decomp_result_dev_ptr` (captured in graph); host reads via
|
||||
// `grad_decomp_result_pinned` at epoch boundary (zero-copy).
|
||||
let grad_decomp_result_pinned: *mut f32 = unsafe {
|
||||
let flags = cudarc::driver::sys::CU_MEMHOSTALLOC_DEVICEMAP;
|
||||
cudarc::driver::result::malloc_host(8 * std::mem::size_of::<f32>(), flags)
|
||||
cudarc::driver::result::malloc_host(18 * std::mem::size_of::<f32>(), flags)
|
||||
.map_err(|e| MLError::ModelError(format!("pinned grad_decomp_result alloc: {e}")))?
|
||||
as *mut f32
|
||||
};
|
||||
unsafe {
|
||||
for i in 0..8 { *grad_decomp_result_pinned.add(i) = 0.0; }
|
||||
for i in 0..18 { *grad_decomp_result_pinned.add(i) = 0.0; }
|
||||
}
|
||||
let grad_decomp_result_dev_ptr = unsafe {
|
||||
let mut dp = 0u64;
|
||||
@@ -7899,7 +7995,12 @@ impl GpuDqnTrainer {
|
||||
grad_readback_pinned_capacity,
|
||||
grad_snapshot_iqn,
|
||||
grad_snapshot_cql,
|
||||
grad_snapshot_cql_sx,
|
||||
grad_zero_ref_buf,
|
||||
grad_snapshot_c51_bs,
|
||||
grad_snapshot_distill,
|
||||
grad_snapshot_rec,
|
||||
grad_snapshot_pred,
|
||||
grad_snapshot_ens,
|
||||
grad_decomp_result_pinned,
|
||||
grad_decomp_result_dev_ptr,
|
||||
@@ -7909,8 +8010,8 @@ impl GpuDqnTrainer {
|
||||
grad_decomp_mag_len,
|
||||
grad_decomp_snapshot_len,
|
||||
grad_decomp_kernel,
|
||||
grad_component_norms_mag: [0.0_f32; 4],
|
||||
grad_component_norms_dir: [0.0_f32; 4],
|
||||
grad_component_norms_mag: [0.0_f32; 9],
|
||||
grad_component_norms_dir: [0.0_f32; 9],
|
||||
params_buf,
|
||||
target_params_buf,
|
||||
m_buf,
|
||||
|
||||
@@ -903,30 +903,72 @@ impl FusedTrainingCtx {
|
||||
|
||||
/// Task 2.0 — IQN magnitude/direction grad-norm ratio. Index 0 in the
|
||||
/// `grad_component_norms_*` caches (populated by
|
||||
/// `refresh_grad_component_norms`).
|
||||
/// `refresh_grad_component_norms`). Index order (9 slots, Task 2.0
|
||||
/// extension):
|
||||
/// `[iqn, cql, cql_sx, c51, c51_bs, distill, rec, pred, ens]`.
|
||||
pub(crate) fn grad_mag_iqn_ratio(&self) -> f32 {
|
||||
let m = self.trainer.grad_component_norms_mag()[0];
|
||||
let d = self.trainer.grad_component_norms_dir()[0];
|
||||
if d > 1e-9 { m / d } else { 0.0 }
|
||||
}
|
||||
/// Task 2.0 — CQL magnitude/direction grad-norm ratio (index 1).
|
||||
/// Task 2.0 — CQL magnitude/direction grad-norm ratio (index 1; spans
|
||||
/// the snapshot→reduction pair that wraps `apply_cql_gradient` +
|
||||
/// `apply_cql_saxpy` together).
|
||||
pub(crate) fn grad_mag_cql_ratio(&self) -> f32 {
|
||||
let m = self.trainer.grad_component_norms_mag()[1];
|
||||
let d = self.trainer.grad_component_norms_dir()[1];
|
||||
if d > 1e-9 { m / d } else { 0.0 }
|
||||
}
|
||||
/// Task 2.0 — C51 magnitude/direction grad-norm ratio (index 2).
|
||||
pub(crate) fn grad_mag_c51_ratio(&self) -> f32 {
|
||||
/// Task 2.0 extension — CQL-SAXPY magnitude/direction grad-norm ratio
|
||||
/// (index 2; isolates only `apply_cql_saxpy` — the budget-scaled add of
|
||||
/// `cql_grad_scratch` into `grad_buf`).
|
||||
pub(crate) fn grad_mag_cql_sx_ratio(&self) -> f32 {
|
||||
let m = self.trainer.grad_component_norms_mag()[2];
|
||||
let d = self.trainer.grad_component_norms_dir()[2];
|
||||
if d > 1e-9 { m / d } else { 0.0 }
|
||||
}
|
||||
/// Task 2.0 — Ensemble magnitude/direction grad-norm ratio (index 3).
|
||||
pub(crate) fn grad_mag_ens_ratio(&self) -> f32 {
|
||||
/// Task 2.0 — C51 magnitude/direction grad-norm ratio (index 3; pre-
|
||||
/// budget-scale — same semantics as first-pass Task 2.0).
|
||||
pub(crate) fn grad_mag_c51_ratio(&self) -> f32 {
|
||||
let m = self.trainer.grad_component_norms_mag()[3];
|
||||
let d = self.trainer.grad_component_norms_dir()[3];
|
||||
if d > 1e-9 { m / d } else { 0.0 }
|
||||
}
|
||||
/// Task 2.0 extension — C51-budget-scale magnitude/direction delta ratio
|
||||
/// (index 4; isolates the multiplicative amplitude change applied by
|
||||
/// `apply_c51_budget_scale`).
|
||||
pub(crate) fn grad_mag_c51_bs_ratio(&self) -> f32 {
|
||||
let m = self.trainer.grad_component_norms_mag()[4];
|
||||
let d = self.trainer.grad_component_norms_dir()[4];
|
||||
if d > 1e-9 { m / d } else { 0.0 }
|
||||
}
|
||||
/// Task 2.0 extension — Distillation magnitude/direction grad-norm ratio
|
||||
/// (index 5; isolates `apply_distillation_gradient`).
|
||||
pub(crate) fn grad_mag_distill_ratio(&self) -> f32 {
|
||||
let m = self.trainer.grad_component_norms_mag()[5];
|
||||
let d = self.trainer.grad_component_norms_dir()[5];
|
||||
if d > 1e-9 { m / d } else { 0.0 }
|
||||
}
|
||||
/// Task 2.0 extension — Recursive-confidence magnitude/direction grad-
|
||||
/// norm ratio (index 6; isolates `launch_recursive_confidence_backward`).
|
||||
pub(crate) fn grad_mag_rec_ratio(&self) -> f32 {
|
||||
let m = self.trainer.grad_component_norms_mag()[6];
|
||||
let d = self.trainer.grad_component_norms_dir()[6];
|
||||
if d > 1e-9 { m / d } else { 0.0 }
|
||||
}
|
||||
/// Task 2.0 extension — Predictive-coding magnitude/direction grad-norm
|
||||
/// ratio (index 7; isolates `compute_predictive_coding_loss`).
|
||||
pub(crate) fn grad_mag_pred_ratio(&self) -> f32 {
|
||||
let m = self.trainer.grad_component_norms_mag()[7];
|
||||
let d = self.trainer.grad_component_norms_dir()[7];
|
||||
if d > 1e-9 { m / d } else { 0.0 }
|
||||
}
|
||||
/// Task 2.0 — Ensemble magnitude/direction grad-norm ratio (index 8).
|
||||
pub(crate) fn grad_mag_ens_ratio(&self) -> f32 {
|
||||
let m = self.trainer.grad_component_norms_mag()[8];
|
||||
let d = self.trainer.grad_component_norms_dir()[8];
|
||||
if d > 1e-9 { m / d } else { 0.0 }
|
||||
}
|
||||
|
||||
/// Task 0.6 — per-branch target/online param drift (H8 detection signal).
|
||||
/// Returns `[direction, magnitude, order, urgency]` RMS ‖target − online‖.
|
||||
@@ -1231,8 +1273,16 @@ impl FusedTrainingCtx {
|
||||
// C51 backward already wrote into grad_buf during submit_forward_ops_main (Phase 2).
|
||||
// This scale MUST run before CQL/IQN SAXPYs below so only C51's portion is scaled.
|
||||
// Order: c51_budget × C51_grad (here), then cql_budget × CQL_grad, iqn_budget × IQN_grad.
|
||||
//
|
||||
// Task 2.0 extension — snapshot BEFORE the multiplicative scale so the
|
||||
// post-scale reduction measures the amplitude change on branch 0+1.
|
||||
// When `c51_budget ≈ 1.0` the scale is a no-op and delta = 0.0.
|
||||
self.trainer.grad_decomp_snapshot_c51_bs()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp_snapshot_c51_bs: {e}"))?;
|
||||
self.trainer.apply_c51_budget_scale(c51_budget)
|
||||
.map_err(|e| anyhow::anyhow!("c51_budget_scale: {e}"))?;
|
||||
self.trainer.grad_decomp_launch_c51_bs()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp c51_bs: {e}"))?;
|
||||
|
||||
// DIAGNOSTIC: sync between each aux op to find hanging kernel
|
||||
|
||||
@@ -1568,14 +1618,40 @@ impl FusedTrainingCtx {
|
||||
if self.trainer.has_cql() {
|
||||
match self.trainer.apply_cql_gradient(f5_barrier_weight) {
|
||||
Ok(true) => {
|
||||
// Task 2.0 extension — isolate `apply_cql_saxpy`. The
|
||||
// preceding `apply_cql_gradient` only writes into
|
||||
// `cql_grad_scratch`, not `grad_buf`, so the cql_sx
|
||||
// delta captures exactly the budget-scaled SAXPY into
|
||||
// `grad_buf`.
|
||||
self.trainer.grad_decomp_snapshot_cql_sx()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp_snapshot_cql_sx: {e}"))?;
|
||||
self.trainer.apply_cql_saxpy(cql_budget)
|
||||
.map_err(|e| anyhow::anyhow!("CQL SAXPY: {e}"))?;
|
||||
self.trainer.grad_decomp_launch_cql_sx()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp cql_sx: {e}"))?;
|
||||
}
|
||||
Ok(false) => {
|
||||
// Keep cql_sx slot populated on skip path — delta = 0.
|
||||
self.trainer.grad_decomp_snapshot_cql_sx()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp_snapshot_cql_sx (skip): {e}"))?;
|
||||
self.trainer.grad_decomp_launch_cql_sx()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp cql_sx (skip): {e}"))?;
|
||||
}
|
||||
Ok(false) => {}
|
||||
Err(e) => {
|
||||
tracing::warn!("CQL gradient failed (non-fatal): {e}");
|
||||
// Keep cql_sx slot populated on error path — delta = 0.
|
||||
self.trainer.grad_decomp_snapshot_cql_sx()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp_snapshot_cql_sx (err): {e}"))?;
|
||||
self.trainer.grad_decomp_launch_cql_sx()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp cql_sx (err): {e}"))?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Keep cql_sx slot populated on CQL-disabled path — delta = 0.
|
||||
self.trainer.grad_decomp_snapshot_cql_sx()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp_snapshot_cql_sx (no-cql): {e}"))?;
|
||||
self.trainer.grad_decomp_launch_cql_sx()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp cql_sx (no-cql): {e}"))?;
|
||||
}
|
||||
|
||||
// Task 2.0 — CQL reduction. Delta = grad_buf − snapshot over branch 0+1.
|
||||
@@ -1593,14 +1669,27 @@ impl FusedTrainingCtx {
|
||||
// epoch-boundary placement wrote to grad_buf AFTER the last step,
|
||||
// and the next step's graph_forward memset zeroed it before Adam
|
||||
// could consume it.
|
||||
//
|
||||
// Task 2.0 extension — isolate `apply_distillation_gradient` delta.
|
||||
self.trainer.grad_decomp_snapshot_distill()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp_snapshot_distill: {e}"))?;
|
||||
self.trainer.apply_distillation_gradient()
|
||||
.map_err(|e| anyhow::anyhow!("distill SAXPY: {e}"))?;
|
||||
self.trainer.grad_decomp_launch_distill()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp distill: {e}"))?;
|
||||
|
||||
|
||||
// Recursive confidence backward: MSE grad into trunk + conf weight gradients.
|
||||
// Must run before Adam (which reads grad_buf for the parameter update).
|
||||
//
|
||||
// Task 2.0 extension — isolate `launch_recursive_confidence_backward`
|
||||
// delta.
|
||||
self.trainer.grad_decomp_snapshot_rec()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp_snapshot_rec: {e}"))?;
|
||||
self.trainer.launch_recursive_confidence_backward(self.batch_size)
|
||||
.map_err(|e| anyhow::anyhow!("Recursive confidence backward: {e}"))?;
|
||||
self.trainer.grad_decomp_launch_rec()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp rec: {e}"))?;
|
||||
|
||||
// G12: Predictive coding auxiliary loss + backward.
|
||||
// Self-supervised temporal smoothness on the enriched trunk h_s2 —
|
||||
@@ -1608,8 +1697,15 @@ impl FusedTrainingCtx {
|
||||
// bw_d_h_s2. Runs HERE so the gradient lands in bw_d_h_s2 before
|
||||
// the trunk backward GEMMs consume it (and before recursive
|
||||
// confidence's own trunk gradient — both accumulate via plain +=).
|
||||
//
|
||||
// Task 2.0 extension — isolate `compute_predictive_coding_loss`
|
||||
// delta.
|
||||
self.trainer.grad_decomp_snapshot_pred()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp_snapshot_pred: {e}"))?;
|
||||
self.trainer.compute_predictive_coding_loss(self.batch_size)
|
||||
.map_err(|e| anyhow::anyhow!("Predictive coding (G12): {e}"))?;
|
||||
self.trainer.grad_decomp_launch_pred()
|
||||
.map_err(|e| anyhow::anyhow!("Task 2.0 grad_decomp pred: {e}"))?;
|
||||
|
||||
// G6 + G10 measurement (commit 61ab27ff3) found both penalties are
|
||||
// dominated by existing mechanisms:
|
||||
|
||||
@@ -2228,27 +2228,40 @@ impl DQNTrainer {
|
||||
.map(|f| f.grad_ratio_mag_dir())
|
||||
.unwrap_or(0.0);
|
||||
|
||||
// Task 2.0 — per-component grad decomposition (IQN/CQL/C51/Ens).
|
||||
// Refresh the cached norms from the pinned result slot populated
|
||||
// by the in-graph reduction kernel on the last step of this epoch,
|
||||
// then compute mag/dir ratios per component. Non-fatal on failure
|
||||
// (warn + stale values) — same pattern as update_q_mag_means_cached.
|
||||
let (grad_mag_iqn, grad_mag_cql, grad_mag_c51, grad_mag_ens) =
|
||||
if let Some(fused) = self.fused_ctx.as_mut() {
|
||||
if let Err(e) = fused.refresh_grad_component_norms() {
|
||||
tracing::warn!(
|
||||
"HEALTH_DIAG grad_component_norms refresh failed: {e}"
|
||||
);
|
||||
}
|
||||
(
|
||||
fused.grad_mag_iqn_ratio(),
|
||||
fused.grad_mag_cql_ratio(),
|
||||
fused.grad_mag_c51_ratio(),
|
||||
fused.grad_mag_ens_ratio(),
|
||||
)
|
||||
} else {
|
||||
(0.0_f32, 0.0_f32, 0.0_f32, 0.0_f32)
|
||||
};
|
||||
// Task 2.0 (+ extension) — per-component grad decomposition across
|
||||
// 9 measurement points: IQN / CQL / CQL-SAXPY / C51 / C51-budget-
|
||||
// scale / Distillation / Recursive-confidence / Predictive-coding /
|
||||
// Ensemble. Refresh the cached norms from the pinned result slot
|
||||
// populated by the in-graph reduction kernel on the last step of
|
||||
// this epoch, then compute mag/dir ratios per component. Non-fatal
|
||||
// on failure (warn + stale values) — same pattern as
|
||||
// update_q_mag_means_cached.
|
||||
let (
|
||||
grad_mag_iqn, grad_mag_cql, grad_mag_cql_sx,
|
||||
grad_mag_c51, grad_mag_c51_bs,
|
||||
grad_mag_distill, grad_mag_rec, grad_mag_pred,
|
||||
grad_mag_ens,
|
||||
) = if let Some(fused) = self.fused_ctx.as_mut() {
|
||||
if let Err(e) = fused.refresh_grad_component_norms() {
|
||||
tracing::warn!(
|
||||
"HEALTH_DIAG grad_component_norms refresh failed: {e}"
|
||||
);
|
||||
}
|
||||
(
|
||||
fused.grad_mag_iqn_ratio(),
|
||||
fused.grad_mag_cql_ratio(),
|
||||
fused.grad_mag_cql_sx_ratio(),
|
||||
fused.grad_mag_c51_ratio(),
|
||||
fused.grad_mag_c51_bs_ratio(),
|
||||
fused.grad_mag_distill_ratio(),
|
||||
fused.grad_mag_rec_ratio(),
|
||||
fused.grad_mag_pred_ratio(),
|
||||
fused.grad_mag_ens_ratio(),
|
||||
)
|
||||
} else {
|
||||
(0.0_f32, 0.0_f32, 0.0_f32, 0.0_f32, 0.0_f32,
|
||||
0.0_f32, 0.0_f32, 0.0_f32, 0.0_f32)
|
||||
};
|
||||
|
||||
// Task 0.6 — per-branch NoisyNets σ mean. H7 detection signal.
|
||||
// 0 = direction head, 1 = magnitude head.
|
||||
@@ -2592,7 +2605,7 @@ impl DQNTrainer {
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
"HEALTH_DIAG[{}]: health={:.2} components [q_gap={:.2} q_var={:.2} atoms={:.2} grad_stable={:.2} ens_agree={:.2} grad_cos={:.2} spectral={:.2}] effective [cql_alpha={:.4} iqn_budget={:.2} cql_budget={:.2} c51_budget={:.2} tau={:.5} sarsa_tau={:.2} gamma={:.3} cf_ratio={:.2}] novels [distill={} barrier={:.3} plasticity={} ib={:.3} ensemble_collapse={:.2} contrarian={} meta_q_pred={:.2}] diag [sharpe_ema={:.3} action_entropy={:.2}] gems [g12_predictive={:.4}] mag [q_full={:.3} q_half={:.3} q_quarter={:.3} var_scale={:.3} kelly_f={:.3} avg_win_ratio={:.3} grad_ratio_mag_dir={:.4} dist_q={:.3} dist_h={:.3} dist_f={:.3}] grad_split [iqn={:.4} cql={:.4} c51={:.4} ens={:.4}] trail [fire_q={:.3} fire_h={:.3} fire_f={:.3} hold_q={:.2} hold_h={:.2} hold_f={:.2}] noisy [vsn_mag={:.3} vsn_dir={:.3} sigma_mag={:.4} sigma_dir={:.4} drift_mag={:.3} drift_dir={:.3}] eval_dist [eq={:.3} eh={:.3} ef={:.3}] reward_contrib [popart={:.3} cf={:.3} trail_r={:.3} micro={:.3} la={:.3}] controller [anti_lr={} tau={} gamma={} clip={} cql={} cost={} fire_frac={:.2}] explore [ent_mag={:.2} ent_dir={:.2} sigma_mean={:.4}]",
|
||||
"HEALTH_DIAG[{}]: health={:.2} components [q_gap={:.2} q_var={:.2} atoms={:.2} grad_stable={:.2} ens_agree={:.2} grad_cos={:.2} spectral={:.2}] effective [cql_alpha={:.4} iqn_budget={:.2} cql_budget={:.2} c51_budget={:.2} tau={:.5} sarsa_tau={:.2} gamma={:.3} cf_ratio={:.2}] novels [distill={} barrier={:.3} plasticity={} ib={:.3} ensemble_collapse={:.2} contrarian={} meta_q_pred={:.2}] diag [sharpe_ema={:.3} action_entropy={:.2}] gems [g12_predictive={:.4}] mag [q_full={:.3} q_half={:.3} q_quarter={:.3} var_scale={:.3} kelly_f={:.3} avg_win_ratio={:.3} grad_ratio_mag_dir={:.4} dist_q={:.3} dist_h={:.3} dist_f={:.3}] grad_split_bwd [iqn={:.4} cql={:.4} c51={:.4} ens={:.4}] grad_split_aux [distill={:.4} rec={:.4} pred={:.4} cql_sx={:.4} c51_bs={:.4}] trail [fire_q={:.3} fire_h={:.3} fire_f={:.3} hold_q={:.2} hold_h={:.2} hold_f={:.2}] noisy [vsn_mag={:.3} vsn_dir={:.3} sigma_mag={:.4} sigma_dir={:.4} drift_mag={:.3} drift_dir={:.3}] eval_dist [eq={:.3} eh={:.3} ef={:.3}] reward_contrib [popart={:.3} cf={:.3} trail_r={:.3} micro={:.3} la={:.3}] controller [anti_lr={} tau={} gamma={} clip={} cql={} cost={} fire_frac={:.2}] explore [ent_mag={:.2} ent_dir={:.2} sigma_mean={:.4}]",
|
||||
epoch,
|
||||
health_value,
|
||||
self.learning_health.components.q_gap_norm,
|
||||
@@ -2635,12 +2648,29 @@ impl DQNTrainer {
|
||||
avg_win_ratio,
|
||||
grad_ratio_mag_dir,
|
||||
dist_q, dist_h, dist_f,
|
||||
// Task 2.0 — per-component grad decomposition (4 f32):
|
||||
// iqn / cql / c51 / ens magnitude/direction grad-norm
|
||||
// ratios. In-graph pinned-snapshot + reduction-kernel
|
||||
// pipeline; zero-copy readback at epoch boundary.
|
||||
// Feeds Task 2.1 decision tree for the H4 fix.
|
||||
// Task 2.0 (+ extension) — per-component grad decomposition
|
||||
// (9 f32 split across two HEALTH_DIAG groups to keep each
|
||||
// under ~500 chars and separate backward writers from aux-
|
||||
// graph writers + multiplicative scalings):
|
||||
//
|
||||
// grad_split_bwd [iqn cql c51 ens]
|
||||
// The four original Task 2.0 measurement points — full
|
||||
// backward-gradient contributions from each loss family.
|
||||
//
|
||||
// grad_split_aux [distill rec pred cql_sx c51_bs]
|
||||
// The five Task 2.0 extension points inside the aux-graph
|
||||
// gap between CQL and Ens: three auxiliary-loss writers
|
||||
// (distill / recursive-confidence / predictive-coding)
|
||||
// and two multiplicative scalings (cql_sx = apply_cql_
|
||||
// saxpy; c51_bs = apply_c51_budget_scale). Localizes
|
||||
// exactly which stage zeroes magnitude gradient.
|
||||
//
|
||||
// In-graph pinned-snapshot + reduction-kernel pipeline;
|
||||
// zero-copy readback at epoch boundary. Feeds Task 2.1
|
||||
// decision tree for the H4 fix.
|
||||
grad_mag_iqn, grad_mag_cql, grad_mag_c51, grad_mag_ens,
|
||||
grad_mag_distill, grad_mag_rec, grad_mag_pred,
|
||||
grad_mag_cql_sx, grad_mag_c51_bs,
|
||||
// Track 1 — trail (6 f32): fire_q/h/f, hold_q/h/f (H6 — Task 0.5).
|
||||
// Real values from GPU experience collector's per-sample buffers.
|
||||
trail_rates[0], trail_rates[1], trail_rates[2],
|
||||
|
||||
Reference in New Issue
Block a user