diag(nan): add flag 12 = save_h_s2 — differentiate trunk vs branch GEMM as NaN source
Previous L40S 15-epoch repro fired the wired NaN diagnostic with
flagged=[2=on_b_logits, 3=mse_loss_scalar, 6=grad_buf, 7=save_current_lp,
8=save_projected] while value stream (flag 1) and pre-forward params
(flags 4-5) stayed clean. That isolates the corruption to the branch
advantage GEMM/activation but leaves one open question: is the GEMM
input (h_s2, shared between value and branch heads) finite or already
NaN?
Flag 12 = save_h_s2 covers the post-trunk activation. Outcomes:
- flag 12 clean + flag 2 NaN → branch GEMM overflowed (advantage-side
fp32 overflow under post-S&P + adversarial reward stress).
- flag 12 NaN → trunk encoder corrupted upstream of both heads.
Mechanically: one new check_nan_f32 call against save_h_s2 in
run_nan_checks_post_forward, names array slot 12 updated rsv12 →
save_h_s2 in training_loop.rs error message. Same ~5µs cost as the
other 12 checks. Slots 13-15 still reserved.
Per `feedback_no_partial_refactor.md` (extending the NaN diagnostic
contract atomically) and `feedback_no_stubs.md` (rsv12 was a real
reservation; now consumed).
This commit is contained in:
@@ -14277,7 +14277,8 @@ impl GpuDqnTrainer {
|
||||
/// 9 moe_gate_softmax — MoE gate softmax (saturates → NaN)
|
||||
/// 10 aux_nb_loss_scalar [1] — aux next-bar MSE loss
|
||||
/// 11 aux_rg_loss_scalar [1] — aux regime CE loss
|
||||
/// 12-15 reserved (CQL / IQN / future)
|
||||
/// 12 save_h_s2 — trunk output (input to value+branch FCs)
|
||||
/// 13-15 reserved (CQL / IQN / future)
|
||||
pub fn run_nan_checks_post_forward(&mut self, batch_size: usize) -> Result<(), MLError> {
|
||||
let b = batch_size;
|
||||
let na = self.config.num_atoms;
|
||||
@@ -14314,6 +14315,12 @@ impl GpuDqnTrainer {
|
||||
self.check_nan_f32(self.aux_nb_loss_scalar_buf.raw_ptr(), 1, 10)?;
|
||||
// Flag 11: aux_rg_loss_scalar [1] (aux regime CE loss)
|
||||
self.check_nan_f32(self.aux_rg_loss_scalar_buf.raw_ptr(), 1, 11)?;
|
||||
// Flag 12: save_h_s2 (post-trunk activation, input to value FC + branch FC).
|
||||
// Diagnostic differentiator: if `on_b_logits` (flag 2) is NaN but
|
||||
// `save_h_s2` is finite, the branch GEMM itself overflowed; if both are
|
||||
// NaN, the corruption is upstream in the trunk encoder.
|
||||
let sh2 = self.config.shared_h2;
|
||||
self.check_nan_f32(self.save_h_s2.raw_ptr(), b * sh2, 12)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1860,7 +1860,8 @@ impl DQNTrainer {
|
||||
"moe_gate_softmax", // 9 MoE gate over 8 experts
|
||||
"aux_nb_loss_scalar", // 10 aux next-bar MSE [1]
|
||||
"aux_rg_loss_scalar", // 11 aux regime CE [1]
|
||||
"rsv12", "rsv13", "rsv14", "rsv15", // reserved
|
||||
"save_h_s2", // 12 trunk output (input to FCs)
|
||||
"rsv13", "rsv14", "rsv15", // reserved
|
||||
];
|
||||
let flagged: Vec<String> = flags.iter().enumerate()
|
||||
.filter(|(_, &f)| f != 0)
|
||||
|
||||
@@ -2,6 +2,36 @@
|
||||
|
||||
**Status:** Populated during Plan 1 Task 6 (A.5 orphan audit). Updated on every commit per Invariant 7.
|
||||
|
||||
NaN diagnostic flag 12 = `save_h_s2` (2026-04-28): the previous wire-up
|
||||
commit (`756b1ef31`) extended NaN coverage to flags 0-11 and proved that
|
||||
the L40S fold-1 NaN entered via `flagged=[2=on_b_logits, 3=mse_loss_scalar,
|
||||
6=grad_buf, 7=save_current_lp, 8=save_projected]` while `params_buf_pre_fwd`
|
||||
(flag 4) and `on_v_logits` (flag 1) stayed clean. That isolates the
|
||||
corruption to the branch advantage GEMM/activation (the value stream is
|
||||
fine despite sharing the same trunk input). To complete the differential
|
||||
diagnosis we add flag 12 = `save_h_s2` — the post-trunk activation that
|
||||
is the SHARED input to both the value FC and the branch FC. Two
|
||||
outcomes:
|
||||
|
||||
- flag 12 clean + flag 2 NaN → branch GEMM itself overflowed
|
||||
(likely fp32-overflow under post-S&P + adversarial reward stress
|
||||
in the advantage logits accumulation). Fix is at the branch
|
||||
forward call site / advantage scale clamp.
|
||||
- flag 12 NaN → trunk encoder corrupted upstream of both heads
|
||||
(GRN block produced NaN). Trace further into the GRN backward
|
||||
chain or the OFI/state input.
|
||||
|
||||
`run_nan_checks_post_forward` in `gpu_dqn_trainer.rs` adds one
|
||||
`check_nan_f32` call against `self.save_h_s2.raw_ptr()` with size
|
||||
`batch_size * config.shared_h2`, flag index 12. Same ~5µs cost as the
|
||||
other 12 checks already wired. `training_loop.rs` names array slot 12
|
||||
updated `"rsv12"` → `"save_h_s2"`. Slots 13-15 still reserved (CQL,
|
||||
IQN, per-branch backward). Touched files: `gpu_dqn_trainer.rs`
|
||||
(+1 line in flag map docstring, +5 lines in
|
||||
`run_nan_checks_post_forward`); `training_loop.rs` (1 line in names
|
||||
array). cargo check clean at 13 warnings (workspace baseline). No
|
||||
fingerprint change.
|
||||
|
||||
P5T5 Phase H Site 3 — fuse VSN feature-selection + GLU value/gate
|
||||
GEMM+bias (2026-04-28): migrates the remaining 4 forward sites:
|
||||
1. **VSN Linear_1** (×6 feature groups) — adds 6 per-group RELU_BIAS
|
||||
|
||||
Reference in New Issue
Block a user