open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-loader-gguf/examples/mpp_bt_bench.rs
Claude f5760cc22d
All checks were successful
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 1m2s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 46s
standards workspaces build + test / arl-rs (arl/arl-rs, arl-rs, linux) (pull_request) Successful in 1m16s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m53s
standards workspaces build + test / eoc-rs (eoc/eoc-rs, -p eoc-meter --features cuda, eoc-rs, linux) (pull_request) Successful in 2m9s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m14s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m11s
standards workspaces build + test / pool-rs (pool/pool-rs, pool-rs, linux) (pull_request) Successful in 1m8s
standards workspaces build + test / proof-rs (proof-conformance, proof/proof-rs, proof-rs, linux, true) (pull_request) Successful in 1m28s
standards workspaces build + test / sense-rs (sense/sense-rs, sense-rs, linux) (pull_request) Successful in 1m16s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m27s
standards workspaces build + test / smart-byte-rs (smart-byte/smart-byte-rs, smart-byte-rs, linux) (pull_request) Successful in 4m10s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 6m50s
standards workspaces build + test / joulecontract-rs (joulecontract/joulecontract-rs, joulecontract-rs, linux-arm64, true) (pull_request) Successful in 1m30s
standards workspaces build + test / map-rs (map-conformance, map/map-rs, map-rs, linux-arm64) (pull_request) Successful in 1m28s
standards workspaces build + test / aac-rs (aac-conformance, aac/aac-rs, aac-rs, linux-arm64) (pull_request) Successful in 5m17s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 5m10s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 2m24s
standards workspaces build + test / openpay (openpay, openpay, linux-arm64) (pull_request) Successful in 6m16s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 8m16s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m52s
jouleclaw-loader-gguf: 0 warnings, and -D warnings now gates it
12 -> 0, in both the default and `rayon fabric-meter` configurations, and
the crate joins the `-D warnings` clippy step in standards-build.yml.

The last dozen needed judgement rather than a script:

- two `from_str` methods that are not FromStr-shaped (one returns Option,
  the other is infallible) renamed to `parse`. Both are file-local, so
  there is no caller churn -- except that my check for callers missed
  mlx.rs, which reaches SafetensorsDtype through a type ALIAS, so
  `SafetensorsDtype::from_str(` never matched. Renaming wants a grep for
  the method name across the workspace, not for the type names you happen
  to know.
- `assert!(FLASH_KV_CHUNK.is_power_of_two())` and `>= 64` moved out of a
  `#[test]` into `const _: () = assert!(..)` beside the constant. These
  are properties of a constant; a test only holds them if someone runs it.
- the `kv_quant_tests` module moved to the end of kv_cache_inplace.rs,
  because 300 lines of real items sat after it.
- an example discarded the `Result` from `matmul_bf16_threaded`, so a
  failure would have been timed as a success.
- `needless_range_loop` allowed per-file in three benches and one test:
  those are separate crates, so src/lib.rs's decision does not reach them.
- checked_div for two guarded divisions, and one unused `mut`.

Two near-misses worth recording:

- `fabric_dispatch.rs` calls `ledger_record` twice with identical
  arguments, which reads exactly like a copy-paste bug. The comment three
  lines above says attention is "recorded as two GEMM-equivalents" so the
  measured µJ/GFLOP table prices it. Deliberate. Left alone.
- clippy's `chunks_exact_mut` suggestion is `as_chunks_mut::<N>().0.iter()`
  -- a non-mutable iterator over a mutable chunk. It does not compile, and
  only the fabric-meter configuration contained one, so the default build
  stayed green while that example was broken. Checking both configurations
  is what caught it.

The gate covers default features only. Under `rayon fabric-meter` this
crate is also clean, but clippy reports jouleclaw-backend-apple through
the path dependency, and that crate has 43 warnings -- 27 `unused_unsafe`
and 15 `unused_imports` that are unused in THAT configuration and needed
in others. Those want cfg work, not deletion, which is the trap #373
already paid for once.

Verified: four lint specs pass, 343 loader-gguf tests, 77 omni tests.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-21 20:40:32 -04:00

59 lines
2.3 KiB
Rust

//! Prefill GEMM throughput: the square-tiling Metal-4 matmul2d
//! wrapper (`matmul_bf16_mpp_bt`) vs the threaded-NEON CPU path,
//! at realistic projection shapes. Run with JOULECLAW_FABRIC=1.
#[cfg(feature = "fabric")]
fn main() {
use std::time::Instant;
fn f32_to_bf16(v: f32) -> u16 {
let b = v.to_bits();
((b + (((b >> 16) & 1) + 0x7fff)) >> 16) as u16
}
// (M tokens, N out, K in)
let shapes = [
(128usize, 4096usize, 4096usize),
(256, 4096, 4096),
(256, 11008, 4096),
(512, 4096, 4096),
];
println!("shape (MxNxK) mpp_bt ms GFLOP/s neon ms GFLOP/s speedup max_rel");
for &(m, n, k) in &shapes {
let a: Vec<f32> = (0..m * k).map(|i| ((i % 101) as f32 / 50.0) - 1.0).collect();
let w: Vec<u8> = (0..n * k)
.flat_map(|i| f32_to_bf16(((i % 97) as f32 / 48.0 - 1.0) * 0.1).to_le_bytes())
.collect();
let gflop = 2.0 * m as f64 * n as f64 * k as f64 / 1e9;
let mut got = vec![0f32; m * n];
if !jouleclaw_loader_gguf::fabric_dispatch::matmul_bf16_mpp_bt(&a, &w, m, n, k, &mut got) {
println!(" {m}x{n}x{k}: mpp_bt fell back (fabric off / pre-Metal-4)");
continue;
}
let iters = 10;
let t0 = Instant::now();
for _ in 0..iters {
jouleclaw_loader_gguf::fabric_dispatch::matmul_bf16_mpp_bt(&a, &w, m, n, k, &mut got);
}
let mpp_ms = t0.elapsed().as_secs_f64() * 1e3 / iters as f64;
let mut neon = vec![0f32; m * n];
let t1 = Instant::now();
for _ in 0..iters {
jouleclaw_loader_gguf::matmul_bf16::matmul_bf16_threaded(&a, &w, &[], m, n, k, &mut neon)
.expect("threaded bf16 matmul");
}
let neon_ms = t1.elapsed().as_secs_f64() * 1e3 / iters as f64;
let mut max_rel = 0f32;
for (g, r) in got.iter().zip(neon.iter()) {
max_rel = max_rel.max((g - r).abs() / (r.abs() + 1e-2));
}
println!(
" {m:>4}x{n:>5}x{k:>5} {mpp_ms:8.3} {:8.1} {neon_ms:8.3} {:8.1} {:5.2}x {max_rel:.2e}",
gflop / (mpp_ms / 1e3),
gflop / (neon_ms / 1e3),
neon_ms / mpp_ms,
);
}
}
#[cfg(not(feature = "fabric"))]
fn main() { println!("build with --features fabric"); }