khtst-rust / src /python /nucleo.rs
PowerMachine's picture
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
Raw History Blame Contribute Delete
8.32 kB
//! `python::nucleo` — 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::*;
#[pyfunction]
pub(crate) fn versao() -> &'static str {
concat!("khtst-rust-", env!("CARGO_PKG_VERSION"))
}
#[pyfunction]
pub(crate) fn info_build() -> String {
format!(
"rustc={}; perfil={}; features={}; alvo={}",
build_info::RUSTC_VERSION,
build_info::PROFILE,
build_info::FEATURES_STR,
build_info::TARGET,
)
}
/// Lint de config com sugestões por Levenshtein (diagnóstico de digitação).
#[pyfunction]
pub(crate) fn lint_config(py: Python, caminho: &str) -> PyResult<PyObject> {
let txt = std::fs::read_to_string(caminho)
.map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{caminho}: {e}")))?;
let v: serde_json::Value = serde_json::from_str(&txt)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?;
let achatado = diagnostico::achatar(&v);
let avisos = diagnostico::lint_chaves(&achatado);
let saida: Vec<serde_json::Value> = avisos
.iter()
.map(|a| serde_json::json!({
"chave": a.chave, "sugestao": a.sugestao, "causa": a.causa, "correcao": a.correcao,
}))
.collect();
Ok(pythonize::pythonize(py, &serde_json::json!(saida))?.into())
}
#[pyclass(name = "Bpe")]
pub(crate) struct BpePy {
bpe: tokens::Bpe,
}
#[pymethods]
impl BpePy {
#[staticmethod]
fn treinar(textos: Vec<String>, vocab_alvo: usize) -> PyResult<Self> {
Ok(BpePy { bpe: tokens::Bpe::treinar(&textos, vocab_alvo) })
}
#[staticmethod]
fn carregar(caminho: &str) -> PyResult<Self> {
let txt = std::fs::read_to_string(caminho)
.map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{e}")))?;
let v: serde_json::Value = serde_json::from_str(&txt)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?;
let bpe = tokens::Bpe::de_json(&v)
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("formato BPE inválido"))?;
Ok(BpePy { bpe })
}
fn codificar(&mut self, texto: &str) -> Vec<u32> {
self.bpe.codificar(texto)
}
fn decodificar(&self, ids: Vec<u32>) -> String {
self.bpe.decodificar(&ids)
}
fn salvar(&self, caminho: &str) -> PyResult<()> {
let txt = serde_json::to_string(&self.bpe.para_json()).unwrap();
crate::checkpoint::salvar_atomico(caminho, &txt)
.map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{e}")))?;
Ok(())
}
#[getter]
fn n_vocab(&self) -> usize {
self.bpe.n_vocab()
}
}
#[pyclass(name = "Config")]
pub(crate) struct ConfigPy {
pub(crate) cfg: config::ConfigKhtst,
caminho: Option<String>,
}
#[pymethods]
impl ConfigPy {
#[new]
#[allow(never_type_fallback)]
#[pyo3(signature = (caminho=None))]
fn new(caminho: Option<String>) -> PyResult<Self> {
let cfg = match &caminho {
Some(c) => {
let txt = std::fs::read_to_string(c)
.map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{c}: {e}")))?;
let v: serde_json::Value = serde_json::from_str(&txt)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?;
serde_json::from_value(v)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("config: {e}")))?
}
None => config::ConfigKhtst::default(),
};
cfg.validar().map_err(pyo3::exceptions::PyValueError::new_err)?;
Ok(ConfigPy { cfg, caminho })
}
fn validar(&self) -> PyResult<()> {
self.cfg.validar().map_err(pyo3::exceptions::PyValueError::new_err)
}
fn checar_orcamento_cache(&self) -> PyResult<usize> {
self.cfg.checar_orcamento_cache().map_err(pyo3::exceptions::PyValueError::new_err)
}
fn resumo(&self) -> String {
format!(
"modelo(vocab={}, d={}, camadas={}, ctx={}) ncm(L={}, niveis={:?}, K={}, d_lat={}, modo={}, ativo={})",
self.cfg.modelo.vocab, self.cfg.modelo.d_modelo, self.cfg.modelo.n_camadas,
self.cfg.modelo.comprimento_ctx, self.cfg.l_niveis(), self.cfg.ncm.niveis,
self.cfg.ncm.k_por_nivel, self.cfg.ncm.d_lat, self.cfg.ncm.modo_quantizacao, self.cfg.ncm.ativo,
)
}
#[getter]
fn l_niveis(&self) -> usize {
self.cfg.l_niveis()
}
}
#[pyclass(name = "PonteDupla")]
pub(crate) struct PonteDuplaPy {
ponte: ponte::PonteDupla,
}
#[pymethods]
impl PonteDuplaPy {
#[new]
#[pyo3(signature = (capacidade=64, timeout_ms=2000))]
fn new(capacidade: usize, timeout_ms: u64) -> Self {
PonteDuplaPy { ponte: ponte::PonteDupla::nova(capacidade, timeout_ms) }
}
fn rodada(&self, carga_a: Vec<f32>, carga_b: Vec<f32>, nivel: usize) -> PyResult<(Vec<f32>, Vec<f32>)> {
let (ma, mb) = self.ponte.rodada(carga_a, carga_b, nivel)
.map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
Ok((ma.carga, mb.carga))
}
fn demo_professor_aluno(&self, rodadas: usize, dim: usize) -> PyResult<PyObject> {
let inicio: Vec<f32> = (0..dim).map(|i| (i as f32) * 0.01).collect();
let stats = ponte::demo_professor_aluno(&self.ponte, rodadas, inicio, &|a, _| {
a.iter().map(|x| x * 1.05).collect()
})
.map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
Python::with_gil(|py| {
Ok(pythonize::pythonize(py, &serde_json::to_value(&stats).unwrap())?.into())
})
}
fn estatisticas(&self) -> PyResult<PyObject> {
let s = self.ponte.estatisticas();
Python::with_gil(|py| {
Ok(pythonize::pythonize(py, &serde_json::to_value(&s).unwrap())?.into())
})
}
}
#[cfg(feature = "ponte_async")]
#[pyclass(name = "PonteAsync")]
pub(crate) struct PonteAsyncPy {
interna: std::sync::Arc<ponte::assincrona::PonteAsync>,
}
#[cfg(feature = "ponte_async")]
#[pymethods]
impl PonteAsyncPy {
#[new]
#[pyo3(signature = (capacidade=64, timeout_ms=2000))]
fn new(capacidade: usize, timeout_ms: u64) -> Self {
PonteAsyncPy { interna: std::sync::Arc::new(ponte::assincrona::PonteAsync::nova(capacidade, timeout_ms)) }
}
/// Corrotina: `await ponte.trocar(a, b, nivel)` — duplex simultânea.
fn trocar<'p>(&self, py: Python<'p>, carga_a: Vec<f32>, carga_b: Vec<f32>, nivel: usize) -> PyResult<Bound<'p, PyAny>> {
let p = self.interna.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let (ma, mb) = p.rodada_async(carga_a, carga_b, nivel)
.await
.map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
Ok::<_, PyErr>((ma.carga, mb.carga))
})
}
}
/// Utilitários de prova expostos ao Python (V-série da suíte).
#[pyfunction]
pub(crate) fn verificar_mascara(cfg: &ConfigPy) -> PyResult<usize> {
let dev = candle_core::Device::Cpu;
let m = mascaras::mascara_block_causal(&cfg.cfg.ncm.niveis, gerador::N_PREFIXO, &dev)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
mascaras::contar_vazamentos(&cfg.cfg.ncm.niveis, gerador::N_PREFIXO, &m)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
}
#[pyfunction]
pub(crate) fn verificar_stiefel(k: usize, d: usize) -> PyResult<f64> {
let dev = candle_core::Device::Cpu;
let c = stiefel::init_stiefel(k, d, &dev)
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
stiefel::erro_ortogonalidade(&c.transpose(0, 1).unwrap()).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
}
#[pyfunction]
pub(crate) fn checkpoint_valido(caminho: &str) -> bool {
crate::checkpoint::validar(caminho)
}
#[pyfunction]
pub(crate) fn checkpoint_manifesto(caminho: &str) -> Option<String> {
std::fs::read_to_string(crate::checkpoint::caminho_manifesto(caminho)).ok()
}
#[pyfunction]
pub(crate) fn sha256_arquivo(caminho: &str) -> Option<String> {
std::fs::read(caminho).ok().map(|d| crate::checkpoint::sha256_hex(&d))
}