open-standards/jcp/jcp-rs
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science a625a7d70c jcp-energy: the GPU rail's period was wrong by two orders, in the unsafe direction
I declared the Apple `GPU Energy` rail's update period as 10 ms yesterday
(ed00cab1) on the strength of "~10 ms refresh on the parts measured here". That
was an assumption dressed as an observation, and it is wrong the dangerous way:
too short a declared period lets `may_grade_span` grade a span the counter
never covered, and a span landing entirely inside one hold reads **zero** and
would have been graded as a measurement of zero.

Measured properly, sampling every 2 ms on an M5 Max, the gap between actual
changes in the rail:

| condition | p50 | p90 | max | median step |
|---|---:|---:|---:|---:|
| idle | 12.7 ms | 91.0 ms | **992.2 ms** | 1.06 mJ |
| sustained MLX matmul | 13.1 ms | 15.0 ms | 42.0 ms | 73.50 mJ |

The rail has no fixed period. It republishes when there is activity to report
and holds when there is not, so the tight figure is only available to a
measurer who can guarantee the fabric stays busy — and a measurer cannot.
**Declared 1 s**, the idle worst case, because that is the condition a short
span is most likely to fall inside.

This also reconciles a fleet note that put the hold at "300–600 ms under load",
measured from a GGUF decode. That decode is dispatch-bound with idle gaps
between tokens, so it was sampling the idle behaviour rather than the loaded
one; under genuinely sustained load the rail is tight (max 42 ms). The two
observations agree once the cause is named, and the cause is what decides which
number to declare.

The headline figure survives, for the reason it was taken that way: the Laya
GPU span was 2.606 s, above even the idle worst case. A single 11.85 ms
decision could never have been one counter read, which is pinned in the test
alongside the 42 ms and 992 ms refusals so the period cannot drift back.

**Also corrects an overclaim in ed00cab1's message**, which said this "stop[s]
metering the Neural Engine as free". The code change there was right — count
the `ANE Energy` aggregate where a part publishes one, else the per-engine
rails — but on this hardware it changes nothing, because `ANE0` is frozen for
an unprivileged reader. The survey already reported `npu/ioreport` as `Frozen`;
it now also records the only route that exists: `sudo powermetrics --samplers
ane_power`, machine-wide, `NeedsPrivilege`. ANE work runs in `aned`, so it is
not this process's CPU energy either, and the load-balancer clients carry no
statistics — a span that used the ANE is CPU+GPU only and Partial.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-05 07:20:58 -04:00
..
conformance/v1 jcp: JCR-1 receipts bind their payload — jcp/2 + version dispatch (SPEC §8, C23) 2026-07-18 03:04:19 -04:00
crates jcp-energy: the GPU rail's period was wrong by two orders, in the unsafe direction 2026-10-05 07:20:58 -04:00
Cargo.lock sandbox-meter consumers: a figure keeps its span's provenance, and work that ran is never 0 J 2026-09-29 11:56:05 -04:00
Cargo.toml jcp-action-receipt: AR-1, the composition envelope 2026-08-28 08:44:07 -04:00