File size: 10,125 Bytes
97df2ae
 
 
 
 
e26f4cc
 
 
 
97df2ae
 
 
 
 
 
 
 
 
 
 
 
 
e26f4cc
97df2ae
 
4dbc907
 
 
 
 
 
97df2ae
 
 
 
 
 
 
 
 
 
 
 
e26f4cc
97df2ae
 
e26f4cc
97df2ae
 
 
 
4dbc907
e26f4cc
97df2ae
4dbc907
 
97df2ae
4dbc907
97df2ae
 
 
e26f4cc
97df2ae
 
 
 
4dbc907
97df2ae
 
 
 
 
 
4dbc907
 
97df2ae
4dbc907
 
97df2ae
 
 
 
4dbc907
 
 
97df2ae
 
 
 
 
 
 
 
 
e26f4cc
 
97df2ae
 
 
 
 
e26f4cc
97df2ae
 
 
 
 
 
e26f4cc
97df2ae
e26f4cc
 
97df2ae
e26f4cc
97df2ae
 
 
 
e26f4cc
 
 
 
 
 
 
 
 
 
 
 
 
97df2ae
 
 
 
 
 
 
 
 
 
 
 
e26f4cc
 
4dbc907
 
 
 
 
97df2ae
 
 
e26f4cc
97df2ae
 
 
4dbc907
 
97df2ae
 
 
e26f4cc
 
97df2ae
e26f4cc
4dbc907
 
 
 
 
 
 
 
97df2ae
e26f4cc
 
97df2ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4dbc907
 
 
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
//! Tronco híbrido 5:1 (port do `mamba3/tronco.py`): 5 camadas ADBA (atenção
//! block-causal com melhorias) + 1 camada SSD (slot Mamba-3 causal) — Teo D.1
//! com a restrição honesta H_D1 (bidirecionalidade intra-nível nas camadas de
//! atenção; a camada recorrente é causal).
//!
//! Sequência do gerador: s = [cond; lev_1; …; lev_L] com entradas
//! ẑ_{ℓ−1}^{(n_ℓ)} + e_ℓ (reconstrução cumulativa interpolada + embedding de
//! nível — Teo D.1). Treino: 1 passada com teacher forcing (D.1(iv));
//! inferência: bloco a bloco com KV-cache (D.1(iii), T2).

use candle_core::{Device, Result, Tensor, Var};
use crate::atencao::{AncoraNivel, BlocoAdba};
pub use crate::atencao::CacheKv;
use crate::camadas::{Embedding, Linear, RmsNorm};
use crate::config::ConfigKhtst;
use crate::ssd::BlocoSsd;

pub struct Tronco {
    pub blocos_atencao: Vec<BlocoAdba>,
    pub bloco_ssd: BlocoSsd,
    pub norm_final: RmsNorm,
    pub ancora: Option<AncoraNivel>,
    /// gamma FiLM por nível (2*L+2: [escala, deslocamento] × nível) — ADBA.
    pub film_gamma: Option<Var>,
    pub film_beta: Option<Var>,
    /// W_f do FiLM (r-04 §1: x̃ = x⊙(1+γ·tanh(W_f·h)) + β·(W_f·h)) — ZERO no
    /// nascimento: tanh(0)=0 e β·0=0 ⇒ identidade bit-a-bit (Teo R.2).
    /// v070-1 (auditoria): a v0.6.0 injetava o resumo do âncora DIRETO (projeção
    /// aleatória) — nascimento não-neutro nos níveis ≥ 2; correção restaura o
    /// enunciado R.2 independentemente de γ=1.
    pub film_w: Option<crate::camadas::Linear>,
    pub n_niveis: usize,
}

impl Tronco {
    pub fn novo(cfg: &ConfigKhtst, dev: &Device) -> Result<Self> {
        let m = &cfg.modelo;
        let l = cfg.l_niveis();
        let adba = &cfg.adba;
        let mut blocos = Vec::with_capacity(m.n_camadas - 1);
        for _ in 0..(m.n_camadas - 1) {
            blocos.push(BlocoAdba::novo(
                m.d_modelo, m.n_cabecas, m.d_ff, l,
                adba.ativo, adba.reponderacao_nivel, adba.temperatura_nivel, dev,
            )?);
        }
        let ancora = if adba.ativo && adba.gamma_ancora > 0.0 {
            Some(AncoraNivel::nova(m.d_modelo, l, dev)?)
        } else {
            None
        };
        let (film_gamma, film_beta, film_w) = if adba.ativo {
            let g = Tensor::ones((l + 1, m.d_modelo), candle_core::DType::F32, dev)?;
            let b = Tensor::zeros((l + 1, m.d_modelo), candle_core::DType::F32, dev)?;
            let w = crate::camadas::Linear::zeros_sem_vies(m.d_modelo, m.d_modelo, dev)?;
            (Some(Var::from_tensor(&g)?), Some(Var::from_tensor(&b)?), Some(w))
        } else {
            (None, None, None)
        };
        Ok(Tronco {
            blocos_atencao: blocos,
            bloco_ssd: BlocoSsd::novo(m.d_modelo, m.d_ff, dev)?,
            norm_final: RmsNorm::novo(m.d_modelo, 1e-5, dev)?,
            ancora,
            film_gamma,
            film_beta,
            film_w,
            n_niveis: l,
        })
    }

    /// Injeção FiLM do resumo-âncora nas posições do nível `nivel`
    /// (ADBA (i): o resumo depende APENAS de c_{<ℓ} — paralelismo preservado).
    /// r-04 §1: x̃ = x⊙(1+γ·tanh(W_f·h)) + β·(W_f·h) com W_f=0 no nascimento
    /// ⇒ identidade bit-a-bit para QUALQUER resumo (Teo R.2, v070-1).
    pub fn aplicar_film(&self, x: &Tensor, resumo: &Tensor, nivel: usize) -> Result<Tensor> {
        match (&self.film_gamma, &self.film_beta, &self.film_w) {
            (Some(g), Some(b), Some(wf)) => {
                let d = x.dims()[2];
                let bsz = resumo.dims()[0].max(1);
                let gv = g.as_tensor().get(nivel)?.reshape((1, 1, d))?;
                let bv = b.as_tensor().get(nivel)?.reshape((1, 1, d))?;
                // r = W_f·resumo — 0 no nascimento (identidade garantida)
                let r = wf.fwd(resumo)?.reshape((bsz, 1, d))?;
                // x ⊙ (1 + γ·tanh(r)) + β·r
                let gate = gv.broadcast_mul(&r.tanh()?)?.affine(1.0, 1.0)?; // 1 + γ·tanh(r)
                let xg = x.broadcast_mul(&gate)?;
                let desloc = bv.broadcast_mul(&r)?;
                xg.broadcast_add(&desloc)
            }
            _ => Ok(x.clone()),
        }
    }

    /// Forward de treino: sequência completa s (b, T, d) com máscara aditiva
    /// (T, T) e mapa de níveis por posição (query e chave). 1 passada — D.1(iv).
    pub fn forward_treino(
        &self,
        s: &Tensor,
        mascara: &Tensor,
        niveis: &[usize],
        resumos_por_nivel: &mut Vec<Tensor>,
    ) -> Result<Tensor> {
        let b = s.dim(0)?;
        let mut x = s.clone();
        for (i, bloco) in self.blocos_atencao.iter().enumerate() {
            let mut cache = CacheKv::default();
            x = bloco.fwd(&x, Some(mascara), &mut cache, Some(niveis), Some(niveis))?;
            // após o 1º bloco: injeta o resumo-âncora por nível (ADBA)
            if i == 0 {
                if let Some(anc) = &self.ancora {
                    resumos_por_nivel.clear();
                    let n_prefixo = x.dims()[1] - self.tamanhos_por_nivel(niveis).iter().sum::<usize>();
                    // prefixo entra inalterado (sem FiLM — nível 0)
                    let mut partes = vec![x.narrow(1, 0, n_prefixo)?];
                    let mut inicio = n_prefixo;
                    for (nivel, &n_n) in self.tamanhos_por_nivel(niveis).iter().enumerate() {
                        let trecho = x.narrow(1, inicio, n_n)?;
                        // resumo do CONTEXTO ANTERIOR (níveis < atual): usa as
                        // posições já processadas (função de c_{<ℓ} apenas)
                        if inicio == n_prefixo {
                            resumos_por_nivel.push(Tensor::zeros((b, s.dims()[2]), candle_core::DType::F32, s.device())?);
                        } else {
                            let ctx = x.narrow(1, 0, inicio)?;
                            let mut r_nivel = Vec::new();
                            for bi in 0..b {
                                r_nivel.push(anc.resumir(&ctx.narrow(0, bi, 1)?, nivel)?);
                            }
                            resumos_por_nivel.push(Tensor::cat(&r_nivel, 0)?);
                        }
                        let r = resumos_por_nivel[nivel].clone();
                        partes.push(self.aplicar_film(&trecho, &r, nivel + 1)?);
                        inicio += n_n;
                    }
                    x = Tensor::cat(&partes, 1)?;
                }
            }
        }
        let mut cache_ssd = None;
        x = self.bloco_ssd.fwd(&x, &mut cache_ssd)?;
        self.norm_final.fwd(&x)
    }

    /// Forward incremental (inferência): bloco novo (b, n_novo, d) + caches.
    /// `niveis_novos`/`niveis_cache` mapeiam posições para níveis.
    /// `film` = Some((resumo, nivel 1-indexado)) aplica o FiLM nas posições novas
    /// após o bloco 0 — PARIDADE com forward_treino (v070-2: a v0.6.0 não
    /// aplicava FiLM na inferência incremental — treino↔geração divergiam).
    /// `saida_b0` = Some(&mut Tensor) recebe os estados pós-bloco-0 das
    /// posições novas (para o âncora acumular contexto nos níveis seguintes).
    pub fn forward_nivel(
        &self,
        bloco_x: &Tensor,
        caches: &mut Vec<CacheKv>,
        mascara_nova: &Tensor,
        niveis_novos: &[usize],
        niveis_totais: &[usize],
        film: Option<(&Tensor, usize)>,
        mut saida_b0: Option<&mut Tensor>,
    ) -> Result<Tensor> {
        let mut x = bloco_x.clone();
        for (i, bloco) in self.blocos_atencao.iter().enumerate() {
            if caches.len() <= i {
                caches.push(CacheKv::default());
            }
            x = bloco.fwd(&x, Some(mascara_nova), &mut caches[i], Some(niveis_novos), Some(niveis_totais))?;
            if i == 0 {
                if let Some(slot) = saida_b0.as_deref_mut() {
                    *slot = x.clone();
                }
                if let Some((resumo, nivel)) = film {
                    x = self.aplicar_film(&x, resumo, nivel)?;
                }
            }
        }
        let mut cache_ssd = None;
        x = self.bloco_ssd.fwd(&x, &mut cache_ssd)?;
        self.norm_final.fwd(&x)
    }

    fn tamanhos_por_nivel(&self, niveis: &[usize]) -> Vec<usize> {
        let max_n = niveis.iter().copied().max().unwrap_or(0);
        let mut out = vec![0usize; max_n];
        for &n in niveis {
            if n >= 1 && n <= max_n {
                out[n - 1] += 1;
            }
        }
        out
    }

    pub fn vars(&self) -> Vec<Var> {
        let mut v: Vec<Var> = self.blocos_atencao.iter().flat_map(|b| b.vars()).collect();
        v.extend(self.bloco_ssd.vars());
        v.extend(self.norm_final.vars());
        if let Some(a) = &self.ancora {
            v.extend(a.vars());
        }
        if let Some(g) = &self.film_gamma {
            v.push(g.clone());
        }
        if let Some(b) = &self.film_beta {
            v.push(b.clone());
        }
        if let Some(wf) = &self.film_w {
            v.push(wf.w.clone());
        }
        v
    }
}

/// Cabeças de conceitos por nível (via A de T1): linear d → K_ℓ.
pub struct CabecaConceitos {
    pub cabecas: Vec<Linear>,
}

impl CabecaConceitos {
    pub fn nova(cfg: &ConfigKhtst, dev: &Device) -> Result<Self> {
        let mut c = Vec::with_capacity(cfg.l_niveis());
        for _ in 0..cfg.l_niveis() {
            c.push(Linear::sem_vies(cfg.modelo.d_modelo, cfg.ncm.k_por_nivel, 0.02, dev)?);
        }
        Ok(CabecaConceitos { cabecas: c })
    }

    pub fn vars(&self) -> Vec<Var> {
        self.cabecas.iter().flat_map(|c| c.vars()).collect()
    }
}

/// Embeddings de nível e_ℓ (T1 via A).
pub struct EmbNiveis {
    pub tabela: Embedding,
}

impl EmbNiveis {
    pub fn nova(cfg: &ConfigKhtst, dev: &Device) -> Result<Self> {
        Ok(EmbNiveis { tabela: Embedding::novo(cfg.l_niveis() + 1, cfg.modelo.d_modelo, dev)? })
    }
    pub fn vars(&self) -> Vec<Var> {
        self.tabela.vars()
    }
}