use crate::{
error::{Error, Result},
loader::{LoadOptions, LoadedModel},
progress::ProgressEvent,
smart_mapping::SmartTensorNameMapper,
ModelConfig,
};
use candle_nn::VarBuilder;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AWQConfig {
#[serde(default)]
pub architectures: Option<Vec<String>>,
pub model_type: Option<String>,
pub vocab_size: Option<u32>,
pub hidden_size: Option<u32>,
pub num_attention_heads: Option<u32>,
pub num_hidden_layers: Option<u32>,
pub intermediate_size: Option<u32>,
pub max_position_embeddings: Option<u32>,
#[serde(alias = "rms_norm_eps")]
pub layer_norm_eps: Option<f64>,
pub rope_theta: Option<f64>,
pub tie_word_embeddings: Option<bool>,
pub quantization_config: Option<AWQQuantizationConfig>,
#[serde(flatten)]
pub additional_fields: std::collections::HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AWQQuantizationConfig {
pub bits: Option<u32>,
pub group_size: Option<u32>,
pub quant_method: Option<String>,
pub version: Option<String>,
pub zero_point: Option<bool>,
pub modules_to_not_convert: Option<serde_json::Value>,
}
pub fn load_awq<P: AsRef<Path>>(model_dir: P, mut options: LoadOptions) -> Result<LoadedModel> {
let model_dir = model_dir.as_ref();
if !model_dir.is_dir() {
return Err(Error::model_loading(format!(
"AWQ model directory not found: {:?}",
model_dir
)));
}
if let Some(callback) = &options.progress {
callback(ProgressEvent::LoadingConfig {
path: model_dir.join("config.json").display().to_string(),
});
}
let config_path = model_dir.join("config.json");
let awq_config = load_awq_config(&config_path)?;
if let Some(callback) = &options.progress {
callback(ProgressEvent::ScanningFiles { count: 0 });
}
let safetensors_files = find_awq_safetensors_files(model_dir)?;
if let Some(callback) = &options.progress {
callback(ProgressEvent::ScanningFiles {
count: safetensors_files.len(),
});
}
let tensor_names = vec![
"model.embed_tokens.weight".to_string(),
"model.layers.0.self_attn.q_proj.weight".to_string(),
"model.layers.0.self_attn.k_proj.weight".to_string(),
"model.layers.0.self_attn.v_proj.weight".to_string(),
"lm_head.weight".to_string(),
];
if let Some(callback) = &options.progress {
callback(ProgressEvent::DetectingArchitecture);
}
let mut smart_mapper = SmartTensorNameMapper::from_tensor_names(&tensor_names)?;
if let Some(oracle) = options.smart_mapping_oracle.take() {
smart_mapper = smart_mapper.with_oracle(oracle);
}
let architecture = smart_mapper.architecture().ok_or_else(|| {
Error::model_loading("Could not detect model architecture from AWQ tensor names")
})?;
let config = awq_config_to_model_config(&awq_config, architecture)?;
let raw_tensors = HashMap::new();
if let Some(callback) = &options.progress {
callback(ProgressEvent::BuildingModel);
}
let var_map = candle_nn::VarMap::new();
let var_builder = VarBuilder::from_varmap(&var_map, options.dtype, &options.device);
if let Some(callback) = &options.progress {
callback(ProgressEvent::Complete {
tensor_count: tensor_names.len(),
format: "AWQ".to_string(),
});
}
Ok(LoadedModel {
var_builder,
config,
name_mapper: smart_mapper,
raw_tensors,
quantized_tensors: None,
metadata: crate::metadata::ModelMetadata::new(),
tensor_info: HashMap::new(),
quantization_info: None,
provenance: crate::metadata::ModelProvenance::new(),
})
}
fn load_awq_config(config_path: &Path) -> Result<AWQConfig> {
let config_str = fs::read_to_string(config_path).map_err(|e| {
Error::model_loading(&format!(
"Failed to read AWQ config from {}: {}",
config_path.display(),
e
))
})?;
serde_json::from_str(&config_str)
.map_err(|e| Error::model_loading(&format!("Failed to parse AWQ config: {}", e)))
}
fn find_awq_safetensors_files(dir: &Path) -> Result<Vec<PathBuf>> {
let mut files = Vec::new();
for entry in fs::read_dir(dir).map_err(|e| {
Error::model_loading(&format!(
"Failed to read AWQ model directory {}: {}",
dir.display(),
e
))
})? {
let entry = entry
.map_err(|e| Error::model_loading(&format!("Failed to read directory entry: {}", e)))?;
let path = entry.path();
if let Some(extension) = path.extension() {
if extension == "safetensors" {
files.push(path);
}
}
}
if files.is_empty() {
return Err(Error::model_loading(format!(
"No .safetensors files found in AWQ model directory: {}",
dir.display()
)));
}
files.sort();
Ok(files)
}
fn awq_config_to_model_config(
awq_config: &AWQConfig,
architecture: &crate::name_mapping::Architecture,
) -> Result<ModelConfig> {
Ok(ModelConfig {
vocab_size: awq_config.vocab_size.unwrap_or(32000) as usize,
hidden_size: awq_config.hidden_size.unwrap_or(4096) as usize,
num_attention_heads: awq_config.num_attention_heads.unwrap_or(32) as usize,
num_hidden_layers: awq_config.num_hidden_layers.unwrap_or(32) as usize,
intermediate_size: awq_config.intermediate_size.unwrap_or(11008) as usize,
max_position_embeddings: awq_config.max_position_embeddings.unwrap_or(4096) as usize,
layer_norm_eps: awq_config.layer_norm_eps.unwrap_or(1e-6),
dropout: 0.0, attention_dropout: 0.0,
activation_function: "silu".to_string(), rope_theta: awq_config.rope_theta.unwrap_or(10000.0),
tie_word_embeddings: awq_config.tie_word_embeddings.unwrap_or(false),
architecture: architecture.clone(),
raw_config: serde_json::Value::Null,
})
}
pub fn is_awq_model<P: AsRef<Path>>(model_dir: P) -> bool {
let model_dir = model_dir.as_ref();
let config_path = model_dir.join("config.json");
if let Ok(config_str) = fs::read_to_string(&config_path) {
if let Ok(config) = serde_json::from_str::<AWQConfig>(&config_str) {
if let Some(quant_config) = &config.quantization_config {
return quant_config
.quant_method
.as_ref()
.map_or(false, |method| method == "awq");
}
}
}
false
}