From af9373ced90bb1f0768baeea98dcd8a0e46a951f Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Mon, 20 Apr 2026 20:41:25 +0200 Subject: [PATCH] =?UTF-8?q?feat(D6/N6):=20ensemble=20as=20collapse=20oracl?= =?UTF-8?q?e=20=E2=80=94=20pairwise=20Q-gap=20triggers=20plasticity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../src/trainers/dqn/trainer/training_loop.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/ml/src/trainers/dqn/trainer/training_loop.rs b/crates/ml/src/trainers/dqn/trainer/training_loop.rs index f58511239..2ce0a8549 100644 --- a/crates/ml/src/trainers/dqn/trainer/training_loop.rs +++ b/crates/ml/src/trainers/dqn/trainer/training_loop.rs @@ -1943,6 +1943,37 @@ impl DQNTrainer { self.last_ib_penalty = Some(ib_weight * ib_penalty_raw); } + // D6/N6: Ensemble as collapse oracle. + // High ensemble_collapse_score ⇒ ensembles agree (often on uniform during collapse). + // When score > 0.8 AND we haven't already triggered N3 recently, force a plasticity + // injection — this shortcut catches collapse even when the moving-average of + // individual health inputs hasn't accumulated to 3 unhealthy epochs yet. + // Uses last_ens_disagreement (Path C scalar proxy — populated by A3 readback). + { + use crate::cuda_pipeline::learning_health::smoothstep; + let mean_pairwise_gap = self.last_ens_disagreement.unwrap_or(0.1); + let ensemble_collapse_score = 1.0 - smoothstep(0.01, 0.1, mean_pairwise_gap); + self.last_ensemble_collapse_score = Some(ensemble_collapse_score); + + if ensemble_collapse_score > 0.8 && self.unhealthy_epoch_count < 3 { + tracing::info!( + "Ensemble collapse detected (score={:.2}): forcing plasticity injection", + ensemble_collapse_score + ); + if let Some(ref mut fused) = self.fused_ctx { + let alpha = self.hyperparams.shrink_perturb_alpha as f32; + let sigma = self.hyperparams.shrink_perturb_sigma as f32; + match fused.shrink_and_perturb(alpha, sigma) { + Ok(()) => { + self.unhealthy_epoch_count = 0; + self.last_plasticity_ready = Some(false); + } + Err(e) => tracing::warn!("D6/N6 shrink_and_perturb failed: {e}"), + } + } + } + } + // HEALTH_DIAG: components are [0, 1] normalized. effective = hyperparams after health-adaptation. novels = mechanism states. 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}]",