All checks were successful
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 2m10s
standards workspaces build + test / aac-rs (pull_request) Successful in 39s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 44s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 36s
standards workspaces build + test / map-rs (pull_request) Successful in 31s
standards workspaces build + test / mesh-rs (pull_request) Successful in 25s
standards workspaces build + test / openpay (pull_request) Successful in 1m23s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 1m29s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 1m1s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m47s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m17s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m16s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m9s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 2m35s
standards workspaces build + test / arl-rs (pull_request) Successful in 2m17s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 2m49s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 4m29s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 2m44s
standards workspaces build + test / sense-rs (pull_request) Successful in 2m47s
standards workspaces build + test / macOS energy backend (jcp-energy) (pull_request) Successful in 2m22s
`detect_in_process_meter` is for work that runs on this process's own threads. Each caller that meters model work was checked against that, and adopted it only where it holds. - jouleclaw-harness-econ: `local_model_econ`, `swebench_local_coder` and `swebench_local_suite` meter a decode that runs in this process, on the loader's CPU kernels and rayon pool; the examples build the loader with `rayon` only, without its GPU `fabric`, and the metered window is the decode alone (the `python3` judge, a subprocess, runs outside it). They now use the in-process meter, so on macOS 27.0.1, where the host meter is the load-average estimator, a dispatch is a live hardware reading of its own CPU energy. `window_joules` already refuses a window the counter did not advance across. `meter_probe` reports both meters. Run here on a host under load average ~50: the host meter was the estimator (115.6 J over 2.89 s, Estimator, SocTotal), the in-process meter process-cpu (2.397 J over 2.84 s, HwShunt, ProcessCpu) for the same one-thread spin. - jouledesk-github stays on `detect_meter`: its model runs in a `llama-completion` subprocess, which a per-process counter would bill nothing. Its docs say so, and a test pins the meter to the host meter. - l3-jouleclaw's `run` binary stays on the host counter, and says why: `llama` is a subprocess, and the in-process `jouleclaw-runtime` engine boots the Apple backend's GPU kernels (Metal and MPS matmuls, the quantized GEMVs that serve decode), whose energy is the GPU's. - jouleclaw-loader-gguf is unchanged: its metered paths (`tune_shape_live`, `fabric_calibrate`, `mpp_energy`) compare or calibrate GPU against CPU members, so they need the whole SoC, not one process's CPU. Ran: `cargo test -p jouleclaw-harness-econ`, `cargo check` of its examples, `cargo test -p jouledesk-github` (13 passed), and `cargo test` in l3-jouleclaw. No model was loaded; the examples' model runs were not repeated. Co-Authored-By: Claude Opus 5.5 <[email protected]>
33 lines
1.3 KiB
Rust
33 lines
1.3 KiB
Rust
//! Probe: what do jouleclaw-energy's meters report on this box? The host meter
|
|
//! (`detect_meter`, for work wherever it runs) and the in-process meter
|
|
//! (`detect_in_process_meter`, for work on this process's own threads — this
|
|
//! probe's own CPU burn is such work).
|
|
use jouleclaw_energy::meter::{detect_in_process_meter, detect_meter, CompositeMeter};
|
|
|
|
fn probe(label: &str, m: &CompositeMeter) {
|
|
println!("{label}: active meter: {} (holds {:?})", m.active_name(), m.names());
|
|
println!("{label}: available: {}", m.is_available());
|
|
let r0 = m.read().expect("read0");
|
|
println!("{label}: provenance: {:?}, domain: {:?}", r0.provenance, r0.domain);
|
|
// Burn some CPU on this thread so a counter shows a delta.
|
|
let t = std::time::Instant::now();
|
|
let mut x = 0u64;
|
|
for i in 0..3_000_000_000u64 {
|
|
x = x.wrapping_add(i ^ (x >> 1));
|
|
}
|
|
let r1 = m.read().expect("read1");
|
|
let secs = t.elapsed().as_secs_f64();
|
|
let dj = r1.uj.saturating_sub(r0.uj) as f64 / 1e6;
|
|
println!("{label}: spin sentinel: {x}");
|
|
println!(
|
|
"{label}: over {:.2}s: delta {:.3} J (~{:.1} W avg)",
|
|
secs,
|
|
dj,
|
|
if secs > 0.0 { dj / secs } else { 0.0 }
|
|
);
|
|
}
|
|
|
|
fn main() {
|
|
probe("host", &detect_meter());
|
|
probe("in-process", &detect_in_process_meter());
|
|
}
|