open-standards/eoc/eoc-rs
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science 0f6a9a3bb3 eoc-meter: the process counter reads its own count only once the reading thread has left its core
The kernel adds a thread's CPU energy to its process's count
(proc_pid_rusage ri_energy_nj) only when the thread leaves a core, and a
process's read of its own count does not take the reading thread off its
core. MacosProcessEnergyCounter read the count as it stood, so a span
bracketed by two reads held up to a scheduling quantum of what the
thread spent before the span and lacked the span's own tail, and every
consumer here tags a positive delta of the counter Measured.

Measured on an M3 Ultra (macOS 27.0.1, load average about 14) against
the tests' own reader, which sleeps before each read until the process's
context-switch count rose; three runs of 40 spans of each kind:
- 1 ms of the reading thread's CPU between two reads: 39, 40 and 40
  spans read nothing or under two thirds of the work;
- a 2 ms sleep between two reads, straight after 8 ms of CPU: 40, 40 and
  37 spans read the burn before them, about 41 mJ for a sleep.

read_microjoules now sleeps (50 us, then 200 us, 1 ms, 5 ms) until the
reading thread's own energy count (proc_pidinfo PROC_PIDTHREADCOUNTS,
read only in the one layout known: 88 bytes, two performance levels
here) rose across a sleep, and only then reads the process. A read whose
thread was not seen to leave is an error, not a short count: eoc-cascade,
eoc-agent and eoc-local all take a failed read as no span and keep their
estimate. process_energy_status is Unavailable, with no probe run, where
the thread's count does not read, and the liveness probe still samples
with no sleep. The rule and the layout decoding are pure and compiled on
every platform.

With it, on that host, quiet and under 40 busy loops (110 runs): every
1 ms span read 79% or more of the tests' window around it (98% when
quiet) and no sleep read more than 3% of the window that held the burn.
A read costs 83 us at the median on the quiet host and 226 us under the
busy loops, against under 1 us; detect_in_process's docs say so, and
that a batch of short operations is bracketed by one span, not one each.

tests/process_energy_short_span.rs is the test that fails without it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 20:50:53 -04:00
..
crates eoc-meter: the process counter reads its own count only once the reading thread has left its core 2026-10-12 20:50:53 -04:00
Cargo.lock eoc-agent: a local model's measured step is a measured span 2026-10-01 01:27:42 -04:00
Cargo.toml msrv: raise the rust-version floor to 1.97 across the workspaces 2026-07-23 00:17:08 -04:00
LICENSE licences: ship the Apache-2.0 text the code already declares 2026-10-09 17:22:32 -04:00
README.md eoc-meter: the GPU counter is never the cascade's counter, and one past its board is Implausible 2026-10-01 10:18:29 -04:00
rust-toolchain.toml chore(deps): cargo update across the workspace; eoc toolchain → 1.98.0 2026-08-26 07:39:26 -04:00

eoc-rs — EOC reference implementation in Rust

Apache-2.0 reference implementation of the Energy-Optimized Compute specification (CC-BY-4.0 spec lives in ../spec/).

EOC is a four-stage memoizing cascade. Every query is tried at each stage in turn and the first that can answer wins. The four stages, cheapest to most expensive, are:

  1. Cache (eoc-cache) — content-addressed LRU. Cache hits are nearly free.
  2. Key-value (eoc-kv) — exact key match plus cosine-similarity embedding match.
  3. Graph (eoc-graph) — triple-store retrieval. Reference impl is a small in-memory matcher; production deployments swap in a real graph backend driven by DCY (EOC-4).
  4. Neural (eoc-neural) — last-resort inference. Reference impl ships an EchoBackend; production deployments wire in llama.cpp / Ollama / Anthropic API / etc.

Joule cost is the unit of accounting. The eoc-meter crate reads cumulative micro-joules from whatever whole-work hardware counter is available and live (Linux RAPL, Apple Silicon IOReport; NVIDIA NVML behind the cuda feature is GPU telemetry, never the cascade's counter) and falls back to a StubCounter everywhere else — including WASM.

Workspace layout

Crate Purpose
eoc-core Types: Query, Response, Stage, JouleCost, Receipt.
eoc-cache Stage 1 — LRU cache + the Stage trait.
eoc-kv Stage 2 — KV + embedding-similarity lookup.
eoc-graph Stage 3 — in-memory triple store.
eoc-neural Stage 4 — neural inference trait + EchoBackend.
eoc-meter Hardware energy counters with stub fallback.
eoc-cascade Glue: walks the four stages, attributes joule cost.
eoc Reference CLI.
eoc-wasm wasm-bindgen wrapper — browser-runnable cascade.

Running

cargo test --workspace
cargo clippy --workspace --all-targets
cargo run --bin eoc -- query "what is 2+2"
cargo run --bin eoc -- meter

Example output:

$ cargo run --bin eoc -- query "what is 2+2"
stage  : kv
payload: 4
cost   : 50 µJ (estimated)
receipt: <64-char hex>

CUDA / NVML

NVIDIA NVML support is feature-gated and off by default so the workspace builds without libnvidia-ml. NVML's total-energy counter reads the GPU alone, so detect() never hands it to the cascade, whose costs carry no field to say a figure covers part of the work: NvmlCounter is GPU telemetry, read beside the whole-work counter, and it is constructed only while its counter is live and counts no faster than the board can draw. To enable:

cargo build -p eoc-meter --features cuda

WASM

The cascade compiles to wasm32-unknown-unknown. The meter does not — RAPL, NVML, and powermetrics are all absent in the browser, so WASM builds use StubCounter and report estimated joule cost.

rustup target add wasm32-unknown-unknown
cargo build -p eoc-wasm --target wasm32-unknown-unknown --release
wasm-bindgen target/wasm32-unknown-unknown/release/eoc_wasm.wasm \
    --out-dir ./pkg --target web

Then from JavaScript:

import init, { WasmCascade } from "./pkg/eoc_wasm.js";
await init();
const cascade = new WasmCascade();
const r = await cascade.resolve("ping");
console.log(r); // { stage: "kv", payload: "pong", microjoules: 50, ... }

License

Apache-2.0 for the Rust source in this workspace. The EOC specification text in ../spec/ is CC-BY-4.0. See ../LICENSE.