1use std::fs::File;
3use std::io::Read;
4use std::path::{Path, PathBuf};
5use std::collections::HashMap;
6use serde::{Deserialize, Serialize};
7use memmap2::{Mmap, MmapOptions};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct UniversalModel {
12 pub format: ModelFormat,
13 pub metadata: UniversalMetadata,
14 pub layers: Vec<Layer>,
15 pub tokenizer: Option<TokenizerInfo>,
16 pub config: ModelConfig,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub enum ModelFormat {
21 GGML, GGUF, ONNX, SafeTensors, PyTorch, TensorFlow, JAX, Paddle, MXNet, CoreML, TensorRT, OpenVINO, NCNN, TFLite, Custom(String), }
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct UniversalMetadata {
40 pub name: String,
41 pub architecture: String,
42 pub parameters: u64,
43 pub precision: Precision,
44 pub context_length: u32,
45 pub hidden_size: u32,
46 pub num_layers: u32,
47 pub num_heads: u32,
48 pub vocab_size: u32,
49 pub intermediate_size: u32,
50 pub rope_theta: Option<f32>,
51 pub max_position_embeddings: u32,
52 pub layer_norm_epsilon: f32,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub enum Precision {
57 FP32,
58 FP16,
59 BF16,
60 INT8,
61 INT4,
62 Mixed,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct Layer {
67 pub name: String,
68 pub layer_type: LayerType,
69 pub weights: Vec<Tensor>,
70 pub shape: Vec<usize>,
71 pub parameters: u64,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub enum LayerType {
76 Embedding,
77 Attention,
78 MLP,
79 LayerNorm,
80 RMSNorm,
81 Linear,
82 Conv1D,
83 Conv2D,
84 GeLU,
85 SiLU,
86 Softmax,
87 Dropout,
88 Custom(String),
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct Tensor {
93 pub name: String,
94 pub shape: Vec<usize>,
95 pub dtype: DataType,
96 pub data: TensorData,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub enum DataType {
101 Float32,
102 Float16,
103 BFloat16,
104 Int8,
105 Int16,
106 Int32,
107 Int64,
108 UInt8,
109 Bool,
110 Quantized(QuantizationType),
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub enum QuantizationType {
115 Q4_0,
116 Q4_1,
117 Q5_0,
118 Q5_1,
119 Q8_0,
120 Q2K,
121 Q3K,
122 Q4K,
123 Q5K,
124 Q6K,
125 Q8K,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub enum TensorData {
130 Float32(Vec<f32>),
131 Float16(Vec<F16>),
132 Int8(Vec<i8>),
133 UInt8(Vec<u8>),
134 Quantized(Vec<u8>),
135 MemoryMapped { offset: u64, size: u64 },
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct TokenizerInfo {
140 pub vocab_size: u32,
141 pub tokenizer_type: String,
142 pub special_tokens: HashMap<String, u32>,
143 pub vocab_path: Option<PathBuf>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct ModelConfig {
148 pub model_type: String,
149 pub architectures: Vec<String>,
150 pub attention_bias: bool,
151 pub attention_dropout: f32,
152 pub hidden_act: String,
153 pub hidden_dropout: f32,
154 pub initializer_range: f32,
155 pub intermediate_size: u32,
156 pub max_position_embeddings: u32,
157 pub num_attention_heads: u32,
158 pub num_hidden_layers: u32,
159 pub num_key_value_heads: Option<u32>,
160 pub pretraining_tp: Option<u32>,
161 pub rms_norm_eps: f32,
162 pub rope_scaling: Option<HashMap<String, serde_json::Value>>,
163 pub tie_word_embeddings: bool,
164 pub torch_dtype: Option<String>,
165 pub transformers_version: Option<String>,
166 pub use_cache: bool,
167 pub vocab_size: u32,
168}
169
170#[derive(Debug, Clone, Copy)]
172pub struct F16(u16);
173
174impl Serialize for F16 {
175 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
176 where S: serde::Serializer {
177 serializer.serialize_f32(self.to_f32())
178 }
179}
180
181impl<'de> Deserialize<'de> for F16 {
182 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
183 where D: serde::Deserializer<'de> {
184 let val = f32::deserialize(deserializer)?;
185 Ok(F16::from_f32(val))
186 }
187}
188
189impl F16 {
190 pub fn from_f32(f: f32) -> Self {
191 let bits = f.to_bits();
193 let sign = (bits >> 31) as u16;
194 let exp = ((bits >> 23) & 0xff) as i32;
195 let frac = (bits & 0x7fffff) as u32;
196
197 let half_exp = (exp - 127 + 15).max(0).min(31) as u16;
198 let half_frac = (frac >> 13) as u16;
199
200 F16((sign << 15) | (half_exp << 10) | half_frac)
201 }
202
203 pub fn to_f32(&self) -> f32 {
204 let sign = (self.0 >> 15) as u32;
205 let exp = ((self.0 >> 10) & 0x1f) as i32;
206 let frac = (self.0 & 0x3ff) as u32;
207
208 let float_exp = (exp - 15 + 127) as u32;
209 let float_frac = frac << 13;
210
211 f32::from_bits((sign << 31) | (float_exp << 23) | float_frac)
212 }
213}
214
215pub struct UniversalLoader {
216 mmap_cache: HashMap<PathBuf, Mmap>,
217}
218
219impl UniversalLoader {
220 pub fn new() -> Self {
221 Self {
222 mmap_cache: HashMap::new(),
223 }
224 }
225
226 pub fn load_model<P: AsRef<Path>>(&mut self, path: P) -> Result<UniversalModel, Box<dyn std::error::Error>> {
228 let path = path.as_ref();
229 let format = self.detect_format(path)?;
230
231 match format {
232 ModelFormat::ONNX => self.load_onnx(path),
233 ModelFormat::SafeTensors => self.load_safetensors(path),
234 ModelFormat::PyTorch => self.load_pytorch(path),
235 ModelFormat::TensorFlow => self.load_tensorflow(path),
236 ModelFormat::GGML | ModelFormat::GGUF => self.load_ggml(path),
237 _ => self.load_generic(path),
238 }
239 }
240
241 fn detect_format(&self, path: &Path) -> Result<ModelFormat, Box<dyn std::error::Error>> {
243 let extension = path.extension()
244 .and_then(|e| e.to_str())
245 .unwrap_or("");
246
247 let format = match extension {
249 "onnx" => ModelFormat::ONNX,
250 "safetensors" => ModelFormat::SafeTensors,
251 "pt" | "pth" | "bin" => ModelFormat::PyTorch,
252 "pb" | "h5" | "keras" => ModelFormat::TensorFlow,
253 "gguf" => ModelFormat::GGUF,
254 "ggml" => ModelFormat::GGML,
255 "mlmodel" => ModelFormat::CoreML,
256 "tflite" => ModelFormat::TFLite,
257 "pdmodel" => ModelFormat::Paddle,
258 _ => {
259 self.detect_by_magic(path)?
261 }
262 };
263
264 Ok(format)
265 }
266
267 fn detect_by_magic(&self, path: &Path) -> Result<ModelFormat, Box<dyn std::error::Error>> {
268 let mut file = File::open(path)?;
269 let mut magic = [0u8; 8];
270 file.read_exact(&mut magic)?;
271
272 if &magic[0..4] == b"GGUF" {
274 Ok(ModelFormat::GGUF)
275 } else if &magic[0..4] == b"ggml" {
276 Ok(ModelFormat::GGML)
277 } else if &magic[0..2] == b"\x08\x00" {
278 Ok(ModelFormat::ONNX)
279 } else if &magic[0..2] == b"PK" {
280 Ok(ModelFormat::PyTorch) } else if &magic[0..4] == b"\x93NUMPY" {
282 Ok(ModelFormat::SafeTensors)
283 } else {
284 Ok(ModelFormat::Custom("unknown".to_string()))
285 }
286 }
287
288 fn load_onnx(&mut self, path: &Path) -> Result<UniversalModel, Box<dyn std::error::Error>> {
290 let file = File::open(path)?;
291 let mmap = unsafe { MmapOptions::new().map(&file)? };
292
293 let metadata = UniversalMetadata {
295 name: path.file_stem().unwrap_or_default().to_string_lossy().to_string(),
296 architecture: "onnx".to_string(),
297 parameters: 0,
298 precision: Precision::FP32,
299 context_length: 2048,
300 hidden_size: 768,
301 num_layers: 12,
302 num_heads: 12,
303 vocab_size: 50257,
304 intermediate_size: 3072,
305 rope_theta: None,
306 max_position_embeddings: 2048,
307 layer_norm_epsilon: 1e-5,
308 };
309
310 let config = ModelConfig {
311 model_type: "onnx".to_string(),
312 architectures: vec!["transformer".to_string()],
313 attention_bias: false,
314 attention_dropout: 0.1,
315 hidden_act: "gelu".to_string(),
316 hidden_dropout: 0.1,
317 initializer_range: 0.02,
318 intermediate_size: metadata.intermediate_size,
319 max_position_embeddings: metadata.max_position_embeddings,
320 num_attention_heads: metadata.num_heads,
321 num_hidden_layers: metadata.num_layers,
322 num_key_value_heads: Some(metadata.num_heads),
323 pretraining_tp: None,
324 rms_norm_eps: metadata.layer_norm_epsilon,
325 rope_scaling: None,
326 tie_word_embeddings: false,
327 torch_dtype: Some("float32".to_string()),
328 transformers_version: None,
329 use_cache: true,
330 vocab_size: metadata.vocab_size,
331 };
332
333 self.mmap_cache.insert(path.to_path_buf(), mmap);
334
335 Ok(UniversalModel {
336 format: ModelFormat::ONNX,
337 metadata,
338 layers: self.extract_layers_from_mmap(path)?,
339 tokenizer: None,
340 config,
341 })
342 }
343
344 fn load_safetensors(&mut self, path: &Path) -> Result<UniversalModel, Box<dyn std::error::Error>> {
346 let bytes = std::fs::read(path)?;
347 let header_size = u64::from_le_bytes(bytes[0..8].try_into()?) as usize;
348 let header: serde_json::Value = serde_json::from_slice(&bytes[8..8 + header_size])?;
349
350 let mut layers: Vec<Layer> = Vec::new();
352 if let serde_json::Value::Object(map) = &header {
353 let mut by_layer: HashMap<String, Vec<Tensor>> = HashMap::new();
355 for (name, info) in map {
356 if let Some(obj) = info.as_object() {
357 let dtype = match obj.get("dtype").and_then(|v| v.as_str()).unwrap_or("") {
358 "F32" | "float32" => DataType::Float32,
359 "F16" | "float16" => DataType::Float16,
360 "BF16" | "bfloat16" => DataType::BFloat16,
361 _ => DataType::Float32,
362 };
363 if let (Some(shape_v), Some(offset_v), Some(size_v)) = (obj.get("shape"), obj.get("data_offsets"), obj.get("data_offsets")) {
364 let shape = shape_v.as_array().unwrap_or(&vec![]).iter().filter_map(|x| x.as_u64()).map(|x| x as usize).collect::<Vec<_>>();
365 let tmp = vec![];
366 let offsets = offset_v.as_array().unwrap_or(&tmp);
367 if offsets.len() == 2 {
368 let off = offsets[0].as_u64().unwrap_or(0) + 8 + header_size as u64;
369 let end = offsets[1].as_u64().unwrap_or(off);
370 let size = end.saturating_sub(off);
371 let data = TensorData::MemoryMapped { offset: off, size };
372 let key = name.split('.').take(2).collect::<Vec<_>>().join(".");
373 let tensor = Tensor { name: name.clone(), shape: shape.clone(), dtype: dtype.clone(), data };
374 by_layer.entry(key).or_default().push(tensor);
375 }
376 }
377 }
378 }
379 for (lname, tensors) in by_layer.into_iter() {
380 let params: u64 = tensors.iter().map(|t| t.shape.iter().product::<usize>() as u64).sum();
381 layers.push(Layer {
382 name: lname.clone(),
383 layer_type: self.infer_layer_type(&lname),
384 weights: tensors,
385 shape: vec![],
386 parameters: params,
387 });
388 }
389 }
390
391 let metadata = UniversalMetadata {
392 name: path.file_stem().unwrap_or_default().to_string_lossy().to_string(),
393 architecture: "transformer".to_string(),
394 parameters: layers.iter().map(|l| l.parameters).sum(),
395 precision: Precision::FP16,
396 context_length: 2048,
397 hidden_size: 0,
398 num_layers: layers.len() as u32,
399 num_heads: 0,
400 vocab_size: 0,
401 intermediate_size: 0,
402 rope_theta: None,
403 max_position_embeddings: 0,
404 layer_norm_epsilon: 1e-5,
405 };
406 let config = ModelConfig {
407 model_type: "safetensors".to_string(),
408 architectures: vec!["transformer".to_string()],
409 attention_bias: false,
410 attention_dropout: 0.0,
411 hidden_act: "gelu".to_string(),
412 hidden_dropout: 0.0,
413 initializer_range: 0.02,
414 intermediate_size: 0,
415 max_position_embeddings: 0,
416 num_attention_heads: 0,
417 num_hidden_layers: layers.len() as u32,
418 num_key_value_heads: None,
419 pretraining_tp: None,
420 rms_norm_eps: 1e-5,
421 rope_scaling: None,
422 tie_word_embeddings: false,
423 torch_dtype: Some("float16".to_string()),
424 transformers_version: None,
425 use_cache: true,
426 vocab_size: 0,
427 };
428
429 Ok(UniversalModel { format: ModelFormat::SafeTensors, metadata, layers, tokenizer: None, config })
430 }
431
432 fn load_pytorch(&mut self, path: &Path) -> Result<UniversalModel, Box<dyn std::error::Error>> {
434 let file = File::open(path)?;
436 let mmap = unsafe { MmapOptions::new().map(&file)? };
437
438 let metadata = self.extract_pytorch_metadata(&mmap)?;
439 let config = self.extract_pytorch_config(&mmap)?;
440
441 self.mmap_cache.insert(path.to_path_buf(), mmap);
442
443 Ok(UniversalModel {
444 format: ModelFormat::PyTorch,
445 metadata,
446 layers: self.extract_pytorch_layers(path)?,
447 tokenizer: None,
448 config,
449 })
450 }
451
452 fn load_tensorflow(&mut self, path: &Path) -> Result<UniversalModel, Box<dyn std::error::Error>> {
454 let metadata = UniversalMetadata {
455 name: path.file_stem().unwrap_or_default().to_string_lossy().to_string(),
456 architecture: "tensorflow".to_string(),
457 parameters: 0,
458 precision: Precision::FP32,
459 context_length: 2048,
460 hidden_size: 768,
461 num_layers: 12,
462 num_heads: 12,
463 vocab_size: 50257,
464 intermediate_size: 3072,
465 rope_theta: None,
466 max_position_embeddings: 2048,
467 layer_norm_epsilon: 1e-5,
468 };
469
470 let config = ModelConfig {
471 model_type: "tensorflow".to_string(),
472 architectures: vec!["transformer".to_string()],
473 attention_bias: false,
474 attention_dropout: 0.1,
475 hidden_act: "gelu".to_string(),
476 hidden_dropout: 0.1,
477 initializer_range: 0.02,
478 intermediate_size: metadata.intermediate_size,
479 max_position_embeddings: metadata.max_position_embeddings,
480 num_attention_heads: metadata.num_heads,
481 num_hidden_layers: metadata.num_layers,
482 num_key_value_heads: Some(metadata.num_heads),
483 pretraining_tp: None,
484 rms_norm_eps: metadata.layer_norm_epsilon,
485 rope_scaling: None,
486 tie_word_embeddings: false,
487 torch_dtype: Some("float32".to_string()),
488 transformers_version: None,
489 use_cache: true,
490 vocab_size: metadata.vocab_size,
491 };
492
493 Ok(UniversalModel {
494 format: ModelFormat::TensorFlow,
495 metadata,
496 layers: Vec::new(),
497 tokenizer: None,
498 config,
499 })
500 }
501
502 fn load_ggml(&mut self, path: &Path) -> Result<UniversalModel, Box<dyn std::error::Error>> {
504 let file_size = std::fs::metadata(path)?.len();
506 let name = path.file_stem().unwrap_or_default().to_string_lossy().to_string();
507 let metadata = UniversalMetadata {
508 name,
509 architecture: "ggml".to_string(),
510 parameters: file_size / 4,
511 precision: Precision::Mixed,
512 context_length: 4096,
513 hidden_size: 4096,
514 num_layers: 32,
515 num_heads: 32,
516 vocab_size: 32000,
517 intermediate_size: 11008,
518 rope_theta: Some(10000.0),
519 max_position_embeddings: 4096,
520 layer_norm_epsilon: 1e-5,
521 };
522 let config = ModelConfig {
523 model_type: "ggml".to_string(),
524 architectures: vec!["transformer".to_string()],
525 attention_bias: false,
526 attention_dropout: 0.0,
527 hidden_act: "silu".to_string(),
528 hidden_dropout: 0.0,
529 initializer_range: 0.02,
530 intermediate_size: metadata.intermediate_size,
531 max_position_embeddings: metadata.max_position_embeddings,
532 num_attention_heads: metadata.num_heads,
533 num_hidden_layers: metadata.num_layers,
534 num_key_value_heads: Some(metadata.num_heads),
535 pretraining_tp: None,
536 rms_norm_eps: metadata.layer_norm_epsilon,
537 rope_scaling: None,
538 tie_word_embeddings: false,
539 torch_dtype: Some("float16".to_string()),
540 transformers_version: None,
541 use_cache: true,
542 vocab_size: metadata.vocab_size,
543 };
544 Ok(UniversalModel {
545 format: ModelFormat::GGUF,
546 metadata,
547 layers: Vec::new(),
548 tokenizer: None,
549 config,
550 })
551 }
552
553 fn load_generic(&mut self, path: &Path) -> Result<UniversalModel, Box<dyn std::error::Error>> {
555 let file_size = std::fs::metadata(path)?.len();
556
557 let metadata = UniversalMetadata {
558 name: path.file_stem().unwrap_or_default().to_string_lossy().to_string(),
559 architecture: "unknown".to_string(),
560 parameters: file_size / 4, precision: Precision::FP32,
562 context_length: 2048,
563 hidden_size: 768,
564 num_layers: 12,
565 num_heads: 12,
566 vocab_size: 50257,
567 intermediate_size: 3072,
568 rope_theta: None,
569 max_position_embeddings: 2048,
570 layer_norm_epsilon: 1e-5,
571 };
572
573 let config = ModelConfig {
574 model_type: "generic".to_string(),
575 architectures: vec!["unknown".to_string()],
576 attention_bias: false,
577 attention_dropout: 0.1,
578 hidden_act: "gelu".to_string(),
579 hidden_dropout: 0.1,
580 initializer_range: 0.02,
581 intermediate_size: metadata.intermediate_size,
582 max_position_embeddings: metadata.max_position_embeddings,
583 num_attention_heads: metadata.num_heads,
584 num_hidden_layers: metadata.num_layers,
585 num_key_value_heads: Some(metadata.num_heads),
586 pretraining_tp: None,
587 rms_norm_eps: metadata.layer_norm_epsilon,
588 rope_scaling: None,
589 tie_word_embeddings: false,
590 torch_dtype: Some("float32".to_string()),
591 transformers_version: None,
592 use_cache: true,
593 vocab_size: metadata.vocab_size,
594 };
595
596 Ok(UniversalModel {
597 format: ModelFormat::Custom("generic".to_string()),
598 metadata,
599 layers: Vec::new(),
600 tokenizer: None,
601 config,
602 })
603 }
604
605 fn extract_layers_from_mmap(&self, path: &Path) -> Result<Vec<Layer>, Box<dyn std::error::Error>> {
607 Ok(Vec::new())
609 }
610
611 fn parse_safetensors_metadata(&self, _header: &serde_json::Value) -> Result<UniversalMetadata, Box<dyn std::error::Error>> {
612 Ok(UniversalMetadata {
613 name: "safetensors_model".to_string(),
614 architecture: "transformer".to_string(),
615 parameters: 0,
616 precision: Precision::FP16,
617 context_length: 2048,
618 hidden_size: 768,
619 num_layers: 12,
620 num_heads: 12,
621 vocab_size: 50257,
622 intermediate_size: 3072,
623 rope_theta: None,
624 max_position_embeddings: 2048,
625 layer_norm_epsilon: 1e-5,
626 })
627 }
628
629 fn parse_safetensors_config(&self, _header: &serde_json::Value) -> Result<ModelConfig, Box<dyn std::error::Error>> {
630 Ok(ModelConfig {
631 model_type: "safetensors".to_string(),
632 architectures: vec!["transformer".to_string()],
633 attention_bias: false,
634 attention_dropout: 0.1,
635 hidden_act: "gelu".to_string(),
636 hidden_dropout: 0.1,
637 initializer_range: 0.02,
638 intermediate_size: 3072,
639 max_position_embeddings: 2048,
640 num_attention_heads: 12,
641 num_hidden_layers: 12,
642 num_key_value_heads: Some(12),
643 pretraining_tp: None,
644 rms_norm_eps: 1e-5,
645 rope_scaling: None,
646 tie_word_embeddings: false,
647 torch_dtype: Some("float16".to_string()),
648 transformers_version: None,
649 use_cache: true,
650 vocab_size: 50257,
651 })
652 }
653
654 fn extract_safetensors_layers(&self, _data: &[u8], _header: &serde_json::Value) -> Result<Vec<Layer>, Box<dyn std::error::Error>> {
655 Ok(Vec::new())
656 }
657
658 fn extract_pytorch_metadata(&self, _mmap: &Mmap) -> Result<UniversalMetadata, Box<dyn std::error::Error>> {
659 Ok(UniversalMetadata {
660 name: "pytorch_model".to_string(),
661 architecture: "transformer".to_string(),
662 parameters: 0,
663 precision: Precision::FP32,
664 context_length: 2048,
665 hidden_size: 768,
666 num_layers: 12,
667 num_heads: 12,
668 vocab_size: 50257,
669 intermediate_size: 3072,
670 rope_theta: None,
671 max_position_embeddings: 2048,
672 layer_norm_epsilon: 1e-5,
673 })
674 }
675
676 fn extract_pytorch_config(&self, _mmap: &Mmap) -> Result<ModelConfig, Box<dyn std::error::Error>> {
677 Ok(ModelConfig {
678 model_type: "pytorch".to_string(),
679 architectures: vec!["transformer".to_string()],
680 attention_bias: false,
681 attention_dropout: 0.1,
682 hidden_act: "gelu".to_string(),
683 hidden_dropout: 0.1,
684 initializer_range: 0.02,
685 intermediate_size: 3072,
686 max_position_embeddings: 2048,
687 num_attention_heads: 12,
688 num_hidden_layers: 12,
689 num_key_value_heads: Some(12),
690 pretraining_tp: None,
691 rms_norm_eps: 1e-5,
692 rope_scaling: None,
693 tie_word_embeddings: false,
694 torch_dtype: Some("float32".to_string()),
695 transformers_version: None,
696 use_cache: true,
697 vocab_size: 50257,
698 })
699 }
700
701 fn extract_pytorch_layers(&self, _path: &Path) -> Result<Vec<Layer>, Box<dyn std::error::Error>> {
702 Ok(Vec::new())
703 }
704
705 fn convert_ggml_tensors_to_layers(&self, _tensors: Vec<()>) -> Result<Vec<Layer>, Box<dyn std::error::Error>> {
707 Ok(Vec::new())
708 }
709
710 fn infer_layer_type(&self, name: &str) -> LayerType {
711 if name.contains("embed") {
712 LayerType::Embedding
713 } else if name.contains("attn") || name.contains("attention") {
714 LayerType::Attention
715 } else if name.contains("mlp") || name.contains("ffn") {
716 LayerType::MLP
717 } else if name.contains("norm") {
718 if name.contains("rms") {
719 LayerType::RMSNorm
720 } else {
721 LayerType::LayerNorm
722 }
723 } else if name.contains("linear") || name.contains("fc") {
724 LayerType::Linear
725 } else {
726 LayerType::Custom(name.to_string())
727 }
728 }
729
730 fn convert_ggml_dtype(&self, _dtype: ()) -> DataType { DataType::Float32 }
731}
732
733pub fn load_any_model<P: AsRef<Path>>(path: P) -> Result<UniversalModel, Box<dyn std::error::Error>> {
735 let mut loader = UniversalLoader::new();
736 loader.load_model(path)
737}
738
739pub fn find_model(model_name: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
741 let search_paths = get_model_search_paths()?;
742
743 for dir in search_paths {
744 let dir_path = Path::new(&dir);
745 if dir_path.exists() {
746 if let Ok(entries) = std::fs::read_dir(dir_path) {
748 for entry in entries.flatten() {
749 let path = entry.path();
750 if path.is_file() {
751 let filename = path.file_name()
752 .and_then(|n| n.to_str())
753 .unwrap_or("");
754
755 if filename.contains(model_name) {
756 return Ok(path);
757 }
758 }
759 }
760 }
761 }
762 }
763
764 let path = Path::new(model_name);
766 if path.exists() {
767 return Ok(path.to_path_buf());
768 }
769
770 Err(format!("Model '{}' not found in any standard location", model_name).into())
771}
772
773fn get_model_search_paths() -> Result<Vec<String>, Box<dyn std::error::Error>> {
775 let mut paths = Vec::new();
776
777 if cfg!(target_os = "windows") {
778 let appdata = std::env::var("LOCALAPPDATA")
780 .unwrap_or_else(|_| "C:\\Users\\Default\\AppData\\Local".to_string());
781 let userprofile = std::env::var("USERPROFILE")
782 .unwrap_or_else(|_| "C:\\Users\\Default".to_string());
783
784 paths.extend(vec![
785 format!("{}\\Ollama\\.ollama\\models\\blobs", appdata),
786 format!("{}\\.cache\\huggingface\\hub", userprofile),
787 format!("{}\\models", userprofile),
788 format!("{}\\LLM", userprofile),
789 "C:\\models".to_string(),
790 ".".to_string(),
791 ".\\models".to_string(),
792 ]);
793 } else if cfg!(target_os = "macos") {
794 let home = std::env::var("HOME").unwrap_or_else(|_| "/Users/Shared".to_string());
796
797 paths.extend(vec![
798 format!("{}/.ollama/models/blobs", home),
799 format!("{}/Library/Caches/huggingface/hub", home),
800 format!("{}/models", home),
801 format!("{}/LLM", home),
802 "/Applications/Ollama.app/Contents/Resources/models".to_string(),
803 "/usr/local/share/ollama/.ollama/models/blobs".to_string(),
804 "/models".to_string(),
805 ".".to_string(),
806 "./models".to_string(),
807 ]);
808 } else {
809 let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
811
812 paths.extend(vec![
813 format!("{}/.ollama/models/blobs", home),
815
816 "/usr/share/ollama/.ollama/models/blobs".to_string(),
818 "/usr/local/share/ollama/.ollama/models/blobs".to_string(),
819 "/var/lib/ollama/.ollama/models/blobs".to_string(),
820 "/opt/ollama/.ollama/models/blobs".to_string(),
821
822 format!("{}/.cache/huggingface/hub", home),
824
825 format!("{}/models", home),
827 format!("{}/LLM", home),
828 "/models".to_string(),
829 "/data/models".to_string(),
830 "/opt/models".to_string(),
831
832 ".".to_string(),
834 "./models".to_string(),
835 ]);
836 }
837
838 Ok(paths)
839}