mod cells;
use candle_core::DType;
use candle_core::Device;
pub use cells::Backend;
pub(crate) use cells::CellException;
#[cfg(test)]
pub(crate) use cells::rules;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum Support {
Verified,
Pending,
Untested,
Unsupported,
NotApplicable,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct BackendSupport {
pub cpu: Support,
pub metal: Support,
pub cuda: Support,
}
impl BackendSupport {
pub fn untested() -> Self {
Self {
cpu: Support::Untested,
metal: Support::Untested,
cuda: Support::Untested,
}
}
pub fn needs_accelerator(&self) -> bool {
self.cpu == Support::Unsupported && self.metal != Support::Unsupported
}
pub const fn not_applicable() -> Self {
Self {
cpu: Support::NotApplicable,
metal: Support::NotApplicable,
cuda: Support::NotApplicable,
}
}
pub fn for_device(&self, device: &Device) -> Support {
self.get(Backend::of(device))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct DtypeSupport {
pub f32: Support,
pub f16: Support,
pub bf16: Support,
}
impl DtypeSupport {
pub const fn pending() -> Self {
Self {
f32: Support::Pending,
f16: Support::Pending,
bf16: Support::Pending,
}
}
pub const fn not_applicable() -> Self {
Self {
f32: Support::NotApplicable,
f16: Support::NotApplicable,
bf16: Support::NotApplicable,
}
}
pub const fn verified() -> Self {
Self {
f32: Support::Verified,
f16: Support::Verified,
bf16: Support::Verified,
}
}
pub fn status(&self, dtype: DType) -> Support {
match dtype {
DType::F16 => self.f16,
DType::BF16 => self.bf16,
_ => self.f32,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct DtypeBackendSupport {
pub f32: BackendSupport,
pub f16: BackendSupport,
pub bf16: BackendSupport,
}
impl DtypeBackendSupport {
pub fn untested() -> Self {
Self {
f32: BackendSupport::untested(),
f16: BackendSupport::untested(),
bf16: BackendSupport::untested(),
}
}
pub fn row(&self, dtype: DType) -> BackendSupport {
match dtype {
DType::F16 => self.f16,
DType::BF16 => self.bf16,
_ => self.f32,
}
}
pub fn status(&self, dtype: DType, device: &Device) -> Support {
self.row(dtype).for_device(device)
}
pub(crate) fn derive(
backends: BackendSupport,
dtypes: DtypeSupport,
accel_only: &[DType],
) -> Self {
let row = |dtype: DType| -> BackendSupport {
let flat = dtypes.status(dtype);
let cell = |device_status: Support, refused_here: bool| -> Support {
if flat == Support::Unsupported || refused_here {
Support::Unsupported
} else if matches!(device_status, Support::Untested | Support::Pending) {
device_status
} else {
flat
}
};
let no_cpu_bf16 = dtype == DType::BF16 && backends.cpu != Support::NotApplicable;
BackendSupport {
cpu: cell(backends.cpu, no_cpu_bf16 || accel_only.contains(&dtype)),
metal: cell(backends.metal, false),
cuda: cell(backends.cuda, false),
}
};
Self {
f32: row(DType::F32),
f16: row(DType::F16),
bf16: row(DType::BF16),
}
}
}
pub(crate) mod dtype_serde {
use candle_core::DType;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serializer;
pub fn serialize<S: Serializer>(dtype: &DType, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(dtype.as_str())
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<DType, D::Error> {
let name = String::deserialize(deserializer)?;
match name.as_str() {
"f32" => Ok(DType::F32),
"f16" => Ok(DType::F16),
"bf16" => Ok(DType::BF16),
other => Err(serde::de::Error::custom(format!(
"unsupported model dtype `{other}`; expected f32, f16 or bf16"
))),
}
}
}
#[cfg(test)]
mod tests;