use crate::tensor_core::{Tensor, Device};
use std::collections::HashMap;
use std::sync::Arc;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use candle_core::quantized::QMatMul;
pub trait Model: Send + Sync {
type Config: ModelConfig;
fn new(config: Self::Config) -> Result<Self> where Self: Sized;
fn from_weights(config: Self::Config, weights: ModelWeights) -> Result<Self> where Self: Sized;
fn forward(&self, inputs: &ModelInputs) -> Result<ModelOutputs>;
fn generate(&self, prompt: &str, config: &GenerationConfig) -> Result<String>;
fn config(&self) -> &Self::Config;
fn memory_requirements(&self) -> MemoryRequirements;
fn to_device(&mut self, device: &Device) -> Result<()>;
}
pub trait ModelConfig: Send + Sync + std::fmt::Debug {
fn architecture(&self) -> &str;
fn vocab_size(&self) -> usize;
fn hidden_size(&self) -> usize;
fn num_layers(&self) -> usize;
fn validate(&self) -> Result<()>;
}
#[derive(Debug, Clone)]
pub enum ModelInputs {
Text {
input_ids: Tensor,
attention_mask: Option<Tensor>,
position_ids: Option<Tensor>,
},
Image {
pixel_values: Tensor,
image_mask: Option<Tensor>,
},
Multimodal {
input_ids: Tensor,
pixel_values: Option<Tensor>,
attention_mask: Option<Tensor>,
image_mask: Option<Tensor>,
},
Audio {
input_features: Tensor,
attention_mask: Option<Tensor>,
},
}
#[derive(Debug, Clone)]
pub enum ModelOutputs {
Logits {
logits: Tensor,
hidden_states: Option<Tensor>,
},
Embeddings {
embeddings: Tensor,
pooled: Option<Tensor>,
},
Multimodal {
text_logits: Option<Tensor>,
image_logits: Option<Tensor>,
text_embeddings: Option<Tensor>,
image_embeddings: Option<Tensor>,
},
Sequence {
logits: Tensor,
encoder_hidden_states: Option<Tensor>,
decoder_hidden_states: Option<Tensor>,
},
CLIP {
logits_per_text: Tensor,
logits_per_image: Tensor,
text_embeds: Tensor,
image_embeds: Tensor,
},
}
#[derive(Clone)]
pub struct ModelWeights {
pub tensors: HashMap<String, Tensor>,
pub metadata: WeightMetadata,
pub gguf_config: Option<crate::weight_loader_core::GGUFModelConfig>,
pub gguf_tokenizer: Option<crate::weight_loader_core::GGUFTokenizer>,
pub quantized_tensors: HashMap<String, Arc<QMatMul>>,
#[cfg(feature = "simd")]
pub simd_quantized: HashMap<String, Arc<crate::simd::quant::QuantizedTensor>>,
}
impl std::fmt::Debug for ModelWeights {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("ModelWeights");
debug
.field("tensors", &format!("{} tensors", self.tensors.len()))
.field("metadata", &self.metadata)
.field("gguf_config", &self.gguf_config)
.field("gguf_tokenizer", &self.gguf_tokenizer.as_ref().map(|_| "..."))
.field("quantized_tensors", &format!("{} quantized", self.quantized_tensors.len()));
#[cfg(feature = "simd")]
debug.field("simd_quantized", &format!("{} simd", self.simd_quantized.len()));
debug.finish()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WeightMetadata {
pub architecture: String,
pub total_params: usize,
pub format: WeightFormat,
pub dtype: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WeightFormat {
SafeTensors,
PyTorch,
GGUF,
HuggingFace,
}
#[derive(Debug, Clone)]
pub struct GenerationConfig {
pub max_new_tokens: usize,
pub temperature: f32,
pub top_p: f32,
pub top_k: Option<usize>,
pub do_sample: bool,
pub repetition_penalty: f32,
pub stop_sequences: Vec<String>,
pub eos_token_id: u32,
pub pad_token_id: u32,
}
impl Default for GenerationConfig {
fn default() -> Self {
Self {
max_new_tokens: 100,
temperature: 1.0,
top_p: 0.9,
top_k: None,
do_sample: true,
repetition_penalty: 1.0,
stop_sequences: vec![],
eos_token_id: 2,
pad_token_id: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct MemoryRequirements {
pub gpu_memory: usize,
pub cpu_memory: usize,
pub kv_cache_memory: usize,
pub peak_memory: usize,
}
pub trait ModelFactory: Send + Sync {
fn create_model(&self, config_json: &str) -> Result<Box<dyn Model<Config = Box<dyn ModelConfig>>>>;
fn supported_architectures(&self) -> Vec<&str>;
fn supports(&self, architecture: &str) -> bool;
}
pub struct ModelRegistry {
factories: HashMap<String, Box<dyn ModelFactory>>,
}
impl ModelRegistry {
pub fn new() -> Self {
Self {
factories: HashMap::new(),
}
}
pub fn register<F>(&mut self, architecture: &str, factory: F)
where
F: ModelFactory + 'static,
{
self.factories.insert(architecture.to_string(), Box::new(factory));
}
pub fn create_model(&self, architecture: &str, config_json: &str) -> Result<Box<dyn Model<Config = Box<dyn ModelConfig>>>> {
let factory = self.factories.get(architecture)
.ok_or_else(|| anyhow::anyhow!("Unsupported architecture: {}", architecture))?;
factory.create_model(config_json)
}
pub fn supported_architectures(&self) -> Vec<String> {
self.factories.keys().cloned().collect()
}
pub fn supports(&self, architecture: &str) -> bool {
self.factories.contains_key(architecture)
}
}
static MODEL_REGISTRY: std::sync::OnceLock<std::sync::Mutex<ModelRegistry>> = std::sync::OnceLock::new();
pub fn registry() -> &'static std::sync::Mutex<ModelRegistry> {
MODEL_REGISTRY.get_or_init(|| std::sync::Mutex::new(ModelRegistry::new()))
}
impl ModelInputs {
pub fn text(input_ids: Tensor) -> Self {
Self::Text {
input_ids,
attention_mask: None,
position_ids: None,
}
}
pub fn text_with_mask(input_ids: Tensor, attention_mask: Tensor) -> Self {
Self::Text {
input_ids,
attention_mask: Some(attention_mask),
position_ids: None,
}
}
pub fn image(pixel_values: Tensor) -> Self {
Self::Image {
pixel_values,
image_mask: None,
}
}
pub fn multimodal(input_ids: Tensor, pixel_values: Option<Tensor>) -> Self {
Self::Multimodal {
input_ids,
pixel_values,
attention_mask: None,
image_mask: None,
}
}
pub fn batch_size(&self) -> usize {
match self {
Self::Text { input_ids, .. } => input_ids.shape()[0],
Self::Image { pixel_values, .. } => pixel_values.shape()[0],
Self::Multimodal { input_ids, .. } => input_ids.shape()[0],
Self::Audio { input_features, .. } => input_features.shape()[0],
}
}
pub fn sequence_length(&self) -> Option<usize> {
match self {
Self::Text { input_ids, .. } => Some(input_ids.shape()[1]),
Self::Multimodal { input_ids, .. } => Some(input_ids.shape()[1]),
_ => None,
}
}
}
impl ModelOutputs {
pub fn logits(logits: Tensor) -> Self {
Self::Logits {
logits,
hidden_states: None,
}
}
pub fn embeddings(embeddings: Tensor) -> Self {
Self::Embeddings {
embeddings,
pooled: None,
}
}
pub fn main_tensor(&self) -> &Tensor {
match self {
Self::Logits { logits, .. } => logits,
Self::Embeddings { embeddings, .. } => embeddings,
Self::Multimodal { text_logits: Some(logits), .. } => logits,
Self::Multimodal { image_logits: Some(logits), .. } => logits,
Self::Sequence { logits, .. } => logits,
_ => panic!("No main tensor available"),
}
}
}
impl ModelWeights {
pub fn new(tensors: HashMap<String, Tensor>, metadata: WeightMetadata) -> Self {
Self {
tensors,
metadata,
gguf_config: None,
gguf_tokenizer: None,
quantized_tensors: HashMap::new(),
#[cfg(feature = "simd")]
simd_quantized: HashMap::new(),
}
}
pub fn with_gguf_config(
tensors: HashMap<String, Tensor>,
metadata: WeightMetadata,
gguf_config: crate::weight_loader_core::GGUFModelConfig,
) -> Self {
Self {
tensors,
metadata,
gguf_config: Some(gguf_config),
gguf_tokenizer: None,
quantized_tensors: HashMap::new(),
#[cfg(feature = "simd")]
simd_quantized: HashMap::new(),
}
}
pub fn with_gguf_config_and_tokenizer(
tensors: HashMap<String, Tensor>,
metadata: WeightMetadata,
gguf_config: crate::weight_loader_core::GGUFModelConfig,
gguf_tokenizer: Option<crate::weight_loader_core::GGUFTokenizer>,
) -> Self {
Self {
tensors,
metadata,
gguf_config: Some(gguf_config),
gguf_tokenizer,
quantized_tensors: HashMap::new(),
#[cfg(feature = "simd")]
simd_quantized: HashMap::new(),
}
}
pub fn with_quantized(
tensors: HashMap<String, Tensor>,
metadata: WeightMetadata,
gguf_config: crate::weight_loader_core::GGUFModelConfig,
gguf_tokenizer: Option<crate::weight_loader_core::GGUFTokenizer>,
quantized_tensors: HashMap<String, Arc<QMatMul>>,
) -> Self {
Self {
tensors,
metadata,
gguf_config: Some(gguf_config),
gguf_tokenizer,
quantized_tensors,
#[cfg(feature = "simd")]
simd_quantized: HashMap::new(),
}
}
#[cfg(feature = "simd")]
pub fn with_simd_quantized(
tensors: HashMap<String, Tensor>,
metadata: WeightMetadata,
gguf_config: crate::weight_loader_core::GGUFModelConfig,
gguf_tokenizer: Option<crate::weight_loader_core::GGUFTokenizer>,
quantized_tensors: HashMap<String, Arc<QMatMul>>,
simd_quantized: HashMap<String, Arc<crate::simd::quant::QuantizedTensor>>,
) -> Self {
Self {
tensors,
metadata,
gguf_config: Some(gguf_config),
gguf_tokenizer,
quantized_tensors,
simd_quantized,
}
}
pub fn get(&self, name: &str) -> Option<&Tensor> {
self.tensors.get(name)
}
pub fn require(&self, name: &str) -> Result<&Tensor> {
self.tensors.get(name)
.ok_or_else(|| anyhow::anyhow!("Required tensor '{}' not found", name))
}
pub fn get_quantized(&self, name: &str) -> Option<Arc<QMatMul>> {
self.quantized_tensors.get(name).cloned()
}
pub fn has_quantized(&self, name: &str) -> bool {
self.quantized_tensors.contains_key(name)
}
#[cfg(feature = "simd")]
pub fn get_simd_quantized(&self, name: &str) -> Option<Arc<crate::simd::quant::QuantizedTensor>> {
self.simd_quantized.get(name).cloned()
}
#[cfg(feature = "simd")]
pub fn has_simd_quantized(&self, name: &str) -> bool {
self.simd_quantized.contains_key(name)
}
pub fn tensor_names(&self) -> Vec<&String> {
self.tensors.keys().collect()
}
pub fn to_device(&mut self, device: &Device) -> Result<()> {
for tensor in self.tensors.values_mut() {
*tensor = tensor.to_device(device)?;
}
Ok(())
}
}
pub trait MLPLayer: Send + Sync {
fn forward(&self, hidden_states: &Tensor) -> Result<Tensor>;
}
#[derive(Debug, Clone)]
pub struct MoEConfig {
pub num_experts: usize,
pub num_experts_per_tok: usize,
pub aux_loss: bool,
pub router_type: RouterType,
}
impl Default for MoEConfig {
fn default() -> Self {
Self {
num_experts: 8,
num_experts_per_tok: 2,
aux_loss: true,
router_type: RouterType::TopK,
}
}
}
#[derive(Debug, Clone)]
pub enum RouterType {
TopK,
ExpertChoice,
Soft,
}
#[derive(Debug)]
pub struct MoELayer {
pub router_weights: Tensor,
pub num_experts: usize,
pub num_experts_per_tok: usize,
pub device: Device,
}
impl MoELayer {
pub fn new(
router_weights: Tensor,
num_experts: usize,
num_experts_per_tok: usize,
device: Device,
) -> Self {
Self {
router_weights,
num_experts,
num_experts_per_tok,
device,
}
}
pub fn route(&self, hidden_states: &Tensor) -> Result<(Tensor, Tensor)> {
use crate::tensor_core::ops_fn;
let shape = hidden_states.shape();
let (batch, seq_len, _hidden_size) = (shape[0], shape[1], shape[2]);
let num_tokens = batch * seq_len;
let flat_hidden = hidden_states.reshape(&[num_tokens, shape[2]])?;
let router_logits = ops_fn::matmul(&flat_hidden, &self.router_weights)?;
let (topk_weights, topk_indices) = ops_fn::topk(&router_logits, self.num_experts_per_tok, -1)?;
let routing_weights = ops_fn::softmax(&topk_weights, -1)?;
Ok((routing_weights, topk_indices))
}
pub fn forward_with_experts<F>(&self, hidden_states: &Tensor, expert_fn: F) -> Result<Tensor>
where
F: Fn(&Tensor, usize) -> Result<Tensor>,
{
use crate::tensor_core::ops_fn;
let shape = hidden_states.shape();
let (batch, seq_len, hidden_size) = (shape[0], shape[1], shape[2]);
let num_tokens = batch * seq_len;
let (routing_weights, expert_indices) = self.route(hidden_states)?;
let flat_hidden = hidden_states.reshape(&[num_tokens, hidden_size])?;
let mut output = ops_fn::zeros(&[num_tokens, hidden_size], hidden_states.dtype(), &self.device)?;
for expert_idx in 0..self.num_experts {
let expert_indices_candle = expert_indices.to_candle()?;
let routing_weights_candle = routing_weights.to_candle()?;
for tok_idx in 0..num_tokens {
for k in 0..self.num_experts_per_tok {
let idx_val: Vec<i64> = expert_indices_candle.get(tok_idx)?.to_vec1()?;
if idx_val[k] as usize == expert_idx {
let token_hidden = flat_hidden.to_candle()?.get(tok_idx)?;
let token_tensor = Tensor::from_candle(token_hidden.unsqueeze(0)?);
let expert_output = expert_fn(&token_tensor, expert_idx)?;
let weight_val: Vec<f32> = routing_weights_candle.get(tok_idx)?.to_vec1()?;
let weight = weight_val[k];
let scaled_output = ops_fn::scale(&expert_output, weight)?;
let output_candle = output.to_candle()?;
let current = output_candle.get(tok_idx)?;
let new_val = (current + scaled_output.to_candle()?.squeeze(0)?)?;
let mut output_data: Vec<f32> = output.to_candle()?.flatten_all()?.to_vec1()?;
let new_data: Vec<f32> = new_val.to_vec1()?;
for (i, v) in new_data.iter().enumerate() {
output_data[tok_idx * hidden_size + i] = *v;
}
output = Tensor::from_f32_slice(&output_data, &[num_tokens, hidden_size], &self.device)?;
}
}
}
}
output.reshape(&[batch, seq_len, hidden_size])
}
}
#[derive(Debug)]
pub struct MoEExpert {
pub gate_proj: Tensor,
pub up_proj: Tensor,
pub down_proj: Tensor,
}
impl MoEExpert {
pub fn new(gate_proj: Tensor, up_proj: Tensor, down_proj: Tensor) -> Self {
Self { gate_proj, up_proj, down_proj }
}
pub fn forward(&self, hidden_states: &Tensor) -> Result<Tensor> {
use crate::tensor_core::ops_fn;
let gate = ops_fn::matmul(hidden_states, &self.gate_proj)?;
let gate_activated = ops_fn::silu(&gate)?;
let up = ops_fn::matmul(hidden_states, &self.up_proj)?;
let gated = ops_fn::mul(&gate_activated, &up)?;
ops_fn::matmul(&gated, &self.down_proj)
}
}
#[macro_export]
macro_rules! model_config {
($name:ident {
$($field:ident: $type:ty = $default:expr),* $(,)?
}) => {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct $name {
$(pub $field: $type,)*
}
impl Default for $name {
fn default() -> Self {
Self {
$($field: $default,)*
}
}
}
impl ModelConfig for $name {
fn architecture(&self) -> &str {
stringify!($name)
}
fn vocab_size(&self) -> usize {
self.vocab_size
}
fn hidden_size(&self) -> usize {
self.hidden_size
}
fn num_layers(&self) -> usize {
self.num_hidden_layers
}
fn validate(&self) -> Result<()> {
if self.vocab_size() == 0 {
return Err(anyhow::anyhow!("vocab_size must be > 0"));
}
if self.hidden_size() == 0 {
return Err(anyhow::anyhow!("hidden_size must be > 0"));
}
if self.num_layers() == 0 {
return Err(anyhow::anyhow!("num_layers must be > 0"));
}
Ok(())
}
}
};
}