Skip to main content

ohms_adaptq/
universal_loader.rs

1// Universal Model Loader - Supports ANY LLM format
2use 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/// Universal model that can represent any LLM
10#[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,           // Ollama, llama.cpp models
22    GGUF,           // New GGML format
23    ONNX,           // ONNX models
24    SafeTensors,    // HuggingFace SafeTensors
25    PyTorch,        // .pt, .pth, .bin files
26    TensorFlow,     // .pb, .h5 files
27    JAX,            // JAX/Flax models
28    Paddle,         // PaddlePaddle models
29    MXNet,          // Apache MXNet
30    CoreML,         // Apple CoreML
31    TensorRT,       // NVIDIA TensorRT
32    OpenVINO,       // Intel OpenVINO
33    NCNN,           // Tencent NCNN
34    TFLite,         // TensorFlow Lite
35    Custom(String), // Any other format
36}
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// f16 type for Rust
171#[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        // Simple f32 to f16 conversion
192        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    /// Load any model format
227    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    /// Detect model format from file extension and magic bytes
242    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        // Check by extension first
248        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                // Try to detect by magic bytes
260                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        // Check magic bytes
273        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) // ZIP file
281        } else if &magic[0..4] == b"\x93NUMPY" {
282            Ok(ModelFormat::SafeTensors)
283        } else {
284            Ok(ModelFormat::Custom("unknown".to_string()))
285        }
286    }
287    
288    /// Load ONNX model
289    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        // Parse ONNX protobuf (simplified)
294        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    /// Load SafeTensors model
345    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        // Build tensor list from header entries
351        let mut layers: Vec<Layer> = Vec::new();
352        if let serde_json::Value::Object(map) = &header {
353            // Group by layer prefix (naive heuristic): split names by '.' and use first two segments
354            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    /// Load PyTorch model
433    fn load_pytorch(&mut self, path: &Path) -> Result<UniversalModel, Box<dyn std::error::Error>> {
434        // PyTorch models are ZIP files with pickle data
435        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    /// Load TensorFlow model
453    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    /// Load GGML/GGUF models
503    fn load_ggml(&mut self, path: &Path) -> Result<UniversalModel, Box<dyn std::error::Error>> {
504        // Fallback generic loader for GGML/GGUF when specialized loader is not available
505        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    /// Load generic/unknown format
554    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, // Rough estimate
561            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    // Helper methods
606    fn extract_layers_from_mmap(&self, path: &Path) -> Result<Vec<Layer>, Box<dyn std::error::Error>> {
607        // Extract layers from memory-mapped file
608        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    // No-op stub when specialized GGML parsing is unavailable
706    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
733/// Auto-detect and load any model
734pub 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
739/// Find model from various sources
740pub 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            // Search for model files
747            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    // Try exact path
765    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
773/// Get cross-platform model search paths
774fn 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        // Windows paths
779        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        // macOS paths
795        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        // Linux/Unix paths
810        let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
811        
812        paths.extend(vec![
813            // User-specific Ollama
814            format!("{}/.ollama/models/blobs", home),
815            
816            // System Ollama locations
817            "/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            // HuggingFace cache
823            format!("{}/.cache/huggingface/hub", home),
824            
825            // Common model directories
826            format!("{}/models", home),
827            format!("{}/LLM", home),
828            "/models".to_string(),
829            "/data/models".to_string(),
830            "/opt/models".to_string(),
831            
832            // Current directory
833            ".".to_string(),
834            "./models".to_string(),
835        ]);
836    }
837    
838    Ok(paths)
839}