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/bin/bench_v090.rs from PowerMachine/khtst-rust: direct link, hf CLI and curl.
- Browser
- Download file 13.5 kB
-
https://huggingface.co/PowerMachine/khtst-rust/resolve/main/src/bin/bench_v090.rs
- Command line
-
hf download hf://PowerMachine/khtst-rust/src/bin/bench_v090.rs
-
curl -L -o bench_v090.rs https://huggingface.co/PowerMachine/khtst-rust/resolve/main/src/bin/bench_v090.rs
13.5 kB
| //! Bench v0.9.0 — medições honestas para r-34 (críticas Qwen3.8 nº 4/D3 e | |
| //! plano itens 1/3/5). Saída JSON em stdout. | |
| //! | |
| //! Cada bloco mede a MESMA computação em duas variantes (A/B) com | |
| //! black_box e repetições suficientes; reporta mediana e razão. | |
| use khtst_rust::rng::RngDeterministico; | |
| use khtst_rust::simd::{self, BufAlinhado64}; | |
| use std::hint::black_box; | |
| use std::time::Instant; | |
| fn mediana(mut xs: Vec<f64>) -> f64 { | |
| xs.sort_by(|a, b| a.partial_cmp(b).unwrap()); | |
| xs[xs.len() / 2] | |
| } | |
| fn bench<F: FnMut()>(mut f: F, reps: usize) -> f64 { | |
| let mut tempos = Vec::with_capacity(reps); | |
| for _ in 0..reps { | |
| let t = Instant::now(); | |
| f(); | |
| tempos.push(t.elapsed().as_secs_f64() * 1e3); // ms | |
| } | |
| mediana(tempos) | |
| } | |
| fn main() { | |
| let reps = 21usize; | |
| let mut resultado = serde_json::Map::new(); | |
| resultado.insert("avx512_disponivel".into(), simd::tem_avx512().into()); | |
| resultado.insert("cpus".into(), std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1).into()); | |
| // ---------------- A) passo AdamW: escalar vs SIMD (despacho) ---------------- | |
| { | |
| let n = 2_000_000usize; | |
| let mut rng = RngDeterministico::novo(1); | |
| let w: Vec<f32> = (0..n).map(|_| rng.normal(0.0, 0.5) as f32).collect(); | |
| let g: Vec<f32> = (0..n).map(|_| rng.normal(0.0, 0.1) as f32).collect(); | |
| let mut m = vec![0f32; n]; | |
| let mut v = vec![0f32; n]; | |
| let mut out = vec![0f32; n]; | |
| let (lr, b1, b2, eps, wd, bc1, bc2) = (3e-4f64, 0.9, 0.999, 1e-8, 0.01, 0.91, 0.001); | |
| let t_esc = bench(|| { | |
| simd::passo_adamw_escalar(&w, &g, &mut m, &mut v, &mut out, lr, b1, b2, eps, wd, bc1, bc2); | |
| black_box(&out); | |
| }, reps); | |
| let t_simd = bench(|| { | |
| simd::passo_adamw(&w, &g, &mut m, &mut v, &mut out, lr, b1, b2, eps, wd, bc1, bc2); | |
| black_box(&out); | |
| }, reps); | |
| // bit-identidade em cena real (falsificação) — estados FRESCHOS iguais | |
| let (mut ma, mut va, mut oa) = (vec![0f32; n], vec![0f32; n], vec![0f32; n]); | |
| let (mut mb, mut vb, mut ob) = (vec![0f32; n], vec![0f32; n], vec![0f32; n]); | |
| simd::passo_adamw_escalar(&w, &g, &mut ma, &mut va, &mut oa, lr, b1, b2, eps, wd, bc1, bc2); | |
| simd::passo_adamw(&w, &g, &mut mb, &mut vb, &mut ob, lr, b1, b2, eps, wd, bc1, bc2); | |
| let identico = oa.iter().zip(ob.iter()).all(|(a, b)| a.to_bits() == b.to_bits()); | |
| resultado.insert("adamw_escalar_ms".into(), t_esc.into()); | |
| resultado.insert("adamw_simd_ms".into(), t_simd.into()); | |
| resultado.insert("adamw_speedup".into(), (t_esc / t_simd).into()); | |
| resultado.insert("adamw_bit_identico".into(), identico.into()); | |
| } | |
| // ---------------- B) BMU 4D: Vec<Vec<f64>> vs SoA+AVX-512 ---------------- | |
| { | |
| let (c, d, n_consultas) = (4096usize, 4usize, 2000usize); | |
| let mut rng = RngDeterministico::novo(2); | |
| let ps: Vec<Vec<f64>> = (0..c).map(|_| (0..d).map(|_| rng.normal(0.0, 2.0)).collect()).collect(); | |
| let consultas: Vec<Vec<f64>> = (0..n_consultas).map(|_| (0..d).map(|_| rng.normal(0.0, 2.0)).collect()).collect(); | |
| let dist = |a: &[f64], b: &[f64]| -> f64 { a.iter().zip(b).map(|(x, y)| (x - y) * (x - y)).sum() }; | |
| let t_vecvec = bench(|| { | |
| let mut soma = 0usize; | |
| for x in &consultas { | |
| let mut melhor = 0usize; | |
| let mut dm = f64::INFINITY; | |
| for (i, p) in ps.iter().enumerate() { | |
| let dd = dist(x, p); | |
| if dd < dm { dm = dd; melhor = i; } | |
| } | |
| soma += melhor; | |
| } | |
| black_box(soma); | |
| }, 7); | |
| let (soa, c2, d2) = simd::soa_de_prototipos(&ps); | |
| let mut buf = Vec::with_capacity(c2); | |
| let t_soa = bench(|| { | |
| let mut soma = 0usize; | |
| for x in &consultas { | |
| soma += simd::melhor_indice_soa_buf(&soa, c2, d2, x, &mut buf); | |
| } | |
| black_box(soma); | |
| }, 7); | |
| let (rm, _, _) = simd::rm_de_prototipos(&ps); | |
| let mut buf_rm = Vec::with_capacity(c2); | |
| let t_rm = bench(|| { | |
| let mut soma = 0usize; | |
| for x in &consultas { | |
| soma += simd::melhor_indice_rm_buf(&rm, c2, x, &mut buf_rm); | |
| } | |
| black_box(soma); | |
| }, 7); | |
| // equivalencia de indices | |
| let mut iguais = 0usize; | |
| for x in &consultas { | |
| let mut melhor = 0usize; let mut dm = f64::INFINITY; | |
| for (i, p) in ps.iter().enumerate() { let dd = dist(x, p); if dd < dm { dm = dd; melhor = i; } } | |
| if simd::melhor_indice_soa_buf(&soa, c2, d2, x, &mut buf) == melhor { iguais += 1; } | |
| } | |
| resultado.insert("bmu_vecvec_ms".into(), t_vecvec.into()); | |
| resultado.insert("bmu_soa_ms".into(), t_soa.into()); | |
| resultado.insert("bmu_rm_ms".into(), t_rm.into()); | |
| resultado.insert("bmu_speedup_soa".into(), (t_vecvec / t_soa).into()); | |
| resultado.insert("bmu_speedup_rm".into(), (t_vecvec / t_rm).into()); | |
| resultado.insert("bmu_indices_iguais".into(), format!("{iguais}/{n_consultas}").into()); | |
| } | |
| // ---------------- C) alinhamento 64B: alinhado vs malloc vs desalinhado ---------------- | |
| { | |
| let n = 4_000_000usize; | |
| let mut alinhado = BufAlinhado64::novo_zeros(n); | |
| let mut comum: Vec<f64> = vec![0.42; n]; | |
| // desalinhado proposital: pula 1 f64 (8 B) — pior caso de split de linha | |
| let mut base = BufAlinhado64::novo_zeros(n + 8); | |
| let desalinhado_ptr = unsafe { base.as_mut_slice().as_mut_ptr().add(1) }; | |
| let fator = 1.0000001f64; | |
| let t_al = bench(|| { | |
| for x in alinhado.as_mut_slice() { *x = *x * fator + 0.1; } | |
| black_box(alinhado.as_slice().last()); | |
| }, 7); | |
| let t_comum = bench(|| { | |
| for x in comum.iter_mut() { *x = *x * fator + 0.1; } | |
| black_box(comum.last()); | |
| }, 7); | |
| let t_des = bench(|| { | |
| let fatia = unsafe { std::slice::from_raw_parts_mut(desalinhado_ptr, n) }; | |
| for x in fatia.iter_mut() { *x = *x * fator + 0.1; } | |
| black_box(fatia.last()); | |
| }, 7); | |
| resultado.insert("axpy_alinhado64_ms".into(), t_al.into()); | |
| resultado.insert("axpy_malloc16_ms".into(), t_comum.into()); | |
| resultado.insert("axpy_desalinhado8_ms".into(), t_des.into()); | |
| resultado.insert("alinhado_endereco_mod64".into(), (alinhado.endereco() % 64 == 0).into()); | |
| } | |
| // ---------------- D) f32 vs bf16 no candle CPU (crítica D3) ---------------- | |
| { | |
| use candle_core::{DType, Device, Tensor}; | |
| let dev = Device::Cpu; | |
| let n = 8_000_000usize; | |
| let dados: Vec<f32> = (0..n).map(|i| ((i as f32) * 0.001) % 1.0).collect(); | |
| let a = Tensor::from_vec(dados.clone(), (n,), &dev).unwrap(); | |
| let b = Tensor::from_vec(dados.clone(), (n,), &dev).unwrap(); | |
| let t_f32 = bench(|| { | |
| let c = (&a + &b).unwrap().affine(1.5, 0.1).unwrap().sqr().unwrap(); | |
| black_box(c.to_scalar::<f32>()); | |
| }, 7); | |
| let ab = a.to_dtype(DType::BF16).unwrap(); | |
| let bb = b.to_dtype(DType::BF16).unwrap(); | |
| let t_bf16 = bench(|| { | |
| let c = (&ab + &bb).unwrap().affine(1.5, 0.1).unwrap().sqr().unwrap(); | |
| black_box(c.to_dtype(DType::F32).unwrap().to_scalar::<f32>()); | |
| }, 7); | |
| resultado.insert("candle_f32_ms".into(), t_f32.into()); | |
| resultado.insert("candle_bf16_ms".into(), t_bf16.into()); | |
| resultado.insert("bf16_vs_f32".into(), (t_f32 / t_bf16).into()); | |
| } | |
| // ---------------- E) nosso AdamW vs candle_nn::optim::AdamW ---------------- | |
| { | |
| use candle_core::{Device, Tensor, Var}; | |
| use candle_nn::optim::{AdamW as AdamWC, Optimizer, ParamsAdamW}; | |
| let dev = Device::Cpu; | |
| // 16 variáveis × 100k params ~ escala do tronco real | |
| let mut vars = Vec::new(); | |
| for _ in 0..16 { | |
| let t = Tensor::randn(0f32, 0.1, (100_000,), &dev).unwrap(); | |
| vars.push(Var::from_tensor(&t).unwrap()); | |
| } | |
| let alvo = Tensor::randn(0f32, 1.0, (100_000,), &dev).unwrap(); | |
| let roda = |nosso: bool| -> f64 { | |
| let mut opt_nosso = khtst_rust::treino::AdamWManual::novo(vars.clone(), 1e-3, 0.01); | |
| let params = ParamsAdamW { lr: 1e-3, beta1: 0.9, beta2: 0.999, eps: 1e-8, weight_decay: 0.01 }; | |
| let mut opt_c = AdamWC::new(vars.clone(), params).unwrap(); | |
| let t0 = Instant::now(); | |
| for _ in 0..30 { | |
| let perda = { | |
| let mut total = None::<Tensor>; | |
| for v in &vars { | |
| let d = v.as_tensor().sub(&alvo).unwrap().sqr().unwrap().mean_all().unwrap(); | |
| total = Some(match total { None => d, Some(t) => (&t + &d).unwrap() }); | |
| } | |
| total.unwrap() | |
| }; | |
| if nosso { | |
| let grads = khtst_rust::treino::grads_achatados(&perda, &vars).unwrap(); | |
| opt_nosso.passo(grads); | |
| } else { | |
| let gs = perda.backward().unwrap(); | |
| opt_c.step(&gs).unwrap(); | |
| } | |
| } | |
| t0.elapsed().as_secs_f64() * 1e3 / 30.0 | |
| }; | |
| let t_nosso = mediana((0..5).map(|_| roda(true)).collect()); | |
| let t_candle = mediana((0..5).map(|_| roda(false)).collect()); | |
| resultado.insert("adamw_nosso_ms_por_passo".into(), t_nosso.into()); | |
| resultado.insert("adamw_candle_nn_ms_por_passo".into(), t_candle.into()); | |
| resultado.insert("nosso_vs_candle_nn".into(), (t_candle / t_nosso).into()); | |
| } | |
| // ---------------- F) clip: escalar vs SIMD ---------------- | |
| { | |
| let n = 2_000_000usize; | |
| let mut a: Vec<f32> = (0..n).map(|i| ((i as f32) * 0.001) % 0.5).collect(); | |
| let mut b = a.clone(); | |
| let t_esc = bench(|| { | |
| for x in a.iter_mut() { *x *= 0.37f32; } | |
| black_box(a.last()); | |
| }, reps); | |
| let t_simd = bench(|| { | |
| simd::escalar_slice(&mut b, 0.37f32); | |
| black_box(b.last()); | |
| }, reps); | |
| resultado.insert("clip_escalar_ms".into(), t_esc.into()); | |
| resultado.insert("clip_simd_ms".into(), t_simd.into()); | |
| resultado.insert("clip_speedup".into(), (t_esc / t_simd).into()); | |
| } | |
| // ---------------- G) distâncias via MATMUL (gemm do candle: AVX-512+FMA) ---------------- | |
| // D²[i,k] = ||q_i||² - 2·(q @ w^T)[i,k] + ||w_k||² — muda a ordem de soma | |
| // (ε, não bit-a-bit); é a forma CANÔNICA de vetorizar distâncias em lote. | |
| { | |
| use candle_core::{Device, Tensor}; | |
| let dev = Device::Cpu; | |
| let (c, d, nq) = (4096usize, 4usize, 2000usize); | |
| let mut rng = RngDeterministico::novo(4); | |
| let ps: Vec<Vec<f64>> = (0..c).map(|_| (0..d).map(|_| rng.normal(0.0, 2.0)).collect()).collect(); | |
| let consultas: Vec<Vec<f64>> = (0..nq).map(|_| (0..d).map(|_| rng.normal(0.0, 2.0)).collect()).collect(); | |
| let q_flat: Vec<f32> = consultas.iter().flat_map(|r| r.iter().map(|&v| v as f32)).collect(); | |
| let w_flat: Vec<f32> = ps.iter().flat_map(|r| r.iter().map(|&v| v as f32)).collect(); | |
| let q_t = Tensor::from_vec(q_flat, (nq, d), &dev).unwrap(); | |
| let w_t = Tensor::from_vec(w_flat, (c, d), &dev).unwrap(); | |
| let t_mm = bench(|| { | |
| // -2 q w^T | |
| let cross = q_t.matmul(&w_t.transpose(0, 1).unwrap()).unwrap().affine(-2.0, 0.0).unwrap(); | |
| // + ||q||² por linha (broadcast) e + ||w||² por coluna | |
| let qn = q_t.sqr().unwrap().sum(1).unwrap().reshape((nq, 1)).unwrap().broadcast_as((nq, c)).unwrap(); | |
| let wn = w_t.sqr().unwrap().sum(1).unwrap().reshape((1, c)).unwrap().broadcast_as((nq, c)).unwrap(); | |
| let d2 = (&cross + &qn).unwrap().add(&wn).unwrap().clamp(0f32, f32::INFINITY).unwrap(); | |
| black_box(d2.to_scalar::<f32>()); | |
| }, 7); | |
| // referencia escalar p/ precisao | |
| let dist = |a: &[f64], b: &[f64]| -> f64 { a.iter().zip(b).map(|(x, y)| (x - y) * (x - y)).sum() }; | |
| let d2 = { | |
| let cross = q_t.matmul(&w_t.transpose(0, 1).unwrap()).unwrap().affine(-2.0, 0.0).unwrap(); | |
| let qn = q_t.sqr().unwrap().sum(1).unwrap().reshape((nq, 1)).unwrap().broadcast_as((nq, c)).unwrap(); | |
| let wn = w_t.sqr().unwrap().sum(1).unwrap().reshape((1, c)).unwrap().broadcast_as((nq, c)).unwrap(); | |
| (&cross + &qn).unwrap().add(&wn).unwrap() | |
| }; | |
| let mm_v = d2.to_vec2::<f32>().unwrap(); | |
| let mut max_err = 0f64; | |
| for (i, qi) in consultas.iter().enumerate() { | |
| for (k, pk) in ps.iter().enumerate() { | |
| let exato = dist(qi, pk); | |
| let obt = mm_v[i][k] as f64; | |
| max_err = max_err.max((exato - obt).abs()); | |
| } | |
| } | |
| resultado.insert("bmu_matmul_ms".into(), t_mm.into()); | |
| resultado.insert("bmu_matmul_erro_max".into(), max_err.into()); | |
| resultado.insert("bmu_speedup_matmul".into(), (15.313_f64 / t_mm).into()); | |
| } | |
| let out = serde_json::json!({ "bench_v090": resultado }); | |
| println!("{}", serde_json::to_string_pretty(&out).unwrap()); | |
| } | |