pub mod lfm2;
pub mod llama;
#[cfg(feature = "gpu")]
pub mod gpu_lfm2;
#[cfg(all(feature = "metal", target_os = "macos"))]
pub mod metal_lfm2;
#[cfg(all(feature = "metal", target_os = "macos"))]
pub mod metal_audio_decoder;
use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::{Result, bail};
use crate::gguf::GgufFile;
use crate::kv_cache::InferenceState;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockType {
Attention,
GatedConv,
}
#[derive(Debug, Clone)]
pub struct ModelConfig {
pub architecture: String,
pub n_layers: usize,
pub hidden_size: usize,
pub intermediate_size: usize,
pub n_heads: usize,
pub n_kv_heads: usize,
pub vocab_size: usize,
pub max_seq_len: usize,
pub rope_theta: f32,
pub rms_norm_eps: f32,
pub block_types: Vec<BlockType>,
pub conv_kernel_size: Option<usize>,
pub kv_heads_per_layer: Vec<usize>,
}
pub trait Model: Send + Sync {
fn forward(&self, tokens: &[u32], pos: usize, state: &mut InferenceState) -> Vec<f32>;
fn forward_prefill(
&self,
tokens: &[u32],
start_pos: usize,
state: &mut InferenceState,
) -> Vec<f32> {
let mut logits = Vec::new();
for (i, &token) in tokens.iter().enumerate() {
logits = self.forward(&[token], start_pos + i, state);
}
logits
}
fn forward_prefill_chunked(
&self,
tokens: &[u32],
start_pos: usize,
state: &mut InferenceState,
ubatch: usize,
cancel: &AtomicBool,
) -> (usize, Option<Vec<f32>>) {
let ubatch = if ubatch == 0 {
tokens.len().max(1)
} else {
ubatch
};
let mut consumed = 0usize;
let mut last_logits: Option<Vec<f32>> = None;
for chunk in tokens.chunks(ubatch) {
let logits = self.forward_prefill(chunk, start_pos + consumed, state);
consumed += chunk.len();
last_logits = Some(logits);
if cancel.load(Ordering::Relaxed) && consumed < tokens.len() {
break;
}
}
(consumed, last_logits)
}
fn config(&self) -> &ModelConfig;
fn supports_kv_shift(&self) -> bool {
false
}
fn shift_kv(&self, _state: &mut InferenceState, _n_keep: usize, _shift: usize) {}
fn forward_embedding(
&self,
tokens: &[u32],
_pos: usize,
_state: &mut InferenceState,
) -> Vec<f32> {
let _ = tokens;
unimplemented!("forward_embedding not supported by this backend")
}
fn supports_embedding_input(&self) -> bool {
false
}
fn forward_from_embedding(
&self,
_embedding: &[f32],
_pos: usize,
_state: &mut InferenceState,
) -> Vec<f32> {
unimplemented!("forward_from_embedding not supported by this backend")
}
fn forward_hidden_from_embedding(
&self,
_embedding: &[f32],
_pos: usize,
_state: &mut InferenceState,
) -> Vec<f32> {
unimplemented!("forward_hidden_from_embedding not supported by this backend")
}
fn forward_prefill_from_embeddings(
&self,
embeddings: &[f32],
n_tokens: usize,
start_pos: usize,
state: &mut InferenceState,
) -> Vec<f32> {
let hidden_size = self.config().hidden_size;
assert!(
n_tokens > 0,
"forward_prefill_from_embeddings requires at least one frame"
);
assert_eq!(
embeddings.len(),
n_tokens * hidden_size,
"embeddings.len() ({}) != n_tokens ({}) * hidden_size ({})",
embeddings.len(),
n_tokens,
hidden_size
);
let mut logits = Vec::new();
for i in 0..n_tokens {
let frame = &embeddings[i * hidden_size..(i + 1) * hidden_size];
logits = self.forward_from_embedding(frame, start_pos + i, state);
}
logits
}
fn forward_greedy(&self, tokens: &[u32], pos: usize, state: &mut InferenceState) -> u32 {
let logits = self.forward(tokens, pos, state);
crate::sampler::cpu_argmax(&logits)
}
fn gpu_memory_bytes(&self) -> u64 {
0
}
fn configure_cache(&self, _config: crate::kv_cache::KvCacheConfig) {}
fn snapshot_state(&self) -> crate::kv_cache::StateSnapshot {
unimplemented!("snapshot_state not supported by this backend")
}
fn restore_state(&self, _snapshot: &crate::kv_cache::StateSnapshot) {
unimplemented!("restore_state not supported by this backend")
}
fn turboquant_supported(&self) -> bool {
false
}
}
pub fn load_model(
gguf: GgufFile,
path: Option<&std::path::Path>,
context_size: usize,
) -> Result<Box<dyn Model>> {
let arch = gguf
.get_str("general.architecture")
.unwrap_or("unknown")
.to_string();
let model_id = path
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default();
match arch.as_str() {
"lfm2" => Ok(Box::new(lfm2::Lfm2Model::from_gguf_with_id(
gguf,
context_size,
model_id,
)?)),
other => bail!("unsupported architecture: {other}"),
}
}
#[cfg(feature = "gpu")]
pub fn load_model_gpu(
gguf: GgufFile,
path: Option<&std::path::Path>,
context_size: usize,
) -> Result<Box<dyn Model>> {
let arch = gguf
.get_str("general.architecture")
.unwrap_or("unknown")
.to_string();
let model_id = path
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default();
match arch.as_str() {
"lfm2" => Ok(Box::new(gpu_lfm2::GpuLfm2Model::from_gguf_with_id(
gguf,
context_size,
model_id,
)?)),
other => bail!("unsupported architecture for GPU: {other}"),
}
}
#[cfg(all(feature = "metal", target_os = "macos"))]
pub fn load_model_metal(
gguf: GgufFile,
path: &std::path::Path,
context_size: usize,
) -> Result<Box<dyn Model>> {
let arch = gguf
.get_str("general.architecture")
.unwrap_or("unknown")
.to_string();
match arch.as_str() {
"lfm2" => Ok(Box::new(metal_lfm2::MetalLfm2Model::from_gguf(
gguf,
path,
context_size,
)?)),
other => bail!("unsupported architecture for Metal: {other}"),
}
}
#[allow(
clippy::too_many_arguments,
clippy::needless_range_loop,
clippy::manual_saturating_arithmetic,
unused_variables
)]
pub mod audio_decoder;
pub mod audio_encoder;
pub mod audio_preprocessor;
pub mod vision_encoder;
#[cfg(feature = "vl-preprocess")]
pub mod vision_preprocessor;
pub mod weights;
#[allow(dead_code)]
fn _assert_arc_dyn_model_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<std::sync::Arc<dyn Model>>();
}