use crate::config::ModelConfig;
use crate::error::{Error, Result};
use candle_core::{DType, Device};
#[derive(Debug, Clone)]
pub struct MemoryEstimate {
pub parameters_gb: f64,
pub activation_gb: f64,
pub total_gb: f64,
pub breakdown: MemoryBreakdown,
}
#[derive(Debug, Clone)]
pub struct MemoryBreakdown {
pub token_embeddings_gb: f64,
pub position_embeddings_gb: f64,
pub attention_layers_gb: f64,
pub ffn_layers_gb: f64,
pub layer_norms_gb: f64,
pub output_layer_gb: f64,
}
impl MemoryEstimate {
pub fn summary(&self) -> String {
format!(
"Memory estimate: {:.2}GB total ({:.2}GB parameters + {:.2}GB activations)",
self.total_gb, self.parameters_gb, self.activation_gb
)
}
pub fn exceeds_system_memory(&self) -> bool {
if let Ok(sys_info) = get_system_memory_gb() {
self.total_gb > sys_info * 0.8 } else {
false }
}
}
pub fn ensure_cuda_available() -> Result<Device> {
match Device::new_cuda(0) {
Ok(device) => Ok(device),
Err(_) => Err(Error::cuda_validation(
"CUDA device not available. This operation requires CUDA support.",
)),
}
}
pub fn get_best_device() -> Device {
Device::cuda_if_available(0).unwrap_or(Device::Cpu)
}
pub fn validate_dtype_for_awq(dtype: DType) -> Result<()> {
match dtype {
DType::F16 | DType::BF16 => Ok(()),
_ => Err(Error::device_validation(format!(
"AWQ quantization requires F16 or BF16 dtype, got {:?}. \
F32 is not supported due to memory and performance constraints.",
dtype
))),
}
}
pub fn validate_dtype_for_device(dtype: DType, device: &Device) -> Result<()> {
match device {
Device::Cpu => {
match dtype {
DType::U8 | DType::U32 | DType::I64 | DType::F16 | DType::F32 | DType::F64 => {
Ok(())
}
DType::BF16 => {
Ok(())
}
}
}
Device::Cuda(_) => {
match dtype {
DType::U8
| DType::U32
| DType::I64
| DType::F16
| DType::F32
| DType::F64
| DType::BF16 => Ok(()),
}
}
#[allow(unreachable_patterns)]
_ => Ok(()), }
}
pub fn estimate_memory_usage(
config: &ModelConfig,
dtype: DType,
batch_size: Option<usize>,
sequence_length: Option<usize>,
) -> MemoryEstimate {
let batch_size = batch_size.unwrap_or(1);
let sequence_length = sequence_length.unwrap_or(config.max_position_embeddings);
let bytes_per_param = match dtype {
DType::F32 => 4.0,
DType::F16 | DType::BF16 => 2.0,
DType::U8 => 1.0,
_ => 4.0, };
let token_emb_params = config.vocab_size * config.hidden_size;
let pos_emb_params = if config.tie_word_embeddings {
0
} else {
config.max_position_embeddings * config.hidden_size
};
let attention_params_per_layer = 4 * config.hidden_size * config.hidden_size + 4 * config.hidden_size;
let ffn_params_per_layer = if config.is_gated_ffn() {
3 * config.hidden_size * config.intermediate_size + 3 * config.intermediate_size
} else {
2 * config.hidden_size * config.intermediate_size + 2 * config.intermediate_size
};
let layernorm_params_per_layer = 2 * config.hidden_size;
let total_attention_params = attention_params_per_layer * config.num_hidden_layers;
let total_ffn_params = ffn_params_per_layer * config.num_hidden_layers;
let total_layernorm_params = layernorm_params_per_layer * config.num_hidden_layers;
let output_params = if config.tie_word_embeddings {
0
} else {
config.vocab_size * config.hidden_size
};
let total_params = token_emb_params
+ pos_emb_params
+ total_attention_params
+ total_ffn_params
+ total_layernorm_params
+ output_params;
let parameters_gb = (total_params as f64) * bytes_per_param / (1024.0_f64.powi(3));
let activations_per_layer = batch_size * sequence_length * config.hidden_size;
let attention_activations =
batch_size * config.num_attention_heads * sequence_length * sequence_length;
let ffn_activations = batch_size * sequence_length * config.intermediate_size;
let total_activations = (
activations_per_layer * config.num_hidden_layers * 4 + attention_activations * config.num_hidden_layers + ffn_activations * config.num_hidden_layers
) as f64;
let activation_gb = total_activations * bytes_per_param / (1024.0_f64.powi(3));
let breakdown = MemoryBreakdown {
token_embeddings_gb: (token_emb_params as f64) * bytes_per_param / (1024.0_f64.powi(3)),
position_embeddings_gb: (pos_emb_params as f64) * bytes_per_param / (1024.0_f64.powi(3)),
attention_layers_gb: (total_attention_params as f64) * bytes_per_param
/ (1024.0_f64.powi(3)),
ffn_layers_gb: (total_ffn_params as f64) * bytes_per_param / (1024.0_f64.powi(3)),
layer_norms_gb: (total_layernorm_params as f64) * bytes_per_param / (1024.0_f64.powi(3)),
output_layer_gb: (output_params as f64) * bytes_per_param / (1024.0_f64.powi(3)),
};
MemoryEstimate {
parameters_gb,
activation_gb,
total_gb: parameters_gb + activation_gb,
breakdown,
}
}
fn get_system_memory_gb() -> Result<f64> {
#[cfg(target_os = "windows")]
{
Ok(16.0) }
#[cfg(target_os = "linux")]
{
Ok(16.0) }
#[cfg(target_os = "macos")]
{
Ok(16.0) }
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
{
Ok(16.0) }
}
pub fn validate_memory_requirements(config: &ModelConfig, dtype: DType) -> Result<()> {
let estimate = estimate_memory_usage(config, dtype, Some(1), None);
if estimate.exceeds_system_memory() {
return Err(Error::device_validation(format!(
"Model requires {:.2}GB memory but system may not have enough available. \
Consider using a smaller model, quantization, or adding more RAM.",
estimate.total_gb
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::name_mapping::Architecture;
fn sample_config() -> ModelConfig {
ModelConfig {
vocab_size: 32000,
hidden_size: 4096,
num_attention_heads: 32,
num_hidden_layers: 32,
intermediate_size: 11008,
max_position_embeddings: 4096,
dropout: 0.0,
layer_norm_eps: 1e-6,
attention_dropout: 0.0,
activation_function: "silu".to_string(),
rope_theta: 10000.0,
tie_word_embeddings: false,
architecture: Architecture::LLaMA,
raw_config: serde_json::Value::Object(serde_json::Map::new()),
}
}
#[test]
fn test_memory_estimation() {
let config = sample_config();
let estimate = estimate_memory_usage(&config, DType::F16, Some(1), None);
assert!(estimate.parameters_gb > 10.0); assert!(estimate.parameters_gb < 20.0); assert!(estimate.activation_gb > 0.0); assert!(estimate.total_gb > estimate.parameters_gb);
println!("Memory estimate: {}", estimate.summary());
}
#[test]
fn test_dtype_validation() {
assert!(validate_dtype_for_awq(DType::F16).is_ok());
assert!(validate_dtype_for_awq(DType::BF16).is_ok());
assert!(validate_dtype_for_awq(DType::F32).is_err());
let cpu_device = Device::Cpu;
assert!(validate_dtype_for_device(DType::F32, &cpu_device).is_ok());
assert!(validate_dtype_for_device(DType::F16, &cpu_device).is_ok());
}
#[test]
fn test_best_device_selection() {
let device = get_best_device();
println!("Best device: {:?}", device);
}
#[test]
fn test_memory_breakdown() {
let config = sample_config();
let estimate = estimate_memory_usage(&config, DType::F16, Some(1), None);
let breakdown_total = estimate.breakdown.token_embeddings_gb
+ estimate.breakdown.position_embeddings_gb
+ estimate.breakdown.attention_layers_gb
+ estimate.breakdown.ffn_layers_gb
+ estimate.breakdown.layer_norms_gb
+ estimate.breakdown.output_layer_gb;
let diff = (breakdown_total - estimate.parameters_gb).abs();
assert!(
diff < 0.1,
"Breakdown total {:.3} != parameters {:.3}",
breakdown_total,
estimate.parameters_gb
);
}
}