use anyhow::Result;
use metrale_config::ModelConfig;
use metrale_model_layers::ssm_reserve::{self, PoolShape, SsmRollbackMode};
use super::runtime_headroom::{RuntimeHeadroom, carry_stash_bytes};
#[derive(Debug, Clone)]
pub(crate) struct ReservePlan {
pub(super) config: ModelConfig,
pub(super) spec: bool,
pub(super) num_drafts: usize,
pub(super) uniform_h: bool,
pub(super) rollback: SsmRollbackMode,
pub(super) h_f16_pool: bool,
pub(super) marconi_bytes: usize,
pub(super) gdn_two_phase_bytes: usize,
pub(super) budget_bytes: usize,
pub(super) per_sequence_bytes: usize,
pub(super) ring_slots: usize,
pub(super) per_seq_blob: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ReserveTerms {
pub ssm_pool: usize,
pub ssm_h_stage: usize,
pub replay_ring: usize,
pub marconi: usize,
pub gdn_two_phase: usize,
pub runtime: RuntimeHeadroom,
pub per_sequence: usize,
pub decode_ring: usize,
}
impl ReserveTerms {
pub(crate) fn fixed(&self) -> usize {
self.ssm_pool
+ self.ssm_h_stage
+ self.replay_ring
+ self.marconi
+ self.gdn_two_phase
+ self.runtime.total()
+ self.per_sequence
}
pub(crate) fn total(&self) -> usize {
self.fixed() + self.decode_ring
}
}
impl ReservePlan {
pub(crate) fn terms(&self, slots: usize) -> Result<ReserveTerms> {
let counts = ssm_reserve::pool_counts(&PoolShape {
max_slots: slots,
spec: self.spec,
num_intermediates: self.num_drafts + 1,
num_drafts: self.num_drafts,
uniform_h: self.uniform_h,
rollback: self.rollback,
});
let pool = ssm_reserve::PoolPlan::new(&self.config, &counts, self.h_f16_pool)?;
let verify_slots = counts.verify.as_ref().map(|v| v.slots());
let replay_ring = match verify_slots {
Some(v) if self.rollback == SsmRollbackMode::Replay => {
ssm_reserve::ssm_replay_ring_bytes(
pool.layers,
ssm_reserve::ssm_replay_row_bytes_for(&self.config),
ssm_reserve::tier_rows(self.num_drafts + 1, ssm_reserve::copy_tier()),
v,
)
}
_ => 0,
};
Ok(ReserveTerms {
ssm_pool: pool.total(),
ssm_h_stage: ssm_reserve::ssm_h_prefill_stage_bytes(
counts.slots,
pool.h_f32_unit,
self.h_f16_pool,
),
replay_ring,
marconi: self.marconi_bytes,
gdn_two_phase: self.gdn_two_phase_bytes,
runtime: RuntimeHeadroom::new(
self.budget_bytes,
carry_stash_bytes(&self.config, verify_slots, self.h_f16_pool),
),
per_sequence: self.per_sequence_bytes * slots.max(1),
decode_ring: self.ring_slots * slots * self.per_seq_blob,
})
}
pub(crate) fn inference_reserve(&self, slots: usize) -> Result<usize> {
Ok(self.terms(slots)?.total())
}
}
#[cfg(test)]
#[path = "reserve_plan_tests.rs"]
mod tests;