Skip to main content

runtime/
model_core.rs

1//! Core Model Abstraction
2//!
3//! This module provides the foundational model abstraction that all
4//! model implementations build upon. It defines clean interfaces that
5//! hide implementation complexity.
6
7use 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
14/// Core model trait - the single interface all models implement
15pub trait Model: Send + Sync {
16    /// Model configuration type
17    type Config: ModelConfig;
18
19    /// Create new model instance
20    fn new(config: Self::Config) -> Result<Self> where Self: Sized;
21
22    /// Load model with weights
23    fn from_weights(config: Self::Config, weights: ModelWeights) -> Result<Self> where Self: Sized;
24
25    /// Forward pass - core inference method
26    fn forward(&self, inputs: &ModelInputs) -> Result<ModelOutputs>;
27
28    /// Generate text (high-level interface)
29    fn generate(&self, prompt: &str, config: &GenerationConfig) -> Result<String>;
30
31    /// Get model configuration
32    fn config(&self) -> &Self::Config;
33
34    /// Get model memory requirements
35    fn memory_requirements(&self) -> MemoryRequirements;
36
37    /// Move model to device
38    fn to_device(&mut self, device: &Device) -> Result<()>;
39}
40
41/// Model configuration trait
42pub trait ModelConfig: Send + Sync + std::fmt::Debug {
43    /// Get model architecture name
44    fn architecture(&self) -> &str;
45
46    /// Get vocabulary size
47    fn vocab_size(&self) -> usize;
48
49    /// Get hidden dimension
50    fn hidden_size(&self) -> usize;
51
52    /// Get number of layers
53    fn num_layers(&self) -> usize;
54
55    /// Validate configuration
56    fn validate(&self) -> Result<()>;
57}
58
59/// Unified model inputs - supports all model types
60#[derive(Debug, Clone)]
61pub enum ModelInputs {
62    /// Text-only inputs (most language models)
63    Text {
64        input_ids: Tensor,
65        attention_mask: Option<Tensor>,
66        position_ids: Option<Tensor>,
67    },
68    /// Image-only inputs (vision models)
69    Image {
70        pixel_values: Tensor,
71        image_mask: Option<Tensor>,
72    },
73    /// Multimodal inputs (vision-language models)
74    Multimodal {
75        input_ids: Tensor,
76        pixel_values: Option<Tensor>,
77        attention_mask: Option<Tensor>,
78        image_mask: Option<Tensor>,
79    },
80    /// Audio inputs (speech models)
81    Audio {
82        input_features: Tensor,
83        attention_mask: Option<Tensor>,
84    },
85}
86
87/// Unified model outputs - supports all model types
88#[derive(Debug, Clone)]
89pub enum ModelOutputs {
90    /// Language model logits
91    Logits {
92        logits: Tensor,
93        hidden_states: Option<Tensor>,
94    },
95    /// Embeddings/representations
96    Embeddings {
97        embeddings: Tensor,
98        pooled: Option<Tensor>,
99    },
100    /// Multimodal outputs
101    Multimodal {
102        text_logits: Option<Tensor>,
103        image_logits: Option<Tensor>,
104        text_embeddings: Option<Tensor>,
105        image_embeddings: Option<Tensor>,
106    },
107    /// Sequence-to-sequence outputs
108    Sequence {
109        logits: Tensor,
110        encoder_hidden_states: Option<Tensor>,
111        decoder_hidden_states: Option<Tensor>,
112    },
113    /// CLIP contrastive outputs
114    CLIP {
115        logits_per_text: Tensor,
116        logits_per_image: Tensor,
117        text_embeds: Tensor,
118        image_embeds: Tensor,
119    },
120}
121
122/// Model weights container
123#[derive(Clone)]
124pub struct ModelWeights {
125    /// Tensor weights by name (F32 dequantized, for compatibility)
126    pub tensors: HashMap<String, Tensor>,
127    /// Metadata
128    pub metadata: WeightMetadata,
129    /// GGUF-specific config (populated when loading from GGUF)
130    pub gguf_config: Option<crate::weight_loader_core::GGUFModelConfig>,
131    /// GGUF tokenizer data (populated when loading from GGUF)
132    pub gguf_tokenizer: Option<crate::weight_loader_core::GGUFTokenizer>,
133    /// Quantized weights (QMatMul) for efficient inference
134    pub quantized_tensors: HashMap<String, Arc<QMatMul>>,
135    /// Native SIMD quantized weights for maximum performance
136    #[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/// Weight metadata
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct WeightMetadata {
160    /// Model architecture
161    pub architecture: String,
162    /// Total parameters
163    pub total_params: usize,
164    /// Weight format
165    pub format: WeightFormat,
166    /// Precision
167    pub dtype: String,
168}
169
170/// Supported weight formats
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub enum WeightFormat {
173    SafeTensors,
174    PyTorch,
175    GGUF,
176    HuggingFace,
177}
178
179/// Generation configuration
180#[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/// Memory requirements for a model
210#[derive(Debug, Clone)]
211pub struct MemoryRequirements {
212    /// GPU memory in bytes
213    pub gpu_memory: usize,
214    /// CPU memory in bytes
215    pub cpu_memory: usize,
216    /// KV cache memory in bytes
217    pub kv_cache_memory: usize,
218    /// Peak memory during inference
219    pub peak_memory: usize,
220}
221
222/// Model factory trait for creating models by name
223pub trait ModelFactory: Send + Sync {
224    /// Create model from configuration
225    fn create_model(&self, config_json: &str) -> Result<Box<dyn Model<Config = Box<dyn ModelConfig>>>>;
226
227    /// Get supported architecture names
228    fn supported_architectures(&self) -> Vec<&str>;
229
230    /// Check if architecture is supported
231    fn supports(&self, architecture: &str) -> bool;
232}
233
234/// Model registry for managing all supported architectures
235pub 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    /// Register a model factory
247    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    /// Create model by architecture name
255    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    /// Get all supported architectures
263    pub fn supported_architectures(&self) -> Vec<String> {
264        self.factories.keys().cloned().collect()
265    }
266
267    /// Check if architecture is supported
268    pub fn supports(&self, architecture: &str) -> bool {
269        self.factories.contains_key(architecture)
270    }
271}
272
273/// Global model registry
274static MODEL_REGISTRY: std::sync::OnceLock<std::sync::Mutex<ModelRegistry>> = std::sync::OnceLock::new();
275
276/// Get global model registry
277pub fn registry() -> &'static std::sync::Mutex<ModelRegistry> {
278    MODEL_REGISTRY.get_or_init(|| std::sync::Mutex::new(ModelRegistry::new()))
279}
280
281/// Utility functions for model inputs/outputs
282impl ModelInputs {
283    /// Create text inputs
284    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    /// Create text inputs with attention mask
293    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    /// Create image inputs
302    pub fn image(pixel_values: Tensor) -> Self {
303        Self::Image {
304            pixel_values,
305            image_mask: None,
306        }
307    }
308
309    /// Create multimodal inputs
310    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    /// Get batch size
320    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    /// Get sequence length (for text inputs)
330    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    /// Create logits output
341    pub fn logits(logits: Tensor) -> Self {
342        Self::Logits {
343            logits,
344            hidden_states: None,
345        }
346    }
347
348    /// Create embeddings output
349    pub fn embeddings(embeddings: Tensor) -> Self {
350        Self::Embeddings {
351            embeddings,
352            pooled: None,
353        }
354    }
355
356    /// Get main tensor output
357    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    /// Create new model weights
371    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    /// Create new model weights with GGUF config
384    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    /// Create new model weights with GGUF config and tokenizer
401    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    /// Create new model weights with GGUF config, tokenizer, and quantized tensors
419    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    /// Create new model weights with GGUF config, tokenizer, quantized tensors, and SIMD quantized
438    #[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    /// Get tensor by name
458    pub fn get(&self, name: &str) -> Option<&Tensor> {
459        self.tensors.get(name)
460    }
461
462    /// Get tensor by name (required)
463    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    /// Get quantized tensor (QMatMul) by name
469    pub fn get_quantized(&self, name: &str) -> Option<Arc<QMatMul>> {
470        self.quantized_tensors.get(name).cloned()
471    }
472
473    /// Check if a tensor has a quantized version
474    pub fn has_quantized(&self, name: &str) -> bool {
475        self.quantized_tensors.contains_key(name)
476    }
477
478    /// Get SIMD quantized tensor by name
479    #[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    /// Check if a tensor has a SIMD quantized version
485    #[cfg(feature = "simd")]
486    pub fn has_simd_quantized(&self, name: &str) -> bool {
487        self.simd_quantized.contains_key(name)
488    }
489
490    /// Get all tensor names
491    pub fn tensor_names(&self) -> Vec<&String> {
492        self.tensors.keys().collect()
493    }
494
495    /// Move all tensors to device
496    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
504// ============================================================================
505// Mixture of Experts (MoE) Support
506// ============================================================================
507
508/// MLP trait for expert networks in MoE layers
509pub trait MLPLayer: Send + Sync {
510    /// Forward pass through the MLP
511    fn forward(&self, hidden_states: &Tensor) -> Result<Tensor>;
512}
513
514/// Configuration for MoE layer
515#[derive(Debug, Clone)]
516pub struct MoEConfig {
517    /// Number of experts
518    pub num_experts: usize,
519    /// Number of experts activated per token
520    pub num_experts_per_tok: usize,
521    /// Whether to use auxiliary load balancing loss
522    pub aux_loss: bool,
523    /// Router type (softmax, top-k, etc.)
524    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/// Router types for MoE
539#[derive(Debug, Clone)]
540pub enum RouterType {
541    /// Standard top-k routing
542    TopK,
543    /// Expert choice routing (each expert chooses tokens)
544    ExpertChoice,
545    /// Soft routing with weighted combination
546    Soft,
547}
548
549/// Mixture of Experts layer
550/// Routes tokens to top-k experts and combines their outputs
551#[derive(Debug)]
552pub struct MoELayer {
553    /// Router weights: [hidden_size, num_experts]
554    pub router_weights: Tensor,
555    /// Number of experts
556    pub num_experts: usize,
557    /// Number of experts per token
558    pub num_experts_per_tok: usize,
559    /// Device
560    pub device: Device,
561}
562
563impl MoELayer {
564    /// Create new MoE layer
565    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    /// Compute routing weights and indices for each token
580    /// Returns (routing_weights, expert_indices) where:
581    /// - routing_weights: [batch * seq_len, num_experts_per_tok] - normalized weights
582    /// - expert_indices: [batch * seq_len, num_experts_per_tok] - expert indices
583    pub fn route(&self, hidden_states: &Tensor) -> Result<(Tensor, Tensor)> {
584        use crate::tensor_core::ops_fn;
585
586        // hidden_states: [batch, seq_len, hidden_size]
587        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        // Reshape to [batch * seq_len, hidden_size]
592        let flat_hidden = hidden_states.reshape(&[num_tokens, shape[2]])?;
593
594        // Compute router logits: [batch * seq_len, num_experts]
595        let router_logits = ops_fn::matmul(&flat_hidden, &self.router_weights)?;
596
597        // Get top-k experts
598        let (topk_weights, topk_indices) = ops_fn::topk(&router_logits, self.num_experts_per_tok, -1)?;
599
600        // Softmax over the selected experts to get normalized weights
601        let routing_weights = ops_fn::softmax(&topk_weights, -1)?;
602
603        Ok((routing_weights, topk_indices))
604    }
605
606    /// Forward pass through MoE layer
607    /// expert_fn: function that takes (hidden_states, expert_idx) and returns expert output
608    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        // Get routing weights and indices
619        let (routing_weights, expert_indices) = self.route(hidden_states)?;
620
621        // Flatten hidden states for processing
622        let flat_hidden = hidden_states.reshape(&[num_tokens, hidden_size])?;
623
624        // Initialize output tensor
625        let mut output = ops_fn::zeros(&[num_tokens, hidden_size], hidden_states.dtype(), &self.device)?;
626
627        // Process each expert
628        // This is a basic implementation - production would batch tokens by expert
629        for expert_idx in 0..self.num_experts {
630            // Find tokens routed to this expert
631            // For each position in top-k, check if it matches this expert
632            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                        // Get this token's hidden state
640                        let token_hidden = flat_hidden.to_candle()?.get(tok_idx)?;
641                        let token_tensor = Tensor::from_candle(token_hidden.unsqueeze(0)?);
642
643                        // Get expert output
644                        let expert_output = expert_fn(&token_tensor, expert_idx)?;
645
646                        // Get routing weight for this expert
647                        let weight_val: Vec<f32> = routing_weights_candle.get(tok_idx)?.to_vec1()?;
648                        let weight = weight_val[k];
649
650                        // Scale by routing weight and add to output
651                        let scaled_output = ops_fn::scale(&expert_output, weight)?;
652
653                        // Add to output at this position
654                        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                        // Update output tensor at this position
659                        // This is inefficient but works for the basic implementation
660                        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        // Reshape back to [batch, seq_len, hidden_size]
672        output.reshape(&[batch, seq_len, hidden_size])
673    }
674}
675
676/// Helper struct for a standard MoE expert (simple MLP)
677#[derive(Debug)]
678pub struct MoEExpert {
679    /// Gate projection: hidden_size -> intermediate_size
680    pub gate_proj: Tensor,
681    /// Up projection: hidden_size -> intermediate_size
682    pub up_proj: Tensor,
683    /// Down projection: intermediate_size -> hidden_size
684    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    /// Forward pass: SwiGLU activation
693    pub fn forward(&self, hidden_states: &Tensor) -> Result<Tensor> {
694        use crate::tensor_core::ops_fn;
695
696        // SwiGLU: down(silu(gate(x)) * up(x))
697        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// ============================================================================
706// Model Configuration Macro
707// ============================================================================
708
709/// Helper macro for creating model configurations
710#[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// Example usage would be:
762// model_config!(LlamaConfig {
763//     vocab_size: usize = 32000,
764//     hidden_size: usize = 4096,
765//     // ... other fields
766// });