use metrale_config::ModelConfig;
use crate::cli;
use super::decode_ring;
pub(super) struct Refusal {
pub(super) total_reserve: usize,
pub(super) free_mem: usize,
pub(super) seq_len_independent: usize,
pub(super) ring_requested: usize,
pub(super) ring_slots: usize,
pub(super) per_seq_blob: usize,
pub(super) ring_pinned: bool,
}
pub(super) fn reserve_refusal(
args: &cli::ServeArgs,
config: &ModelConfig,
r: Refusal,
) -> anyhow::Error {
let need_gb = r.total_reserve as f64 / (1024.0 * 1024.0 * 1024.0);
let free_gb = r.free_mem as f64 / (1024.0 * 1024.0 * 1024.0);
let budget_for_seq_term = r.free_mem.saturating_sub(r.seq_len_independent) / 2;
let per_tok_bytes = {
let key_dim = config.linear_num_key_heads * config.linear_key_head_dim;
let value_dim = config.linear_num_value_heads * config.linear_value_head_dim;
let nv = config.linear_num_value_heads;
let conv_dim = key_dim * 2 + value_dim;
if conv_dim > 0 && config.num_ssm_layers() > 0 {
(conv_dim * 2) + (nv * 2 * 4) + (value_dim * 2) + (value_dim * 2)
} else {
0
}
};
let suggested = budget_for_seq_term
.checked_div(per_tok_bytes)
.map(|q| q.max(2048))
.unwrap_or(0);
let hint = if suggested > 0 && suggested < args.max_seq_len {
format!(
" Try --max-seq-len {} (or lower --max-batch-size / --num-drafts).",
suggested
)
} else if args.max_batch_size.ceiling() > 1 {
" Reduce --max-batch-size.".to_string()
} else {
" Use a smaller model or a GPU with more memory.".to_string()
};
anyhow::anyhow!(
"Preflight failed: inference buffers alone need {:.2} GB but only {:.2} GB is free on the GPU \
(before weights load). SSM pool + GDN chunked prefill scales with --max-seq-len={} × --max-batch-size={}.{}{}",
need_gb,
free_gb,
args.max_seq_len,
args.max_batch_size.ceiling(),
hint,
ring_note(args, &r),
)
}
fn ring_note(args: &cli::ServeArgs, r: &Refusal) -> String {
if r.ring_requested == 0 {
return String::new();
}
format!(
" {} — {}.",
decode_ring::formula(r.ring_slots, args.max_batch_size.ceiling(), r.per_seq_blob),
if r.ring_pinned {
"an explicit --ssm-decode-ring-slots pins the depth, so it was not shrunk"
} else {
"even 0 ring slots does not fit, so shrinking it cannot rescue this boot"
},
)
}
#[cfg(test)]
#[path = "refusal_tests.rs"]
mod tests;