jcp §5: a GPU counter's plausibility verdict was a bool, and conflated two findings #185

Merged
dcharlot merged 1 commit from fix/gpu-plausibility-magnitude into main 2026-10-08 10:26:50 -04:00

2026-10-07

Transaction Science
jcp §5: a GPU counter's plausibility verdict was a bool, and conflated two findings
All checks were successful
energy meters on real hardware / RAPL and NVML on jetson-hub (pull_request) Successful in 38s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 2m6s
standards workspaces build + test / aac-rs (pull_request) Successful in 55s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 44s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 29s
standards workspaces build + test / map-rs (pull_request) Successful in 28s
standards workspaces build + test / mesh-rs (pull_request) Successful in 25s
standards workspaces build + test / openpay (pull_request) Successful in 1m25s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 2m51s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 5m2s
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 4s
no-vanity-metrics / WAI public text names no company or product (pull_request) Successful in 3s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m9s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m58s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m28s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m8s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m10s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m12s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m15s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m10s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m42s
`plausible_gpu_counter` compares a counter's implied rate against two bounds —
1.5x the board's maximum power limit, and 2x the board's mean draw plus 5 W —
and returned `bool`. Everything that made the comparison useful was discarded at
the return: which bound broke, by how much, and the rate it broke it at. A
counter a tenth over a bound and one twenty times over were the same answer.
That also made the judgement irreproducible: the margins are constants in this
crate, and re-grading a past refusal against a revised margin needs the rate,
which did not survive.

It conflated two findings, too. One branch fired when the **board's own draw
reading** exceeded the board's own limit — an RTX 4050 Laptop GPU reports about
590 W for a second after it wakes. That tells you the yardstick is broken, not
that the counter is overstating, and `CounterStatus::Implausible`'s doc then
asserted "it is not counting the board's energy" for a case whose evidence does
not support it. A broken yardstick is an indeterminate.

`GpuPlausibility` now carries the verdict with its figures:
`Plausible { rate_mw }`, `CounterOverRate { rate_mw, bound_mw, bound }` with
`GpuBound ∈ { PowerLimit, MeanDraw }`, and `BoardTelemetryUnusable { draw_mw,
limit_mw }`. `CounterStatus` gains `Unjudgeable` for the last. Both still
decline the rung — unjudgeable is not admissible and declining is the safe
direction — so selection behaviour is unchanged; what changes is the diagnosis,
which is the difference between replacing a counter and waiting for a board to
settle.

Two ordering choices, both written down where they are made. The board's reading
is checked **first**: a counter measured against a yardstick known to be wrong
would be reported with a magnitude that means nothing, so a broken reading yields
no magnitude at all. And when both bounds break, the power limit is reported,
because a board cannot draw past its own limit by construction while the
mean-draw bound is soft (telemetry lags a load step) — naming the harder bound
is the more informative diagnosis.

**The jetson-hub hardware job corrected my first shape of this.** I had the
broken-reading check void the whole judgement, and the real RTX 4050 refused it:
one 590 W post-wake spike among otherwise sane 14.8 W samples turned a counter
running persistently at four to thirty times the board's draw into "cannot
judge" — a worse diagnosis than the overrun it actually is, and a disagreement
with the test's independent oracle.

A reading past the board's own limit is now discarded individually, and only
that reading. One broken sample is not a broken bracket. `Unjudgeable` is
reserved for a bracket with no usable reading left. That also fixes something
the old code hid: the mean summed every reading, so a 590 W spike inflated it
and made the mean-draw bound *more permissive* — invisible before only because
any such reading short-circuited the check first. A test pins it, asserting that
the spike-inflated mean would have admitted a counter the sane reading alone
refuses. And the power limit, being a static spec value rather than a reading,
is never discarded, so a broken draw sample cannot excuse a counter that
overran the limit outright.

SPEC §5 gains **Plausibility (REQUIRED where a bound is readable)** beside the
existing Liveness requirement. Liveness is necessary and not sufficient, and the
spec said nothing about this: the RTX 4050's counter advances in every sample
interval while running at four to thirty times the board's draw — perfectly
live, and no count of that board's energy. The new text requires the rate be
checked against an independently-read bound where one exists, SHOULD-records the
rate and the bound rather than only the refusal, and requires that an unusable
bound reading be reported as unjudgeable rather than as a counter that overran.
Where no bound is readable the requirement does not apply and liveness stands
alone.

One of my own tests was wrong first time and the code was right: I asserted the
mean-draw bound bound first on the jetson-hub figures, but 119.5 W is over the
97.5 W power cap, so that bound binds. Both cases are now pinned separately,
with a case where the mean-draw bound binds alone.

jcp-rs workspace: 403 passed, clippy clean on jcp-energy.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-07 15:04:57 -04:00