use anyhow::Result;
use metrale_config::ModelConfig;
use crate::cli;
#[allow(clippy::too_many_arguments)]
pub(crate) fn post_load_memory_audit(
args: &cli::ServeArgs,
config: &ModelConfig,
gpu: &dyn metrale_gpu_runtime::gpu::GpuBackend,
weight_bytes: usize,
free_mem: usize,
inference_reserve: usize,
total_reserve: usize,
gdn_two_phase_bytes: usize,
max_batch_tokens_pre: usize,
) -> Result<()> {
let estimated_free = free_mem.saturating_sub(weight_bytes);
let actual_free = gpu.free_memory().unwrap_or(estimated_free);
let available_free = if actual_free > 0 {
actual_free
} else {
estimated_free
};
if available_free < total_reserve {
let avail_gb = available_free as f64 / (1024.0 * 1024.0 * 1024.0);
let need_gb = total_reserve as f64 / (1024.0 * 1024.0 * 1024.0);
let hint = if args.max_batch_size.ceiling() > 1 {
format!(
" Reduce --max-batch-size (currently {}) or --max-seq-len (currently {}).",
args.max_batch_size.ceiling(),
args.max_seq_len
)
} else {
format!(
" Reduce --max-seq-len (currently {}) or use a smaller model.",
args.max_seq_len
)
};
anyhow::bail!(
"Insufficient GPU memory for inference buffers. \
After loading {:.2} GB of weights, only {:.2} GB remains \
but {:.2} GB is needed for SSM state pool ({} slots × {} layers) + scratch buffers.{}",
weight_bytes as f64 / (1024.0 * 1024.0 * 1024.0),
avail_gb,
need_gb,
args.max_batch_size.ceiling(),
config.num_ssm_layers(),
hint,
);
}
if gdn_two_phase_bytes > 0 {
tracing::info!(
"GDN chunked prefill reserve: {} MB (chunk_size={}, max_seq_len={})",
gdn_two_phase_bytes / (1024 * 1024),
max_batch_tokens_pre,
args.max_seq_len,
);
}
tracing::info!(
"Weights: {:.2} GB, estimated free: {:.1} GB, actual free: {:.1} GB (reserve: {} MB)",
weight_bytes as f64 / (1024.0 * 1024.0 * 1024.0),
estimated_free as f64 / (1024.0 * 1024.0 * 1024.0),
actual_free as f64 / (1024.0 * 1024.0 * 1024.0),
inference_reserve / (1024 * 1024),
);
Ok(())
}