use crate::quant::{Q4Matrix, QMatrix};
use std::any::Any;
use std::sync::OnceLock;
pub enum WeightRef<'a> {
Bf16(&'a [u16]),
Q8(&'a QMatrix),
Q4(&'a Q4Matrix),
}
impl WeightRef<'_> {
pub fn bytes(&self) -> usize {
match self {
WeightRef::Bf16(w) => w.len() * 2,
WeightRef::Q8(m) => m.bytes(),
WeightRef::Q4(m) => m.bytes(),
}
}
}
pub type DeviceWeight = Box<dyn Any + Send + Sync>;
pub struct Job<'a> {
pub w: WeightRef<'a>,
pub dev: &'a DeviceWeight,
pub n_out: usize,
pub y: &'a mut [f32],
}
#[derive(Clone, Copy, Debug)]
pub struct ConvGeom {
pub h: usize,
pub w: usize,
pub cin: usize,
pub k: usize,
pub stride: usize,
pub oh: usize,
pub ow: usize,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum NormKind {
Group(usize),
Layer,
}
#[derive(Clone, Copy, Debug)]
pub struct NormSpec<'a> {
pub kind: NormKind,
pub eps: f32,
pub gamma: &'a [f32],
pub beta: &'a [f32],
pub silu: bool,
}
pub trait Backend: Send + Sync {
fn name(&self) -> &str;
fn wants(&self, n_out: usize, n_in: usize, t: usize) -> bool;
fn upload(&self, w: &WeightRef, n_out: usize, n_in: usize) -> Option<DeviceWeight>;
fn matmul(&self, x: &[f32], t: usize, n_in: usize, jobs: &mut [Job]) -> bool;
fn conv2d(&self, _x: &[f32], _g: &ConvGeom, _norm: Option<&NormSpec>, _job: &mut Job) -> bool {
false
}
#[allow(clippy::too_many_arguments)]
fn attention(
&self,
_q: &[f32],
_k: &[f32],
_v: &[f32],
_tq: usize,
_tk: usize,
_heads: usize,
_dh: usize,
_causal: bool,
_out: &mut [f32],
) -> bool {
false
}
fn stats(&self) -> Option<String> {
None
}
}
static ACTIVE: OnceLock<Option<Box<dyn Backend>>> = OnceLock::new();
pub fn install(b: Option<Box<dyn Backend>>) {
let _ = ACTIVE.set(b);
}
pub fn active() -> Option<&'static dyn Backend> {
ACTIVE.get().and_then(|b| b.as_deref())
}
pub fn init(plat: &crate::platform::Platform, pref: crate::platform::DevicePref) -> Option<&'static dyn Backend> {
use crate::platform::DevicePref;
let chosen: Option<Box<dyn Backend>> = match plat.pick(pref) {
None => None,
Some(g) => {
let r: Result<Box<dyn Backend>, String> = match g.kind {
#[cfg(all(feature = "gpu", any(target_os = "macos", target_os = "ios"), target_arch = "aarch64"))]
crate::platform::GpuKind::Metal => crate::metal::MetalBackend::new()
.map(|b| Box::new(b) as Box<dyn Backend>)
.map_err(|e| e.to_string()),
#[cfg(all(feature = "gpu", not(any(target_os = "macos", target_os = "ios", target_arch = "wasm32"))))]
crate::platform::GpuKind::Cuda => crate::cuda::CudaBackend::new(g.index)
.map(|b| Box::new(b) as Box<dyn Backend>)
.map_err(|e| e.to_string()),
_ => Err(format!("{} compute is not implemented in this build", g.kind.as_str())),
};
match r {
Ok(b) => Some(b),
Err(e) => {
if pref != DevicePref::Auto || !e.contains("not implemented") {
eprintln!("[{}: {e}; using CPU]", g.kind.as_str());
}
None
}
}
}
};
install(chosen);
active()
}