use std::io::{IsTerminal, Write};
use anyhow::{Context, Result, bail};
use metrale_circuit::display::{DisplayOpts, Expand, Glyphs};
use metrale_circuit::{AvailableKernels, Instance, Mode};
use super::{CircuitAction, CircuitArgs, CircuitMode, CircuitPlanArgs, circuit_paint};
pub(crate) use metrale_model_layers::circuit_exec::sources::{instance, shape_drift, sources};
fn mode_of(m: CircuitMode) -> Mode {
match m {
CircuitMode::Decode => Mode::Decode,
CircuitMode::MultiSeq => Mode::MultiSeq,
CircuitMode::Verify => Mode::Verify,
CircuitMode::Draft => Mode::Draft,
}
}
pub(crate) fn rows_of(inst: &Instance, args: &CircuitPlanArgs) -> Result<u64> {
let mode = mode_of(args.mode);
match (args.rows, mode) {
(Some(0), _) => bail!("--rows must be at least 1"),
(Some(r), _) => Ok(r),
(None, Mode::Decode | Mode::Draft) => Ok(1),
(None, _) => {
let listed = inst.plans.get(&mode).cloned().unwrap_or_default();
bail!(
"--mode {} needs --rows; the checked-in plans cover {listed:?}",
mode.name()
)
}
}
}
fn check_shape(inst: &Instance) -> Result<()> {
let Ok(dir) = crate::model_resolver::resolve_model_dir(&inst.checkpoint, None) else {
eprintln!(
"note: {} is not in the local cache; the shape is INSTANCES.toml's, unchecked",
inst.checkpoint
);
return Ok(());
};
let text = std::fs::read_to_string(dir.join("config.json"))
.with_context(|| format!("reading {}", dir.join("config.json").display()))?;
if let metrale_circuit::PrecisionSpec::Policy {
checkpoint_plan, ..
} = &inst.precision
{
let cached: serde_json::Value = serde_json::from_str(&text)?;
let fixture = metrale_model_layers::circuit_exec::sources::lookup(
&metrale_model_layers::circuit_exec::sources::CHECKPOINTS,
checkpoint_plan,
"checkpoint plan",
)?;
let table: toml::Table = toml::from_str(fixture)?;
let stored: serde_json::Value = serde_json::from_str(
table
.get("quantization_config")
.and_then(|v| v.as_str())
.context("checkpoint plan has no quantization_config")?,
)?;
if cached.get("quantization_config") != Some(&stored) {
bail!(
"kernels/circuits/checkpoints/{checkpoint_plan}.toml's quantization_config differs \
from {}'s config.json",
inst.checkpoint
);
}
}
let drift = shape_drift(&inst.shape, &metrale_circuit::map_checkpoint(&text)?.shape);
if !drift.is_empty() {
bail!(
"kernels/circuits/INSTANCES.toml disagrees with {}'s config.json:\n {}",
inst.checkpoint,
drift.join("\n ")
);
}
Ok(())
}
pub(crate) fn dispatch(args: CircuitArgs) -> Result<()> {
let plan_args = match &args.action {
CircuitAction::Show(p) => p.clone(),
CircuitAction::Display(d) => d.plan.clone(),
CircuitAction::Diff(_) => {
let CircuitAction::Diff(d) = args.action else {
unreachable!("matched above")
};
return tokio::task::block_in_place(|| super::circuit_diff::run_diff(*d));
}
CircuitAction::Venn(_) => {
let CircuitAction::Venn(v) = args.action else {
unreachable!("matched above")
};
return super::circuit_venn::run(*v);
}
CircuitAction::Plan(_) => {
let CircuitAction::Plan(p) = args.action else {
unreachable!("matched above")
};
return super::circuit_hw::run(*p);
}
CircuitAction::Memory(_) => {
let CircuitAction::Memory(m) = args.action else {
unreachable!("matched above")
};
return super::circuit_memory::run(*m);
}
CircuitAction::Precision(_) => {
let CircuitAction::Precision(p) = args.action else {
unreachable!("matched above")
};
return super::circuit_precision::run(*p);
}
};
let inst = instance(&plan_args.recipe)?;
let rows = rows_of(&inst, &plan_args)?;
let mode = mode_of(plan_args.mode);
check_shape(&inst)?;
let loaded = metrale_circuit::load(&inst, sources(&inst)?)?;
let avail = AvailableKernels::all_named_by(&loaded.rules);
eprintln!("rules: FUSIONS.toml sha256 {}", loaded.rules_digest);
let text = match args.action {
CircuitAction::Show(_) => {
let families = families_for(&inst)?;
metrale_circuit::render_plan(&inst, &loaded, &avail, mode, rows, &families)?
}
CircuitAction::Diff(_)
| CircuitAction::Venn(_)
| CircuitAction::Plan(_)
| CircuitAction::Memory(_)
| CircuitAction::Precision(_) => {
unreachable!("returned above")
}
CircuitAction::Display(d) => {
let tty = std::io::stdout().is_terminal();
let width = if tty {
crossterm::terminal::size().map_or(100, |(w, _)| w as usize)
} else {
100
};
let expand = match (d.layer, d.all_layers) {
(Some(n), _) => Expand::Layer(n),
(None, true) => Expand::AllLayers,
(None, false) => Expand::Summary,
};
let glyphs = if d.ascii {
Glyphs::Ascii
} else {
Glyphs::Unicode
};
let opts = DisplayOpts {
width,
glyphs,
expand,
};
let doc = match &d.hardware {
Some(hw) => {
let (tree, reg, _) = super::circuit_hw::tree_here()?;
let model = metrale_circuit::hardware::model::model_of(
&tree,
&inst,
format!("recipe {}", inst.recipe),
metrale_circuit::hardware::PrecisionChoice::Recipe,
)?;
let run = metrale_circuit::venn::Run { mode, rows };
let one = metrale_circuit::hardware::plan_one(®, hw, &tree, &model, run)?;
metrale_circuit::hardware::display_on(&model, &one, &opts)?
}
None => metrale_circuit::display_plan(
&inst,
&loaded,
&avail,
(mode, rows),
&opts,
&families_for(&inst)?,
)?,
};
let depth = circuit_paint::resolve_depth(
d.color,
tty,
std::env::var("NO_COLOR").ok().as_deref(),
std::env::var("COLORTERM").ok().as_deref(),
);
circuit_paint::paint(&doc, depth)
}
};
match std::io::stdout().lock().write_all(text.as_bytes()) {
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => Ok(()),
other => Ok(other?),
}
}
const FAMILIES: [(&str, &str); 1] = [(
"gb10",
include_str!(concat!(
env!("METRALE_KERNEL_TREE_ROOT"),
"/kernels/gb10/common/KERNEL_FAMILIES.toml"
)),
)];
pub(crate) fn families_for(inst: &Instance) -> Result<metrale_circuit::venn::Families> {
let hw = inst.target.split('/').next().unwrap_or_default();
let text = FAMILIES
.iter()
.find(|(h, _)| *h == hw)
.map(|(_, t)| *t)
.with_context(|| format!("no KERNEL_FAMILIES.toml embedded for hardware `{hw}`"))?;
Ok(metrale_circuit::venn::parse_families(text)?)
}
#[cfg(test)]
#[path = "circuit_tests.rs"]
mod circuit_tests;