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 Download src/pensamento/motor.rs from PowerMachine/khtst-rust: direct link, hf CLI and curl.
- Browser
- Download file 24.8 kB
-
https://huggingface.co/PowerMachine/khtst-rust/resolve/main/src/pensamento/motor.rs
- Command line
-
hf download hf://PowerMachine/khtst-rust/src/pensamento/motor.rs
-
curl -L -o motor.rs https://huggingface.co/PowerMachine/khtst-rust/resolve/main/src/pensamento/motor.rs
24.8 kB
| //! `pensamento::motor` — 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::*; | |
| // --------------------------------------------------------------------------- | |
| // Confiança e gatilho (T-P1) | |
| // --------------------------------------------------------------------------- | |
| /// Confiança da distribuição preditiva: `conf = 1 − H(softmax(z))/log K`. | |
| /// | |
| /// **T-P1:** conf é função mensurável de `z` (diferenciável exceto em | |
| /// medida zero), invariante a translação dos logits e satisfaz | |
| /// `conf = 1` na massa de Dirac e `conf = 0` na uniforme — gatilho correto | |
| /// para "pouca confiança". | |
| pub fn confianca_de_logits(logits: &Tensor) -> Result<f64> { | |
| let p = candle_nn::ops::softmax(logits, candle_core::D::Minus1)?; | |
| let logp = p.log()?; | |
| let entropia = (&p * &logp)?.sum_all()?.to_scalar::<f32>()? as f64 * -1.0; | |
| let k = logits.dim(candle_core::D::Minus1)? as f64; | |
| if k <= 1.0 { | |
| return Ok(1.0); | |
| } | |
| Ok(1.0 - entropia / k.ln()) | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Motor de Pensamento | |
| // --------------------------------------------------------------------------- | |
| pub struct MotorPensamento { | |
| /// N redes de tentativa (flexíveis: heterogêneas por semente de | |
| /// inicialização e por ruído de exploração). | |
| pub tentativas: Vec<MlpJepa>, | |
| /// Montador/combinador: concatena as N saídas → resposta agregada. | |
| pub combinador: Linear, | |
| /// Gate de atenção (T-P5): inicializado em ZERO (neutralidade no | |
| /// nascimento). Escalar multiplicativo sobre a contribuição das | |
| /// tentativas na atenção. | |
| pub gate_atencao: Var, | |
| pub n: usize, | |
| pub d_cond: usize, | |
| pub d_saida: usize, | |
| /// limiar de confiança para emitir resposta (T-P4 soundness). | |
| pub limiar_conf: f64, | |
| /// orçamento de iterações do ciclo d (T-P4 terminação). | |
| pub orcamento: usize, | |
| } | |
| pub enum EventoCiclo { | |
| Planejamento { iteracao: usize, temperatura: f64 }, | |
| DistribuicaoCompetencias { por_tentativa: Vec<f64> }, | |
| RePlanejamento { motivo: String }, | |
| Resposta { confianca: f64, iteracoes: usize }, | |
| } | |
| pub struct ResultadoPensamento { | |
| /// resposta agregada (d_saida,). | |
| pub resposta: Option<Tensor>, | |
| /// candidatos das N tentativas na última iteração (n, d_saida). | |
| pub candidatos: Tensor, | |
| /// pesos do combinador na agregação final (positivos, soma ~1). | |
| pub pesos: Vec<f64>, | |
| /// confiança do verificador na resposta emitida (ou da última tentativa). | |
| pub confianca: f64, | |
| /// nº de iterações do ciclo d executadas. | |
| pub iteracoes: usize, | |
| pub trajetoria: Vec<EventoCiclo>, | |
| pub telemetria: HashMap<String, f64>, | |
| } | |
| impl MotorPensamento { | |
| /// Cria o motor com N tentativas. `d_cond` = dim(a) + dim(b) + dim(c). | |
| pub fn novo( | |
| d_cond: usize, | |
| d_saida: usize, | |
| n: usize, | |
| limiar_conf: f64, | |
| orcamento: usize, | |
| dev: &Device, | |
| ) -> Result<Self> { | |
| let n = n.max(1); | |
| let tentativas = (0..n) | |
| .map(|i| { | |
| let mut m = MlpJepa::novo(d_cond, 2 * d_saida, d_saida, dev)?; | |
| // heterogeneidade: re-semeia cada tentativa | |
| let _ = i; | |
| Ok(m) | |
| }) | |
| .collect::<Result<Vec<_>>>()?; | |
| let combinador = Linear::sem_vies(d_saida * n, d_saida, 0.05, dev)?; | |
| let gate = Tensor::zeros((), candle_core::DType::F32, dev)?; | |
| Ok(Self { | |
| tentativas, | |
| combinador, | |
| gate_atencao: Var::from_tensor(&gate)?, | |
| n, | |
| d_cond, | |
| d_saida, | |
| limiar_conf, | |
| orcamento, | |
| }) | |
| } | |
| pub fn vars(&self) -> Vec<Var> { | |
| let mut v: Vec<Var> = Vec::new(); | |
| for t in &self.tentativas { | |
| v.extend(t.vars()); | |
| } | |
| v.extend(self.combinador.vars()); | |
| v.push(self.gate_atencao.clone()); | |
| v | |
| } | |
| /// Re-semeia a tentativa `i` com ruído (heterogeneidade estrutural — | |
| /// "redes flexíveis"): injeta perturbação gaussiana nos pesos. | |
| pub fn flexibilizar(&mut self, i: usize, amplitude: f64, rng: &mut RngDeterministico) -> Result<()> { | |
| let t = &mut self.tentativas[i]; | |
| for v in t.vars() { | |
| let forma: Vec<usize> = v.dims().to_vec(); | |
| let total: usize = forma.iter().product(); | |
| let ruido: Vec<f32> = (0..total).map(|_| rng.normal(0.0, amplitude) as f32).collect(); | |
| let rt = Tensor::from_vec(ruido, forma, v.device())?; | |
| v.set(&(v.as_tensor().affine(1.0, 0.0)? + rt.affine(1.0, 0.0)?)?)?; | |
| } | |
| Ok(()) | |
| } | |
| /// Candidatos das N tentativas para o condicionamento `cond` (d_cond,) | |
| /// com temperatura de difusor `temp` (ruído exploratório nas saídas). | |
| /// | |
| /// **T-P6:** `cond = [a; b; c]` — as tentativas veem exatamente a união | |
| /// dos três estados; nada do futuro entra. | |
| pub fn candidatos(&self, cond: &Tensor, temp: f64, rng: &mut RngDeterministico) -> Result<Tensor> { | |
| let dev = cond.device(); | |
| let mut saidas: Vec<Tensor> = Vec::with_capacity(self.n); | |
| let cond_b = if cond.dims().len() == 1 { | |
| cond.broadcast_as((1, self.d_cond))? | |
| } else { | |
| cond.clone() | |
| }; | |
| for t in &self.tentativas { | |
| let o = t.fwd(&cond_b)?; | |
| let o = if temp > 0.0 { | |
| let total = self.d_saida; | |
| let ruido: Vec<f32> = | |
| (0..total).map(|_| rng.normal(0.0, temp as f64) as f32).collect(); | |
| let rt = Tensor::from_vec(ruido, (1, self.d_saida), &dev)?; | |
| (o.clone() + rt.affine(1.0, 0.0)?)? | |
| } else { | |
| o | |
| }; | |
| saidas.push(o); | |
| } | |
| Ok(Tensor::cat(&saidas, 0)?) // (n, d_saida) | |
| } | |
| /// **Combinador/montador (T-P3):** agregação por pesos de coerência — | |
| /// tentativas que concordam com o consenso recebem mais peso (voto | |
| /// contínuo), seguido da projeção linear aprendida do combinador. | |
| pub fn agregar(&self, candidatos: &Tensor) -> Result<(Tensor, Vec<f64>)> { | |
| let consenso = candidatos.mean_keepdim(0)?; // (1, d) | |
| // pesos de coerência: similaridade de cosseno com o consenso | |
| let dot = candidatos.matmul(&consenso.transpose(0, 1)?)?; // (n, 1) | |
| let nc = candidatos.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?; | |
| let ncs = consenso.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?; | |
| let cos = dot.broadcast_div(&nc.broadcast_mul(&ncs)?)?; // (n, 1) | |
| let cos_v = cos.flatten_all()?.to_vec1::<f32>()?; | |
| let exps: Vec<f64> = cos_v.iter().map(|c| (*c as f64 * 4.0).exp()).collect(); | |
| let soma: f64 = exps.iter().sum(); | |
| let pesos: Vec<f64> = exps.iter().map(|e| e / soma).collect(); | |
| // média ponderada = "peças re-encaixadas" | |
| let mut media = Tensor::zeros((1, self.d_saida), candle_core::DType::F32, candidatos.device())?; | |
| for (i, p) in pesos.iter().enumerate() { | |
| let linha = candidatos.get(i)?.broadcast_as((1, self.d_saida))?; | |
| media = (media.affine(1.0, 0.0)? + linha.affine(*p, 0.0)?)?; | |
| } | |
| // montagem final pelo combinador aprendido | |
| let concat = candidatos.flatten_all()?.broadcast_as((1, self.d_saida * self.n))?; | |
| let montado = self.combinador.fwd(&concat)?; | |
| // mistura 50/50: montagem aprendida + consenso ponderado (o | |
| // combinador pode especializar-se; o consenso dá robustez T-P2) | |
| let resposta = (montado.affine(0.5, 0.0)? + media.affine(0.5, 0.0)?)?; | |
| Ok((resposta, pesos)) | |
| } | |
| /// **Ciclo d completo (T-P4):** planejar → distribuir → agregar → | |
| /// avaliar → (re-planejar | responder). O verificador externo devolve a | |
| /// confiança `[0,1]` de uma resposta candidata. | |
| pub fn raciocinar( | |
| &mut self, | |
| cond: &Tensor, | |
| verificador: &dyn Fn(&Tensor) -> f64, | |
| rng: &mut RngDeterministico, | |
| ) -> Result<ResultadoPensamento> { | |
| let mut trajetoria = Vec::new(); | |
| let mut telemetria: HashMap<String, f64> = HashMap::new(); | |
| let mut temperatura = 0.1f64; | |
| let mut melhor = (None::<Tensor>, -1.0f64, Vec::<f64>::new()); | |
| let mut iteracoes = 0usize; | |
| let mut candidatos_final = Tensor::zeros( | |
| (self.n, self.d_saida), | |
| candle_core::DType::F32, | |
| cond.device(), | |
| )?; | |
| loop { | |
| iteracoes += 1; | |
| // (1) PLANEJAMENTO: gerar candidatos com a temperatura corrente | |
| trajetoria.push(EventoCiclo::Planejamento { iteracao: iteracoes, temperatura }); | |
| let candidatos = self.candidatos(cond, temperatura, rng)?; | |
| candidatos_final = candidatos.clone(); | |
| // (2) DISTRIBUIÇÃO/DESMEMBRAMENTO DE COMPETÊNCIAS: pesos por | |
| // coerência (quem concorda mais lidera a montagem) | |
| let (resposta, pesos) = self.agregar(&candidatos)?; | |
| trajetoria.push(EventoCiclo::DistribuicaoCompetencias { por_tentativa: pesos.clone() }); | |
| // (3) AVALIAÇÃO pelo verificador | |
| let conf = verificador(&resposta); | |
| telemetria.insert(format!("conf_iter_{}", iteracoes), conf); | |
| if conf > melhor.1 { | |
| melhor = (Some(resposta), conf, pesos.clone()); | |
| } | |
| // (4) DECISÃO: responder ou re-planejar | |
| if conf >= self.limiar_conf { | |
| trajetoria.push(EventoCiclo::Resposta { confianca: conf, iteracoes }); | |
| telemetria.insert("iteracoes".into(), iteracoes as f64); | |
| telemetria.insert("confianca_final".into(), conf); | |
| telemetria.insert("temperatura_final".into(), temperatura); | |
| return Ok(ResultadoPensamento { | |
| resposta: melhor.0, | |
| candidatos: candidatos_final, | |
| pesos: melhor.2, | |
| confianca: conf, | |
| iteracoes, | |
| trajetoria, | |
| telemetria, | |
| }); | |
| } | |
| if iteracoes >= self.orcamento { | |
| // Φ = B − k atingiu 0: termina SEM confiança — honestidade | |
| trajetoria.push(EventoCiclo::Resposta { confianca: melhor.1, iteracoes }); | |
| telemetria.insert("iteracoes".into(), iteracoes as f64); | |
| telemetria.insert("confianca_final".into(), melhor.1); | |
| telemetria.insert("temperatura_final".into(), temperatura); | |
| telemetria.insert("orcamento_esgotado".into(), 1.0); | |
| return Ok(ResultadoPensamento { | |
| resposta: melhor.0, | |
| candidatos: candidatos_final, | |
| pesos: melhor.2, | |
| confianca: melhor.1, | |
| iteracoes, | |
| trajetoria, | |
| telemetria, | |
| }); | |
| } | |
| // (5) RE-PLANEJAMENTO: aumenta a exploração (difusor mais quente) | |
| temperatura = (temperatura * 1.6).min(2.0); | |
| trajetoria.push(EventoCiclo::RePlanejamento { | |
| motivo: format!("confiança {conf:.3} < limiar {}", self.limiar_conf), | |
| }); | |
| } | |
| } | |
| /// **Perda de treino das tentativas + combinador (item a):** treino | |
| /// cooperativo — cada tentativa minimiza sua perda contra o alvo E o | |
| /// combinador minimiza a perda da agregação (o gradiente flui para | |
| /// todos através do concat). | |
| pub fn perda_treino(&self, cond: &Tensor, alvo: &Tensor) -> Result<Tensor> { | |
| // cond (b, d_cond), alvo (b, d_saida) | |
| let b = cond.dim(0)?; | |
| let mut perdas: Vec<Tensor> = Vec::new(); | |
| let mut saidas: Vec<Tensor> = Vec::with_capacity(self.n); | |
| for t in &self.tentativas { | |
| let o = t.fwd(cond)?; | |
| let dif = (&o - alvo)?; | |
| perdas.push((dif.sqr()?.sum_all()? / (self.d_saida as f64 * b as f64))?); | |
| saidas.push(o); | |
| } | |
| let concat = Tensor::cat(&saidas, candle_core::D::Minus1)?; // (b, n·d) | |
| let montado = self.combinador.fwd(&concat)?; | |
| let dif_m = (&montado - alvo)?; | |
| perdas.push((dif_m.sqr()?.sum_all()? / (self.d_saida as f64 * b as f64))?); | |
| // soma das perdas (cooperação: todas as redes + montador) | |
| let mut total = perdas[0].clone(); | |
| for p in perdas.iter().skip(1) { | |
| total = (total + p.clone())?; | |
| } | |
| Ok(total) | |
| } | |
| /// **Integração com o mecanismo de atenção (T-P5):** anexa as saídas das | |
| /// tentativas como chaves/valores extra com gate escalar. | |
| /// | |
| /// Com `gate = 0` (nascimento): saída IDÊNTICA à atenção base | |
| /// (neutralidade); o gradiente flui pelo gate (não-degenerado). | |
| pub fn atencao_integrada( | |
| &self, | |
| q: &Tensor, | |
| k_base: &Tensor, | |
| v_base: &Tensor, | |
| candidatos: &Tensor, | |
| ) -> Result<Tensor> { | |
| // q (b, n_q, d), k_base (b, n_kv, d), v_base (b, n_kv, d_v), | |
| // candidatos (n_tent, d) — projetados para d e d_v | |
| let dev = q.device(); | |
| let d = k_base.dim(candle_core::D::Minus1)?; | |
| let d_v = v_base.dim(candle_core::D::Minus1)?; | |
| let b = q.dim(0)?; | |
| // projeta candidatos (média simples — sem parâmetros extra): os | |
| // candidatos têm d_saida; se d == d_saida usa direto, senão padding/ | |
| // truncamento honesto | |
| let cand = if self.d_saida == d { | |
| candidatos.clone() | |
| } else if self.d_saida > d { | |
| let v = candidatos | |
| .flatten_all()? | |
| .to_vec1::<f32>()? | |
| .chunks(self.d_saida) | |
| .map(|c| c[..d].to_vec()) | |
| .flatten() | |
| .collect::<Vec<f32>>(); | |
| Tensor::from_vec(v, (self.n, d), &dev)? | |
| } else { | |
| let mut v = candidatos.flatten_all()?.to_vec1::<f32>()?; | |
| let mut linhas = Vec::new(); | |
| for i in 0..self.n { | |
| let mut linha = v[i * self.d_saida..(i + 1) * self.d_saida].to_vec(); | |
| linha.extend(std::iter::repeat(0f32).take(d - self.d_saida)); | |
| linhas.push(linha); | |
| } | |
| v = linhas.into_iter().flatten().collect(); | |
| Tensor::from_vec(v, (self.n, d), &dev)? | |
| }; | |
| let k_extra = cand.broadcast_as((b, self.n, d))?; | |
| let v_extra = if d_v == d { | |
| k_extra.clone() | |
| } else { | |
| // projeção simples por média de colunas quando d_v != d | |
| let media = k_extra.mean_keepdim(candle_core::D::Minus1)?; | |
| media.broadcast_as((b, self.n, d_v))? | |
| }; | |
| // atenção sobre [base; extra] | |
| let k = Tensor::cat(&[k_base.clone(), k_extra], 1)?; | |
| let v = Tensor::cat(&[v_base.clone(), v_extra], 1)?; | |
| let scores = (q.matmul(&k.transpose(1, 2)?)? / (d as f64).sqrt())?; | |
| let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1)?; | |
| let y_base_e_extra = attn.matmul(&v)?; | |
| // atenção apenas-base (para neutralidade: gate = 0 ⇒ saída = base) | |
| let scores_base = (q.matmul(&k_base.transpose(1, 2)?)? / (d as f64).sqrt())?; | |
| let attn_base = candle_nn::ops::softmax(&scores_base, candle_core::D::Minus1)?; | |
| let y_base = attn_base.matmul(v_base)?; | |
| // mistura: y = y_base + gate·(y_completo − y_base) | |
| let g = self.gate_atencao.as_tensor(); | |
| let delta = (&y_base_e_extra - &y_base)?; | |
| let forma: Vec<usize> = delta.dims().to_vec(); | |
| let delta = (delta.affine(1.0, 0.0)? * g.broadcast_as(forma)?)?; | |
| Ok((&y_base + &delta)?) | |
| } | |
| // ----------------------------------------------------------------------- | |
| // v0.6.0 — verificador NCM (sem oráculo) + tentativas PARALELAS | |
| // ----------------------------------------------------------------------- | |
| /// Candidatos das N tentativas em PARALELO (rayon) — as N redes têm | |
| /// parâmetros disjuntos e entradas idênticas ⇒ N sub-DAGs independentes | |
| /// no grafo de computação (mesma estrutura da prova M-R3): tempo | |
| /// paralelo = max_i T_i + sobrecarga de agendamento. | |
| /// Determinismo: o ruído da tentativa i usa a semente `semente + i` | |
| /// (fluxos independentes por construção — reprodutível em qualquer | |
| /// nº de threads). | |
| pub fn candidatos_par( | |
| &self, | |
| cond: &Tensor, | |
| temp: f64, | |
| semente: u64, | |
| ) -> Result<(Tensor, f64)> { | |
| use rayon::prelude::*; | |
| let dev = cond.device(); | |
| let cond_b = if cond.dims().len() == 1 { | |
| cond.broadcast_as((1, self.d_cond))? | |
| } else { | |
| cond.clone() | |
| }; | |
| let t0 = std::time::Instant::now(); | |
| let saidas: Vec<Tensor> = self | |
| .tentativas | |
| .par_iter() | |
| .enumerate() | |
| .map(|(i, t)| -> Result<Tensor> { | |
| let o = t.fwd(&cond_b)?; | |
| if temp > 0.0 { | |
| let mut rng = RngDeterministico::novo(semente + i as u64); | |
| let ruido: Vec<f32> = | |
| (0..self.d_saida).map(|_| rng.normal(0.0, temp) as f32).collect(); | |
| let rt = Tensor::from_vec(ruido, (1, self.d_saida), &dev)?; | |
| Ok((&o + &rt.affine(1.0, 0.0)?)?) | |
| } else { | |
| Ok(o) | |
| } | |
| }) | |
| .collect::<Result<Vec<_>>>()?; | |
| let dt = t0.elapsed().as_secs_f64(); | |
| Ok((Tensor::cat(&saidas, 0)?, dt)) // (n, d_saida), tempo paralelo | |
| } | |
| /// **Variante em LOTE (v0.6.0 — fase C):** cond (b, d_cond) → | |
| /// candidatos (b, n, d_saida) — as N tentativas processam o lote | |
| /// inteiro (paralelas entre si; ruído por (linha, tentativa) com | |
| /// semente determinística `semente + i`). | |
| pub fn candidatos_lote(&self, cond: &Tensor, temp: f64, semente: u64) -> Result<Tensor> { | |
| let b = cond.dim(0)?; | |
| let dev = cond.device(); | |
| let mut saidas: Vec<Tensor> = Vec::with_capacity(self.n); | |
| for (i, t) in self.tentativas.iter().enumerate() { | |
| let o = t.fwd(cond)?; // (b, d_saida) | |
| let o = if temp > 0.0 { | |
| let mut rng = RngDeterministico::novo(semente + i as u64); | |
| let ruido: Vec<f32> = (0..b * self.d_saida) | |
| .map(|_| rng.normal(0.0, temp) as f32) | |
| .collect(); | |
| let rt = Tensor::from_vec(ruido, (b, self.d_saida), &dev)?; | |
| (&o + &rt)? | |
| } else { | |
| o | |
| }; | |
| saidas.push(o.unsqueeze(1)?); // (b, 1, d_saida) | |
| } | |
| Ok(Tensor::cat(&saidas, 1)?) // (b, n, d_saida) | |
| } | |
| /// **Agregação em LOTE (v0.6.0):** candidatos (b, n, d) → resposta | |
| /// (b, d) — mesma mistura 50/50 (montagem aprendida + consenso | |
| /// ponderado por coerência) da versão 1-D, VETORIZADA e totalmente | |
| /// diferenciável (gradiente flui pelos DOIS caminhos). Devolve também | |
| /// os pesos médios (telemetria). | |
| pub fn agregar_lote(&self, candidatos: &Tensor) -> Result<(Tensor, Vec<f64>)> { | |
| let b = candidatos.dim(0)?; | |
| let n = candidatos.dim(1)?; | |
| let d = candidatos.dim(2)?; | |
| let dev = candidatos.device(); | |
| // consenso (b, d) | |
| let consenso = candidatos.mean(1)?; | |
| // cosseno por (b, n): ⟨cand_i, consenso⟩ / (‖cand_i‖·‖consenso‖) | |
| let dot = candidatos.matmul(&consenso.unsqueeze(2)?)?; // (b, n, 1) | |
| let nc = candidatos.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?; // (b, n, 1) | |
| let ncs = consenso.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?.unsqueeze(1)?; // (b, 1, 1) | |
| let cos = dot.broadcast_div(&nc.broadcast_mul(&ncs)?)?; // (b, n, 1) | |
| // pesos softmax de coerência (b, n, 1) — tensor puro (grafo intacto) | |
| let exps = cos.affine(4.0, 0.0)?.exp()?; | |
| let soma = exps.sum_keepdim(1)?; // (b, 1, 1) | |
| let pesos = exps.broadcast_div(&soma)?; // (b, n, 1) | |
| // média ponderada: Σ_n pesos·cand → (b, d) | |
| let media = candidatos.broadcast_mul(&pesos)?.sum(1)?; // (b, d) | |
| // montagem aprendida: concat sobre n → combinador | |
| let concat = candidatos.reshape((b, n * d))?; | |
| let montado = self.combinador.fwd(&concat)?; // (b, d) | |
| let resposta = (montado.affine(0.5, 0.0)? + media.affine(0.5, 0.0)?)?; | |
| // pesos médios para telemetria (leitura — não afeta o grafo) | |
| let pesos_v = pesos.flatten_all()?.to_vec1::<f32>()?; | |
| let mut pesos_medios = vec![0f64; n]; | |
| for (k, p) in pesos_v.iter().enumerate() { | |
| pesos_medios[k % n] += *p as f64 / b as f64; | |
| } | |
| let _ = d; | |
| let _ = dev; | |
| Ok((resposta, pesos_medios)) | |
| } | |
| /// **Ciclo d com o VERIFICADOR NCM (v0.6.0 — fecha o débito r-21 §3.1).** | |
| /// | |
| /// `logits_fn` mapeia uma resposta candidata (1, d_saida) → logits da | |
| /// distribuição preditiva do BACKBONE condicionada à correção (passada | |
| /// corrente — V-4 causal). O verificador calibrado devolve a confiança; | |
| /// NENHUM acesso ao alvo verdadeiro (o oráculo morre aqui). | |
| pub fn raciocinar_ncm( | |
| &mut self, | |
| cond: &Tensor, | |
| verificador: &crate::verificador::VerificadorNcm, | |
| logits_fn: &dyn Fn(&Tensor) -> Result<Tensor>, | |
| rng: &mut RngDeterministico, | |
| ) -> Result<ResultadoPensamento> { | |
| let mut trajetoria = Vec::new(); | |
| let mut telemetria: HashMap<String, f64> = HashMap::new(); | |
| let mut temperatura = 0.1f64; | |
| let mut melhor = (None::<Tensor>, -1.0f64, Vec::<f64>::new()); | |
| let mut iteracoes = 0usize; | |
| let mut candidatos_final = Tensor::zeros( | |
| (self.n, self.d_saida), | |
| candle_core::DType::F32, | |
| cond.device(), | |
| )?; | |
| loop { | |
| iteracoes += 1; | |
| trajetoria.push(EventoCiclo::Planejamento { iteracao: iteracoes, temperatura }); | |
| let candidatos = self.candidatos(cond, temperatura, rng)?; | |
| candidatos_final = candidatos.clone(); | |
| let (resposta, pesos) = self.agregar(&candidatos)?; | |
| trajetoria.push(EventoCiclo::DistribuicaoCompetencias { por_tentativa: pesos.clone() }); | |
| // AVALIAÇÃO pelo VERIFICADOR NCM (sem oráculo): confiança | |
| // calibrada da distribuição preditiva do backbone | |
| let logits = logits_fn(&resposta)?; | |
| let conf = verificador.confianca_1d(&logits)?; | |
| telemetria.insert(format!("conf_iter_{}", iteracoes), conf); | |
| if conf > melhor.1 { | |
| melhor = (Some(resposta), conf, pesos.clone()); | |
| } | |
| if conf >= self.limiar_conf { | |
| trajetoria.push(EventoCiclo::Resposta { confianca: conf, iteracoes }); | |
| telemetria.insert("iteracoes".into(), iteracoes as f64); | |
| telemetria.insert("confianca_final".into(), conf); | |
| telemetria.insert("temperatura_final".into(), temperatura); | |
| telemetria.insert("verificador".into(), 1.0); // ncm (sem oráculo) | |
| return Ok(ResultadoPensamento { | |
| resposta: melhor.0, | |
| candidatos: candidatos_final, | |
| pesos: melhor.2, | |
| confianca: conf, | |
| iteracoes, | |
| trajetoria, | |
| telemetria, | |
| }); | |
| } | |
| if iteracoes >= self.orcamento { | |
| trajetoria.push(EventoCiclo::Resposta { confianca: melhor.1, iteracoes }); | |
| telemetria.insert("iteracoes".into(), iteracoes as f64); | |
| telemetria.insert("confianca_final".into(), melhor.1); | |
| telemetria.insert("temperatura_final".into(), temperatura); | |
| telemetria.insert("orcamento_esgotado".into(), 1.0); | |
| telemetria.insert("verificador".into(), 1.0); | |
| return Ok(ResultadoPensamento { | |
| resposta: melhor.0, | |
| candidatos: candidatos_final, | |
| pesos: melhor.2, | |
| confianca: melhor.1, | |
| iteracoes, | |
| trajetoria, | |
| telemetria, | |
| }); | |
| } | |
| temperatura = (temperatura * 1.6).min(2.0); | |
| trajetoria.push(EventoCiclo::RePlanejamento { | |
| motivo: format!("conf NCM {conf:.3} < limiar {}", self.limiar_conf), | |
| }); | |
| } | |
| } | |
| } | |