open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-loader-gguf/examples/energy_autotune.rs
Transaction Science c0813bcfb7 jouleclaw-loader-gguf: a meter that did not measure records nothing and writes nothing
The family contract: measured when the platform exposes it, estimated when it
doesn't, and the provenance always says which. This crate's energy paths
price work as marginal draw, `ΔE_busy − P_idle·Δt`, on the IOReport counter,
and three of them turned a counter that measured nothing into a figure that is
then treated as measured.

The defect (b9d7e945, unchanged since origin/main 54e89b56):
- fabric_dispatch::tune_shape_live read the counter with `.unwrap_or(0)` /
  `.unwrap_or(e0)`, clamped the marginal with `.max(0.0)` and truncated the
  per-op figure to u64. On a CPU rail that does not advance (macOS 27.0.1, both
  our Macs) every backend came out at 0 µJ per op, joules_minimal picked one,
  and TuningProfile::record kept it: a recorded 0 beats every later
  measurement, so the shape was pinned to that backend for the process.
- examples/fabric_calibrate wrote ~/.jouleclaw/fabric-calibration.json, which
  jouleclaw-fabric loads as Measured placement costs, with idle
  `(e1 - e0) / t` (0 on a frozen rail) and each member's marginal clamped to
  `.max(0.0)`: zeros written as measurements.
- examples/mpp_energy overlaid its matmul2d figure into the same file with an
  unclamped marginal (negative on a frozen rail), printed a CPU/GPU ratio of
  0/0 = NaN, and ignored whether its CPU matmul ran at all.
b9d7e945 now declines the counter on those hosts, so today these paths stop
at `is_available()`. That covers today's hosts. A counter that is available but
does not see a window would still have written zeros.

The fix.
- New module marginal_energy, pure arithmetic over raw readings, which the
  three paths share: idle_draw_uw refuses an idle window that did not advance
  (an SoC draws power doing nothing, so a counter that holds is not metering),
  marginal_uj refuses a busy window at or below idle and is never clamped to
  zero, per_op_uj refuses a per-op cost that would round to 0, energy_ratio is
  None unless both figures are positive and finite, and refusal() renders what
  an example prints before it exits non-zero.
- tune_shape_live: a failed read, an idle baseline that did not advance, or
  either backend's window drawing nothing above idle voids the tune (None,
  nothing recorded), through the new tune_measured: an unmeasured backend
  neither wins nor loses against a measured one.
- TuningProfile::record (and so record_global and tune_and_record) never
  records a zero-joule cost; tune_and_record returns None for one.
- fabric_calibrate and mpp_energy refuse to write, exit 1, and print the file,
  the reason and jouleclaw_energy::ioreport_status() when the counter is
  unavailable, the idle baseline did not advance, or any marginal is <= 0. They
  refuse before any write, so no partial file. mpp_energy's ratio is n/a unless
  both runs were measured, and its CPU run now asserts the matmul ran, as its
  GPU run already did (a refused shape would meter an empty loop, and that run
  is one of the refusal gates).
- energy_autotune (example) says why a shape was not tuned, with the status.

Observed, M5 Max and M3 Ultra (macOS 27.0.1), examples run with HOME set to a
fresh directory and JOULECLAW_FABRIC_CALIBRATION unset:
  refusing to write <tmp>/.jouleclaw/fabric-calibration.json: the energy
  counter is not available on this host (apple-ioreport declined)
  IOReport status: CpuRailFrozen; nothing was written (its figures are loaded
  as measurements)
exit 1 for fabric_calibrate and mpp_energy on both, no file under the temp
HOME. The real ~/.jouleclaw/fabric-calibration.json kept its 2026-06-11
11:02:14 mtime and SHA-256 (792b00e6...) on the M5 Max, and its mtime on the
M3 Ultra. energy_autotune: "not tuned: the meter produced no figure (IOReport
CpuRailFrozen)", global profile empty.

Tests. Default features, what CI's jouleclaw-rs job runs: 294 lib tests pass
(the marginal_energy and energy_autotune tests below are not feature-gated).
With `--features "rayon fabric-meter"`: 302 pass on the M5 Max and, from the
same binary, on the M3 Ultra.
- a_counter_that_holds_at_idle_has_no_idle_baseline, a_busy_window_at_or_below_
  idle_is_refused_never_clamped_to_zero, a_per_op_cost_never_rounds_to_zero,
  the_cpu_gpu_ratio_is_undefined_unless_both_are_measured,
  a_refusal_names_the_file_the_reason_and_the_ioreport_status, a_refusal_says_why.
- a_zero_joule_cost_is_never_recorded (incl. through record_global),
  tune_and_record_records_nothing_when_the_winner_drew_zero_joules,
  an_unmeasured_candidate_voids_the_tune.
- a_live_tune_records_only_what_the_meter_measured (fabric-meter, on this
  host): calls tune_shape_live itself and asserts it records exactly what it
  returns, never a zero, and None where ioreport_status() is not Live. Both
  machines: "ioreport=CpuRailFrozen tuned=None recorded=None".

Falsified, each mutation applied to this commit and restored with git checkout:
- FL1, record accepts a zero: "the meter did not see the work: nothing to record".
- FL2, a held idle counter as a 0 W baseline: "left: Ok(0.0) right:
  Err(IdleDidNotAdvance { secs: 3.0 })".
- FL3, the marginal clamped to zero again: "left: Ok(0.0) right:
  Err(NoMarginal { marginal_uj: 0.0 })".
- FL4, the raw ratio: "left: Some(NaN) right: None".
- FL5, an unmeasured backend taken as 0 µJ: "left: Some(Cost { backend: Cpu,
  uj: 0, ns: 0 }) right: None".
- FL6, tune_shape_live returning and recording a zero cost when the counter is
  declined: the host test fails, "a live tune records exactly what it
  returns", tuned=Some(Cost { uj: 0 .. }) recorded=None (the record gate
  dropped it; the test caught the returned zero).
- FL7, the refusal without the IOReport status, and FL8, a per-op cost
  rounding to 0 accepted: their tests fail.

clippy --no-deps --tests on the crate (with and without fabric-meter, plus the
three energy examples): no warning in the lines this commit adds. The crate
does not pass clippy on its own: two deny-level lints elsewhere
(clippy::erasing_op at gemma4.rs:562 and :582, clippy::approx_constant in a lib
test) stop it before the tests and examples, so they were checked with those
two allowed. The pre-existing warnings in the touched files (mpp_energy's
double_parens, fabric_calibrate's chunks_exact_mut, fabric_dispatch's
too_many_arguments) are unchanged, and mpp_energy's unused-Result warning is
gone. `cargo check --tests --examples` for aarch64-unknown-linux-gnu (CI's
jouleclaw-rs target, with +fp16): clean.

Not done here, deliberately:
- jouleclaw-fabric loads every calibration row as Measured, and the file
  carries no date, OS or provenance. A file written on a live counter is still
  loaded at that grade on a host where the counter is now declined.
- TuningProfile and the autotune/1 receipt carry no provenance field, so a
  sealed tuning profile cannot say how its joules were obtained. Both are wire
  or format changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 01:50:18 -04:00

87 lines
3.5 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Live energy-autotune: for a couple of representative matmul shapes, measure the
//! CPU (AMX) path against the GPU path **under the IOReport meter**, pick the
//! joules-minimal backend, and print the resulting tuning profile.
//!
//! Run on a QUIET box for meaningful marginal-energy figures:
//! cargo run --release --features "rayon fabric-meter" --example energy_autotune
//!
//! The path being validated — meter → real kernels → joules-minimal selection →
//! global profile — executes regardless of box load; only the *accuracy* of the
//! µJ figures needs a quiet machine (the marginal-energy subtraction assumes a
//! stable idle draw). A backend the platform can't run for a shape reports
//! `u64::MAX` and simply never wins. A shape the meter cannot measure — the IOReport
//! counter unavailable (its CPU energy rail not advancing, among other reasons), or a
//! window that draws nothing above idle — is left untuned: nothing is recorded.
use jouleclaw_loader_gguf::energy_autotune;
use jouleclaw_loader_gguf::fabric_dispatch;
fn f32_to_bf16(v: f32) -> u16 {
let b = v.to_bits();
((b + (((b >> 16) & 1) + 0x7fff)) >> 16) as u16
}
fn main() {
// Decode (m = 1) and a prefill window (m = 256), same n×k projection — the two
// regimes whose joules-minimal backend can genuinely differ.
let shapes = [(1usize, 4096usize, 4096usize), (256, 4096, 4096)];
println!("tuning {} shape(s) live on the IOReport meter…\n", shapes.len());
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();
// ~1 s per backend past the counter's update window.
match fabric_dispatch::tune_shape_live(&a, &w, m, n, k, 1.0) {
Some(cost) => println!(
" m={m:<4} n={n} k={k} → {:>3} wins @ {:>10} µJ/op, {:>10} ns/op",
cost.backend.label(),
cost.uj,
cost.ns
),
None => println!(
" m={m:<4} n={n} k={k} → not tuned: the meter produced no figure (IOReport {:?})",
jouleclaw_energy::ioreport_status()
),
}
}
println!("\nglobal tuning profile (the dispatcher's lookup):");
let snapshot = energy_autotune::global_snapshot();
if snapshot.is_empty() {
println!(
" (empty — no shape was measured; IOReport {:?})",
jouleclaw_energy::ioreport_status()
);
}
for (key, cost) in snapshot {
println!(
" m_bucket={:<4} n={} k={} → {} ({} µJ/op)",
key.m_bucket,
key.n,
key.k,
cost.backend.label(),
cost.uj
);
}
// With the `attest` feature also on, seal the profile as a verifiable
// autotune/1 JCR-1 receipt — the tuning decisions become an artifact, not a claim.
#[cfg(feature = "attest")]
{
use ed25519_dalek::SigningKey;
let sk = SigningKey::from_bytes(&[0x5e; 32]);
match energy_autotune::seal_profile(&sk, "did:jouleclaw:autotune") {
Ok(env) => println!(
"\nsealed autotune/1 receipt: {} bytes — verifiable under the steward key",
env.len()
),
Err(e) => println!("\nseal failed: {e}"),
}
}
println!(
"\nrun the model with JOULECLAW_ENERGY_AUTOTUNE=1 to dispatch by this profile."
);
}