khtst-rust / src /jepa /replay_ssm.rs
PowerMachine's picture
v0.10.0: análise/prova/teste das críticas de engenharia Qwen3.8 lote 3 (r-35) — divisão SRP dos monólitos jepa/pensamento/moe/python em 26 submódulos (API estável via re-exports; 'Amdahl de compilação' refutada por experimento controlado, builds frios neutros), checkpoints atômicos SHA-256 puro FIPS 180-4 + fsync + rename + manifesto (CP-1..CP-3; correções da proposta: sem destruir estado anterior, manifesto com barreira, sem bincode/unwrap), TemperaturaInversa T-V3 (fronteira tipada da convexidade N-2; alegação compile-time corrigida), trait CamadaKhtst+CamadaHomeostatica (premissa do controlador refutada no fonte; Vec<Var>; adaptador MoeComPapel bit-a-bit), CLI Typer 75 (fachada aditiva com resume verificado por hash) — 123/123 cargo, 107/107 gates, treino real Wikipédia PT-BR com checkpoints íntegros
06e4b6f verified
Raw History Blame Contribute Delete
4.17 kB
//! `jepa::replay_ssm` — extraído do monólito na v0.10.0
//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2).
use super::*;
// ---------------------------------------------------------------------------
// Parte 6 — ReplaySSM
// ---------------------------------------------------------------------------
/// SSM de porta diagonal com replay para aprendizado continuado.
///
/// Recorrência: `h_t = σ(a) ⊙ h_{t−1} + (1 − σ(a)) ⊙ (B x_t)`.
///
/// **Prova R-1 (estabilidade):** `|σ(a)| < 1` ⇒ perturbações de `h_0`
/// decaem geometricamente (mesma estrutura do Teo S-2' do SSD-Δ).
///
/// **Prova R-2 (limite de esquecimento):** o passo de treino mistura
/// gradientes `(1−α)·∇L_novo + α·∇L_replay`; para `L_antiga` μ-fortemente
/// convexa com gradientes novos limitados por `G`, o afastamento do ótimo
/// antigo por passo é `≤ (1−α)·η·G` (sem replay: `≤ η·G`) — o replay
/// contrai o drift por um fator `1/(1−α)` no regime linear.
pub struct ReplaySsm {
pub a: Var, // (d,) — porta (sigmoid no uso)
pub b: Var, // (d_in, d) — projeção de entrada
pub d: usize,
pub alpha_replay: f64,
pub buffer: Vec<Tensor>,
pub capacidade_buffer: usize,
}
impl ReplaySsm {
pub fn novo(d_in: usize, d: usize, alpha_replay: f64, capacidade_buffer: usize, dev: &Device) -> Result<Self> {
let a_t = Tensor::randn(0f32, 0.5, (d,), dev)?;
let b_t = Tensor::randn(0f32, 0.1, (d_in, d), dev)?;
Ok(Self {
a: Var::from_tensor(&a_t)?,
b: Var::from_tensor(&b_t)?,
d,
alpha_replay,
buffer: Vec::new(),
capacidade_buffer,
})
}
pub fn vars(&self) -> Vec<Var> {
vec![self.a.clone(), self.b.clone()]
}
/// Scan: x (T, d_in) → h (T, d). Diferenciável (candle autograd).
pub fn scan(&self, x: &Tensor) -> Result<Tensor> {
let t = x.dim(0)?;
let dev = x.device();
let porta = candle_nn::ops::sigmoid(&self.a.as_tensor().broadcast_as((1, self.d))?)?; // (1, d)
let um_menos = porta.affine(-1.0, 1.0)?; // 1 − σ(a)
let entrada = x.matmul(&self.b.as_tensor())?; // (T, d)
let mut h = Tensor::zeros((1, self.d), candle_core::DType::F32, &dev)?;
let mut saidas: Vec<Tensor> = Vec::with_capacity(t);
for i in 0..t {
let xt = entrada.narrow(0, i, 1)?;
h = ((&porta * &h)? + (&um_menos * &xt)?)?;
saidas.push(h.clone());
}
Ok(Tensor::stack(&saidas, 0)?)
}
/// Empurra uma janela de treino no buffer (FIFO).
pub fn guardar(&mut self, x: &Tensor) {
if self.buffer.len() >= self.capacidade_buffer {
self.buffer.remove(0);
}
self.buffer.push(x.detach());
}
/// Um passo de treino no lote novo (com mistura de replay).
/// Devolve (perda_total, perda_novo, perda_replay).
pub fn passo_treino(
&mut self,
x: &Tensor,
alvo: &Tensor,
otimizador: &mut crate::treino::AdamWManual,
) -> Result<(f64, f64, f64)> {
// perda nova
let h = self.scan(x)?;
let dif = (&h - alvo)?;
let perda_novo = dif.sqr()?.mean_all()?;
// replay
let mut perda_total = perda_novo.clone();
let mut p_r = 0.0;
if !self.buffer.is_empty() {
let idx = self.buffer.len() - 1;
let antigo = self.buffer[idx].clone();
let hr = self.scan(&antigo)?;
// alvo do replay: manter a dinâmica aprendida estável (magnitudes
// finitas) — auto-supervisão de estabilidade
let dr = hr.sqr()?.mean_all()?;
perda_total = (perda_total.affine(1.0 - self.alpha_replay, 0.0)?
+ dr.affine(self.alpha_replay, 0.0)?)?;
p_r = dr.to_scalar::<f32>()? as f64;
}
let pn = perda_novo.to_scalar::<f32>()? as f64;
otimizador.passo(crate::treino::grads_achatados(&perda_total, &self.vars())?);
self.guardar(x);
let pt = perda_total.to_scalar::<f32>()? as f64;
Ok((pt, pn, p_r))
}
}