jcp-energy: the Neural Engine is reported only once its rail is proved live #158

Merged
dcharlot merged 1 commit from jcp-energy/npu-needs-proof into main 2026-10-05 20:52:35 -04:00
Owner

Defect. FabricMeter listed the Neural Engine as readable whenever IOReport opened. It then reported the ANE0 delta over every span, and documented that a rail which did not advance reads zero, "on an idle fabric that is the truth".

That is not true here. On macOS 27.0.1 an unprivileged reader's ANE0 is frozen. It did not advance in any of 25 sample intervals (100 ms each) over 3 s of Core ML inference running on the ANE. Over the same span, root powermetrics read 0.47 J of ANE energy. So real Neural Engine work was reported as 0 J, the "metered as free" outcome the ANE0 fix (6f2dd38a) set out to stop. Before this change the meter emitted FabricEnergy { fabric: Npu, uj: 0, … } over a span on this Mac.

Fix

  • npu_rail_liveness(load), cached once per process. It runs the shared liveness rule (≥5 advances, ≥80% of intervals, ≥300 ms) over the ANE rails the counter sums: the ANE Energy aggregate where a part has one, else every ANE<n>, never both. The caller's load runs between samples and must put work on the Neural Engine; burning CPU moves no ANE rail. A sample with no ANE rail counts as no sample, so a part without one is Unavailable, never a vacuous Live. npu_rail_liveness_cached() reads the cached result.
  • FabricMeter lists and reports the Neural Engine only once that proof says Live.
  • survey() used to call a rail live after one advance over a 60 ms CPU burn. A rail republished in lumps passes that test, which is the case the family rule exists for. The CPU status now comes from ioreport_status() (the sustained rule) and the Neural Engine status from the proof. The GPU check is unchanged.

Tests

  • The ANE rails the probe watches are the ones the counter sums.
  • The scripted probe, through npu_status_with (the function the cached liveness runs):
    • an ANE0 holding through the load → Frozen;
    • one advancing every interval → Live;
    • a single lump → Frozen;
    • one engine holding beside a live one → Frozen;
    • no ANE rail → Unavailable.
  • On this Mac (tests/npu_needs_proof.rs, its own process): unproved, the meter lists only (Cpu, Process), (Gpu, Machine) and gives no Neural Engine figure. A load that never touches the ANE leaves its rail Frozen.
  • Mutations caught:
    • emitting the Neural Engine figure without proof: the hardware test sees Npu, uj: 0;
    • dropping the empty-rail filter: the no-ANE-rail case.
  • cargo test -p jcp-energy passes all 102 tests. The jcp-rs --workspace --all-targets check is clean, and the jcp-energy Linux check adds no new warnings. Clippy reports nothing in the changed code.

🤖 Generated with Claude Code

**Defect.** `FabricMeter` listed the Neural Engine as readable whenever IOReport opened. It then reported the `ANE0` delta over every span, and documented that a rail which did not advance reads zero, "on an idle fabric that is the truth". That is not true here. On macOS 27.0.1 an unprivileged reader's `ANE0` is **frozen**. It did not advance in any of 25 sample intervals (100 ms each) over 3 s of Core ML inference running on the ANE. Over the same span, root `powermetrics` read **0.47 J** of ANE energy. So real Neural Engine work was reported as **0 J**, the "metered as free" outcome the `ANE0` fix (`6f2dd38a`) set out to stop. Before this change the meter emitted `FabricEnergy { fabric: Npu, uj: 0, … }` over a span on this Mac. **Fix** - **`npu_rail_liveness(load)`**, cached once per process. It runs the shared liveness rule (≥5 advances, ≥80% of intervals, ≥300 ms) over the ANE rails the counter sums: the `ANE Energy` aggregate where a part has one, else every `ANE<n>`, never both. The caller's load runs between samples and must put work on the **Neural Engine**; burning CPU moves no ANE rail. A sample with no ANE rail counts as no sample, so a part without one is `Unavailable`, never a vacuous `Live`. `npu_rail_liveness_cached()` reads the cached result. - **`FabricMeter`** lists and reports the Neural Engine only once that proof says Live. - **`survey()`** used to call a rail live after one advance over a 60 ms CPU burn. A rail republished in lumps passes that test, which is the case the family rule exists for. The CPU status now comes from `ioreport_status()` (the sustained rule) and the Neural Engine status from the proof. The GPU check is unchanged. **Tests** - The ANE rails the probe watches are the ones the counter sums. - The scripted probe, through `npu_status_with` (the function the cached liveness runs): - an `ANE0` holding through the load → Frozen; - one advancing every interval → Live; - a single lump → Frozen; - one engine holding beside a live one → Frozen; - no ANE rail → Unavailable. - **On this Mac** (`tests/npu_needs_proof.rs`, its own process): unproved, the meter lists only `(Cpu, Process), (Gpu, Machine)` and gives no Neural Engine figure. A load that never touches the ANE leaves its rail **Frozen**. - **Mutations caught:** - emitting the Neural Engine figure without proof: the hardware test sees `Npu, uj: 0`; - dropping the empty-rail filter: the no-ANE-rail case. - `cargo test -p jcp-energy` passes all 102 tests. The jcp-rs `--workspace --all-targets` check is clean, and the jcp-energy Linux check adds no new warnings. Clippy reports nothing in the changed code. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jcp-energy: the Neural Engine is reported only once its rail is proved live
All checks were successful
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m54s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m30s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m11s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m12s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m35s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 8m55s
standards workspaces build + test / aac-rs (pull_request) Successful in 3m55s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 4m25s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 1m48s
standards workspaces build + test / map-rs (pull_request) Successful in 2m16s
standards workspaces build + test / mesh-rs (pull_request) Successful in 1m31s
standards workspaces build + test / openpay (pull_request) Successful in 8m19s
standards workspaces build + test / arl-rs (pull_request) Successful in 2m6s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 2m37s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 2m34s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 4m14s
standards workspaces build + test / sense-rs (pull_request) Successful in 2m34s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 4m27s
633dc71f82
FabricMeter listed the Neural Engine as readable whenever IOReport
opened, and reported its `ANE0` delta over every span. A rail that did
not advance was read as zero, "on an idle fabric that is the truth". On
macOS 27.0.1 an unprivileged reader's ANE0 is frozen. It did not advance
in any of 25 sample intervals over 3 s of Core ML inference on the ANE,
while root powermetrics read 0.47 J of ANE energy over the same span. So
real Neural Engine work was reported as 0 J, which is what the ANE0 fix
set out to stop.

- `npu_rail_liveness(load)`, cached once per process. It runs the shared
  liveness rule over the ANE rails the counter sums, the `ANE Energy`
  aggregate or else every `ANE<n>` rail, with the caller's
  Neural-Engine load between samples. A sample with no ANE rail is no
  sample, so a part without one is Unavailable, never a vacuous Live.
- FabricMeter lists and reports the Neural Engine only once that proof
  says Live.
- The survey now uses the shared rule instead of a single advance over
  60 ms. Its CPU status comes from `ioreport_status()`, and its Neural
  Engine status from the proof. A rail republished in lumps passes a
  one-advance test.

Tests:
- the ANE rails the probe watches match the counter's sum;
- the scripted probe gives Frozen for a holding ANE0, Live for an
  advancing one, Frozen for a lump and for one engine of two, and
  Unavailable with no ANE rail;
- on this Mac the meter lists only the CPU and the GPU, and a load that
  never touches the ANE leaves its rail Frozen.

Mutations caught: emitting the Neural Engine figure unproved, and
dropping the empty-rail filter.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!158
No description provided.