| Filename | Latest commit message | Latest commit date |
|---|---|---|
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]> |
||
| .. | ||
| crates | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
| rust-toolchain.toml | ||
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:
- Cache (
eoc-cache) — content-addressed LRU. Cache hits are nearly free. - Key-value (
eoc-kv) — exact key match plus cosine-similarity embedding match. - 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). - Neural (
eoc-neural) — last-resort inference. Reference impl ships anEchoBackend; 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.