All checks were successful
energy meters on real hardware / RAPL and NVML on jetson-hub (pull_request) Successful in 37s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 2m6s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 2m48s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 3s
no-vanity-metrics / WAI public text names no comparison (pull_request) Successful in 3s
no-vanity-metrics / WAI public text names no company or product (pull_request) Successful in 3s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 1m1s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m9s
standards workspaces build + test / eoc-rs (pull_request) Successful in 2m0s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m35s
standards workspaces build + test / aac-rs (pull_request) Successful in 3m23s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m11s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m11s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 56s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m22s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 39s
standards workspaces build + test / map-rs (pull_request) Successful in 28s
standards workspaces build + test / mesh-rs (pull_request) Successful in 28s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m9s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m23s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 4s
standards workspaces build + test / openpay (pull_request) Successful in 1m19s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 4m5s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 2m58s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 9m53s
No job ran the oracle assertions this branch added. joule-code-rs's matrix
entry runs on linux-arm64, where joule-fix's L3 energy test has no tree
meter and takes its fallback branch; jouleclaw-loader-gguf's calibration
tests need the fabric-meter feature, which no job passed, and their oracle
needs macOS.
The macOS energy job now runs both, each step in the job's style (pipefail,
a tee'd log, a grep gate on a line the test prints only where its gated
assertion ran):
- joule-code-remediate `l3_`: 'l3 model process:' (the model's reading
checked against the stand-in's own count). The stand-in itself stays
#[ignore]: it is the subprocess the oracle test runs (filtered to it,
--ignored), not a test of its own, and the oracle test is not ignored.
- jouleclaw-loader-gguf --features "rayon fabric-meter": the marginal_energy
tests ('calibration row stamped on macOS:') and tests/process_calibration
('process calibration: CPU member'); this also builds the fabric-meter
examples. joule-code-rs's target dir and Cargo.lock join the job's cache.
The l3-jouleclaw example workspace stays out of this job (a heavy separate
build on the one macOS runner); its README says its oracle tests run locally.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
84 lines
4.3 KiB
Rust
84 lines
4.3 KiB
Rust
//! A fabric member priced on this process's own counters
|
|
//! (`marginal_energy::measure_member_process`, what `fabric_calibrate` and
|
|
//! `mpp_energy` fall back to where the IOReport CPU rail is frozen), against an
|
|
//! oracle sharing no code with the meter: this process's own CPU energy count,
|
|
//! read around the same span with a bare `proc_pid_rusage`.
|
|
//!
|
|
//! One test in its own binary, so no other test's thread runs in this process
|
|
//! during the span (the span, and the oracle, read every thread of it).
|
|
|
|
#![cfg(feature = "fabric-meter")]
|
|
|
|
use jouleclaw_energy::span::ProcessSpanMeter;
|
|
use jouleclaw_loader_gguf::marginal_energy::{measure_member_process, MemberRuns, NotMeasured};
|
|
|
|
/// This process's own CPU energy count, nJ: `proc_pid_rusage(getpid(),
|
|
/// RUSAGE_INFO_V6)`'s `ri_energy_nj`, read with a bare buffer of the struct's 464
|
|
/// bytes at its SDK offset, 336. Written out here, sharing no code with the meter
|
|
/// it checks.
|
|
#[cfg(target_os = "macos")]
|
|
fn own_energy_nj() -> u64 {
|
|
unsafe extern "C" {
|
|
fn proc_pid_rusage(pid: i32, flavor: i32, buffer: *mut std::ffi::c_void) -> i32;
|
|
}
|
|
let mut buf = [0u64; 58];
|
|
// SAFETY: `buf` is 464 writable bytes, the size of `rusage_info_v6`.
|
|
let rc = unsafe { proc_pid_rusage(std::process::id() as i32, 6, buf.as_mut_ptr().cast()) };
|
|
assert_eq!(rc, 0, "proc_pid_rusage on this process");
|
|
buf[336 / 8]
|
|
}
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
fn own_energy_nj() -> u64 {
|
|
0
|
|
}
|
|
|
|
/// A CPU member (the NEON BF16 matmul) priced on this process: where both of this
|
|
/// process's counters are live, the figure is given, its GPU part an exact zero, and
|
|
/// its CPU part agrees with the oracle, read inside the span at the first operation's
|
|
/// start and the last one's end — so the span holds the oracle's window and differs
|
|
/// from it only by the meter's last and first reads around it — and the per-GFLOP
|
|
/// figure is that energy over the work. Where they are not, the member is refused,
|
|
/// never priced on the host meter. Twice, three seconds each, however loaded the host.
|
|
#[test]
|
|
fn a_cpu_member_is_priced_on_this_process_as_its_own_count_says() {
|
|
use std::cell::Cell;
|
|
let meter = ProcessSpanMeter::new(jouleclaw_backend_apple::gpu_liveness_load);
|
|
let (m, n, k) = (16usize, 512usize, 512usize);
|
|
let gflop = 2.0 * (m * n * k) as f64 / 1e9;
|
|
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| jouleclaw_loader_gguf::matmul_bf16::f32_to_bf16_rne(((i % 97) as f32 / 48.0 - 1.0) * 0.1).to_le_bytes())
|
|
.collect();
|
|
let mut out = vec![0f32; m * n];
|
|
for round in 1..=2 {
|
|
let (first, last) = (Cell::new(None), Cell::new(0u64));
|
|
let figure = measure_member_process(&meter, MemberRuns::Cpu, gflop, 3.0, || {
|
|
if first.get().is_none() {
|
|
first.set(Some(own_energy_nj()));
|
|
}
|
|
jouleclaw_loader_gguf::matmul_bf16::matmul_bf16_neon(&a, &w, &[], m, n, k, &mut out);
|
|
last.set(own_energy_nj());
|
|
});
|
|
let oracle_uj = (last.get() - first.get().expect("the work ran")) / 1_000;
|
|
eprintln!("CPU member round {round}: oracle {oracle_uj} µJ; metered={} {figure:?}", meter.is_metered());
|
|
if meter.is_metered() {
|
|
let f = figure.expect("a CPU member on live counters is priced");
|
|
assert_eq!(f.gpu_uj, 0, "the NEON matmul spent GPU energy: {f:?}");
|
|
assert_eq!(f.provenance, jouleclaw_energy::Provenance::HwShunt);
|
|
assert!(oracle_uj > 100_000, "setup: the work read {oracle_uj} µJ");
|
|
let ratio = f.cpu_uj as f64 / oracle_uj as f64;
|
|
assert!((0.9..=1.1).contains(&ratio), "the span read {} µJ of {oracle_uj} µJ", f.cpu_uj);
|
|
let total = f.uj_per_gflop * f.ops as f64 * gflop;
|
|
assert!((total - f.cpu_uj as f64).abs() <= 1.0, "{total} µJ priced of {} µJ read", f.cpu_uj);
|
|
// CI's macOS energy job greps for this line: the oracle ran on live counters.
|
|
eprintln!("process calibration: CPU member round {round} read {} µJ of {oracle_uj} µJ", f.cpu_uj);
|
|
} else {
|
|
let why = figure.expect_err("no live counters, yet a figure");
|
|
assert!(
|
|
matches!(why, NotMeasured::NotWhole(_) | NotMeasured::NotOwnCounters(_) | NotMeasured::SpanFailed(_)),
|
|
"{why}"
|
|
);
|
|
}
|
|
}
|
|
}
|