jouleclaw-energy: per-process GPU energy on Apple Silicon; a shared GPU is never split by time #146

Merged
dcharlot merged 14 commits from jouleclaw-energy/apple-gpu-share into main 2026-10-05 20:52:16 -04:00

2026-10-05

Transaction Science
jouleclaw-energy test: two dies' steps fall within the probe's guaranteed samples
All checks were successful
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 58s
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
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m54s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m25s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m12s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m12s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m38s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 10m27s
standards workspaces build + test / aac-rs (pull_request) Successful in 4m16s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 3m2s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 1m38s
standards workspaces build + test / map-rs (pull_request) Successful in 1m49s
standards workspaces build + test / mesh-rs (pull_request) Successful in 1m20s
standards workspaces build + test / openpay (pull_request) Successful in 9m52s
standards workspaces build + test / arl-rs (pull_request) Successful in 2m9s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 2m38s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 2m33s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 4m10s
standards workspaces build + test / sense-rs (pull_request) Successful in 2m33s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 6m22s
the_production_status_of_two_dies_stepping_apart... put DIE_1's step at
sample 6 and required 8 samples. The probe stops once its budget is spent
and it has five intervals (six samples). On the loaded macOS CI runner
its ticks ran slowly, it stopped at six samples, and the test failed
before the second step was ever followed (#146's macOS energy job). It
passed 5 of 5 runs locally.

The steps are now at samples 2 and 4, both followed within the six
samples the probe always takes, and the test asserts at least six. It
still checks what it set out to check: two dies stepping in different
intervals are each held to the rule at their own value, and never
summed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 09:09:46 -04:00

2026-10-04

Transaction Science
jouleclaw-energy test: pass the CPU tick directly
Some checks failed
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 9m14s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 58s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 4s
no-vanity-metrics / WAI public text names no comparison (pull_request) Successful in 4s
no-vanity-metrics / WAI public text names no company or product (pull_request) Successful in 4s
standards workspaces build + test / aac-rs (pull_request) Successful in 1m31s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 1m6s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 42s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m54s
standards workspaces build + test / map-rs (pull_request) Successful in 54s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m27s
standards workspaces build + test / mesh-rs (pull_request) Successful in 46s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m12s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m12s
standards workspaces build + test / openpay (pull_request) Successful in 3m14s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m39s
standards workspaces build + test / arl-rs (pull_request) Successful in 5m9s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 6m22s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 5m50s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 5m10s
standards workspaces build + test / sense-rs (pull_request) Successful in 18m10s
standards workspaces build + test / macOS energy backends (pull_request) Failing after 28m20s
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 16:19:35 -04:00
Transaction Science
jouleclaw-energy test: the oracle ignores a client that came and went inside the counter's span
A host process closing a GPU client inside the oracle's window failed the
self-zero check beside the child, which needs no other client. The oracle
now sums the clients it saw at both reads (and new ones), and checks that
any it saw vanish was absent from the counter's start snapshot.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 16:11:49 -04:00
Transaction Science
jouleclaw-energy: MEASURED_ENERGY and SPEC: no split of a shared GPU rail; re-measured
tinyllama-1.1b Q8_0, Q8_PACKED=1, 63 decode tokens, M5 Max:
sole GPU user 0.128 J/token = CPU 0.069 + GPU 0.060, HwShunt, Whole
(99.67% of the GPU time); beside a compute burner, CPU alone 0.053
J/token, HwShunt, Partial (GPU shared: 9.81 s of 13.98 s).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 16:09:31 -04:00
Transaction Science
jouleclaw-energy: no time-share GPU figure; an exact zero; the CPU alone, Partial
The time-share model is falsified by this branch's own measurements (a
GGUF decode at ~1.9 W per GPU-second beside a ~17 W job; contended
figures 3-15x the sole-user figure), so it is no longer a figure:

- apportion(): this process's own GPU time first, from its own clients
  (one gone: GpuClientVanished). None spent: 0 uJ, exact, HwShunt, with
  no rail, currency or other-client check. Spent, but under 99% of every
  client's time: EnergyError::GpuShared { self_ns, all_ns }. ModelBased
  is no longer a GPU grade; GpuShare.provenance is now a method
  (HwShunt), rail_uj / all_ns are None for an exact zero.
- combine(): CPU figure and no GPU figure is SpanSource::CpuOnly, the CPU
  alone, HwShunt, Partial, with why; the fallback meter only when the CPU
  counter gives none.
- fabric live tune: only spans metered Whole compete; a Partial one voids
  the tune. harness-econ refuses CpuOnly as a dispatch total.
- gguf_energy prints the CPU-only line; the hardware test expects
  GpuShared and CpuOnly under contention and exact zeros beside a
  burning child and across its exit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 16:09:30 -04:00
Transaction Science
jouleclaw-energy test: a process's GPU time runs ~0.6 ms a command buffer past the buffers' own GPU spans
Seen as the sole user: 1618.3 ms of accumulatedGPUTime for 1500.9 ms of
GPUStartTime..GPUEndTime over ~190 buffers. The ceiling allows 2 ms a buffer.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 15:57:03 -04:00
Transaction Science
jouleclaw-energy: record the GGUF decode measurement (sole user HwShunt, contended ModelBased)
tinyllama-1.1b Q8_0, Q8_PACKED=1, 63 decode tokens on an M5 Max:
sole GPU user 0.130 J/token (CPU 0.076 + GPU 0.054, HwShunt, 100.00%);
beside another GPU job 0.222 J/token (GPU 0.171, ModelBased, 11.5%).
On a quiet GPU the rail did not change in 4 s at idle.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 15:55:40 -04:00
Transaction Science
jouleclaw-energy: document the per-process sources; keep GPU snapshot overhead out of a span's CPU window
MEASURED_ENERGY.md and SPEC.md list the per-process CPU and GPU counters,
their grades and when they give no figure.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 15:37:33 -04:00
Transaction Science
Wire the per-process GPU counter: fabric live tune, mesh-meter; idle-aware rail reads
- jouleclaw-energy: a snapshot takes a held GPU rail as current once no
  client has accrued GPU time for 100 ms (a quiet GPU may not republish
  for over a second); a rail held for 1 s while the GPU is busy is
  EnergyError::NotCurrent, no figure.
- loader-gguf: tune_shape_live meters each backend on this process's CPU
  and GPU counters where the IOReport counter is declined (macOS 27.0.1),
  recorded as process-cpu+gpu at the weakest span's grade.
- mesh-meter: ProcessGpu does not cover a whole request.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 15:29:09 -04:00
Transaction Science
backend-apple: gpu_liveness_load; runtime: gguf_energy example (CPU+GPU joules per token)
jouleclaw-energy holds no Metal, so the Metal owner supplies the GPU
counter's liveness load: ~25 ms of a small FMA kernel, waited on.

gguf_energy meters a GGUF prefill and decode with ProcessSpanMeter and
prints joules per token, CPU and GPU apart, with the GPU figure's grade
and this process's share of the GPU's time.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 15:20:03 -04:00
Transaction Science
jouleclaw-energy: read the GPU rail on a republish; hardware test against ioreg + IOReport oracles
The GPU rail at times holds for 300-600 ms and catches up in one step
(7 of 37 ~315 ms windows read zero on a loaded M5 Max), so each snapshot
reads it on its next change and the clients right after; a snapshot whose
rail held for 1 s bounds no figure.

tests/process_gpu.rs: liveness once per process (and Frozen under a CPU
load), a GPU burn checked against the test's own IOReport read and ioreg's
text, a zero-GPU span, a contending child process, and a child exiting
inside a span (no GPU figure; the span meter falls back and says why).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 15:08:11 -04:00
Transaction Science
jouleclaw-energy: per-process GPU energy on Apple Silicon (WIP: counter, apportionment, span meter)
The IOReport GPU rail (Energy Model / GPU Energy, nJ, live unprivileged)
apportioned by this process's share of every IOAccelerator client's
accumulatedGPUTime (I/O Registry). HwShunt only as the sole user (>=99%),
ModelBased otherwise; a vanished client leaves no figure. ProcessSpanMeter
combines it with the process-cpu counter, falling back to the host meter.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 14:53:30 -04:00