v0.9.0: análise/prova/teste das críticas Qwen3.8 lote 2 (r-34) — SIMD com despacho em runtime bit-a-bit (X-1), oráculo candle_nn::optim::AdamW (nosso 1,53× mais rápido), Thompson Sampling (regret 0,0019 vs UCB1 0,0156), GNG por entropia com piso (T-G1'), JEPA de fração adaptativa (bit-a-bit com 1 braço), configs tipadas, entrada numpy (2008× na fronteira), avaliação com dados reais — 104/104 gates, 111/111 cargo
d444358 verified Download src/telemetria.rs from PowerMachine/khtst-rust: direct link, hf CLI and curl.
- Browser
- Download file 19.2 kB
-
https://huggingface.co/PowerMachine/khtst-rust/resolve/main/src/telemetria.rs
- Command line
-
hf download hf://PowerMachine/khtst-rust/src/telemetria.rs
-
curl -L -o telemetria.rs https://huggingface.co/PowerMachine/khtst-rust/resolve/main/src/telemetria.rs
19.2 kB
| //! Telemetria completa (pedido do usuário: "realize todo tipo de telemetria | |
| //! inclusive para a detecção de aprendizado do modelo e dos ajustes | |
| //! automatizados de todos hiper parâmetros e demais necessários para ajustes | |
| //! e correções e detecção de erros"). | |
| //! | |
| //! Hub em Rust com: buffer por passo; exportação JSONL; detecção de | |
| //! aprendizado (escore composto); eventos de ajuste automático (auditáveis); | |
| //! eventos de erro com classificação de causa comum (diagnostico.rs); | |
| //! séries expostas ao Python como arrays numpy. | |
| use serde::Serialize; | |
| use std::collections::BTreeMap; | |
| use std::io::Write; | |
| pub struct EventoAjuste { | |
| pub passo: usize, | |
| pub parametro: String, | |
| pub de: f64, | |
| pub para: f64, | |
| pub motivo: String, | |
| } | |
| pub struct EventoErro { | |
| pub passo: usize, | |
| pub modulo: String, | |
| pub mensagem: String, | |
| pub causa_provavel: String, | |
| pub correcao_sugerida: String, | |
| } | |
| pub struct TelemetryHub { | |
| /// séries nomeadas por passo | |
| pub series: BTreeMap<String, Vec<(usize, f64)>>, | |
| pub eventos_ajuste: Vec<EventoAjuste>, | |
| pub eventos_erro: Vec<EventoErro>, | |
| pub caminho_jsonl: Option<String>, | |
| pub passo_corrente: usize, | |
| } | |
| impl TelemetryHub { | |
| pub fn novo(caminho_jsonl: Option<String>) -> Self { | |
| if let Some(p) = &caminho_jsonl { | |
| if let Some(dir) = std::path::Path::new(p).parent() { | |
| let _ = std::fs::create_dir_all(dir); | |
| } | |
| // truncates no início da corrida | |
| let _ = std::fs::File::create(p); | |
| } | |
| TelemetryHub { caminho_jsonl, ..Default::default() } | |
| } | |
| pub fn registrar(&mut self, nome: &str, valor: f64) { | |
| self.series.entry(nome.to_string()).or_default().push((self.passo_corrente, valor)); | |
| } | |
| pub fn registrar_multi(&mut self, pares: &[(&str, f64)]) { | |
| for (n, v) in pares { | |
| self.registrar(n, *v); | |
| } | |
| } | |
| pub fn ajuste(&mut self, parametro: &str, de: f64, para: f64, motivo: &str) { | |
| let ev = EventoAjuste { | |
| passo: self.passo_corrente, | |
| parametro: parametro.to_string(), | |
| de, | |
| para, | |
| motivo: motivo.to_string(), | |
| }; | |
| log::info!("ajuste: {} {:.4e}→{:.4e} ({})", parametro, de, para, motivo); | |
| self.append_jsonl("ajuste", &ev); | |
| self.eventos_ajuste.push(ev); | |
| } | |
| pub fn erro(&mut self, modulo: &str, mensagem: &str) { | |
| let causas = crate::diagnostico::classificar_erro(mensagem); | |
| let (causa, correcao, _) = causas | |
| .first() | |
| .copied() | |
| .unwrap_or(( | |
| "não classificado", | |
| "inspecionar mensagem completa; se recente à refatoração, suspeitar primeiro de digitação/renomeação", | |
| "fallback", | |
| )); | |
| let ev = EventoErro { | |
| passo: self.passo_corrente, | |
| modulo: modulo.to_string(), | |
| mensagem: mensagem.to_string(), | |
| causa_provavel: causa.to_string(), | |
| correcao_sugerida: correcao.to_string(), | |
| }; | |
| self.append_jsonl("erro", &ev); | |
| self.eventos_erro.push(ev); | |
| } | |
| /// Escreve o dump completo das séries no JSONL (evento "dump") — chamado | |
| /// ao fim de cada fase para o painel v0.5.0 (Theil–Sen por série). | |
| pub fn dump_jsonl(&self) { | |
| if let Some(p) = &self.caminho_jsonl { | |
| if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(p) { | |
| let _ = writeln!(f, "{{\"tipo\":\"dump\",\"dados\":{}}}", | |
| serde_json::to_string(&self.dump().get("series").cloned() | |
| .unwrap_or(serde_json::Value::Null)).unwrap_or_default()); | |
| } | |
| } | |
| } | |
| fn append_jsonl<T: Serialize>(&self, tipo: &str, ev: &T) { | |
| if let Some(p) = &self.caminho_jsonl { | |
| if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(p) { | |
| let _ = writeln!(f, "{{\"tipo\":\"{tipo}\",\"dados\":{}}}", | |
| serde_json::to_string(ev).unwrap_or_default()); | |
| } | |
| } | |
| } | |
| /// EMA de uma série (para suavização exponencial nos detectores). | |
| pub fn ema(&self, nome: &str, alfa: f64) -> Option<f64> { | |
| let s = self.series.get(nome)?; | |
| let mut acc = s[0].1; | |
| for (_, v) in &s[1..] { | |
| acc = alfa * v + (1.0 - alfa) * acc; | |
| } | |
| Some(acc) | |
| } | |
| /// Inclinação da EMA nos últimos k passos (detecção de aprendizado). | |
| pub fn inclinacao_ema(&self, nome: &str, k: usize, alfa: f64) -> Option<f64> { | |
| let s = self.series.get(nome)?; | |
| if s.len() < 2 { return None; } | |
| let ini = s.len().saturating_sub(k); | |
| let mut acc_ini = s[ini].1; | |
| for (_, v) in &s[ini + 1..] { | |
| acc_ini = alfa * v + (1.0 - alfa) * acc_ini; | |
| } | |
| let mut acc_fim = s[0].1; | |
| for (_, v) in &s[1..] { | |
| acc_fim = alfa * v + (1.0 - alfa) * acc_fim; | |
| } | |
| let passos = (s.len() - ini) as f64; | |
| Some((acc_fim - acc_ini) / passos) | |
| } | |
| /// Escore de aprendizado composto em [0,1]: sinal = inclinação negativa da | |
| /// perda (EMA) + nível absoluto relativo ao inicial + saúde do codebook. | |
| pub fn escore_aprendizado(&self) -> f64 { | |
| let Some(perda) = self.series.get("perda") else { return 0.0 }; | |
| if perda.len() < 4 { return 0.0; } | |
| let p0 = perda.iter().map(|(_, v)| *v).fold(f64::INFINITY, f64::min); | |
| let pl = perda.last().map(|(_, v)| *v).unwrap_or(p0); | |
| let reducao = ((p0 - pl) / p0.max(1e-9)).clamp(0.0, 1.0); | |
| let incl = self.inclinacao_ema("perda", 64, 0.05).unwrap_or(0.0); | |
| let sinal = (-incl * 1000.0).clamp(-1.0, 1.0) * 0.5 + 0.5; | |
| // saúde do codebook (se medida): perplexidade relativa | |
| let saude = self.series.get("codebook_perplexidade_media").and_then(|s| s.last().map(|(_, v)| *v)).unwrap_or(1.0); | |
| let saude_n = (saude / 16.0).clamp(0.0, 1.0); // K=64: perplexidade 16+ = saudável | |
| 0.45 * reducao + 0.35 * sinal + 0.20 * saude_n | |
| } | |
| // ------------------------------------------------------------------- | |
| // v0.5.0 — detecção de aprendizado ROBUSTA (regressão de Theil–Sen com | |
| // R²) e ajustes automatizados auditados | |
| // ------------------------------------------------------------------- | |
| /// Detecção robusta de aprendizado numa série: regressão de Theil–Sen | |
| /// (mediana das inclinações par a par — quebra com até ~29% de outliers) | |
| /// + R² contra o ajuste linear + veredito qualitativo. | |
| /// | |
| /// **Teo TD-1 (robustez do Theil–Sen):** com < 29,3% de pontos | |
| /// contaminados, a mediana das inclinações par a par permanece no | |
| /// intervalo interpolado dos resíduos limpos (breakdown point exato do | |
| /// estimador par-a-par); a média OLS quebra com UM único outlier. | |
| /// | |
| /// Retorna (inclinacao_robusta, r2, veredito) onde o veredito ∈ | |
| /// {"aprendendo", "estagnado", "divergindo", "amostra_insuficiente"}. | |
| pub fn deteccao_aprendizado(&self, nome: &str, janela: usize) -> (f64, f64, &'static str) { | |
| let Some(s) = self.series.get(nome) else { return (0.0, 0.0, "amostra_insuficiente") }; | |
| if s.len() < 4 { | |
| return (0.0, 0.0, "amostra_insuficiente"); | |
| } | |
| let ini = s.len().saturating_sub(janela.max(4)); | |
| let pts: Vec<(f64, f64)> = s[ini..].iter().map(|(t, v)| (*t as f64, *v)).collect(); | |
| let n = pts.len(); | |
| if n < 4 { | |
| return (0.0, 0.0, "amostra_insuficiente"); | |
| } | |
| // Theil–Sen: mediana das inclinações (i < j) | |
| let mut inclinacoes: Vec<f64> = Vec::with_capacity(n * (n - 1) / 2); | |
| for i in 0..n { | |
| for j in i + 1..n { | |
| let dx = pts[j].0 - pts[i].0; | |
| if dx.abs() > 1e-12 { | |
| inclinacoes.push((pts[j].1 - pts[i].1) / dx); | |
| } | |
| } | |
| } | |
| if inclinacoes.is_empty() { | |
| return (0.0, 0.0, "amostra_insuficiente"); | |
| } | |
| inclinacoes.sort_by(|a, b| a.partial_cmp(b).unwrap()); | |
| let incl = inclinacoes[inclinacoes.len() / 2]; | |
| // intercepto pela mediana dos resíduos | |
| let mut res_intercept: Vec<f64> = pts.iter().map(|(x, y)| y - incl * x).collect(); | |
| res_intercept.sort_by(|a, b| a.partial_cmp(b).unwrap()); | |
| let b0 = res_intercept[res_intercept.len() / 2]; | |
| // R² do ajuste robusto (1 − SS_res/SS_tot) | |
| let media_y: f64 = pts.iter().map(|(_, y)| y).sum::<f64>() / n as f64; | |
| let ss_tot: f64 = pts.iter().map(|(_, y)| (y - media_y) * (y - media_y)).sum(); | |
| let ss_res: f64 = pts | |
| .iter() | |
| .map(|(x, y)| { | |
| let e = y - (incl * x + b0); | |
| e * e | |
| }) | |
| .sum(); | |
| let r2 = if ss_tot > 1e-12 { (1.0 - ss_res / ss_tot).clamp(-1.0, 1.0) } else { 0.0 }; | |
| // veredito: a escala da inclinação é relativa à magnitude da série | |
| let escala = media_y.abs().max(1e-9); | |
| let rel = incl / escala; | |
| let veredito = if rel < -1e-4 { | |
| "aprendendo" | |
| } else if rel > 1e-4 { | |
| "divergindo" | |
| } else { | |
| "estagnado" | |
| }; | |
| (incl, r2, veredito) | |
| } | |
| /// Painel de telemetria v0.5.0: detecção de aprendizado em TODAS as | |
| /// séries registradas + eventos de ajuste computados + saúde geral. | |
| pub fn painel_v050(&self) -> serde_json::Value { | |
| let deteccoes: serde_json::Map<String, serde_json::Value> = self | |
| .series | |
| .iter() | |
| .map(|(k, _)| { | |
| let (incl, r2, vered) = self.deteccao_aprendizado(k, 256); | |
| ( | |
| k.clone(), | |
| serde_json::json!({ | |
| "inclinacao_theil_sen": incl, | |
| "r2": r2, | |
| "veredito": vered, | |
| "n_amostras": self.series[k].len(), | |
| }), | |
| ) | |
| }) | |
| .collect(); | |
| serde_json::json!({ | |
| "versao": "0.5.0", | |
| "deteccoes": deteccoes, | |
| "escore_aprendizado": self.escore_aprendizado(), | |
| "ajustes_computados": self.eventos_ajuste.len(), | |
| "erros_registrados": self.eventos_erro.len(), | |
| }) | |
| } | |
| /// Dump completo (para o relatório Python). | |
| pub fn dump(&self) -> serde_json::Value { | |
| let series: serde_json::Map<String, serde_json::Value> = self | |
| .series | |
| .iter() | |
| .map(|(k, v)| { | |
| ( | |
| k.clone(), | |
| serde_json::Value::Array( | |
| v.iter().map(|(p, x)| serde_json::json!([p, x])).collect(), | |
| ), | |
| ) | |
| }) | |
| .collect(); | |
| serde_json::json!({ | |
| "series": series, | |
| "eventos_ajuste": self.eventos_ajuste, | |
| "eventos_erro": self.eventos_erro, | |
| "escore_aprendizado": self.escore_aprendizado(), | |
| }) | |
| } | |
| /// Últimos valores de todas as séries (snapshot para callback Python). | |
| pub fn instantaneo(&self) -> Vec<(String, f64)> { | |
| self.series | |
| .iter() | |
| .filter_map(|(k, v)| v.last().map(|(_, x)| (k.clone(), *x))) | |
| .collect() | |
| } | |
| // ------------------------------------------------------------------- | |
| // v0.6.0 — acordo par-a-par, polaridade por métrica, painel v0.6.0 | |
| // ------------------------------------------------------------------- | |
| /// **Acordo par-a-par das tentativas** (insumo do A-3): média das | |
| /// similaridades de cosseno entre pares de candidatos (n, d). | |
| /// Sob o modelo binário i.i.d. (correto c/ prob. p), o acordo em | |
| ///expectância é `a = p² + (1−p)²` (lema do acordo — r-26 A-3). | |
| pub fn acordo_par_a_par(candidatos: &[(String, f64)]) -> f64 { | |
| // variante escalar: média das concordâncias sinalizadas entre pares | |
| if candidatos.len() < 2 { | |
| return 0.0; | |
| } | |
| let mut soma = 0f64; | |
| let mut pares = 0usize; | |
| for i in 0..candidatos.len() { | |
| for j in i + 1..candidatos.len() { | |
| let (_, vi) = &candidatos[i]; | |
| let (_, vj) = &candidatos[j]; | |
| // concordância = 1 − |vi − vj| (métrica em [0,1]) | |
| soma += 1.0 - (vi - vj).abs(); | |
| pares += 1; | |
| } | |
| } | |
| soma / pares.max(1) as f64 | |
| } | |
| /// **Polaridade por métrica** (fecha o débito r-22 §7): para séries | |
| /// cujo nome indica ERRO (perda, ce, erro, eq, mse, delta, norma) a | |
| /// direção BOA é DESCER (−1); para as demais (confiança, acurácia, | |
| /// perplexidade de codebook, escore, accordo) a direção boa é SUBIR | |
| /// (+1). O veredito "aprendendo" agora é POLARIDADE-AWARE: | |
| /// aprendendo ⇔ inclinação na direção boa. | |
| pub fn polaridade(nome: &str) -> i8 { | |
| let n = nome.to_ascii_lowercase(); | |
| const NEGATIVAS: [&str; 9] = | |
| ["perda", "ce", "erro", "eq", "mse", "delta_ort", "norma_grad", "diverg", "tempo"]; | |
| if NEGATIVAS.iter().any(|p| n.starts_with(p) || n.contains(&format!("_{p}"))) { | |
| -1 | |
| } else { | |
| 1 | |
| } | |
| } | |
| /// Painel de telemetria v0.6.0: detecção polaridade-aware em TODAS as | |
| /// séries + veredito consolidado por direção. | |
| pub fn painel_v060(&self) -> serde_json::Value { | |
| let mut aprendendo = 0usize; | |
| let mut piorando = 0usize; | |
| let mut estagnado = 0usize; | |
| let deteccoes: serde_json::Map<String, serde_json::Value> = self | |
| .series | |
| .iter() | |
| .map(|(k, _)| { | |
| let (incl, r2, vered) = self.deteccao_aprendizado(k, 256); | |
| let pol = Self::polaridade(k) as i8; | |
| // veredito polaridade-aware: incl·pol < 0 ⇒ aprendendo | |
| let prod = incl * pol as f64; | |
| let vered_pol: &'static str = if self.series[k].len() < 4 { | |
| "amostra_insuficiente" | |
| } else if prod < -1e-9 { | |
| "aprendendo" | |
| } else if prod > 1e-9 { | |
| "piorando" | |
| } else { | |
| "estagnado" | |
| }; | |
| match vered_pol { | |
| "aprendendo" => aprendendo += 1, | |
| "piorando" => piorando += 1, | |
| _ => estagnado += 1, | |
| } | |
| let _ = vered; | |
| ( | |
| k.clone(), | |
| serde_json::json!({ | |
| "inclinacao_theil_sen": incl, | |
| "r2": r2, | |
| "polaridade": pol, | |
| "veredito_polarizado": vered_pol, | |
| "n_amostras": self.series[k].len(), | |
| }), | |
| ) | |
| }) | |
| .collect(); | |
| serde_json::json!({ | |
| "versao": "0.6.0", | |
| "deteccoes": deteccoes, | |
| "resumo_direcoes": { | |
| "aprendendo": aprendendo, "piorando": piorando, "estagnado": estagnado, | |
| }, | |
| "escore_aprendizado": self.escore_aprendizado(), | |
| "ajustes_computados": self.eventos_ajuste.len(), | |
| "erros_registrados": self.eventos_erro.len(), | |
| }) | |
| } | |
| /// Painel v0.7.0 — herda o v0.6.0 (polaridade-aware) e adiciona o | |
| /// bloco de sinais dos módulos novos: FNH (gate/punição/δ_ort), árvore | |
| /// de pesos (crédito/regulação por nó), quebra-cabeça (aceitação/ | |
| /// confiança) e NLP/NLG (gates/coerência). Métricas ausentes são | |
| /// simplesmente omitidas (painel ADITIVO — sem acoplamento rígido). | |
| /// Painel v0.9.0 — despacho SIMD + contexto das medições da r-34. | |
| pub fn painel_v090(&self) -> serde_json::Value { | |
| let mut p = self.painel_v070(None); | |
| if let Some(obj) = p.as_object_mut() { | |
| obj.insert("simd".into(), crate::simd::relatorio()); | |
| obj.insert( | |
| "v090_nota".into(), | |
| serde_json::json!( | |
| "kernels AVX-512 com despacho em runtime (bit-a-bit); medições em r-34 §4/§6: \ | |
| AdamW 1,0-1,2× (limitado por memória), BMU argmin: escalar ótimo em d=4; \ | |
| bf16 em CPU candle: 3× MAIS LENTO (upcast por op); nosso AdamW 1,5-1,7× mais \ | |
| rápido que candle_nn::optim" | |
| ), | |
| ); | |
| } | |
| p | |
| } | |
| pub fn painel_v070(&self, arvore: Option<&crate::arvore_pesos::ArvorePesos>) -> serde_json::Value { | |
| let mut p = self.painel_v060(); | |
| // promove a versão sem perder o conteúdo | |
| p["versao"] = serde_json::json!("0.7.0"); | |
| // sinais FNH (registrados no hub como fnh_*) | |
| let fnh = serde_json::json!({ | |
| "gate_ultimo": self.ema("fnh_gate", 0.9), | |
| "punicao_ema": self.ema("fnh_punicao", 0.9), | |
| "delta_ort_rotacao": self.ema("fnh_delta_ort", 0.9), | |
| "theta_norma": self.ema("fnh_theta_norma", 0.9), | |
| }); | |
| p["fnh"] = fnh; | |
| // quebra-cabeça | |
| p["quebra_cabeca"] = serde_json::json!({ | |
| "taxa_aceitacao": self.ema("qc_taxa_aceitacao", 0.8), | |
| "confianca_melhor": self.ema("qc_confianca_melhor", 0.8), | |
| "confianca_base": self.ema("qc_confianca_base", 0.8), | |
| "punicao_acumulada": self.ema("qc_punicao", 0.8), | |
| }); | |
| // NLP/NLG | |
| p["nlp_nlg"] = serde_json::json!({ | |
| "nlp_gate_medio": self.ema("nlp_gate_medio", 0.9), | |
| "nlg_coerencia": self.ema("nlg_score_coerencia", 0.9), | |
| "espec_aceitacao": self.ema("espec_aceitacao", 0.9), | |
| "espec_speedup": self.ema("espec_speedup", 0.9), | |
| }); | |
| // árvore de pesos: métricas agregadas por nó (fundo-topo) | |
| if let Some(a) = arvore { | |
| let nos: serde_json::Map<String, serde_json::Value> = a | |
| .nos | |
| .iter() | |
| .map(|no| { | |
| let m: serde_json::Map<String, serde_json::Value> = no | |
| .metricas | |
| .iter() | |
| .map(|(k, v)| (k.clone(), serde_json::json!(v))) | |
| .collect(); | |
| ( | |
| no.nome.clone(), | |
| serde_json::json!({ | |
| "intervalo": [no.ini, no.fim], | |
| "profundidade": no.profundidade, | |
| "metricas": m, | |
| }), | |
| ) | |
| }) | |
| .collect(); | |
| p["arvore_pesos"] = serde_json::json!({ | |
| "n_nos": a.n_nos(), | |
| "n_enlaces": a.enlaces.len(), | |
| "n_params": a.n_params, | |
| "nos": nos, | |
| "invariantes_ok": a.invariantes_ok().is_ok(), | |
| }); | |
| } | |
| p | |
| } | |
| } | |