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]>
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]>
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]>
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]>
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]>
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]>
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]>
- 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]>
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]>
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]>
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]>