//! `python::demos_qwen` — 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::*; /// **v0.8.0 — análise/prova/teste das 4 propostas Qwen3.8** (r-33): mede /// custo e exatidão de (1) Newton analítico vs golden-section (verificador), /// (2) Cayley vs polar (FNH), (3) árvore pós-ordem iterativa vs recursiva, /// (4) MoE vetorizada vs referência (valores + gradiente restaurado — bug /// v080-1), e re-executa a ablação JEPA de 3 braços com a correção. #[pyfunction] #[allow(deprecated)] // mede o bug v080-1 intencionalmente (fwd = oráculo) pub(crate) fn demo_qwen_v080(py: Python) -> PyResult { use candle_core::{Device, Tensor}; use std::time::Instant; let dev = Device::Cpu; // ---------- (1) Newton vs golden-section (verificador) ---------- let mut rng = crate::rng::RngDeterministico::novo(808); let (n, k) = (1024usize, 32usize); let mut lv = Vec::with_capacity(n * k); let mut av = Vec::with_capacity(n); for _ in 0..n { let alvo = rng.inteiro(k) as u32; av.push(alvo); for j in 0..k { let base = if j as u32 == alvo { 3.0 } else { 0.0 }; lv.push(base + rng.normal(0.0, 1.0) as f32); } } let lt = Tensor::from_vec(lv, (n, k), &dev).unwrap(); let at = Tensor::from_vec(av, (n,), &dev).unwrap(); let t0 = Instant::now(); let (t_g, _, nll_g) = verificador::calibrar_temperatura(<, &at).unwrap(); let tempo_golden = t0.elapsed().as_secs_f64(); let t0 = Instant::now(); let (t_n, _, nll_n, n_aval) = verificador::calibrar_temperatura_newton(<, &at, 40).unwrap(); let tempo_newton = t0.elapsed().as_secs_f64(); // ---------- (2) Cayley vs polar (FNH) ---------- let n_rot = 16usize; let r = fnh::RotacaoSkew::nova(n_rot, &dev).unwrap(); let th: Vec = (0..n_rot * (n_rot - 1) / 2) .map(|i| ((i as f32) * 0.11) % 0.5) .collect(); r.theta .set(&Tensor::from_vec(th, (n_rot * (n_rot - 1) / 2,), &dev).unwrap()) .unwrap(); let rep = 25usize; let t0 = Instant::now(); let mut delta_polar = 0.0; for _ in 0..rep { delta_polar = crate::stiefel::erro_ortogonalidade(&r.rotacao_polar().unwrap()).unwrap(); } let tempo_polar = t0.elapsed().as_secs_f64() / rep as f64; let t0 = Instant::now(); let mut delta_cayley = 0.0; for _ in 0..rep { delta_cayley = crate::stiefel::erro_ortogonalidade(&r.rotacao_cayley().unwrap()).unwrap(); } let tempo_cayley = t0.elapsed().as_secs_f64() / rep as f64; // ---------- (3) árvore iterativa vs recursiva ---------- let mut arv = arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new()); let mut pai = 1usize; for i in 0..20_000u32 { pai = arv.anexar_filho(pai, &format!("c{i}"), 1); } arv.nos[pai].metricas.insert("m".into(), 1.0); let t0 = Instant::now(); let total_it = arv.agregar_fundo_topo("m"); let tempo_arv_it = t0.elapsed().as_secs_f64(); // repõe TODAS as métricas locais (a agregação sobrescreveu com os // agregados) — só a folha tem valor; o resto volta a 0 — e roda a // recursiva (profundidade 20k ok na thread principal do Python) for no in arv.nos.iter_mut() { no.metricas.insert("m".into(), 0.0); } arv.nos[pai].metricas.insert("m".into(), 1.0); let t0 = Instant::now(); let total_rec = arv.agregar_fundo_topo_rec("m"); let tempo_arv_rec = t0.elapsed().as_secs_f64(); // ---------- (4) MoE vetorizada: valores + gradiente + custo ---------- let (nm, dm, em) = (256usize, 32usize, 8usize); let xv: Vec = (0..nm * dm).map(|_| rng.normal(0.0, 1.0) as f32).collect(); let x = Tensor::from_vec(xv, (nm, dm), &dev).unwrap(); let moe = moe_redirecionavel::MoeRedirecionavel::novo(dm, em, 2, nm, &dev).unwrap(); let s_ref = moe.fwd(&x, moe_redirecionavel::Papel::Codificador).unwrap(); let s_vet = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap(); let dif_moe = (&s_ref.saida - &s_vet.saida) .unwrap() .abs() .unwrap() .max_all() .unwrap() .to_scalar::() .unwrap() as f64; let rep_moe = 10usize; let t0 = Instant::now(); for _ in 0..rep_moe { let _ = moe.fwd(&x, moe_redirecionavel::Papel::Codificador).unwrap(); } let tempo_moe_ref = t0.elapsed().as_secs_f64() / rep_moe as f64; let t0 = Instant::now(); for _ in 0..rep_moe { let _ = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap(); } let tempo_moe_vet = t0.elapsed().as_secs_f64() / rep_moe as f64; // normas de gradiente (bug v080-1: fwd = 0; vetorizada > 0) let norma = |vet: bool| -> (f64, f64) { let s = if vet { moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap() } else { moe.fwd(&x, moe_redirecionavel::Papel::Codificador).unwrap() }; let perda = s.saida.sqr().unwrap().sum_all().unwrap(); let gs = perda.backward().unwrap(); let mut nr = 0.0; for v in moe.roteador.vars() { if let Some(g) = gs.get(&v) { nr += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; } } let mut ne = 0.0; for e in &moe.especialistas { for v in e.vars() { if let Some(g) = gs.get(&v) { ne += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; } } } (nr, ne) }; let (grad_ref_r, grad_ref_e) = norma(false); let (grad_vet_r, grad_vet_e) = norma(true); // ---------- (5) re-ablação JEPA 3 braços (gradiente restaurado) ---------- let xv: Vec = (0..64 * 12).map(|_| rng.normal(0.0, 1.0) as f32).collect(); let xj = Tensor::from_vec(xv, (64, 12), &dev).unwrap(); let mut ablac = serde_json::Map::new(); for modo in ["pequena", "densa", "moe"] { let rj = moe_redirecionavel::treinar_jepa_com_moe(&xj, modo, 120, 0.5, 42).unwrap(); ablac.insert( modo.into(), serde_json::json!({ "perda_inicial": rj.perda_pred_inicial, "perda_final": rj.perda_pred_final, "queda_pct": 100.0 * (1.0 - rj.perda_pred_final / rj.perda_pred_inicial.max(1e-9)), }), ); } let d_ = serde_json::json!({ "versao": "qwen-v080-1.0", "newton_vs_golden": { "t_golden": t_g, "t_newton": t_n, "dif_t": (t_g - t_n).abs(), "nll_golden": nll_g, "nll_newton": nll_n, "nll_dif": (nll_g - nll_n).abs(), "n_aval_newton": n_aval, "tempo_golden_s": tempo_golden, "tempo_newton_s": tempo_newton, "speedup": tempo_golden / tempo_newton.max(1e-9), "gates": { "mesmo_t": (t_g - t_n).abs() < 1e-3, "nll_nao_piora": nll_n <= nll_g + 1e-4, "menos_avaliacoes": n_aval < 36, }, }, "cayley_vs_polar": { "delta_ort_polar": delta_polar, "delta_ort_cayley": delta_cayley, "tempo_polar_s": tempo_polar, "tempo_cayley_s": tempo_cayley, "speedup": tempo_polar / tempo_cayley.max(1e-9), "gates": { "ambos_exatos": delta_polar < 1e-6 && delta_cayley < 1e-6, "cayley_mais_rapido": tempo_cayley < tempo_polar, }, }, "arvore_iter_vs_rec": { "n_nos": arv.n_nos(), "total_iter": total_it, "total_rec": total_rec, "bit_a_bit": total_it.to_bits() == total_rec.to_bits(), "tempo_iter_s": tempo_arv_it, "tempo_rec_s": tempo_arv_rec, "gates": { "iguais_bit_a_bit": total_it == total_rec }, }, "moe_vetorizada": { "max_dif_valores": dif_moe, "tempo_ref_s": tempo_moe_ref, "tempo_vet_s": tempo_moe_vet, "grad_roteador_ref": grad_ref_r, "grad_especialistas_ref": grad_ref_e, "grad_roteador_vet": grad_vet_r, "grad_especialistas_vet": grad_vet_e, "gates": { "valores_iguais": dif_moe < 1e-4, "bug_v080_1_confirmado": grad_ref_r == 0.0 && grad_ref_e == 0.0, "gradiente_restaurado": grad_vet_r > 0.0 && grad_vet_e > 0.0, }, }, "ablacao_jepa_3_bracos_v080": ablac, }); Ok(pythonize::pythonize(py, &d_)?.into()) } /// **v0.9.0 — veredito executável das críticas Qwen3.8 (r-34):** mede os /// aprimoramentos contra as referências v0.8.0 (oráculos preservados): /// (1) skew O(n²) via index_add — bit-a-bit + tempo + memória analítica; /// (2) calibrar vetorial — bit-a-bit em 3 passos EMA + tempo; /// (3) forward canônico (ex-fwd_vetorizado) — regressão de valores e /// gradiente; /// (4) newtypes de domínio — rejeição de fora-de-domínio. #[pyfunction] pub(crate) fn demo_criticas_v090(py: Python) -> PyResult { use candle_core::{Device, Tensor}; use std::time::Instant; let dev = Device::Cpu; let mut rng = crate::rng::RngDeterministico::novo(909); // ---------- (1) skew: index_add O(n²) vs matmul de seleção O(n⁴) ---------- let mut skew_rel = serde_json::Map::new(); let mut bits_todos = true; for &n in &[16usize, 64, 128] { let r = fnh::RotacaoSkew::nova(n, &dev).unwrap(); let m = n * (n - 1) / 2; let th: Vec = (0..m).map(|_| rng.uniforme_intervalo(-0.9, 0.9) as f32).collect(); r.theta.set(&Tensor::from_vec(th, (m,), &dev).unwrap()).unwrap(); // bit-a-bit (F-13) let an = r.skew().unwrap().flatten_all().unwrap().to_vec1::().unwrap(); let ar = r.skew_matmul_referencia().unwrap().flatten_all().unwrap().to_vec1::().unwrap(); let bits_iguais = an.iter().zip(ar.iter()).all(|(a, b)| a.to_bits() == b.to_bits()); bits_todos &= bits_iguais; // tempo (referência esquentada 1×; média de 5) let _ = r.skew_matmul_referencia().unwrap(); let t0 = Instant::now(); for _ in 0..5 { let _ = r.skew_matmul_referencia().unwrap(); } let t_ref = t0.elapsed().as_secs_f64() / 5.0; let _ = r.skew().unwrap(); let t0 = Instant::now(); for _ in 0..5 { let _ = r.skew().unwrap(); } let t_novo = t0.elapsed().as_secs_f64() / 5.0; let bytes_ref = (m * n * n * 4) as f64; let bytes_novo = ((n * n + 3 * m) * 4 + 2 * m * 4) as f64; skew_rel.insert( format!("n_{n}"), serde_json::json!({ "tempo_ref_s": t_ref, "tempo_novo_s": t_novo, "speedup": t_ref / t_novo.max(1e-12), "bytes_ref_selecao": bytes_ref, "bytes_novo": bytes_novo, "razao_memoria": bytes_ref / bytes_novo, "bits_iguais": bits_iguais, }), ); } // ---------- (2) calibrar: tensor vs laços nativos ---------- let (d_out, d_in, n_lin) = (512usize, 512usize, 4096usize); let wv: Vec = (0..d_out * d_in).map(|_| rng.uniforme_intervalo(-2.0, 2.0) as f32).collect(); let w = Tensor::from_vec(wv, (d_out, d_in), &dev).unwrap(); let xv: Vec = (0..n_lin * d_in).map(|_| rng.uniforme_intervalo(-3.0, 3.0) as f32).collect(); let x = Tensor::from_vec(xv, (n_lin, d_in), &dev).unwrap(); let mut s_novo = fnh::SuavizadorQuant::novo(0.5, 0.98); let mut s_tens = fnh::SuavizadorQuant::novo(0.5, 0.98); let mut s_ref = fnh::SuavizadorQuant::novo(0.5, 0.98); let mut bits_cal = true; for _ in 0..3 { s_novo.calibrar(&w, &x).unwrap(); s_tens.calibrar_tensor(&w, &x).unwrap(); s_ref.calibrar_referencia(&w, &x).unwrap(); for (a, b) in s_novo.w_max.as_ref().unwrap().iter().zip(s_ref.w_max.as_ref().unwrap()) { bits_cal &= a.to_bits() == b.to_bits(); } for (a, b) in s_novo.x_max.as_ref().unwrap().iter().zip(s_ref.x_max.as_ref().unwrap()) { bits_cal &= a.to_bits() == b.to_bits(); } } // tempo (esquentadas 1×; média de 5) let _ = s_ref.calibrar_referencia(&w, &x); let t0 = Instant::now(); for _ in 0..5 { s_ref.calibrar_referencia(&w, &x).unwrap(); } let t_cal_ref = t0.elapsed().as_secs_f64() / 5.0; let _ = s_novo.calibrar(&w, &x); let t0 = Instant::now(); for _ in 0..5 { s_novo.calibrar(&w, &x).unwrap(); } let t_cal_novo = t0.elapsed().as_secs_f64() / 5.0; let _ = s_tens.calibrar_tensor(&w, &x); let t0 = Instant::now(); for _ in 0..5 { s_tens.calibrar_tensor(&w, &x).unwrap(); } let t_cal_tens = t0.elapsed().as_secs_f64() / 5.0; // ---------- (3) forward: regressão de valores + gradiente ---------- let (nm, dm, em) = (256usize, 32usize, 8usize); let xv2: Vec = (0..nm * dm).map(|_| rng.normal(0.0, 1.0) as f32).collect(); let x2 = Tensor::from_vec(xv2, (nm, dm), &dev).unwrap(); #[allow(deprecated)] // oráculo do bug v080-1 (r-33 §5) let moe = moe_redirecionavel::MoeRedirecionavel::novo(dm, em, 2, nm, &dev).unwrap(); #[allow(deprecated)] let s_refm = moe.fwd(&x2, moe_redirecionavel::Papel::Codificador).unwrap(); let s_fwd = moe.forward(&x2, moe_redirecionavel::Papel::Codificador).unwrap(); let dif_moe = (&s_refm.saida - &s_fwd.saida).unwrap().abs().unwrap() .max_all().unwrap().to_scalar::().unwrap() as f64; let perda = s_fwd.saida.sqr().unwrap().sum_all().unwrap(); let gs = perda.backward().unwrap(); let mut gr = 0.0f64; for v in moe.roteador.vars() { if let Some(g) = gs.get(&v) { gr += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; } } let mut ge = 0.0f64; for e in &moe.especialistas { for v in e.vars() { if let Some(g) = gs.get(&v) { ge += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; } } } // ---------- (4) newtypes de domínio (T-V1) ---------- let rejeita = crate::tipos::Probabilidade::novo(7.0).is_err() && crate::tipos::Probabilidade::novo(-0.5).is_err() && crate::tipos::Probabilidade::novo(f64::NAN).is_err() && crate::tipos::GateAtivacao::novo(1.5).is_err(); let aceita = crate::tipos::Probabilidade::novo(0.37).unwrap().valor() == 0.37; let gate_tipado = fnh::GatePunicao::default() .valor_tipado(&crate::tipos::Probabilidade::novo(0.9).unwrap()) .valor(); let d_ = serde_json::json!({ "versao": "criticas-v090-1.0", "skew_index_add": { "por_n": skew_rel, "gates": { "bits_iguais_todos_n": bits_todos, }, }, "calibrar_zero_copia": { "bits_iguais_3_passos": bits_cal, "tempo_ref_s": t_cal_ref, "tempo_novo_s": t_cal_novo, "tempo_tensor_s": t_cal_tens, "speedup_zero_copia": t_cal_ref / t_cal_novo.max(1e-12), "razao_tensor_vs_zero_copia": t_cal_tens / t_cal_novo.max(1e-12), "gates": { "bit_a_bit": bits_cal, "nao_regride": t_cal_novo <= t_cal_ref * 1.05 }, }, "forward_canonico": { "max_dif_valores_vs_oraculo": dif_moe, "grad_roteador": gr, "grad_especialistas": ge, "gates": { "valores_iguais": dif_moe < 1e-4, "gradiente_ativo": gr > 0.0 && ge > 0.0, }, }, "newtypes_dominio": { "rejeita_fora_do_dominio": rejeita, "aceita_dentro": aceita, "gate_tipado_em_0_9": gate_tipado, "gates": { "fronteira_validada": rejeita && aceita }, }, }); Ok(pythonize::pythonize(py, &d_)?.into()) }