efficiency/1: a partial energy figure is sealed as partial and never headlines as a task total #369
7 changed files with 644 additions and 11 deletions
10
jcp/SPEC.md
10
jcp/SPEC.md
|
|
@ -926,6 +926,16 @@ interceptor chain (`jcp-intercept`) carries it as `coverage`, beside its
|
|||
`"Partial"`; a verifier MUST refuse any other value, `"Whole"` included, rather
|
||||
than read it either way.
|
||||
|
||||
The `efficiency/1` outcome receipt (§5.3) carries it as `energy_coverage`,
|
||||
under the same one-value rule. Its headline figures are ratios of a task total:
|
||||
energy per accepted token on the receipt, and energy per completed unit over a
|
||||
ledger. So a `Partial` receipt MUST NOT carry `uj_per_accepted_token`, and a
|
||||
verifier MUST refuse one that does. A total summed from receipts of which any
|
||||
is `Partial` is `Partial` and gives no per-unit energy figure. A partial figure
|
||||
divided by tokens is not what the work cost per token, nor a bound on it,
|
||||
because a partial figure may fall short of the work or exceed it. The vectors
|
||||
are in [`conformance/v1/efficiency.json`](conformance/v1/efficiency.json).
|
||||
|
||||
### 5.3 Traceability across operation, activity and outcome (REQUIRED where an outcome claims energy)
|
||||
|
||||
§5.1 grades a figure and §5.2 says how much of the work it covers. Neither says
|
||||
|
|
|
|||
228
jcp/conformance/v1/efficiency.json
Normal file
228
jcp/conformance/v1/efficiency.json
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
{
|
||||
"aggregate": {
|
||||
"cases": [
|
||||
{
|
||||
"expected": {
|
||||
"completed_uj": 1600000,
|
||||
"coverage": "Partial",
|
||||
"provenance_floor": "Estimator",
|
||||
"tokens_per_completed_unit": 3000,
|
||||
"uj_per_completed_unit": null,
|
||||
"wasted_uj": 400000
|
||||
},
|
||||
"input": [
|
||||
{
|
||||
"completed": 1,
|
||||
"efficiency_receipt": "efficiency/1",
|
||||
"energy_uj": 1000000,
|
||||
"prev": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"provenance": "HwShunt",
|
||||
"seq": 0,
|
||||
"steward": "did:example:steward",
|
||||
"task": "whole",
|
||||
"tokens_in": 2000,
|
||||
"tokens_out": 500,
|
||||
"uj_per_accepted_token": 2000,
|
||||
"verdict": "tests-pass",
|
||||
"wall_ns": 1000000000
|
||||
},
|
||||
{
|
||||
"completed": 1,
|
||||
"efficiency_receipt": "efficiency/1",
|
||||
"energy_coverage": "Partial",
|
||||
"energy_uj": 600000,
|
||||
"prev": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"provenance": "HwShunt",
|
||||
"seq": 0,
|
||||
"steward": "did:example:steward",
|
||||
"task": "gpu-only",
|
||||
"tokens_in": 2000,
|
||||
"tokens_out": 500,
|
||||
"verdict": "tests-pass",
|
||||
"wall_ns": 1000000000
|
||||
},
|
||||
{
|
||||
"completed": 0,
|
||||
"efficiency_receipt": "efficiency/1",
|
||||
"energy_uj": 400000,
|
||||
"prev": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"provenance": "Estimator",
|
||||
"seq": 0,
|
||||
"steward": "did:example:steward",
|
||||
"task": "estimated",
|
||||
"tokens_in": 800,
|
||||
"tokens_out": 200,
|
||||
"uj_per_accepted_token": 2000,
|
||||
"verdict": "gate-failed",
|
||||
"wall_ns": 1000000000
|
||||
}
|
||||
],
|
||||
"note": "a total over a partial figure is partial and gives no per-unit energy; its provenance is the floor"
|
||||
},
|
||||
{
|
||||
"expected": {
|
||||
"completed_uj": 1000000,
|
||||
"coverage": "Whole",
|
||||
"provenance_floor": "Estimator",
|
||||
"tokens_per_completed_unit": 3500,
|
||||
"uj_per_completed_unit": 1400000,
|
||||
"wasted_uj": 400000
|
||||
},
|
||||
"input": [
|
||||
{
|
||||
"completed": 1,
|
||||
"efficiency_receipt": "efficiency/1",
|
||||
"energy_uj": 1000000,
|
||||
"prev": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"provenance": "HwShunt",
|
||||
"seq": 0,
|
||||
"steward": "did:example:steward",
|
||||
"task": "whole",
|
||||
"tokens_in": 2000,
|
||||
"tokens_out": 500,
|
||||
"uj_per_accepted_token": 2000,
|
||||
"verdict": "tests-pass",
|
||||
"wall_ns": 1000000000
|
||||
},
|
||||
{
|
||||
"completed": 0,
|
||||
"efficiency_receipt": "efficiency/1",
|
||||
"energy_uj": 400000,
|
||||
"prev": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"provenance": "Estimator",
|
||||
"seq": 0,
|
||||
"steward": "did:example:steward",
|
||||
"task": "estimated",
|
||||
"tokens_in": 800,
|
||||
"tokens_out": 200,
|
||||
"uj_per_accepted_token": 2000,
|
||||
"verdict": "gate-failed",
|
||||
"wall_ns": 1000000000
|
||||
}
|
||||
],
|
||||
"note": "a total over whole figures gives its per-unit energy, at the weakest provenance"
|
||||
}
|
||||
]
|
||||
},
|
||||
"payload": {
|
||||
"negative": [
|
||||
{
|
||||
"expected": {
|
||||
"refused": "presented as a task total"
|
||||
},
|
||||
"input": {
|
||||
"completed": 1,
|
||||
"efficiency_receipt": "efficiency/1",
|
||||
"energy_coverage": "Partial",
|
||||
"energy_uj": 600000,
|
||||
"prev": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"provenance": "HwShunt",
|
||||
"seq": 0,
|
||||
"steward": "did:example:steward",
|
||||
"task": "gpu-only",
|
||||
"tokens_in": 2000,
|
||||
"tokens_out": 500,
|
||||
"uj_per_accepted_token": 1200,
|
||||
"verdict": "tests-pass",
|
||||
"wall_ns": 1000000000
|
||||
},
|
||||
"note": "a GPU-only figure presented as a task total, with a per-token headline"
|
||||
},
|
||||
{
|
||||
"expected": {
|
||||
"refused": "energy_coverage"
|
||||
},
|
||||
"input": {
|
||||
"completed": 1,
|
||||
"efficiency_receipt": "efficiency/1",
|
||||
"energy_coverage": "Whole",
|
||||
"energy_uj": 1000000,
|
||||
"prev": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"provenance": "HwShunt",
|
||||
"seq": 0,
|
||||
"steward": "did:example:steward",
|
||||
"task": "whole",
|
||||
"tokens_in": 2000,
|
||||
"tokens_out": 500,
|
||||
"uj_per_accepted_token": 2000,
|
||||
"verdict": "tests-pass",
|
||||
"wall_ns": 1000000000
|
||||
},
|
||||
"note": "an absent coverage is Whole; \"Whole\" on the wire is refused"
|
||||
},
|
||||
{
|
||||
"expected": {
|
||||
"refused": "not a provenance label"
|
||||
},
|
||||
"input": {
|
||||
"completed": 1,
|
||||
"efficiency_receipt": "efficiency/1",
|
||||
"energy_uj": 1000000,
|
||||
"prev": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"provenance": "Measured",
|
||||
"seq": 0,
|
||||
"steward": "did:example:steward",
|
||||
"task": "whole",
|
||||
"tokens_in": 2000,
|
||||
"tokens_out": 500,
|
||||
"uj_per_accepted_token": 2000,
|
||||
"verdict": "tests-pass",
|
||||
"wall_ns": 1000000000
|
||||
},
|
||||
"note": "a provenance that is not one of the three labels"
|
||||
}
|
||||
],
|
||||
"positive": [
|
||||
{
|
||||
"expected": {
|
||||
"energy_coverage": "Whole",
|
||||
"energy_uj": 1000000,
|
||||
"provenance": "HwShunt",
|
||||
"uj_per_accepted_token": 2000
|
||||
},
|
||||
"input": {
|
||||
"completed": 1,
|
||||
"efficiency_receipt": "efficiency/1",
|
||||
"energy_uj": 1000000,
|
||||
"prev": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"provenance": "HwShunt",
|
||||
"seq": 0,
|
||||
"steward": "did:example:steward",
|
||||
"task": "whole",
|
||||
"tokens_in": 2000,
|
||||
"tokens_out": 500,
|
||||
"uj_per_accepted_token": 2000,
|
||||
"verdict": "tests-pass",
|
||||
"wall_ns": 1000000000
|
||||
},
|
||||
"note": "a whole figure seals its per-token headline and no coverage field"
|
||||
},
|
||||
{
|
||||
"expected": {
|
||||
"energy_coverage": "Partial",
|
||||
"energy_uj": 600000,
|
||||
"provenance": "HwShunt",
|
||||
"uj_per_accepted_token": null
|
||||
},
|
||||
"input": {
|
||||
"completed": 1,
|
||||
"efficiency_receipt": "efficiency/1",
|
||||
"energy_coverage": "Partial",
|
||||
"energy_uj": 600000,
|
||||
"prev": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"provenance": "HwShunt",
|
||||
"seq": 0,
|
||||
"steward": "did:example:steward",
|
||||
"task": "gpu-only",
|
||||
"tokens_in": 2000,
|
||||
"tokens_out": 500,
|
||||
"verdict": "tests-pass",
|
||||
"wall_ns": 1000000000
|
||||
},
|
||||
"note": "a GPU-only figure seals its coverage and no per-token headline: it is no task total"
|
||||
}
|
||||
]
|
||||
},
|
||||
"profile": "efficiency/1",
|
||||
"spec": "jcp/SPEC.md §5.2-5.3"
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ receipts.
|
|||
|
||||
| SCI-for-AI concept | `efficiency/1` field / method | What TX adds |
|
||||
|---|---|---|
|
||||
| `E` — energy of the functional unit | `energy_uj` + `provenance` | **Honest provenance** (`HwShunt`/`ModelBased`/`Estimator`) — SCI leaves `E`'s trust unstated |
|
||||
| `E` — energy of the functional unit | `energy_uj` + `provenance` + `energy_coverage` | **Honest provenance** (`HwShunt`/`ModelBased`/`Estimator`) — SCI leaves `E`'s trust unstated — and **coverage**: a `Partial` figure (a GPU counter alone) is sealed as such and gives no per-token rate, since it is not `E` |
|
||||
| `R` — functional unit (per token / inference) | `tokens_out` (accepted) → `uj_per_accepted_token()` | The unit is **sealed**, not asserted |
|
||||
| `SCI` rate | `uj_per_accepted_token`, `tokens_per_sec()`, `uusd_per_accepted_token()` | Rates **recompute from sealed integers** — reproducible, tamper-evident |
|
||||
| — (no outcome concept in SCI) | `completed` + `verdict`, `aggregate()` | The **outcome anchor** + waste accounting SCI has no notion of |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
//! Emit the `efficiency/1` payload conformance pack to `jcp/conformance/v1/`.
|
||||
//!
|
||||
//! jcp SPEC §5.2 says a `Partial` figure MUST NOT be presented as a task total,
|
||||
//! and §5.3 that aggregation is not a step at which a grade improves. These are
|
||||
//! the vectors a third party replays to check an independent `efficiency/1`
|
||||
//! reader against those rules: payloads (the signed core, once its signature
|
||||
//! holds) with the reading a conformant reader must reach, the refusals
|
||||
//! included, and receipt sets with the totals they must sum to. The payloads
|
||||
//! carry no signature, because the rules are about what a verified receipt
|
||||
//! says, not about the signature, which `jcr1` vectors already cover.
|
||||
//!
|
||||
//! Run: `cargo run -p jcp-efficiency --bin gen_efficiency_vectors -- ../conformance/v1`
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use jcp_efficiency::{aggregate, read_payload, Efficiency};
|
||||
use jcp_schema::{ContentId, Energy};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
fn unit(task: &str, completed: bool, tokens_out: u64, energy: Energy) -> Efficiency {
|
||||
Efficiency {
|
||||
task: task.into(),
|
||||
completed,
|
||||
verdict: if completed { "tests-pass".into() } else { "gate-failed".into() },
|
||||
tokens_in: tokens_out * 4,
|
||||
tokens_out,
|
||||
energy,
|
||||
wall_ns: 1_000_000_000,
|
||||
value_uusd: None,
|
||||
energy_attestation: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn payload(e: &Efficiency) -> Value {
|
||||
e.payload(0, &ContentId([0u8; 32]), "did:example:steward")
|
||||
}
|
||||
|
||||
/// A case carries the input and the verdict a conformant reader must reach.
|
||||
fn case(note: &str, input: Value, expected: Value) -> Value {
|
||||
json!({ "note": note, "input": input, "expected": expected })
|
||||
}
|
||||
|
||||
fn read(note: &str, p: Value) -> Value {
|
||||
let r = read_payload(&p).unwrap_or_else(|e| panic!("{note}: a positive must read: {e}"));
|
||||
case(
|
||||
note,
|
||||
p,
|
||||
json!({
|
||||
"energy_uj": r.energy_uj,
|
||||
"provenance": r.provenance,
|
||||
"energy_coverage": r.energy_coverage.label(),
|
||||
"uj_per_accepted_token": r.uj_per_accepted_token,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn refuse(note: &str, p: Value, because: &str) -> Value {
|
||||
let err = read_payload(&p).expect_err(note).to_string();
|
||||
assert!(err.contains(because), "{note}: refused for '{err}', not '{because}'");
|
||||
case(note, p, json!({ "refused": because }))
|
||||
}
|
||||
|
||||
/// A set of payloads and the totals a conformant reader must sum them to.
|
||||
fn totals(note: &str, ps: Vec<Value>) -> Value {
|
||||
let rs: Vec<_> = ps.iter().map(|p| read_payload(p).expect("reads")).collect();
|
||||
let t = aggregate(&rs);
|
||||
case(
|
||||
note,
|
||||
json!(ps),
|
||||
json!({
|
||||
"completed_uj": t.completed_uj,
|
||||
"wasted_uj": t.wasted_uj,
|
||||
"coverage": t.coverage.label(),
|
||||
"provenance_floor": t.provenance_floor.map(|p| p.jcr1_label()),
|
||||
"uj_per_completed_unit": t.uj_per_completed_unit(),
|
||||
"tokens_per_completed_unit": t.tokens_per_completed_unit(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let out = std::env::args()
|
||||
.nth(1)
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../../conformance/v1"));
|
||||
std::fs::create_dir_all(&out).expect("create output dir");
|
||||
|
||||
let whole = unit("whole", true, 500, Energy::measured(1_000_000));
|
||||
let gpu_only = unit("gpu-only", true, 500, Energy::measured_partial(600_000));
|
||||
let estimated = unit("estimated", false, 200, Energy::estimator(400_000));
|
||||
|
||||
let mut presented = payload(&gpu_only);
|
||||
presented["uj_per_accepted_token"] = json!(1_200);
|
||||
let mut said_whole = payload(&whole);
|
||||
said_whole["energy_coverage"] = json!("Whole");
|
||||
let mut unnamed = payload(&whole);
|
||||
unnamed["provenance"] = json!("Measured");
|
||||
|
||||
let pack = json!({
|
||||
"profile": "efficiency/1",
|
||||
"spec": "jcp/SPEC.md §5.2-5.3",
|
||||
"payload": {
|
||||
"positive": [
|
||||
read("a whole figure seals its per-token headline and no coverage field", payload(&whole)),
|
||||
read(
|
||||
"a GPU-only figure seals its coverage and no per-token headline: it is no task total",
|
||||
payload(&gpu_only)
|
||||
),
|
||||
],
|
||||
"negative": [
|
||||
refuse(
|
||||
"a GPU-only figure presented as a task total, with a per-token headline",
|
||||
presented,
|
||||
"presented as a task total"
|
||||
),
|
||||
refuse("an absent coverage is Whole; \"Whole\" on the wire is refused", said_whole, "energy_coverage"),
|
||||
refuse("a provenance that is not one of the three labels", unnamed, "not a provenance label"),
|
||||
],
|
||||
},
|
||||
"aggregate": { "cases": [
|
||||
totals(
|
||||
"a total over a partial figure is partial and gives no per-unit energy; its provenance is the floor",
|
||||
vec![payload(&whole), payload(&gpu_only), payload(&estimated)],
|
||||
),
|
||||
totals(
|
||||
"a total over whole figures gives its per-unit energy, at the weakest provenance",
|
||||
vec![payload(&whole), payload(&estimated)],
|
||||
),
|
||||
] },
|
||||
});
|
||||
let path = out.join("efficiency.json");
|
||||
std::fs::write(&path, serde_json::to_string_pretty(&pack).expect("serialise") + "\n").expect("write pack");
|
||||
println!("wrote {}", path.display());
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
use ed25519_dalek::SigningKey;
|
||||
use jcp_receipt::jcr1;
|
||||
use jcp_schema::{ContentId, Energy};
|
||||
use jcp_schema::{ContentId, Coverage, Energy, Provenance};
|
||||
|
||||
pub mod avoided;
|
||||
/// Energy traceability across operation, activity and outcome.
|
||||
|
|
@ -95,7 +95,18 @@ impl Efficiency {
|
|||
/// sealed and read straight back out rather than recomputed. `mesh/SPEC.md`
|
||||
/// §6 states the family convention this matches: "omitted when no tokens were
|
||||
/// accepted".
|
||||
///
|
||||
/// Also `None` for a `Partial` figure. The headline is the unit's energy
|
||||
/// per token, a ratio of a task total, and a `Partial` figure MUST NOT be
|
||||
/// presented as one (jcp SPEC §5.2). A GPU-only reading divided by the
|
||||
/// tokens is not what the unit cost per token, and not a bound on it
|
||||
/// either: a partial figure can be short of the work or over it. The
|
||||
/// receipt seals `energy_uj` with `energy_coverage` instead, so a reader
|
||||
/// still sees what was measured and how much of the work it covers.
|
||||
pub fn uj_per_accepted_token(&self) -> Option<u64> {
|
||||
if !self.energy.is_whole() {
|
||||
return None;
|
||||
}
|
||||
self.energy.uj().checked_div(self.tokens_out)
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +153,13 @@ impl Efficiency {
|
|||
jcr1::cose_sign1_envelope(&core, steward_sk, steward)
|
||||
}
|
||||
|
||||
/// The payload [`Self::seal`] signs, as JSON: what a verifier reads back
|
||||
/// with [`read_payload`] once the signature holds. Published conformance
|
||||
/// vectors are built from it.
|
||||
pub fn payload(&self, seq: u64, prev: &ContentId, steward: &str) -> Value {
|
||||
self.wire(seq, prev, steward)
|
||||
}
|
||||
|
||||
fn wire(&self, seq: u64, prev: &ContentId, steward: &str) -> Value {
|
||||
// JCR-1: integers + strings only. `completed` is 0/1, energy is integer µJ,
|
||||
// and the rate figures (tokens/sec, joules-in-J) are recomputed on read.
|
||||
|
|
@ -166,6 +184,13 @@ impl Efficiency {
|
|||
if let Some(uj) = self.uj_per_accepted_token() {
|
||||
o["uj_per_accepted_token"] = json!(uj);
|
||||
}
|
||||
// Carried only when `Partial` (jcp SPEC §5.2), so a receipt sealed
|
||||
// before the field existed, and every whole one, keeps its bytes. It
|
||||
// dropped out of the receipt entirely: a GPU-only figure sealed as
|
||||
// `HwShunt` reached a verifier as a whole-task total.
|
||||
if !self.energy.is_whole() {
|
||||
o["energy_coverage"] = json!(self.energy.coverage.label());
|
||||
}
|
||||
if let Some(v) = self.value_uusd {
|
||||
o["value_uusd"] = json!(v);
|
||||
if let Some(per) = self.uusd_per_accepted_token() {
|
||||
|
|
@ -260,8 +285,12 @@ pub struct EfficiencyReceipt {
|
|||
pub tokens_out: u64,
|
||||
/// Total energy, microjoules.
|
||||
pub energy_uj: u64,
|
||||
/// Energy provenance label.
|
||||
/// Energy provenance label: `HwShunt`, `ModelBased` or `Estimator`.
|
||||
pub provenance: String,
|
||||
/// How much of the unit the energy figure covers. `Partial` when the
|
||||
/// receipt carries `energy_coverage`; such a receipt seals no
|
||||
/// `uj_per_accepted_token`, and is not a task total.
|
||||
pub energy_coverage: Coverage,
|
||||
/// Wall-clock nanoseconds.
|
||||
pub wall_ns: u64,
|
||||
/// Microjoules per accepted token (headline). `None` when the receipt sealed
|
||||
|
|
@ -294,11 +323,34 @@ impl EfficiencyReceipt {
|
|||
}
|
||||
|
||||
/// Verify a sealed `efficiency/1` receipt against the steward key and recover its
|
||||
/// fields. A flipped byte anywhere fails the signature.
|
||||
/// fields ([`read_payload`]). A flipped byte anywhere fails the signature.
|
||||
pub fn verify(envelope: &[u8], steward: &str, pubkey: &[u8; 32]) -> Result<EfficiencyReceipt, EfficiencyError> {
|
||||
let payload = jcr1::verify_cose_sign1(envelope, steward, pubkey)?;
|
||||
let v: Value =
|
||||
jcp_schema::from_canonical_cbor(&payload).map_err(|e| EfficiencyError::Schema(e.to_string()))?;
|
||||
read_payload(&v)
|
||||
}
|
||||
|
||||
/// The provenance a sealed label names, and `None` for a label that is not one.
|
||||
fn provenance_of(label: &str) -> Option<Provenance> {
|
||||
[Provenance::HwShunt, Provenance::ModelBased, Provenance::Estimator]
|
||||
.into_iter()
|
||||
.find(|p| p.jcr1_label() == label)
|
||||
}
|
||||
|
||||
/// Read an `efficiency/1` payload, the signed core [`verify`] recovers once the
|
||||
/// signature holds, and refuse one that is not a well-formed receipt. It is
|
||||
/// apart from [`verify`] so that the payload's rules can be replayed from
|
||||
/// published vectors with no signature in them
|
||||
/// (`jcp/conformance/v1/efficiency.json`). Beyond the required fields:
|
||||
///
|
||||
/// - `provenance` MUST be one of the three labels;
|
||||
/// - `energy_coverage`, when present, MUST be `"Partial"`. An absent field is
|
||||
/// `Whole`, so `"Whole"` and every other value are refused rather than read
|
||||
/// either way;
|
||||
/// - a `Partial` receipt MUST NOT carry `uj_per_accepted_token`, since that
|
||||
/// would present a partial figure as a task total (jcp SPEC §5.2).
|
||||
pub fn read_payload(v: &Value) -> Result<EfficiencyReceipt, EfficiencyError> {
|
||||
if v.get("efficiency_receipt").and_then(Value::as_str) != Some("efficiency/1") {
|
||||
return Err(EfficiencyError::Malformed("not an efficiency/1 receipt".into()));
|
||||
}
|
||||
|
|
@ -313,6 +365,25 @@ pub fn verify(envelope: &[u8], steward: &str, pubkey: &[u8; 32]) -> Result<Effic
|
|||
.and_then(Value::as_u64)
|
||||
.ok_or_else(|| EfficiencyError::Malformed(format!("missing uint '{k}'")))
|
||||
};
|
||||
let provenance = s("provenance")?;
|
||||
if provenance_of(&provenance).is_none() {
|
||||
return Err(EfficiencyError::Malformed(format!("'{provenance}' is not a provenance label")));
|
||||
}
|
||||
let energy_coverage = match v.get("energy_coverage") {
|
||||
None => Coverage::Whole,
|
||||
Some(c) if c.as_str() == Some(Coverage::Partial.label()) => Coverage::Partial,
|
||||
Some(c) => {
|
||||
return Err(EfficiencyError::Malformed(format!(
|
||||
"energy_coverage {c} is not \"Partial\", the one value it carries; an absent field is Whole"
|
||||
)))
|
||||
}
|
||||
};
|
||||
let uj_per_accepted_token = v.get("uj_per_accepted_token").and_then(Value::as_u64);
|
||||
if energy_coverage == Coverage::Partial && uj_per_accepted_token.is_some() {
|
||||
return Err(EfficiencyError::Malformed(
|
||||
"a Partial figure presented as a task total: it carries uj_per_accepted_token".into(),
|
||||
));
|
||||
}
|
||||
Ok(EfficiencyReceipt {
|
||||
task: s("task")?,
|
||||
completed: u("completed")? != 0,
|
||||
|
|
@ -320,9 +391,10 @@ pub fn verify(envelope: &[u8], steward: &str, pubkey: &[u8; 32]) -> Result<Effic
|
|||
tokens_in: u("tokens_in")?,
|
||||
tokens_out: u("tokens_out")?,
|
||||
energy_uj: u("energy_uj")?,
|
||||
provenance: s("provenance")?,
|
||||
provenance,
|
||||
energy_coverage,
|
||||
wall_ns: u("wall_ns")?,
|
||||
uj_per_accepted_token: v.get("uj_per_accepted_token").and_then(Value::as_u64),
|
||||
uj_per_accepted_token,
|
||||
value_uusd: v.get("value_uusd").and_then(Value::as_u64),
|
||||
energy_attestation: v.get("energy_attestation").and_then(Value::as_str).map(str::to_string),
|
||||
seq: u("seq")?,
|
||||
|
|
@ -375,6 +447,13 @@ pub struct Totals {
|
|||
pub wasted_uj: u64,
|
||||
/// Value delivered by completed units, micro-USD.
|
||||
pub value_uusd: u64,
|
||||
/// The weakest provenance among the receipts summed, `None` for none: a
|
||||
/// total is never more measured than its least measured part (jcp SPEC
|
||||
/// §5.3).
|
||||
pub provenance_floor: Option<Provenance>,
|
||||
/// `Partial` if any receipt summed was. The energy totals then do not
|
||||
/// cover the work, and no per-unit energy figure is given.
|
||||
pub coverage: Coverage,
|
||||
}
|
||||
|
||||
impl Totals {
|
||||
|
|
@ -393,8 +472,13 @@ impl Totals {
|
|||
}
|
||||
|
||||
/// Total microjoules per completed unit, including waste. `None` when nothing
|
||||
/// completed — see [`Self::tokens_per_completed_unit`].
|
||||
/// completed — see [`Self::tokens_per_completed_unit`] — and when any
|
||||
/// receipt summed was `Partial`: the energy totals then do not cover the
|
||||
/// work, so their ratio is not what a result cost (jcp SPEC §5.2).
|
||||
pub fn uj_per_completed_unit(&self) -> Option<u64> {
|
||||
if !self.coverage.is_whole() {
|
||||
return None;
|
||||
}
|
||||
self.completed_uj
|
||||
.saturating_add(self.wasted_uj)
|
||||
.checked_div(self.completed_units)
|
||||
|
|
@ -407,9 +491,13 @@ impl Totals {
|
|||
/// so the fallback is this figure's *worst* value, not its optimum. The same
|
||||
/// holds for [`Efficiency::tokens_per_sec`]. A zero that understates is not the
|
||||
/// defect those `Option`s fix.
|
||||
///
|
||||
/// `0.0` too when any receipt summed was `Partial`, for the same reason:
|
||||
/// value over an energy total that does not cover the work is not value
|
||||
/// per joule of it, and this figure's fallback is its worst value.
|
||||
pub fn value_per_joule(&self) -> f64 {
|
||||
let total_uj = self.completed_uj.saturating_add(self.wasted_uj);
|
||||
if total_uj == 0 {
|
||||
if total_uj == 0 || !self.coverage.is_whole() {
|
||||
0.0
|
||||
} else {
|
||||
// value (µUSD) per joule = value_uusd / (total_uj / 1e6)
|
||||
|
|
@ -430,10 +518,19 @@ impl Totals {
|
|||
}
|
||||
}
|
||||
|
||||
/// Aggregate verified receipts into the honest efficiency ledger.
|
||||
/// Aggregate verified receipts into the honest efficiency ledger. The energy
|
||||
/// totals carry the floor of what they sum: the weakest provenance and, if any
|
||||
/// receipt was `Partial`, `Partial` coverage. Aggregation is not a step at
|
||||
/// which a grade improves (jcp SPEC §5.3), and a sum of partial figures is no
|
||||
/// more a task total than one of them is.
|
||||
pub fn aggregate<'a>(receipts: impl IntoIterator<Item = &'a EfficiencyReceipt>) -> Totals {
|
||||
let mut t = Totals::default();
|
||||
for r in receipts {
|
||||
// A label [`read_payload`] would refuse counts as the weakest: a
|
||||
// receipt built by hand is not given a grade it does not name.
|
||||
let p = provenance_of(&r.provenance).unwrap_or(Provenance::Estimator);
|
||||
t.provenance_floor = Some(t.provenance_floor.map_or(p, |f| trace::weakest_provenance(f, p)));
|
||||
t.coverage = trace::weakest_coverage(t.coverage, r.energy_coverage);
|
||||
if r.completed {
|
||||
t.completed_units += 1;
|
||||
t.completed_tokens = t.completed_tokens.saturating_add(r.tokens_in).saturating_add(r.tokens_out);
|
||||
|
|
@ -470,6 +567,85 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// **A GPU-only figure is not a task total.** It used to seal as
|
||||
/// `energy_uj` + `provenance: HwShunt` with no coverage, and a per-token
|
||||
/// headline over it, so it reached a verifier as the whole task's energy
|
||||
/// per token. Now it seals its coverage, and no headline.
|
||||
#[test]
|
||||
fn a_partial_figure_seals_its_coverage_and_no_task_total() {
|
||||
let sk = key();
|
||||
let mut e = measurement("gpu-only", true, 500, 0, None);
|
||||
e.energy = Energy::measured_partial(600_000);
|
||||
assert_eq!(e.uj_per_accepted_token(), None, "a partial figure gives no per-token cost of the task");
|
||||
let p = e.payload(0, &ContentId([0u8; 32]), "did:example:steward");
|
||||
assert_eq!(p["energy_coverage"], "Partial");
|
||||
assert!(p.get("uj_per_accepted_token").is_none(), "{p}");
|
||||
|
||||
let env = e.seal(0, &ContentId([0u8; 32]), &sk, "did:example:steward");
|
||||
let r = verify(&env, "did:example:steward", &sk.verifying_key().to_bytes()).unwrap();
|
||||
assert_eq!(r.energy_coverage, Coverage::Partial);
|
||||
assert_eq!((r.energy_uj, r.provenance.as_str()), (600_000, "HwShunt"), "what was measured still reads");
|
||||
assert_eq!(r.uj_per_accepted_token, None);
|
||||
|
||||
// A whole figure carries no coverage field, so its bytes are what they
|
||||
// were before the field existed, and it reads back `Whole`.
|
||||
let whole = measurement("whole", true, 500, 1_000_000, None);
|
||||
let p = whole.payload(0, &ContentId([0u8; 32]), "did:example:steward");
|
||||
assert!(p.get("energy_coverage").is_none(), "{p}");
|
||||
assert_eq!(read_payload(&p).unwrap().energy_coverage, Coverage::Whole);
|
||||
}
|
||||
|
||||
/// A reader refuses a partial figure presented as a task total, a coverage
|
||||
/// other than `"Partial"` (absent is `Whole`), and a provenance label that
|
||||
/// is not one of the three.
|
||||
#[test]
|
||||
fn a_reader_refuses_what_a_conformant_receipt_never_carries() {
|
||||
let mut e = measurement("gpu-only", true, 500, 0, None);
|
||||
e.energy = Energy::measured_partial(600_000);
|
||||
let base = e.payload(0, &ContentId([0u8; 32]), "did:example:steward");
|
||||
let refused = |edit: &dyn Fn(&mut Value), because: &str| {
|
||||
let mut p = base.clone();
|
||||
edit(&mut p);
|
||||
let err = read_payload(&p).expect_err(because).to_string();
|
||||
assert!(err.contains(because), "{err}");
|
||||
};
|
||||
refused(&|p| p["uj_per_accepted_token"] = json!(1_200), "presented as a task total");
|
||||
refused(&|p| p["energy_coverage"] = json!("Whole"), "energy_coverage");
|
||||
refused(&|p| p["energy_coverage"] = json!("partial"), "energy_coverage");
|
||||
refused(&|p| p["provenance"] = json!("Measured"), "not a provenance label");
|
||||
}
|
||||
|
||||
/// A total is never more measured, nor more complete, than what it sums
|
||||
/// (jcp SPEC §5.3): one estimated receipt makes it estimated, and one
|
||||
/// partial receipt makes it partial, with no per-unit energy figure.
|
||||
#[test]
|
||||
fn aggregation_takes_the_floor_of_provenance_and_coverage() {
|
||||
let sk = key();
|
||||
let pk = sk.verifying_key().to_bytes();
|
||||
let mut log = EfficiencyLog::new(sk, "did:example:steward");
|
||||
let mut hw = measurement("a", true, 100, 0, Some(1_000_000));
|
||||
hw.energy = Energy::measured(500_000);
|
||||
let mut gpu = measurement("b", true, 100, 0, Some(1_000_000));
|
||||
gpu.energy = Energy::measured_partial(300_000);
|
||||
let mut est = measurement("c", false, 100, 0, None);
|
||||
est.energy = Energy::estimator(200_000);
|
||||
log.append(&hw);
|
||||
let whole_only = aggregate(&verify_chain(log.receipts(), "did:example:steward", &pk).unwrap());
|
||||
assert_eq!((whole_only.coverage, whole_only.provenance_floor), (Coverage::Whole, Some(Provenance::HwShunt)));
|
||||
assert_eq!(whole_only.uj_per_completed_unit(), Some(500_000));
|
||||
|
||||
log.append(&est);
|
||||
log.append(&gpu);
|
||||
let t = aggregate(&verify_chain(log.receipts(), "did:example:steward", &pk).unwrap());
|
||||
assert_eq!(t.provenance_floor, Some(Provenance::Estimator), "one estimate makes the total estimated");
|
||||
assert_eq!(t.coverage, Coverage::Partial, "one partial figure makes the total partial");
|
||||
assert_eq!(t.uj_per_completed_unit(), None, "a partial total is no cost per result");
|
||||
assert_eq!(t.value_per_joule(), 0.0, "nor value per joule of the work");
|
||||
assert_eq!(t.completed_uj, 800_000, "what was measured is still summed");
|
||||
assert_eq!(t.tokens_per_completed_unit(), Some(750), "token figures do not depend on the energy");
|
||||
assert_eq!(aggregate(&[]).provenance_floor, None, "an empty ledger names no grade");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_four_metrics_compute_from_the_inputs() {
|
||||
let e = measurement("t", true, 500, 1_000_000, Some(2_000_000));
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ impl OutcomeTrace {
|
|||
///
|
||||
/// A composite is never more measured than its least-measured part. Averaging
|
||||
/// would let one hardware reading launder a pile of estimates.
|
||||
fn weakest_provenance(a: Provenance, b: Provenance) -> Provenance {
|
||||
pub(crate) fn weakest_provenance(a: Provenance, b: Provenance) -> Provenance {
|
||||
fn rank(p: Provenance) -> u8 {
|
||||
match p {
|
||||
Provenance::HwShunt => 2,
|
||||
|
|
@ -275,7 +275,7 @@ fn weakest_provenance(a: Provenance, b: Provenance) -> Provenance {
|
|||
|
||||
/// `Partial` if either is partial. An unaccounted share does not become
|
||||
/// accounted for by being summed with accounted ones.
|
||||
fn weakest_coverage(a: Coverage, b: Coverage) -> Coverage {
|
||||
pub(crate) fn weakest_coverage(a: Coverage, b: Coverage) -> Coverage {
|
||||
match (a, b) {
|
||||
(Coverage::Whole, Coverage::Whole) => Coverage::Whole,
|
||||
_ => Coverage::Partial,
|
||||
|
|
|
|||
85
jcp/jcp-rs/crates/jcp-efficiency/tests/efficiency_pack.rs
Normal file
85
jcp/jcp-rs/crates/jcp-efficiency/tests/efficiency_pack.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
//! Replay the published `efficiency/1` payload pack from disk.
|
||||
//!
|
||||
//! jcp SPEC §5.2-5.3 states the rules and `gen_efficiency_vectors` emits the
|
||||
//! vectors. This reads those bytes back, the same bytes a third party would,
|
||||
//! and checks the reference reader reproduces every recorded verdict. The
|
||||
//! refusals are the half that matters: a reader that accepts a partial figure
|
||||
//! presented as a task total has failed in the direction that flatters.
|
||||
|
||||
use jcp_efficiency::{aggregate, read_payload};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
fn pack() -> Value {
|
||||
let p = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../conformance/v1/efficiency.json");
|
||||
// Assert, not skip: a `return` here would pass green in the one case this
|
||||
// exists to catch, the pack going missing.
|
||||
assert!(p.exists(), "conformance pack missing at {} — it is what this test replays", p.display());
|
||||
let bytes = std::fs::read(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()));
|
||||
serde_json::from_slice(&bytes).expect("pack parses")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_pack_declares_its_profile_and_spec() {
|
||||
let v = pack();
|
||||
assert_eq!(v["profile"], "efficiency/1");
|
||||
assert!(v["spec"].as_str().unwrap().contains("5.2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_positive_payload_reads_as_recorded() {
|
||||
let v = pack();
|
||||
let cases = v["payload"]["positive"].as_array().expect("positives");
|
||||
assert!(cases.len() >= 2, "expected the published positive cases");
|
||||
for c in cases {
|
||||
let note = c["note"].as_str().unwrap_or("?");
|
||||
let r = read_payload(&c["input"]).unwrap_or_else(|e| panic!("{note}: must read: {e}"));
|
||||
let got = json!({
|
||||
"energy_uj": r.energy_uj,
|
||||
"provenance": r.provenance,
|
||||
"energy_coverage": r.energy_coverage.label(),
|
||||
"uj_per_accepted_token": r.uj_per_accepted_token,
|
||||
});
|
||||
assert_eq!(got, c["expected"], "{note}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_negative_payload_is_refused_for_its_recorded_reason() {
|
||||
let v = pack();
|
||||
let cases = v["payload"]["negative"].as_array().expect("negatives");
|
||||
assert!(cases.len() >= 3, "expected the published refusals");
|
||||
for c in cases {
|
||||
let note = c["note"].as_str().unwrap_or("?");
|
||||
let because = c["expected"]["refused"].as_str().expect("a refusal reason");
|
||||
match read_payload(&c["input"]) {
|
||||
Ok(r) => panic!("{note}: accepted, as {r:?}"),
|
||||
Err(e) => assert!(e.to_string().contains(because), "{note}: refused for '{e}', not '{because}'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_aggregate_sums_to_its_recorded_totals() {
|
||||
let v = pack();
|
||||
let cases = v["aggregate"]["cases"].as_array().expect("aggregate cases");
|
||||
assert!(cases.len() >= 2);
|
||||
for c in cases {
|
||||
let note = c["note"].as_str().unwrap_or("?");
|
||||
let rs: Vec<_> = c["input"]
|
||||
.as_array()
|
||||
.expect("payloads")
|
||||
.iter()
|
||||
.map(|p| read_payload(p).unwrap_or_else(|e| panic!("{note}: {e}")))
|
||||
.collect();
|
||||
let t = aggregate(&rs);
|
||||
let got = json!({
|
||||
"completed_uj": t.completed_uj,
|
||||
"wasted_uj": t.wasted_uj,
|
||||
"coverage": t.coverage.label(),
|
||||
"provenance_floor": t.provenance_floor.map(|p| p.jcr1_label()),
|
||||
"uj_per_completed_unit": t.uj_per_completed_unit(),
|
||||
"tokens_per_completed_unit": t.tokens_per_completed_unit(),
|
||||
});
|
||||
assert_eq!(got, c["expected"], "{note}");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue