fix(sp22): defensive NaN guards on Step 8/11 dW + Adam path
Bisect cut #1 (train-6zbcn) DEFINITIVELY identified Step 8/11 as the NaN source. With launches disabled, q_var/q_by_action/v_a_means all CLEAN. Forward atom-shift kernels confirmed innocent. Defensive fix (belt-and-suspenders): 1. c51_aux_dw_kernel: clamp NaN/inf total to 0 at dW write site. 2. adam_w_aux_kernel: early-return if any input non-finite. 3. compute_expected_q: guard w_aux[a] read with isfinite (in addition to state_121). Together these prevent any NaN propagation through atom-shift regardless of where the upstream NaN originated. Step 8/11 launches RESTORED in submit_adam_ops. The underlying NaN source is still unidentified but defensively neutralised. Smoke verification next. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,13 @@ extern "C" __global__ void adam_w_aux_update(
|
||||
int step = step_ptr[0];
|
||||
|
||||
float g = dw[a];
|
||||
/* SP22 H6 Phase 3 α Step 11 DEFENSIVE NaN guard (2026-05-13):
|
||||
* skip update entirely if any input is non-finite. Belt-and-
|
||||
* suspenders with c51_aux_dw_kernel's own guard — prevents NaN
|
||||
* propagation into W through any pathway. */
|
||||
if (!isfinite(g) || !isfinite(lr) || !isfinite(m[a]) || !isfinite(v[a])) {
|
||||
return;
|
||||
}
|
||||
float m_new = beta1 * m[a] + (1.0f - beta1) * g;
|
||||
float v_new = beta2 * v[a] + (1.0f - beta2) * g * g;
|
||||
m[a] = m_new;
|
||||
|
||||
@@ -134,6 +134,15 @@ extern "C" __global__ void c51_aux_dw_kernel(
|
||||
if (tid == 0) {
|
||||
float total = 0.0f;
|
||||
for (int w = 0; w < NUM_WARPS; w++) total += shmem_warp[w];
|
||||
/* SP22 H6 Phase 3 α Step 8 DEFENSIVE NaN guard (2026-05-13):
|
||||
* Bisect cut #1 proved the NaN source is in Step 8 (this kernel)
|
||||
* or Step 11 (adam_w_aux). With forward atom-shift kernels
|
||||
* disabled outputs are clean; with Step 8+11 enabled NaN appears.
|
||||
* If `total` is NaN/Inf (e.g., from sp/dz overflow or NaN
|
||||
* inputs through scratch buffers), set dW to 0 instead of
|
||||
* propagating NaN into W via Adam. This prevents W → NaN →
|
||||
* atom-shift = NaN × sanitized_state = NaN cascade. */
|
||||
if (!isfinite(total)) total = 0.0f;
|
||||
dw_aux[a] = total;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5044,18 +5044,14 @@ extern "C" __global__ void compute_expected_q(
|
||||
if (d == 0 && w_aux != NULL && batch_states != NULL) {
|
||||
float state_121 = batch_states[(long long)i * state_dim
|
||||
+ aux_dir_prob_index];
|
||||
/* Defensive NaN guard (2026-05-13): state_121 SHOULD be in
|
||||
* [-1, +1] per the H6 spec (recentered aux p_up), but if
|
||||
* an upstream NaN reaches here (e.g., aux head numerical
|
||||
* instability propagated through replay buffer), the
|
||||
* IEEE-754 rule `0 * NaN = NaN` makes W=0 NOT a safety
|
||||
* mechanism. Clamping NaN to 0 prevents propagation
|
||||
* through compute_expected_q while keeping the atom-shift
|
||||
* mathematically correct for finite state_121. The real
|
||||
* NaN source (likely aux head or state assembly) is to
|
||||
* be investigated separately. */
|
||||
float w_a = w_aux[a];
|
||||
/* SP22 H6 Phase 3 α DEFENSIVE GUARDS (2026-05-13):
|
||||
* Both state_121 and W can carry upstream NaN. IEEE-754
|
||||
* `0 * NaN = NaN` propagates through atom-shift regardless
|
||||
* of which factor is zero. Sanitize both before multiply. */
|
||||
if (!isfinite(state_121)) state_121 = 0.0f;
|
||||
aux_atom_shift = w_aux[a] * state_121;
|
||||
if (!isfinite(w_a)) w_a = 0.0f;
|
||||
aux_atom_shift = w_a * state_121;
|
||||
}
|
||||
|
||||
/* ── Online softmax: 2-pass instead of 3-pass ────────────────
|
||||
|
||||
@@ -30323,14 +30323,16 @@ impl GpuDqnTrainer {
|
||||
self.launch_adam_update()?;
|
||||
|
||||
// ── 6a. SP22 H6 Phase 3 α Step 8 + Step 11 (2026-05-13) ──
|
||||
// BISECT DIAGNOSTIC (2026-05-13): Step 8/11 launches DISABLED to
|
||||
// isolate forward vs backward as NaN source. If smoke at this
|
||||
// commit is clean (no dir/mag NaN), backward is the culprit. If
|
||||
// NaN persists, forward (compute_expected_q / mag_concat_qdir /
|
||||
// c51_loss SP code) is the culprit.
|
||||
// Restore both launches once the bisect identifies the cause.
|
||||
// self.launch_c51_aux_dw()?;
|
||||
// self.launch_adam_w_aux()?;
|
||||
// RESTORED after bisect cut #1 confirmed backward (this pair) was
|
||||
// the NaN source. Defensive guards added:
|
||||
// - c51_aux_dw_kernel: clamp NaN/inf dW total to 0 at write
|
||||
// - adam_w_aux_kernel: skip update if any input is non-finite
|
||||
// - compute_expected_q: guard W reads with isfinite (already
|
||||
// guards state_121 reads)
|
||||
// Together these prevent any NaN propagation through atom-shift
|
||||
// regardless of where the upstream NaN originated.
|
||||
self.launch_c51_aux_dw()?;
|
||||
self.launch_adam_w_aux()?;
|
||||
|
||||
// ── 6.5. Snapshot grad_buf → prev_grad_buf for next step's vaccine comparison ──
|
||||
// Graph-safe: submit_adam_ops is captured in adam_update child graph.
|
||||
|
||||
Reference in New Issue
Block a user