pub mod bert;
pub mod dspark;
pub mod lfm2;
pub mod llama;
pub mod pii;
pub mod transformer;
pub mod whisper;
pub mod whisper_preprocessor;
pub use pii::{DetectedEntity, HybridPiiModel, SlidingWindowScanner};
#[cfg(feature = "gpu")]
pub mod gpu_lfm2;
#[cfg(feature = "gpu")]
pub mod gpu_turboquant;
#[cfg(any(
feature = "gpu",
all(feature = "metal", any(target_os = "macos", target_os = "ios"))
))]
pub mod gpu_weight_source;
#[cfg(any(
feature = "gpu",
all(feature = "metal", any(target_os = "macos", target_os = "ios"))
))]
pub use gpu_weight_source::{GpuWeightSource, RopeType};
pub use transformer::WeightRef;
#[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))]
pub mod metal_lfm2;
#[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))]
pub mod metal_turboquant;
#[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))]
pub mod metal_audio_decoder;
#[cfg(feature = "gpu")]
pub mod wgpu_audio_decoder;
use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::{Result, bail, ensure};
use crate::gguf::GgufFile;
use crate::kv_cache::InferenceState;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockType {
Attention,
GatedConv,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScalarMultipliers {
pub embedding: f32,
pub residual: f32,
pub attn: Option<f32>,
pub logit: f32,
}
impl Default for ScalarMultipliers {
fn default() -> Self {
Self {
embedding: 1.0,
residual: 1.0,
attn: None,
logit: 1.0,
}
}
}
impl ScalarMultipliers {
pub fn from_gguf(gguf: &GgufFile, prefix: &str) -> Result<Self> {
let embedding = gguf
.get_f32(&format!("{prefix}.embedding_scale"))
.unwrap_or(1.0);
let residual = gguf
.get_f32(&format!("{prefix}.residual_scale"))
.unwrap_or(1.0);
let attn = gguf
.get_f32(&format!("{prefix}.attention.scale"))
.filter(|&s| s != 0.0);
let logit = gguf
.get_f32(&format!("{prefix}.logit_scale"))
.unwrap_or(1.0);
ensure!(logit != 0.0, "{prefix}.logit_scale must be non-zero");
Ok(Self {
embedding,
residual,
attn,
logit,
})
}
}
#[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 head_dim: 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 scalars: ScalarMultipliers,
pub moe: Option<MoeConfig>,
pub is_causal: bool,
pub class_labels: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MoeConfig {
pub n_expert: usize,
pub n_expert_used: usize,
pub expert_ff_len: usize,
pub is_moe_layer: Vec<bool>,
}
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_logits_all(
&self,
tokens: &[u32],
start_pos: usize,
state: &mut InferenceState,
) -> Vec<f32> {
let _ = (tokens, start_pos, state);
Vec::new()
}
fn supports_all_logits(&self) -> bool {
false
}
fn truncate_kv(&self, state: &mut InferenceState, len: usize) {
state.truncate_to(len);
}
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 supports_hidden_states(&self) -> bool {
false
}
fn hidden_states(&self, tokens: &[u32], state: &mut InferenceState) -> Vec<f32> {
let _ = (tokens, state);
unimplemented!("hidden_states not supported by this backend")
}
fn forward_greedy(&self, tokens: &[u32], pos: usize, state: &mut InferenceState) -> u32 {
let logits = self.forward(tokens, pos, state);
crate::sampler::argmax(&logits)
}
fn gpu_memory_bytes(&self) -> u64 {
0
}
fn configure_cache(&self, _config: crate::kv_cache::KvCacheConfig) {}
fn clear_warm_cache(&self) {}
fn clear_cache(&self) {}
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 supports_moe_lora(&self) -> bool {
false
}
fn turboquant_supported(&self) -> bool {
false
}
fn configure_kv_compression(
&self,
_compression: &crate::kv_cache::KvCompression,
) -> Result<(), crate::CeraError> {
Ok(())
}
fn f16_kv_supported(&self) -> bool {
false
}
fn is_classifier(&self) -> bool {
false
}
fn num_classes(&self) -> usize {
0
}
fn class_labels(&self) -> &[String] {
&[]
}
fn classify_tokens(
&self,
tokens: &[u32],
state: &mut InferenceState,
) -> Result<Vec<f32>, crate::CeraError> {
let _ = (tokens, state);
Err(crate::CeraError::Backend(
"classification not supported by this model".into(),
))
}
}
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();
#[cfg(all(feature = "parallel", not(target_arch = "wasm32")))]
let shape = crate::backend::calibrate::DecodeShape::from_gguf(&gguf);
let model: Box<dyn Model> =
match arch.as_str() {
"lfm2" | "lfm2moe" => Box::new(lfm2::Lfm2Model::from_gguf_with_id(
gguf,
context_size,
model_id,
)?),
"qwen2" | "qwen3" | "llama" | "granite" => Box::new(
llama::LlamaModel::from_gguf_with_id(gguf, context_size, model_id)?,
),
"bert" | "modernbert" => Box::new(bert::BertModel::from_gguf_with_id(
gguf,
context_size,
model_id,
)?),
other => bail!("unsupported architecture: {other}"),
};
#[cfg(all(feature = "parallel", not(target_arch = "wasm32")))]
if let Some(shape) = shape {
crate::backend::calibrate::set_decode_shape(shape);
}
Ok(model)
}
#[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" | "lfm2moe" => Ok(Box::new(gpu_lfm2::GpuLfm2Model::from_gguf_with_id(
gguf,
context_size,
model_id,
)?)),
"qwen2" | "qwen3" | "llama" | "granite" => Ok(Box::new(
gpu_lfm2::GpuLfm2Model::from_llama_with_id(gguf, context_size, model_id)?,
)),
other => bail!("unsupported architecture for GPU: {other}"),
}
}
#[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))]
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" | "lfm2moe" => Ok(Box::new(metal_lfm2::MetalLfm2Model::from_gguf(
gguf,
path,
context_size,
)?)),
"qwen2" | "qwen3" | "llama" | "granite" => Ok(Box::new(
metal_lfm2::MetalLfm2Model::from_llama(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_encoder_gpu;
pub mod audio_preprocessor;
pub mod vision_encoder;
pub mod vision_encoder_gpu;
#[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>>();
}