1use crate::tensor_core::{Tensor, Device};
8use std::collections::HashMap;
9use std::sync::Arc;
10use anyhow::Result;
11use serde::{Deserialize, Serialize};
12use candle_core::quantized::QMatMul;
13
14pub trait Model: Send + Sync {
16 type Config: ModelConfig;
18
19 fn new(config: Self::Config) -> Result<Self> where Self: Sized;
21
22 fn from_weights(config: Self::Config, weights: ModelWeights) -> Result<Self> where Self: Sized;
24
25 fn forward(&self, inputs: &ModelInputs) -> Result<ModelOutputs>;
27
28 fn generate(&self, prompt: &str, config: &GenerationConfig) -> Result<String>;
30
31 fn config(&self) -> &Self::Config;
33
34 fn memory_requirements(&self) -> MemoryRequirements;
36
37 fn to_device(&mut self, device: &Device) -> Result<()>;
39}
40
41pub trait ModelConfig: Send + Sync + std::fmt::Debug {
43 fn architecture(&self) -> &str;
45
46 fn vocab_size(&self) -> usize;
48
49 fn hidden_size(&self) -> usize;
51
52 fn num_layers(&self) -> usize;
54
55 fn validate(&self) -> Result<()>;
57}
58
59#[derive(Debug, Clone)]
61pub enum ModelInputs {
62 Text {
64 input_ids: Tensor,
65 attention_mask: Option<Tensor>,
66 position_ids: Option<Tensor>,
67 },
68 Image {
70 pixel_values: Tensor,
71 image_mask: Option<Tensor>,
72 },
73 Multimodal {
75 input_ids: Tensor,
76 pixel_values: Option<Tensor>,
77 attention_mask: Option<Tensor>,
78 image_mask: Option<Tensor>,
79 },
80 Audio {
82 input_features: Tensor,
83 attention_mask: Option<Tensor>,
84 },
85}
86
87#[derive(Debug, Clone)]
89pub enum ModelOutputs {
90 Logits {
92 logits: Tensor,
93 hidden_states: Option<Tensor>,
94 },
95 Embeddings {
97 embeddings: Tensor,
98 pooled: Option<Tensor>,
99 },
100 Multimodal {
102 text_logits: Option<Tensor>,
103 image_logits: Option<Tensor>,
104 text_embeddings: Option<Tensor>,
105 image_embeddings: Option<Tensor>,
106 },
107 Sequence {
109 logits: Tensor,
110 encoder_hidden_states: Option<Tensor>,
111 decoder_hidden_states: Option<Tensor>,
112 },
113 CLIP {
115 logits_per_text: Tensor,
116 logits_per_image: Tensor,
117 text_embeds: Tensor,
118 image_embeds: Tensor,
119 },
120}
121
122#[derive(Clone)]
124pub struct ModelWeights {
125 pub tensors: HashMap<String, Tensor>,
127 pub metadata: WeightMetadata,
129 pub gguf_config: Option<crate::weight_loader_core::GGUFModelConfig>,
131 pub gguf_tokenizer: Option<crate::weight_loader_core::GGUFTokenizer>,
133 pub quantized_tensors: HashMap<String, Arc<QMatMul>>,
135 #[cfg(feature = "simd")]
137 pub simd_quantized: HashMap<String, Arc<crate::simd::quant::QuantizedTensor>>,
138}
139
140impl std::fmt::Debug for ModelWeights {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 let mut debug = f.debug_struct("ModelWeights");
143 debug
144 .field("tensors", &format!("{} tensors", self.tensors.len()))
145 .field("metadata", &self.metadata)
146 .field("gguf_config", &self.gguf_config)
147 .field("gguf_tokenizer", &self.gguf_tokenizer.as_ref().map(|_| "..."))
148 .field("quantized_tensors", &format!("{} quantized", self.quantized_tensors.len()));
149
150 #[cfg(feature = "simd")]
151 debug.field("simd_quantized", &format!("{} simd", self.simd_quantized.len()));
152
153 debug.finish()
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct WeightMetadata {
160 pub architecture: String,
162 pub total_params: usize,
164 pub format: WeightFormat,
166 pub dtype: String,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub enum WeightFormat {
173 SafeTensors,
174 PyTorch,
175 GGUF,
176 HuggingFace,
177}
178
179#[derive(Debug, Clone)]
181pub struct GenerationConfig {
182 pub max_new_tokens: usize,
183 pub temperature: f32,
184 pub top_p: f32,
185 pub top_k: Option<usize>,
186 pub do_sample: bool,
187 pub repetition_penalty: f32,
188 pub stop_sequences: Vec<String>,
189 pub eos_token_id: u32,
190 pub pad_token_id: u32,
191}
192
193impl Default for GenerationConfig {
194 fn default() -> Self {
195 Self {
196 max_new_tokens: 100,
197 temperature: 1.0,
198 top_p: 0.9,
199 top_k: None,
200 do_sample: true,
201 repetition_penalty: 1.0,
202 stop_sequences: vec![],
203 eos_token_id: 2,
204 pad_token_id: 0,
205 }
206 }
207}
208
209#[derive(Debug, Clone)]
211pub struct MemoryRequirements {
212 pub gpu_memory: usize,
214 pub cpu_memory: usize,
216 pub kv_cache_memory: usize,
218 pub peak_memory: usize,
220}
221
222pub trait ModelFactory: Send + Sync {
224 fn create_model(&self, config_json: &str) -> Result<Box<dyn Model<Config = Box<dyn ModelConfig>>>>;
226
227 fn supported_architectures(&self) -> Vec<&str>;
229
230 fn supports(&self, architecture: &str) -> bool;
232}
233
234pub struct ModelRegistry {
236 factories: HashMap<String, Box<dyn ModelFactory>>,
237}
238
239impl ModelRegistry {
240 pub fn new() -> Self {
241 Self {
242 factories: HashMap::new(),
243 }
244 }
245
246 pub fn register<F>(&mut self, architecture: &str, factory: F)
248 where
249 F: ModelFactory + 'static,
250 {
251 self.factories.insert(architecture.to_string(), Box::new(factory));
252 }
253
254 pub fn create_model(&self, architecture: &str, config_json: &str) -> Result<Box<dyn Model<Config = Box<dyn ModelConfig>>>> {
256 let factory = self.factories.get(architecture)
257 .ok_or_else(|| anyhow::anyhow!("Unsupported architecture: {}", architecture))?;
258
259 factory.create_model(config_json)
260 }
261
262 pub fn supported_architectures(&self) -> Vec<String> {
264 self.factories.keys().cloned().collect()
265 }
266
267 pub fn supports(&self, architecture: &str) -> bool {
269 self.factories.contains_key(architecture)
270 }
271}
272
273static MODEL_REGISTRY: std::sync::OnceLock<std::sync::Mutex<ModelRegistry>> = std::sync::OnceLock::new();
275
276pub fn registry() -> &'static std::sync::Mutex<ModelRegistry> {
278 MODEL_REGISTRY.get_or_init(|| std::sync::Mutex::new(ModelRegistry::new()))
279}
280
281impl ModelInputs {
283 pub fn text(input_ids: Tensor) -> Self {
285 Self::Text {
286 input_ids,
287 attention_mask: None,
288 position_ids: None,
289 }
290 }
291
292 pub fn text_with_mask(input_ids: Tensor, attention_mask: Tensor) -> Self {
294 Self::Text {
295 input_ids,
296 attention_mask: Some(attention_mask),
297 position_ids: None,
298 }
299 }
300
301 pub fn image(pixel_values: Tensor) -> Self {
303 Self::Image {
304 pixel_values,
305 image_mask: None,
306 }
307 }
308
309 pub fn multimodal(input_ids: Tensor, pixel_values: Option<Tensor>) -> Self {
311 Self::Multimodal {
312 input_ids,
313 pixel_values,
314 attention_mask: None,
315 image_mask: None,
316 }
317 }
318
319 pub fn batch_size(&self) -> usize {
321 match self {
322 Self::Text { input_ids, .. } => input_ids.shape()[0],
323 Self::Image { pixel_values, .. } => pixel_values.shape()[0],
324 Self::Multimodal { input_ids, .. } => input_ids.shape()[0],
325 Self::Audio { input_features, .. } => input_features.shape()[0],
326 }
327 }
328
329 pub fn sequence_length(&self) -> Option<usize> {
331 match self {
332 Self::Text { input_ids, .. } => Some(input_ids.shape()[1]),
333 Self::Multimodal { input_ids, .. } => Some(input_ids.shape()[1]),
334 _ => None,
335 }
336 }
337}
338
339impl ModelOutputs {
340 pub fn logits(logits: Tensor) -> Self {
342 Self::Logits {
343 logits,
344 hidden_states: None,
345 }
346 }
347
348 pub fn embeddings(embeddings: Tensor) -> Self {
350 Self::Embeddings {
351 embeddings,
352 pooled: None,
353 }
354 }
355
356 pub fn main_tensor(&self) -> &Tensor {
358 match self {
359 Self::Logits { logits, .. } => logits,
360 Self::Embeddings { embeddings, .. } => embeddings,
361 Self::Multimodal { text_logits: Some(logits), .. } => logits,
362 Self::Multimodal { image_logits: Some(logits), .. } => logits,
363 Self::Sequence { logits, .. } => logits,
364 _ => panic!("No main tensor available"),
365 }
366 }
367}
368
369impl ModelWeights {
370 pub fn new(tensors: HashMap<String, Tensor>, metadata: WeightMetadata) -> Self {
372 Self {
373 tensors,
374 metadata,
375 gguf_config: None,
376 gguf_tokenizer: None,
377 quantized_tensors: HashMap::new(),
378 #[cfg(feature = "simd")]
379 simd_quantized: HashMap::new(),
380 }
381 }
382
383 pub fn with_gguf_config(
385 tensors: HashMap<String, Tensor>,
386 metadata: WeightMetadata,
387 gguf_config: crate::weight_loader_core::GGUFModelConfig,
388 ) -> Self {
389 Self {
390 tensors,
391 metadata,
392 gguf_config: Some(gguf_config),
393 gguf_tokenizer: None,
394 quantized_tensors: HashMap::new(),
395 #[cfg(feature = "simd")]
396 simd_quantized: HashMap::new(),
397 }
398 }
399
400 pub fn with_gguf_config_and_tokenizer(
402 tensors: HashMap<String, Tensor>,
403 metadata: WeightMetadata,
404 gguf_config: crate::weight_loader_core::GGUFModelConfig,
405 gguf_tokenizer: Option<crate::weight_loader_core::GGUFTokenizer>,
406 ) -> Self {
407 Self {
408 tensors,
409 metadata,
410 gguf_config: Some(gguf_config),
411 gguf_tokenizer,
412 quantized_tensors: HashMap::new(),
413 #[cfg(feature = "simd")]
414 simd_quantized: HashMap::new(),
415 }
416 }
417
418 pub fn with_quantized(
420 tensors: HashMap<String, Tensor>,
421 metadata: WeightMetadata,
422 gguf_config: crate::weight_loader_core::GGUFModelConfig,
423 gguf_tokenizer: Option<crate::weight_loader_core::GGUFTokenizer>,
424 quantized_tensors: HashMap<String, Arc<QMatMul>>,
425 ) -> Self {
426 Self {
427 tensors,
428 metadata,
429 gguf_config: Some(gguf_config),
430 gguf_tokenizer,
431 quantized_tensors,
432 #[cfg(feature = "simd")]
433 simd_quantized: HashMap::new(),
434 }
435 }
436
437 #[cfg(feature = "simd")]
439 pub fn with_simd_quantized(
440 tensors: HashMap<String, Tensor>,
441 metadata: WeightMetadata,
442 gguf_config: crate::weight_loader_core::GGUFModelConfig,
443 gguf_tokenizer: Option<crate::weight_loader_core::GGUFTokenizer>,
444 quantized_tensors: HashMap<String, Arc<QMatMul>>,
445 simd_quantized: HashMap<String, Arc<crate::simd::quant::QuantizedTensor>>,
446 ) -> Self {
447 Self {
448 tensors,
449 metadata,
450 gguf_config: Some(gguf_config),
451 gguf_tokenizer,
452 quantized_tensors,
453 simd_quantized,
454 }
455 }
456
457 pub fn get(&self, name: &str) -> Option<&Tensor> {
459 self.tensors.get(name)
460 }
461
462 pub fn require(&self, name: &str) -> Result<&Tensor> {
464 self.tensors.get(name)
465 .ok_or_else(|| anyhow::anyhow!("Required tensor '{}' not found", name))
466 }
467
468 pub fn get_quantized(&self, name: &str) -> Option<Arc<QMatMul>> {
470 self.quantized_tensors.get(name).cloned()
471 }
472
473 pub fn has_quantized(&self, name: &str) -> bool {
475 self.quantized_tensors.contains_key(name)
476 }
477
478 #[cfg(feature = "simd")]
480 pub fn get_simd_quantized(&self, name: &str) -> Option<Arc<crate::simd::quant::QuantizedTensor>> {
481 self.simd_quantized.get(name).cloned()
482 }
483
484 #[cfg(feature = "simd")]
486 pub fn has_simd_quantized(&self, name: &str) -> bool {
487 self.simd_quantized.contains_key(name)
488 }
489
490 pub fn tensor_names(&self) -> Vec<&String> {
492 self.tensors.keys().collect()
493 }
494
495 pub fn to_device(&mut self, device: &Device) -> Result<()> {
497 for tensor in self.tensors.values_mut() {
498 *tensor = tensor.to_device(device)?;
499 }
500 Ok(())
501 }
502}
503
504pub trait MLPLayer: Send + Sync {
510 fn forward(&self, hidden_states: &Tensor) -> Result<Tensor>;
512}
513
514#[derive(Debug, Clone)]
516pub struct MoEConfig {
517 pub num_experts: usize,
519 pub num_experts_per_tok: usize,
521 pub aux_loss: bool,
523 pub router_type: RouterType,
525}
526
527impl Default for MoEConfig {
528 fn default() -> Self {
529 Self {
530 num_experts: 8,
531 num_experts_per_tok: 2,
532 aux_loss: true,
533 router_type: RouterType::TopK,
534 }
535 }
536}
537
538#[derive(Debug, Clone)]
540pub enum RouterType {
541 TopK,
543 ExpertChoice,
545 Soft,
547}
548
549#[derive(Debug)]
552pub struct MoELayer {
553 pub router_weights: Tensor,
555 pub num_experts: usize,
557 pub num_experts_per_tok: usize,
559 pub device: Device,
561}
562
563impl MoELayer {
564 pub fn new(
566 router_weights: Tensor,
567 num_experts: usize,
568 num_experts_per_tok: usize,
569 device: Device,
570 ) -> Self {
571 Self {
572 router_weights,
573 num_experts,
574 num_experts_per_tok,
575 device,
576 }
577 }
578
579 pub fn route(&self, hidden_states: &Tensor) -> Result<(Tensor, Tensor)> {
584 use crate::tensor_core::ops_fn;
585
586 let shape = hidden_states.shape();
588 let (batch, seq_len, _hidden_size) = (shape[0], shape[1], shape[2]);
589 let num_tokens = batch * seq_len;
590
591 let flat_hidden = hidden_states.reshape(&[num_tokens, shape[2]])?;
593
594 let router_logits = ops_fn::matmul(&flat_hidden, &self.router_weights)?;
596
597 let (topk_weights, topk_indices) = ops_fn::topk(&router_logits, self.num_experts_per_tok, -1)?;
599
600 let routing_weights = ops_fn::softmax(&topk_weights, -1)?;
602
603 Ok((routing_weights, topk_indices))
604 }
605
606 pub fn forward_with_experts<F>(&self, hidden_states: &Tensor, expert_fn: F) -> Result<Tensor>
609 where
610 F: Fn(&Tensor, usize) -> Result<Tensor>,
611 {
612 use crate::tensor_core::ops_fn;
613
614 let shape = hidden_states.shape();
615 let (batch, seq_len, hidden_size) = (shape[0], shape[1], shape[2]);
616 let num_tokens = batch * seq_len;
617
618 let (routing_weights, expert_indices) = self.route(hidden_states)?;
620
621 let flat_hidden = hidden_states.reshape(&[num_tokens, hidden_size])?;
623
624 let mut output = ops_fn::zeros(&[num_tokens, hidden_size], hidden_states.dtype(), &self.device)?;
626
627 for expert_idx in 0..self.num_experts {
630 let expert_indices_candle = expert_indices.to_candle()?;
633 let routing_weights_candle = routing_weights.to_candle()?;
634
635 for tok_idx in 0..num_tokens {
636 for k in 0..self.num_experts_per_tok {
637 let idx_val: Vec<i64> = expert_indices_candle.get(tok_idx)?.to_vec1()?;
638 if idx_val[k] as usize == expert_idx {
639 let token_hidden = flat_hidden.to_candle()?.get(tok_idx)?;
641 let token_tensor = Tensor::from_candle(token_hidden.unsqueeze(0)?);
642
643 let expert_output = expert_fn(&token_tensor, expert_idx)?;
645
646 let weight_val: Vec<f32> = routing_weights_candle.get(tok_idx)?.to_vec1()?;
648 let weight = weight_val[k];
649
650 let scaled_output = ops_fn::scale(&expert_output, weight)?;
652
653 let output_candle = output.to_candle()?;
655 let current = output_candle.get(tok_idx)?;
656 let new_val = (current + scaled_output.to_candle()?.squeeze(0)?)?;
657
658 let mut output_data: Vec<f32> = output.to_candle()?.flatten_all()?.to_vec1()?;
661 let new_data: Vec<f32> = new_val.to_vec1()?;
662 for (i, v) in new_data.iter().enumerate() {
663 output_data[tok_idx * hidden_size + i] = *v;
664 }
665 output = Tensor::from_f32_slice(&output_data, &[num_tokens, hidden_size], &self.device)?;
666 }
667 }
668 }
669 }
670
671 output.reshape(&[batch, seq_len, hidden_size])
673 }
674}
675
676#[derive(Debug)]
678pub struct MoEExpert {
679 pub gate_proj: Tensor,
681 pub up_proj: Tensor,
683 pub down_proj: Tensor,
685}
686
687impl MoEExpert {
688 pub fn new(gate_proj: Tensor, up_proj: Tensor, down_proj: Tensor) -> Self {
689 Self { gate_proj, up_proj, down_proj }
690 }
691
692 pub fn forward(&self, hidden_states: &Tensor) -> Result<Tensor> {
694 use crate::tensor_core::ops_fn;
695
696 let gate = ops_fn::matmul(hidden_states, &self.gate_proj)?;
698 let gate_activated = ops_fn::silu(&gate)?;
699 let up = ops_fn::matmul(hidden_states, &self.up_proj)?;
700 let gated = ops_fn::mul(&gate_activated, &up)?;
701 ops_fn::matmul(&gated, &self.down_proj)
702 }
703}
704
705#[macro_export]
711macro_rules! model_config {
712 ($name:ident {
713 $($field:ident: $type:ty = $default:expr),* $(,)?
714 }) => {
715 #[derive(Debug, Clone, Serialize, Deserialize)]
716 pub struct $name {
717 $(pub $field: $type,)*
718 }
719
720 impl Default for $name {
721 fn default() -> Self {
722 Self {
723 $($field: $default,)*
724 }
725 }
726 }
727
728 impl ModelConfig for $name {
729 fn architecture(&self) -> &str {
730 stringify!($name)
731 }
732
733 fn vocab_size(&self) -> usize {
734 self.vocab_size
735 }
736
737 fn hidden_size(&self) -> usize {
738 self.hidden_size
739 }
740
741 fn num_layers(&self) -> usize {
742 self.num_hidden_layers
743 }
744
745 fn validate(&self) -> Result<()> {
746 if self.vocab_size() == 0 {
747 return Err(anyhow::anyhow!("vocab_size must be > 0"));
748 }
749 if self.hidden_size() == 0 {
750 return Err(anyhow::anyhow!("hidden_size must be > 0"));
751 }
752 if self.num_layers() == 0 {
753 return Err(anyhow::anyhow!("num_layers must be > 0"));
754 }
755 Ok(())
756 }
757 }
758 };
759}
760
761