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
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]>
59 lines
2.3 KiB
Rust
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"); }
|