logicaffeine_compile/spatial/
handoff.rs1use std::collections::BTreeMap;
17
18use crate::model::program::{written, Cut};
19use crate::model::spec::ModelSpec;
20use crate::tensor_graph::front::carried_state;
21use crate::tensor_graph::graph::{Storage, TensorGraph};
22
23#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct Carried {
26 pub name: String,
27 pub layer: usize,
28 pub rows: usize,
29 pub cols: usize,
30}
31
32#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct Contract {
35 pub ctx: usize,
36 pub hidden: usize,
38 pub carried: Vec<Carried>,
39}
40
41#[derive(Clone, Debug, PartialEq)]
43pub struct Handoff {
44 pub position: usize,
45 pub state: BTreeMap<String, Vec<f64>>,
46}
47
48#[derive(Clone, Debug, PartialEq, Eq)]
50pub enum HandoffError {
51 Program { layer: usize, why: String },
53 Missing(String),
55 Unknown(String),
57 Length { name: String, want: usize, got: usize },
59 Position { position: usize, ctx: usize },
62}
63
64impl std::fmt::Display for HandoffError {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 match self {
67 HandoffError::Program { layer, why } => write!(f, "layer {layer}'s decode program: {why}"),
68 HandoffError::Missing(n) => write!(f, "the hand-off leaves out the carried `{n}`"),
69 HandoffError::Unknown(n) => write!(f, "the hand-off carries `{n}`, which no decode step reads"),
70 HandoffError::Length { name, want, got } => write!(f, "`{name}` is {want} values in the contract and {got} in the hand-off"),
71 HandoffError::Position { position, ctx } => {
72 write!(f, "after {position} tokens the next decode writes row {} of a cache of {ctx}", position + 1)
73 }
74 }
75 }
76}
77
78pub fn contract(spec: &ModelSpec, ctx: usize) -> Result<Contract, HandoffError> {
81 let mut carried = Vec::new();
82 for layer in 0..spec.layers.len() {
83 let ((src, _, _), _) = written(spec, Cut::DecodeLayer { layer, ctx });
84 let lifted = TensorGraph::lift_source(&src, &|_| Storage::Values).map_err(|e| HandoffError::Program { layer, why: format!("{e:?}") })?;
85 let rp = &lifted.program;
86 for name in carried_state(rp) {
87 let (rows, cols) = rp.shapes.get(&name).copied().ok_or_else(|| HandoffError::Program {
88 layer,
89 why: format!("carries `{name}`, which it does not declare"),
90 })?;
91 carried.push(Carried { name, layer, rows, cols });
92 }
93 }
94 Ok(Contract { ctx, hidden: spec.d, carried })
95}
96
97impl Contract {
98 pub fn of_layer(&self, layer: usize) -> impl Iterator<Item = &Carried> {
100 self.carried.iter().filter(move |c| c.layer == layer)
101 }
102
103 pub fn check(&self, h: &Handoff) -> Result<(), HandoffError> {
105 if let Some(name) = h.state.keys().find(|n| !self.carried.iter().any(|c| &c.name == *n)) {
106 return Err(HandoffError::Unknown(name.clone()));
107 }
108 for c in &self.carried {
109 let values = h.state.get(&c.name).ok_or_else(|| HandoffError::Missing(c.name.clone()))?;
110 if values.len() != c.rows * c.cols {
111 return Err(HandoffError::Length { name: c.name.clone(), want: c.rows * c.cols, got: values.len() });
112 }
113 }
114 if h.position >= self.ctx {
115 return Err(HandoffError::Position { position: h.position, ctx: self.ctx });
116 }
117 Ok(())
118 }
119
120 pub fn take(&self, position: usize, tensors: &BTreeMap<String, Vec<f64>>) -> Result<Handoff, HandoffError> {
123 let state = self
124 .carried
125 .iter()
126 .map(|c| tensors.get(&c.name).map(|v| (c.name.clone(), v.clone())).ok_or_else(|| HandoffError::Missing(c.name.clone())))
127 .collect::<Result<BTreeMap<_, _>, _>>()?;
128 let h = Handoff { position, state };
129 self.check(&h)?;
130 Ok(h)
131 }
132
133 pub fn to_json(&self) -> serde_json::Value {
135 serde_json::json!({
136 "ctx": self.ctx,
137 "hidden": self.hidden,
138 "carried": self.carried.iter().map(|c| serde_json::json!({ "name": c.name, "layer": c.layer, "rows": c.rows, "cols": c.cols })).collect::<Vec<_>>(),
139 })
140 }
141}