candding 0.1.0

Pure candle embeddings for Rust with no ONNX: dense, sparse, multi-vector and rerank models today, image models on the roadmap
Documentation
//! Cells a run decided that `DtypeBackendSupport::derive` cannot state, such as one device's F32 verified while
//! another's stays unrun; entries list them as `CellException`s, written over the derived rows.

use candle_core::DType;
use candle_core::Device;

use super::BackendSupport;
use super::DtypeBackendSupport;
use super::Support;

#[cfg(test)]
pub(crate) mod rules;

/// A compute backend: the device axis of every support row, and the backend a device error or refusal names.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Backend {
  Cpu,
  Metal,
  Cuda,
}

impl Backend {
  /// The backend a runtime device computes on: `BackendSupport::for_device` reads this field.
  pub(crate) fn of(device: &Device) -> Self {
    if device.is_cpu() {
      Backend::Cpu
    } else if device.is_metal() {
      Backend::Metal
    } else {
      Backend::Cuda
    }
  }

  /// The name errors and `describe` print for the backend.
  pub(crate) fn name(self) -> &'static str {
    match self {
      Backend::Cpu => "cpu",
      Backend::Metal => "metal",
      Backend::Cuda => "cuda",
    }
  }
}

impl BackendSupport {
  /// The cell of one backend.
  pub(crate) fn get(&self, backend: Backend) -> Support {
    match backend {
      Backend::Cpu => self.cpu,
      Backend::Metal => self.metal,
      Backend::Cuda => self.cuda,
    }
  }
}

/// One cell a run decided where the derivation cannot reach: `status` for `dtype` on `backend`, with `reason` naming
/// the run. Every catalog's entries carry them, and `rules` holds each to what a cell may take.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct CellException {
  pub dtype: DType,
  pub backend: Backend,
  pub status: Support,
  pub reason: &'static str,
}

impl DtypeBackendSupport {
  /// The rows with each exception's cell replaced by its status, applied after `derive`, so an exception decides its
  /// one cell and nothing else. The last exception for a cell wins; the registry tests refuse two for one cell.
  pub(crate) fn with_exceptions(mut self, exceptions: &[CellException]) -> Self {
    for exception in exceptions {
      let row = match exception.dtype {
        DType::F16 => &mut self.f16,
        DType::BF16 => &mut self.bf16,
        _ => &mut self.f32,
      };
      let cell = match exception.backend {
        Backend::Cpu => &mut row.cpu,
        Backend::Metal => &mut row.metal,
        Backend::Cuda => &mut row.cuda,
      };
      *cell = exception.status;
    }
    self
  }
}