metrale_model_layers/circuit_exec/
spec_key.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-10-04: The plan digests that key a measured speculative-cost table
4//! (`--spec-cost-model measured`): per measured mode (`verify`, `verify_batch`, `draft`), one
5//! digest over every plan the recipe's circuit instance declares for that mode, each
6//! `verify`/`draft` plan followed by its runtime-route arms, fused offline as `met circuit show` fuses them. The golden plans
7//! specify the kernel routing of the legacy forward too, so the key describes the kernels
8//! either forward runs; it needs no executor. `met benchmark spec-cost-table` writes it, and the
9//! serve recomputes it at boot and refuses a table whose key differs.
10//!
11//! Owner: model-layers (circuit_exec).
12//! Invariants:
13//! - A recipe without a circuit instance has no key: refused, never approximated.
14//! - A measured mode for which the instance declares no plan is refused: its cost would be
15//!   keyed by nothing that changes with its kernels.
16
17use std::collections::BTreeMap;
18
19use anyhow::{Context, Result, bail};
20use metrale_circuit::digest::plans_digest;
21use metrale_circuit::fuser::{fuse, fuse_table};
22use metrale_circuit::runtime::route_arms;
23use metrale_circuit::{AvailableKernels, Instance, Loaded, Mode};
24
25use super::sources::{instance, sources};
26
27/// 2026-10-04: The modes a speculative step runs, in key order.
28pub const KEY_MODES: [Mode; 3] = [Mode::Verify, Mode::VerifyBatch, Mode::Draft];
29
30/// 2026-10-04: Mode name → digest over that mode's declared plans, for `recipe`.
31pub fn spec_cost_plan_digests(recipe: &str) -> Result<BTreeMap<String, String>> {
32    let inst = instance(recipe).context("the speculative cost model needs a circuit instance")?;
33    let loaded = metrale_circuit::load(&inst, sources(&inst)?)?;
34    let mut key = BTreeMap::new();
35    for mode in KEY_MODES {
36        let digests = mode_plan_digests(&inst, &loaded, mode)?;
37        if digests.is_empty() {
38            bail!(
39                "recipe `{recipe}`: its circuit instance declares no {} plan, so the speculative \
40                 cost of that mode would be unkeyed (kernels/circuits/INSTANCES.toml)",
41                mode.name()
42            );
43        }
44        key.insert(
45            mode.name().to_string(),
46            plans_digest(digests.iter().map(String::as_str)),
47        );
48    }
49    Ok(key)
50}
51
52/// 2026-10-04: The digests of every plan `inst` declares for `mode`, in declared order, each
53/// non-batched plan followed by its runtime-route arms: the `digest:` lines of the golden files,
54/// in file order.
55fn mode_plan_digests(inst: &Instance, loaded: &Loaded, mode: Mode) -> Result<Vec<String>> {
56    // 2026-10-04: Offline, as `met circuit show`: every kernel a rule names counts as built.
57    let avail = AvailableKernels::all_named_by(&loaded.rules);
58    let (circuit, rules, policy) = (&loaded.circuit, loaded.rules.as_slice(), &inst.policy);
59    let mut out = Vec::new();
60    if mode == Mode::VerifyBatch {
61        // 2026-10-04: A batched-verify plan has no runtime-route arms (`render_table_plan`).
62        for table in &inst.verify_batch {
63            out.push(fuse_table(circuit, rules, &avail, policy, table)?.digest);
64        }
65        return Ok(out);
66    }
67    for &rows in inst.plans.get(&mode).into_iter().flatten() {
68        let plan = fuse(circuit, rules, &avail, policy, mode, rows)?;
69        let arms = route_arms(circuit, (rules, &loaded.runtime), &avail, policy, &plan)?;
70        out.push(plan.digest);
71        out.extend(arms.into_iter().map(|(_, arm)| arm.digest));
72    }
73    Ok(out)
74}
75
76#[cfg(test)]
77#[path = "spec_key_tests.rs"]
78mod tests;