1use std::collections::BTreeMap;
23
24use anyhow::{Context, Result, bail, ensure};
25use metrale_circuit::state::{
26 Holding, StateDecl, StateDtype, StateFormat, StateInputs, StateKind, StatePlan, VerifyInputs,
27 VerifySteps,
28};
29use metrale_config::ModelConfig;
30
31use super::{SsmRollbackMode, mtp_state_slots, verify_slot_h_intermediates};
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct PoolShape {
36 pub max_slots: usize,
38 pub spec: bool,
40 pub num_intermediates: usize,
42 pub num_drafts: usize,
44 pub uniform_h: bool,
47 pub rollback: SsmRollbackMode,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct VerifyCounts {
54 pub h_steps: Vec<usize>,
56 pub conv_steps: usize,
58}
59
60impl VerifyCounts {
61 pub fn slots(&self) -> usize {
63 self.h_steps.len()
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct PoolCounts {
70 pub slots: usize,
72 pub verify: Option<VerifyCounts>,
74}
75
76pub fn pool_counts(shape: &PoolShape) -> PoolCounts {
82 pool_counts_tiered(shape, super::copy_tier())
83}
84
85pub fn pool_counts_tiered(shape: &PoolShape, tier: Option<super::CopyTier>) -> PoolCounts {
89 let widened = PoolShape {
90 num_intermediates: super::tier_rows(shape.num_intermediates, tier),
91 ..*shape
92 };
93 pool_counts_with(&widened, mtp_state_slots(shape.max_slots), |s| {
94 super::tier_h(
95 verify_slot_h_intermediates(s, shape.num_drafts, false),
96 s,
97 tier,
98 )
99 })
100}
101
102pub fn pool_counts_with(
107 shape: &PoolShape,
108 mtp_slots: usize,
109 slot_h: impl Fn(usize) -> usize,
110) -> PoolCounts {
111 let full = shape.num_intermediates.saturating_sub(1);
112 let verify = shape.spec.then(|| {
113 let replay = shape.rollback == SsmRollbackMode::Replay;
114 VerifyCounts {
115 h_steps: (0..=mtp_slots)
116 .map(|s| match (replay, s == mtp_slots || shape.uniform_h) {
117 (true, _) => 0,
118 (false, true) => full,
119 (false, false) => slot_h(s).min(full),
120 })
121 .collect(),
122 conv_steps: if replay { 0 } else { shape.num_intermediates },
123 }
124 });
125 PoolCounts {
126 slots: shape.max_slots + 1,
127 verify,
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum UnitSource {
134 Circuit,
136 Transitional,
139}
140
141pub fn state_dims(c: &ModelConfig) -> BTreeMap<String, u64> {
144 [
145 ("lin_k_heads", c.linear_num_key_heads),
146 ("lin_k_dim", c.linear_key_head_dim),
147 ("lin_v_heads", c.linear_num_value_heads),
148 ("lin_v_dim", c.linear_value_head_dim),
149 ("mamba_heads", c.mamba_num_heads),
150 ("mamba_head_dim", c.mamba_head_dim),
151 ("ssm_state", c.ssm_state_size),
152 ("ssm_groups", c.n_groups),
153 ("conv_kernel", c.linear_conv_kernel_dim),
154 ]
155 .into_iter()
156 .map(|(k, v)| (k.to_string(), v as u64))
157 .collect()
158}
159
160pub fn recurrent_units(c: &ModelConfig) -> Result<(Vec<StateDecl>, UnitSource)> {
162 if let Some(decls) = metrale_circuit::recurrent_states(&c.model_type, &state_dims(c))
163 .with_context(|| format!("the circuit of `{}` declares no usable state", c.model_type))?
164 {
165 ensure!(
166 !decls.is_empty() || c.num_ssm_layers() == 0,
167 "`{}` has {} recurrent layers and its circuit declares no recurrent state",
168 c.model_type,
169 c.num_ssm_layers()
170 );
171 return Ok((decls, UnitSource::Circuit));
172 }
173 let decl = |local: &str, bytes: usize, format, verify| StateDecl {
174 id: format!("transitional.{local}"),
175 local: local.to_string(),
176 block: "transitional".to_string(),
177 layer: None,
178 section: metrale_circuit::Section::Main,
179 kind: StateKind::Recurrent,
180 format,
181 elements: (bytes / 4) as u64,
182 verify: Some(verify),
183 lifetime: metrale_circuit::state::Lifetime::Sequence,
184 };
185 Ok((
186 vec![
187 decl(
188 "h",
189 c.ssm_h_state_bytes(),
190 StateFormat::Keyed("ssm_h_storage".into()),
191 VerifySteps::H,
192 ),
193 decl(
194 "conv",
195 c.ssm_conv_state_bytes(),
196 StateFormat::Fixed(StateDtype::F32),
197 VerifySteps::Conv,
198 ),
199 ],
200 UnitSource::Transitional,
201 ))
202}
203
204#[derive(Debug, Clone)]
206pub struct PoolPlan {
207 pub source: UnitSource,
209 pub layers: usize,
211 pub h_f32_unit: usize,
213 pub h_stored_unit: usize,
215 pub conv_unit: usize,
217 pub counts: PoolCounts,
219 layer: StatePlan,
220 h_id: String,
221 conv_id: String,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum PoolState {
227 H,
228 Conv,
229}
230
231impl PoolPlan {
232 pub fn new(config: &ModelConfig, counts: &PoolCounts, h_f16_pool: bool) -> Result<Self> {
234 let (decls, source) = recurrent_units(config)?;
235 let find = |v: VerifySteps| {
236 decls
237 .iter()
238 .find(|d| d.verify == Some(v))
239 .with_context(|| format!("no recurrent state keeps {v:?} intermediates"))
240 };
241 let layers = config.num_ssm_layers();
242 if layers == 0 {
243 return Ok(Self::empty(source, counts));
244 }
245 let (h, conv) = (find(VerifySteps::H)?, find(VerifySteps::Conv)?);
246 let storage = if h_f16_pool {
247 StateDtype::F16
248 } else {
249 StateDtype::F32
250 };
251 let inputs = StateInputs {
252 formats: BTreeMap::from([("ssm_h_storage".to_string(), storage)]),
253 slots: counts.slots as u64,
254 verify: counts.verify.as_ref().map(|v| VerifyInputs {
255 h_steps: v.h_steps.iter().map(|&n| n as u64).collect(),
256 conv_steps: v.conv_steps as u64,
257 }),
258 kv: None,
259 draft_kv: None,
260 };
261 let layer = StatePlan::new(&decls, &inputs)?;
262 let unit = |d: &StateDecl, dtype: StateDtype| (d.elements * dtype.size()) as usize;
263 let conv_dtype = match conv.format {
264 StateFormat::Fixed(d) => d,
265 StateFormat::Keyed(_) => bail!("the conv state's format is not fixed"),
266 };
267 Ok(Self {
268 source,
269 layers,
270 h_f32_unit: unit(h, StateDtype::F32),
271 h_stored_unit: unit(h, storage),
272 conv_unit: unit(conv, conv_dtype),
273 counts: counts.clone(),
274 layer,
275 h_id: h.id.clone(),
276 conv_id: conv.id.clone(),
277 })
278 }
279
280 fn empty(source: UnitSource, counts: &PoolCounts) -> Self {
281 Self {
282 source,
283 layers: 0,
284 h_f32_unit: 0,
285 h_stored_unit: 0,
286 conv_unit: 0,
287 counts: counts.clone(),
288 layer: StatePlan { terms: Vec::new() },
289 h_id: String::new(),
290 conv_id: String::new(),
291 }
292 }
293
294 pub fn layer_bytes(&self, state: PoolState, holding: Holding) -> usize {
296 let id = match state {
297 PoolState::H => &self.h_id,
298 PoolState::Conv => &self.conv_id,
299 };
300 self.layer
301 .bytes_where(|t| &t.state == id && t.holding == holding) as usize
302 }
303
304 pub fn total(&self) -> usize {
306 self.layers * self.layer.bytes() as usize
307 }
308}