File size: 8,619 Bytes
97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae e26f4cc 97df2ae | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | //! ADBA — Atenção Dual-Bloco Adaptativa (APRIMORAMENTO 1 do usuário:
//! "aprimoramento de Attention Mechanism durante o processamento paralelo
//! de NCM"). Prova completa na memória r-04; síntese:
//!
//! (i) [paralelismo preservado] o resumo-âncora por nível h_ℓ é função
//! APENAS de c_{<ℓ} (computado do cache/entrada corrente), logo a
//! fatoração p(c_ℓ|c_{<ℓ}) = Π_i p(c_{ℓ,i}|c_{<ℓ}) e NFE = L
//! permanecem EXATOS (Teo B.2/D.1);
//! (ii) [expressividade ⊇ NCM] com γ=0, w_ℓ'=1, τ_ℓ=1 a ADBA reduz-se à
//! atenção block-causal do NCM (classe estritamente contida);
//! (iii)[planejamento reforçado] o âncora agrega o estado global dos níveis
//! anteriores num vetor por nível — injetado por FiLM — fortalecendo a
//! hipótese H2 do Teo C.2 (condicionais consistentes dado o plano).
//!
//! Componentes: (a) atenção multi-cabeça com máscara aditiva arbitrária +
//! KV-cache; (b) reponderação aprendível w_{ℓ'} dos logits por nível-fonte;
//! (c) temperatura aprendível τ_ℓ por nível; (d) injeção FiLM do resumo.
use candle_core::{Result, Tensor, Var, D};
use crate::camadas::{Linear, RmsNorm, Swiglu};
pub struct CabecaAdba {
pub q: Linear,
pub k: Linear,
pub v: Linear,
pub o: Linear,
/// Reponderação de logits por nível-fonte (len = nº de níveis + 1 p/ prefixo).
pub w_nivel: Option<Var>,
/// Temperatura aprendível por nível (log-espaço, neutro = 0).
pub log_tau_nivel: Option<Var>,
pub n_cabecas: usize,
pub d_cabeca: usize,
}
#[derive(Default, Clone)]
pub struct CacheKv {
pub k: Option<Tensor>,
pub v: Option<Tensor>,
}
impl CabecaAdba {
pub fn nova(d_modelo: usize, n_cabecas: usize, n_niveis: usize, adba_ativo: bool, reponderar: bool, tau_nivel: bool, dev: &candle_core::Device) -> Result<Self> {
let d_cabeca = d_modelo / n_cabecas;
let desvio = 1.0 / ((d_modelo * 2) as f64).sqrt();
let mk = || Linear::sem_vies(d_modelo, d_modelo, desvio, dev);
let w_nivel = if adba_ativo && reponderar {
Some(Var::from_tensor(&Tensor::ones((n_niveis + 1,), candle_core::DType::F32, dev)?)?)
} else {
None
};
let log_tau_nivel = if adba_ativo && tau_nivel {
Some(Var::from_tensor(&Tensor::zeros((n_niveis + 1,), candle_core::DType::F32, dev)?)?)
} else {
None
};
Ok(CabecaAdba {
q: mk()?, k: mk()?, v: mk()?, o: mk()?,
w_nivel, log_tau_nivel,
n_cabecas, d_cabeca,
})
}
fn dividir_cabecas(&self, x: &Tensor) -> Result<Tensor> {
let (b, n, _) = (x.dim(0)?, x.dim(1)?, x.dim(2)?);
x.reshape((b, n, self.n_cabecas, self.d_cabeca))?
.transpose(1, 2)?
.contiguous()
}
fn juntar_cabecas(&self, x: &Tensor) -> Result<Tensor> {
let b = x.dim(0)?;
let n = x.dim(2)?;
x.transpose(1, 2)?.contiguous()?
.reshape((b, n, self.n_cabecas * self.d_cabeca))
}
/// Atenção com máscara aditiva (T_k × T_k) e offsets de nível para a
/// reponderação/temperatura da ADBA. `niveis_query`/`niveis_chave` dizem
/// a qual nível (0=prefixo, 1..L) pertence cada posição.
pub fn attention(
&self,
x: &Tensor,
mascara: Option<&Tensor>,
cache: &mut CacheKv,
niveis_query: Option<&[usize]>,
niveis_chave: Option<&[usize]>,
) -> Result<Tensor> {
let b = x.dim(0)?;
let q = self.dividir_cabecas(&self.q.fwd(x)?)?;
let k = self.dividir_cabecas(&self.k.fwd(x)?)?;
let v = self.dividir_cabecas(&self.v.fwd(x)?)?;
let (k, v) = match (&cache.k, &cache.v) {
(Some(pk), Some(pv)) => (Tensor::cat(&[pk, &k], 2)?, Tensor::cat(&[pv, &v], 2)?),
_ => (k, v),
};
cache.k = Some(k.clone());
cache.v = Some(v.clone());
let escala = 1.0 / (self.d_cabeca as f64).sqrt();
let mut scores = q.matmul(&k.transpose(2, 3)?)?.affine(escala, 0.0)?;
// ADBA (ii): reponderação por nível-fonte — score += log w_{ℓ'}
if let (Some(w), Some(nq), Some(nk)) = (&self.w_nivel, niveis_query, niveis_chave) {
let t_k = scores.dim(3)?;
let w_clamped = w.as_tensor().clamp(1e-3f32, 1e3f32)?;
let log_w = (&w_clamped + 1e-8)?.log()?;
let mut biases = vec![0f32; t_k];
for (j, &nivel) in nk.iter().enumerate().take(t_k) {
biases[j] = log_w.get(nivel)?.to_scalar::<f32>()?;
}
let _ = nq;
let bias = Tensor::from_vec(biases, (t_k,), x.device())?;
scores = scores.broadcast_add(&bias.reshape((1, 1, 1, t_k))?)?;
}
// ADBA (iii): temperatura por nível do QUERY
if let (Some(lt), Some(nq), _) = (&self.log_tau_nivel, niveis_query, niveis_chave) {
let t_q = scores.dim(2)?;
let tau = lt.as_tensor().affine(0.1, 0.0)?.exp()?; // suave (±~10% por unidade)
let mut fatores = vec![1f32; t_q];
for (i, &nivel) in nq.iter().enumerate().take(t_q) {
fatores[i] = tau.get(nivel)?.to_scalar::<f32>()?;
}
let f = Tensor::from_vec(fatores, (t_q,), x.device())?
.reshape((1, 1, t_q, 1))?;
scores = scores.broadcast_div(&f)?;
}
if let Some(m) = mascara {
scores = scores.broadcast_add(m)?;
}
let attn = candle_nn::ops::softmax(&scores, D::Minus1)?;
let ctx = attn.matmul(&v)?;
self.juntar_cabecas(&ctx)
}
pub fn vars(&self) -> Vec<Var> {
let mut v: Vec<Var> = [self.q.vars(), self.k.vars(), self.v.vars(), self.o.vars()].concat();
if let Some(w) = &self.w_nivel { v.push(w.clone()); }
if let Some(t) = &self.log_tau_nivel { v.push(t.clone()); }
v
}
}
/// Bloco ADBA completo: pré-norm + atenção + resíduo + pré-norm + SwiGLU.
pub struct BlocoAdba {
pub norm1: RmsNorm,
pub attn: CabecaAdba,
pub norm2: RmsNorm,
pub ffn: Swiglu,
}
impl BlocoAdba {
pub fn novo(d_modelo: usize, n_cabecas: usize, d_ff: usize, n_niveis: usize, adba_ativo: bool, reponderar: bool, tau_nivel: bool, dev: &candle_core::Device) -> Result<Self> {
Ok(BlocoAdba {
norm1: RmsNorm::novo(d_modelo, 1e-5, dev)?,
attn: CabecaAdba::nova(d_modelo, n_cabecas, n_niveis, adba_ativo, reponderar, tau_nivel, dev)?,
norm2: RmsNorm::novo(d_modelo, 1e-5, dev)?,
ffn: Swiglu::novo(d_modelo, d_ff, dev)?,
})
}
pub fn fwd(
&self,
x: &Tensor,
mascara: Option<&Tensor>,
cache: &mut CacheKv,
niveis_query: Option<&[usize]>,
niveis_chave: Option<&[usize]>,
) -> Result<Tensor> {
let h = self.attn.attention(&self.norm1.fwd(x)?, mascara, cache, niveis_query, niveis_chave)?;
let h = self.attn.o.fwd(&h)?;
let x = x.add(&h)?;
let y = self.ffn.fwd(&self.norm2.fwd(&x)?)?;
x.add(&y)
}
pub fn vars(&self) -> Vec<Var> {
[self.norm1.vars(), self.attn.vars(), self.norm2.vars(), self.ffn.vars()].concat()
}
}
/// Resumo-âncora por nível (ADBA i): h_ℓ = Attn(q_ℓ, K/V = c_{<ℓ}) — pooling
/// sobre as posições dos níveis anteriores (função de c_{<ℓ} APENAS).
pub struct AncoraNivel {
pub q: Var, // (d,) vetor de consulta aprendível por nível — (L, d)
pub proj: Linear,
}
impl AncoraNivel {
pub fn nova(d: usize, n_niveis: usize, dev: &candle_core::Device) -> Result<Self> {
let q = Tensor::randn(0f32, 0.02, (n_niveis, d), dev)?;
Ok(AncoraNivel { q: Var::from_tensor(&q)?, proj: Linear::novo(d, d, 0.02, dev)? })
}
/// contexto (1, n_ctx, d) das posições dos níveis < ℓ → resumo (1, d).
pub fn resumir(&self, contexto: &Tensor, nivel: usize) -> Result<Tensor> {
let d = self.q.as_tensor().dim(1)?;
let q = self.q.as_tensor().get(nivel)?.reshape((1, 1, d))?;
// atenção de cabeça única: softmax(q·Kᵀ/√d)·V
let escala = 1.0 / (d as f64).sqrt();
let scores = q.matmul(&contexto.transpose(1, 2)?)?.affine(escala, 0.0)?;
let pesos = candle_nn::ops::softmax(&scores, D::Minus1)?;
let resumo = pesos.matmul(contexto)?.reshape((1, d))?;
self.proj.fwd(&resumo)
}
pub fn vars(&self) -> Vec<Var> {
let mut v = vec![self.q.clone()];
v.extend(self.proj.vars());
v
}
}
|