1use crate::tensor_core::{Tensor, Device, DataType, CpuStorage, CandleStorage};
7use crate::model_core::{ModelWeights, WeightMetadata, WeightFormat};
8use std::collections::HashMap;
9use std::path::Path;
10use std::sync::Arc;
11use anyhow::{Result, anyhow};
12use serde_json::Value;
13use safetensors::SafeTensors;
14use candle_core::quantized::gguf_file;
15use candle_core::quantized::QMatMul;
16
17#[derive(Debug, Clone, Default)]
19pub struct GGUFModelConfig {
20 pub architecture: String,
21 pub vocab_size: usize,
22 pub hidden_size: usize,
23 pub intermediate_size: usize,
24 pub num_hidden_layers: usize,
25 pub num_attention_heads: usize,
26 pub num_key_value_heads: usize,
27 pub head_dim: usize,
28 pub rms_norm_eps: f32,
29 pub rope_theta: f32,
30 pub max_position_embeddings: usize,
31}
32
33impl GGUFModelConfig {
34 pub fn from_gguf_metadata(metadata: &HashMap<String, gguf_file::Value>) -> Self {
36 let architecture = extract_gguf_string(metadata, "general.architecture")
37 .unwrap_or_else(|| "llama".to_string());
38
39 let arch_prefix = &architecture;
41
42 let vocab_size = extract_gguf_u32(metadata, &format!("{}.vocab_size", arch_prefix))
43 .or_else(|| extract_gguf_u32(metadata, "llama.vocab_size"))
44 .unwrap_or(32000) as usize;
45
46 let hidden_size = extract_gguf_u32(metadata, &format!("{}.embedding_length", arch_prefix))
47 .or_else(|| extract_gguf_u32(metadata, "llama.embedding_length"))
48 .unwrap_or(4096) as usize;
49
50 let intermediate_size = extract_gguf_u32(metadata, &format!("{}.feed_forward_length", arch_prefix))
51 .or_else(|| extract_gguf_u32(metadata, "llama.feed_forward_length"))
52 .unwrap_or(11008) as usize;
53
54 let num_hidden_layers = extract_gguf_u32(metadata, &format!("{}.block_count", arch_prefix))
55 .or_else(|| extract_gguf_u32(metadata, "llama.block_count"))
56 .unwrap_or(32) as usize;
57
58 let num_attention_heads = extract_gguf_u32(metadata, &format!("{}.attention.head_count", arch_prefix))
59 .or_else(|| extract_gguf_u32(metadata, "llama.attention.head_count"))
60 .unwrap_or(32) as usize;
61
62 let num_key_value_heads = extract_gguf_u32(metadata, &format!("{}.attention.head_count_kv", arch_prefix))
63 .or_else(|| extract_gguf_u32(metadata, "llama.attention.head_count_kv"))
64 .unwrap_or(num_attention_heads as u32) as usize;
65
66 let head_dim = if num_attention_heads > 0 {
67 hidden_size / num_attention_heads
68 } else {
69 128
70 };
71
72 let rms_norm_eps = extract_gguf_f32(metadata, &format!("{}.attention.layer_norm_rms_epsilon", arch_prefix))
73 .or_else(|| extract_gguf_f32(metadata, "llama.attention.layer_norm_rms_epsilon"))
74 .unwrap_or(1e-5);
75
76 let rope_theta = extract_gguf_f32(metadata, &format!("{}.rope.freq_base", arch_prefix))
77 .or_else(|| extract_gguf_f32(metadata, "llama.rope.freq_base"))
78 .unwrap_or(10000.0);
79
80 let max_position_embeddings = extract_gguf_u32(metadata, &format!("{}.context_length", arch_prefix))
81 .or_else(|| extract_gguf_u32(metadata, "llama.context_length"))
82 .unwrap_or(2048) as usize;
83
84 Self {
85 architecture,
86 vocab_size,
87 hidden_size,
88 intermediate_size,
89 num_hidden_layers,
90 num_attention_heads,
91 num_key_value_heads,
92 head_dim,
93 rms_norm_eps,
94 rope_theta,
95 max_position_embeddings,
96 }
97 }
98}
99
100#[derive(Debug, Clone, Default)]
102pub struct GGUFSpecialTokens {
103 pub bos_token_id: Option<u32>,
104 pub eos_token_id: Option<u32>,
105 pub unk_token_id: Option<u32>,
106 pub pad_token_id: Option<u32>,
107}
108
109#[derive(Debug, Clone)]
111pub struct GGUFTokenizer {
112 pub tokens: Vec<String>,
113 pub token_types: Option<Vec<i32>>,
114 pub scores: Option<Vec<f32>>,
115 pub model_type: String,
116 pub special_tokens: GGUFSpecialTokens,
117}
118
119impl GGUFTokenizer {
120 pub fn from_gguf_metadata(metadata: &HashMap<String, gguf_file::Value>) -> Option<Self> {
122 let tokens = extract_gguf_string_array(metadata, "tokenizer.ggml.tokens")?;
124
125 if tokens.is_empty() {
126 return None;
127 }
128
129 let token_types = extract_gguf_i32_array(metadata, "tokenizer.ggml.token_type");
131
132 let scores = extract_gguf_f32_array(metadata, "tokenizer.ggml.scores");
134
135 let model_type = extract_gguf_string(metadata, "tokenizer.ggml.model")
137 .unwrap_or_else(|| "llama".to_string());
138
139 let special_tokens = GGUFSpecialTokens {
141 bos_token_id: extract_gguf_u32(metadata, "tokenizer.ggml.bos_token_id"),
142 eos_token_id: extract_gguf_u32(metadata, "tokenizer.ggml.eos_token_id"),
143 unk_token_id: extract_gguf_u32(metadata, "tokenizer.ggml.unknown_token_id"),
144 pad_token_id: extract_gguf_u32(metadata, "tokenizer.ggml.padding_token_id"),
145 };
146
147 Some(Self {
148 tokens,
149 token_types,
150 scores,
151 model_type,
152 special_tokens,
153 })
154 }
155
156 pub fn vocab_size(&self) -> usize {
158 self.tokens.len()
159 }
160}
161
162fn extract_gguf_string_array(metadata: &HashMap<String, gguf_file::Value>, key: &str) -> Option<Vec<String>> {
164 match metadata.get(key) {
165 Some(gguf_file::Value::Array(arr)) => {
166 let strings: Vec<String> = arr.iter()
167 .filter_map(|v| {
168 if let gguf_file::Value::String(s) = v {
169 Some(s.clone())
170 } else {
171 None
172 }
173 })
174 .collect();
175 if strings.is_empty() { None } else { Some(strings) }
176 }
177 _ => None,
178 }
179}
180
181fn extract_gguf_i32_array(metadata: &HashMap<String, gguf_file::Value>, key: &str) -> Option<Vec<i32>> {
183 match metadata.get(key) {
184 Some(gguf_file::Value::Array(arr)) => {
185 let nums: Vec<i32> = arr.iter()
186 .filter_map(|v| {
187 match v {
188 gguf_file::Value::I32(n) => Some(*n),
189 gguf_file::Value::U32(n) => Some(*n as i32),
190 _ => None,
191 }
192 })
193 .collect();
194 if nums.is_empty() { None } else { Some(nums) }
195 }
196 _ => None,
197 }
198}
199
200fn extract_gguf_f32_array(metadata: &HashMap<String, gguf_file::Value>, key: &str) -> Option<Vec<f32>> {
202 match metadata.get(key) {
203 Some(gguf_file::Value::Array(arr)) => {
204 let nums: Vec<f32> = arr.iter()
205 .filter_map(|v| {
206 match v {
207 gguf_file::Value::F32(n) => Some(*n),
208 gguf_file::Value::F64(n) => Some(*n as f32),
209 _ => None,
210 }
211 })
212 .collect();
213 if nums.is_empty() { None } else { Some(nums) }
214 }
215 _ => None,
216 }
217}
218
219pub trait WeightLoader: Send + Sync {
221 fn load_weights(&self, path: &Path) -> Result<ModelWeights>;
223
224 fn supports(&self, path: &Path) -> bool;
226
227 fn format_name(&self) -> &str;
229}
230
231pub struct UnifiedWeightLoader {
233 loaders: Vec<Box<dyn WeightLoader>>,
234}
235
236pub struct SafeTensorsWeightLoader;
238
239pub struct PyTorchWeightLoader;
241
242pub struct GGUFWeightLoader;
244
245impl UnifiedWeightLoader {
246 pub fn new() -> Self {
248 let mut loaders: Vec<Box<dyn WeightLoader>> = Vec::new();
249 loaders.push(Box::new(SafeTensorsWeightLoader));
250 loaders.push(Box::new(PyTorchWeightLoader));
251 loaders.push(Box::new(GGUFWeightLoader));
252
253 Self { loaders }
254 }
255
256 pub fn load_weights(&self, path: &Path) -> Result<ModelWeights> {
258 for loader in &self.loaders {
260 if loader.supports(path) {
261 println!("Loading weights using {} loader", loader.format_name());
262 return loader.load_weights(path);
263 }
264 }
265
266 Err(anyhow::anyhow!(
267 "No suitable weight loader found for path: {}",
268 path.display()
269 ))
270 }
271
272 pub fn detect_format(&self, path: &Path) -> Option<&str> {
274 for loader in &self.loaders {
275 if loader.supports(path) {
276 return Some(loader.format_name());
277 }
278 }
279 None
280 }
281
282 pub fn supported_formats(&self) -> Vec<&str> {
284 self.loaders.iter().map(|l| l.format_name()).collect()
285 }
286}
287
288impl SafeTensorsWeightLoader {
289 fn convert_dtype(&self, dtype: safetensors::Dtype) -> Result<DataType> {
291 match dtype {
292 safetensors::Dtype::F32 => Ok(DataType::Float32),
293 safetensors::Dtype::F16 => Ok(DataType::Float16),
294 safetensors::Dtype::BF16 => Ok(DataType::BFloat16),
295 safetensors::Dtype::I32 => Ok(DataType::Int32),
296 safetensors::Dtype::I64 => Ok(DataType::Int64),
297 safetensors::Dtype::I8 => Ok(DataType::Int8),
298 safetensors::Dtype::BOOL => Ok(DataType::Bool),
299 _ => Err(anyhow::anyhow!("Unsupported dtype: {:?}", dtype)),
300 }
301 }
302
303 fn load_safetensors_file(&self, file_path: &Path) -> Result<HashMap<String, Tensor>> {
305 println!("Loading SafeTensors file: {}", file_path.display());
306
307 let data = std::fs::read(file_path)?;
308 let safetensors = SafeTensors::deserialize(&data)?;
309
310 let mut tensors = HashMap::new();
311
312 for (name, tensor_view) in safetensors.tensors() {
314 println!(" Loading tensor: {} {:?} {:?}", name, tensor_view.shape(), tensor_view.dtype());
315
316 let dtype = self.convert_dtype(tensor_view.dtype())?;
318
319 let tensor_data = tensor_view.data();
321
322 let storage = Arc::new(CpuStorage::new(tensor_data.to_vec(), Device::CPU));
324
325 let tensor = Tensor::new(
327 tensor_view.shape().to_vec(),
328 dtype,
329 Device::CPU,
330 storage
331 );
332
333 tensors.insert(name.to_string(), tensor);
334 }
335
336 println!(" ✓ Loaded {} tensors", tensors.len());
337 Ok(tensors)
338 }
339}
340
341impl WeightLoader for SafeTensorsWeightLoader {
342 fn load_weights(&self, path: &Path) -> Result<ModelWeights> {
343 let mut all_tensors = HashMap::new();
344
345 if path.is_file() && path.extension().map_or(false, |ext| ext == "safetensors") {
346 let tensors = self.load_safetensors_file(path)?;
348 all_tensors.extend(tensors);
349 } else if path.is_dir() {
350 let entries = std::fs::read_dir(path)?;
352 for entry in entries {
353 let entry = entry?;
354 let file_path = entry.path();
355 if file_path.extension().map_or(false, |ext| ext == "safetensors") {
356 let tensors = self.load_safetensors_file(&file_path)?;
357 all_tensors.extend(tensors);
358 }
359 }
360 }
361
362 if all_tensors.is_empty() {
363 return Err(anyhow::anyhow!("No SafeTensors files found in {}", path.display()));
364 }
365
366 let total_params: usize = all_tensors.values()
368 .map(|tensor| tensor.numel())
369 .sum();
370
371 let primary_dtype = all_tensors.values()
373 .next()
374 .map(|t| match t.dtype() {
375 DataType::Float32 => "float32",
376 DataType::Float16 => "float16",
377 DataType::BFloat16 => "bfloat16",
378 DataType::Int32 => "int32",
379 DataType::Int64 => "int64",
380 DataType::Int8 => "int8",
381 DataType::Bool => "bool",
382 })
383 .unwrap_or("unknown");
384
385 let metadata = WeightMetadata {
386 architecture: "unknown".to_string(), total_params,
388 format: WeightFormat::SafeTensors,
389 dtype: primary_dtype.to_string(),
390 };
391
392 Ok(ModelWeights::new(all_tensors, metadata))
393 }
394
395 fn supports(&self, path: &Path) -> bool {
396 if path.is_file() {
397 return path.extension().map_or(false, |ext| ext == "safetensors");
398 }
399
400 if path.is_dir() {
401 if let Ok(entries) = std::fs::read_dir(path) {
403 for entry in entries.flatten() {
404 if entry.path().extension().map_or(false, |ext| ext == "safetensors") {
405 return true;
406 }
407 }
408 }
409 }
410
411 false
412 }
413
414 fn format_name(&self) -> &str {
415 "SafeTensors"
416 }
417}
418
419impl WeightLoader for PyTorchWeightLoader {
420 fn load_weights(&self, path: &Path) -> Result<ModelWeights> {
421 println!("Loading PyTorch weights from: {}", path.display());
422
423 let mut tensors = HashMap::new();
427
428 let storage = Arc::new(CpuStorage::zeros(2048 * 4));
430 let tensor = Tensor::new(
431 vec![512, 4],
432 DataType::Float32,
433 Device::CPU,
434 storage
435 );
436
437 tensors.insert("pytorch_weight".to_string(), tensor);
438
439 let metadata = WeightMetadata {
440 architecture: "unknown".to_string(),
441 total_params: tensors.len(),
442 format: WeightFormat::PyTorch,
443 dtype: "float32".to_string(),
444 };
445
446 Ok(ModelWeights::new(tensors, metadata))
447 }
448
449 fn supports(&self, path: &Path) -> bool {
450 if path.is_file() {
451 return path.extension().map_or(false, |ext| ext == "bin" || ext == "pt");
452 }
453
454 if path.is_dir() {
455 return path.join("pytorch_model.bin").exists() ||
456 path.join("model.pt").exists();
457 }
458
459 false
460 }
461
462 fn format_name(&self) -> &str {
463 "PyTorch"
464 }
465}
466
467#[cfg(feature = "simd")]
472fn create_simd_tensor_from_qtensor(
473 qtensor: &candle_core::quantized::QTensor,
474) -> Option<crate::simd::quant::QuantizedTensor> {
475 use crate::simd::quant::{QuantType, QuantizedTensor};
476 use candle_core::quantized::GgmlDType;
477
478 let quant_type = match qtensor.dtype() {
480 GgmlDType::Q4_0 => QuantType::Q4_0,
481 GgmlDType::Q4K => QuantType::Q4_K,
482 _ => return None, };
484
485 let shape = qtensor.shape();
487 if shape.dims().len() != 2 {
488 return None; }
490 let rows = shape.dims()[0];
491 let cols = shape.dims()[1];
492
493 let data = match qtensor.data() {
496 Ok(cow) => cow.to_vec(),
497 Err(_) => return None,
498 };
499
500 Some(QuantizedTensor::new(data, quant_type, rows, cols))
502}
503
504impl WeightLoader for GGUFWeightLoader {
505 fn load_weights(&self, path: &Path) -> Result<ModelWeights> {
506 println!("Loading GGUF weights from: {}", path.display());
507
508 let mut file = std::fs::File::open(path)?;
510 let content = gguf_file::Content::read(&mut file)
511 .map_err(|e| anyhow!("Failed to read GGUF file: {}", e))?;
512
513 println!("GGUF file loaded - {} tensors, {} metadata keys",
514 content.tensor_infos.len(),
515 content.metadata.len());
516
517 let gguf_config = GGUFModelConfig::from_gguf_metadata(&content.metadata);
519
520 println!("Model architecture: {}", gguf_config.architecture);
521 println!("Model config: vocab_size={}, hidden_size={}, num_layers={}, heads={}, kv_heads={}",
522 gguf_config.vocab_size, gguf_config.hidden_size, gguf_config.num_hidden_layers,
523 gguf_config.num_attention_heads, gguf_config.num_key_value_heads);
524
525 let gguf_tokenizer = GGUFTokenizer::from_gguf_metadata(&content.metadata);
527 if let Some(ref tok) = gguf_tokenizer {
528 println!("Tokenizer extracted: vocab_size={}, model_type={}",
529 tok.vocab_size(), tok.model_type);
530 } else {
531 println!("No tokenizer data found in GGUF metadata");
532 }
533
534 let device = candle_core::Device::Cpu;
536 let mut tensors = HashMap::new();
537 let mut quantized_tensors: HashMap<String, Arc<QMatMul>> = HashMap::new();
538 #[cfg(feature = "simd")]
539 let mut simd_quantized: HashMap<String, Arc<crate::simd::quant::QuantizedTensor>> = HashMap::new();
540 let mut total_params = 0usize;
541 let mut quantized_count = 0usize;
542
543 for (name, tensor_info) in content.tensor_infos.iter() {
544 let qtensor = tensor_info.read(&mut file, content.tensor_data_offset, &device)
546 .map_err(|e| anyhow!("Failed to read tensor '{}': {}", name, e))?;
547
548 let hf_name = gguf_to_hf_name(name);
550
551 let is_weight_tensor = hf_name.contains("_proj.weight") || hf_name.contains("lm_head.weight");
555
556 if is_weight_tensor {
557 let shape = qtensor.shape().clone();
560
561 #[cfg(feature = "simd")]
563 {
564 if let Some(simd_tensor) = create_simd_tensor_from_qtensor(&qtensor) {
565 simd_quantized.insert(hf_name.clone(), Arc::new(simd_tensor));
566 }
567 }
568
569 let qmatmul = QMatMul::from_arc(Arc::new(qtensor))
570 .map_err(|e| anyhow!("Failed to create QMatMul for '{}': {}", name, e))?;
571 quantized_tensors.insert(hf_name.clone(), Arc::new(qmatmul));
572 quantized_count += 1;
573
574 total_params += shape.elem_count();
576 } else {
578 let candle_tensor = qtensor.dequantize(&device)
580 .map_err(|e| anyhow!("Failed to dequantize tensor '{}': {}", name, e))?;
581 total_params += candle_tensor.elem_count();
582 let tensor = Tensor::from_candle(candle_tensor);
583 tensors.insert(hf_name, tensor);
584 }
585 }
586
587 #[cfg(feature = "simd")]
588 println!("Loaded {} F32 tensors + {} quantized weights + {} SIMD quantized, {} total parameters",
589 tensors.len(), quantized_count, simd_quantized.len(), total_params);
590 #[cfg(not(feature = "simd"))]
591 println!("Loaded {} F32 tensors + {} quantized weights, {} total parameters",
592 tensors.len(), quantized_count, total_params);
593
594 let metadata = WeightMetadata {
595 architecture: gguf_config.architecture.clone(),
596 total_params,
597 format: WeightFormat::GGUF,
598 dtype: "quantized".to_string(),
599 };
600
601 #[cfg(feature = "simd")]
602 return Ok(ModelWeights::with_simd_quantized(
603 tensors, metadata, gguf_config, gguf_tokenizer, quantized_tensors, simd_quantized
604 ));
605
606 #[cfg(not(feature = "simd"))]
607 Ok(ModelWeights::with_quantized(tensors, metadata, gguf_config, gguf_tokenizer, quantized_tensors))
608 }
609
610 fn supports(&self, path: &Path) -> bool {
611 if path.is_file() {
612 return path.extension().map_or(false, |ext| ext == "gguf");
613 }
614
615 if path.is_dir() {
616 if let Ok(entries) = std::fs::read_dir(path) {
617 for entry in entries.flatten() {
618 if entry.path().extension().map_or(false, |ext| ext == "gguf") {
619 return true;
620 }
621 }
622 }
623 }
624
625 false
626 }
627
628 fn format_name(&self) -> &str {
629 "GGUF"
630 }
631}
632
633pub struct ConfigLoader;
635
636impl ConfigLoader {
637 pub fn load_config(&self, model_path: &Path) -> Result<Value> {
639 let config_path = if model_path.is_file() {
640 model_path.parent()
641 .ok_or_else(|| anyhow::anyhow!("No parent directory"))?
642 .join("config.json")
643 } else {
644 model_path.join("config.json")
645 };
646
647 if !config_path.exists() {
648 return Err(anyhow::anyhow!("config.json not found at {}", config_path.display()));
649 }
650
651 let content = std::fs::read_to_string(&config_path)?;
652 let config: Value = serde_json::from_str(&content)?;
653
654 Ok(config)
655 }
656
657 pub fn get_architecture(&self, config: &Value) -> Result<String> {
659 if let Some(arch) = config.get("architectures").and_then(|a| a.as_array()) {
661 if let Some(first_arch) = arch.first().and_then(|a| a.as_str()) {
662 return Ok(first_arch.to_string());
663 }
664 }
665
666 if let Some(arch) = config.get("model_type").and_then(|a| a.as_str()) {
667 return Ok(arch.to_string());
668 }
669
670 if let Some(arch) = config.get("architecture").and_then(|a| a.as_str()) {
671 return Ok(arch.to_string());
672 }
673
674 Err(anyhow::anyhow!("Could not determine model architecture from config"))
675 }
676}
677
678pub struct ModelLoader {
680 weight_loader: UnifiedWeightLoader,
681 config_loader: ConfigLoader,
682}
683
684impl ModelLoader {
685 pub fn new() -> Self {
686 Self {
687 weight_loader: UnifiedWeightLoader::new(),
688 config_loader: ConfigLoader,
689 }
690 }
691
692 pub fn load_model(&self, path: &Path) -> Result<(Value, ModelWeights)> {
694 println!("Loading model from: {}", path.display());
695
696 let config = self.config_loader.load_config(path)?;
698 println!("✓ Configuration loaded");
699
700 let weights = self.weight_loader.load_weights(path)?;
702 println!("✓ Weights loaded ({} tensors)", weights.tensors.len());
703
704 Ok((config, weights))
705 }
706
707 pub fn supported_formats(&self) -> Vec<&str> {
709 self.weight_loader.supported_formats()
710 }
711}
712
713static MODEL_LOADER: std::sync::OnceLock<ModelLoader> = std::sync::OnceLock::new();
715
716pub fn loader() -> &'static ModelLoader {
718 MODEL_LOADER.get_or_init(|| ModelLoader::new())
719}
720
721fn extract_gguf_string(metadata: &HashMap<String, gguf_file::Value>, key: &str) -> Option<String> {
725 metadata.get(key).and_then(|v| {
726 if let gguf_file::Value::String(s) = v {
727 Some(s.clone())
728 } else {
729 None
730 }
731 })
732}
733
734fn extract_gguf_u32(metadata: &HashMap<String, gguf_file::Value>, key: &str) -> Option<u32> {
736 metadata.get(key).and_then(|v| {
737 match v {
738 gguf_file::Value::U32(n) => Some(*n),
739 gguf_file::Value::I32(n) => Some(*n as u32),
740 gguf_file::Value::U64(n) => Some(*n as u32),
741 gguf_file::Value::I64(n) => Some(*n as u32),
742 _ => None,
743 }
744 })
745}
746
747fn extract_gguf_f32(metadata: &HashMap<String, gguf_file::Value>, key: &str) -> Option<f32> {
749 metadata.get(key).and_then(|v| {
750 match v {
751 gguf_file::Value::F32(n) => Some(*n),
752 gguf_file::Value::F64(n) => Some(*n as f32),
753 _ => None,
754 }
755 })
756}
757
758fn gguf_to_hf_name(gguf_name: &str) -> String {
761 let name = gguf_name
765 .replace("token_embd.weight", "model.embed_tokens.weight")
767 .replace("blk.", "model.layers.")
769 .replace(".attn_output.weight", ".self_attn.o_proj.weight")
771 .replace(".attn_q.weight", ".self_attn.q_proj.weight")
772 .replace(".attn_k.weight", ".self_attn.k_proj.weight")
773 .replace(".attn_v.weight", ".self_attn.v_proj.weight")
774 .replace(".attn_output.bias", ".self_attn.o_proj.bias")
776 .replace(".attn_q.bias", ".self_attn.q_proj.bias")
777 .replace(".attn_k.bias", ".self_attn.k_proj.bias")
778 .replace(".attn_v.bias", ".self_attn.v_proj.bias")
779 .replace("output_norm.weight", "model.norm.weight")
781 .replace("output.weight", "lm_head.weight")
782 .replace(".ffn_gate.weight", ".mlp.gate_proj.weight")
784 .replace(".ffn_up.weight", ".mlp.up_proj.weight")
785 .replace(".ffn_down.weight", ".mlp.down_proj.weight")
786 .replace(".ffn_gate.bias", ".mlp.gate_proj.bias")
788 .replace(".ffn_up.bias", ".mlp.up_proj.bias")
789 .replace(".ffn_down.bias", ".mlp.down_proj.bias")
790 .replace(".attn_norm.weight", ".input_layernorm.weight")
792 .replace(".ffn_norm.weight", ".post_attention_layernorm.weight")
793 .replace(".attn_norm.bias", ".input_layernorm.bias")
795 .replace(".ffn_norm.bias", ".post_attention_layernorm.bias");
796
797 name
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803 use tempfile::tempdir;
804
805 #[test]
806 fn test_weight_loader_creation() {
807 let loader = UnifiedWeightLoader::new();
808 let formats = loader.supported_formats();
809
810 assert!(formats.contains(&"SafeTensors"));
811 assert!(formats.contains(&"PyTorch"));
812 assert!(formats.contains(&"GGUF"));
813 }
814
815 #[test]
816 fn test_config_loader() {
817 let loader = ConfigLoader;
818
819 let config_json = r#"
821 {
822 "architectures": ["LlamaForCausalLM"],
823 "vocab_size": 32000,
824 "hidden_size": 4096
825 }
826 "#;
827
828 let config: Value = serde_json::from_str(config_json).unwrap();
829 let arch = loader.get_architecture(&config).unwrap();
830
831 assert_eq!(arch, "LlamaForCausalLM");
832 }
833}