use crate::{DType, Device, Result, Tensor};
#[derive(Clone, Debug)]
pub struct ComputeCtx {
pub compute: DType,
pub kv: DType,
pub accum: DType,
pub residency: Residency,
pub device: Device,
}
impl ComputeCtx {
pub fn new(compute: DType, device: Device) -> Self {
Self {
compute,
kv: compute,
accum: DType::F32,
residency: Residency::Auto,
device,
}
}
pub fn ds4_parity(device: Device) -> Self {
Self {
compute: DType::F32,
kv: DType::F32,
accum: DType::F32,
residency: Residency::Auto,
device,
}
}
pub fn serving(device: Device) -> Self {
Self {
compute: DType::BF16,
kv: DType::F32,
accum: DType::F32,
residency: Residency::Auto,
device,
}
}
pub fn with_kv(mut self, kv: DType) -> Self {
self.kv = kv;
self
}
pub fn with_accum(mut self, accum: DType) -> Self {
self.accum = accum;
self
}
pub fn precise<F>(&self, t: &Tensor, f: F) -> Result<Tensor>
where
F: FnOnce(&Tensor) -> Result<Tensor>,
{
let orig = t.dtype();
if orig == self.accum {
return f(t);
}
let up = t.to_dtype(self.accum)?;
f(&up)?.to_dtype(orig)
}
pub fn to_compute(&self, t: &Tensor) -> Result<Tensor> {
cast_if(t, self.compute)
}
pub fn to_kv(&self, t: &Tensor) -> Result<Tensor> {
cast_if(t, self.kv)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum Residency {
Device,
Mmap,
#[default]
Auto,
}
impl Residency {
pub fn resolve(self, model_bytes: usize, available_bytes: usize) -> Residency {
match self {
Residency::Auto => {
if model_bytes <= available_bytes {
Residency::Device
} else {
Residency::Mmap
}
}
other => other,
}
}
}
pub fn cast_if(t: &Tensor, dt: DType) -> Result<Tensor> {
if t.dtype() == dt {
Ok(t.clone())
} else {
t.to_dtype(dt)
}
}
fn float_rank(d: DType) -> Option<u8> {
match d {
DType::F64 => Some(4),
DType::F32 => Some(3),
DType::F16 | DType::BF16 => Some(2),
_ => None,
}
}
pub fn promote(a: DType, b: DType) -> DType {
if a == b {
return a;
}
match (float_rank(a), float_rank(b)) {
(Some(ra), Some(rb)) => {
if ra == rb {
DType::F32
} else if ra > rb {
a
} else {
b
}
}
_ => a,
}
}
pub fn promoted(a: &Tensor, b: &Tensor) -> Result<(Tensor, Tensor)> {
let p = promote(a.dtype(), b.dtype());
Ok((cast_if(a, p)?, cast_if(b, p)?))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn promote_is_a_semilattice() {
use DType::*;
for d in [F32, F16, BF16, F64] {
assert_eq!(promote(d, d), d);
}
assert_eq!(promote(BF16, F32), promote(F32, BF16));
assert_eq!(promote(F16, BF16), promote(BF16, F16));
assert_eq!(promote(BF16, F32), F32);
assert_eq!(promote(F16, F32), F32);
assert_eq!(promote(F16, BF16), F32); assert_eq!(promote(F32, F64), F64);
assert_eq!(
promote(promote(BF16, F16), F32),
promote(BF16, promote(F16, F32))
);
}
#[test]
fn promoted_only_casts_the_lower_operand() -> Result<()> {
let dev = Device::Cpu;
let a = Tensor::zeros((2, 2), DType::BF16, &dev)?;
let b = Tensor::zeros((2, 2), DType::F32, &dev)?;
let (a2, b2) = promoted(&a, &b)?;
assert_eq!(a2.dtype(), DType::F32); assert_eq!(b2.dtype(), DType::F32); Ok(())
}
#[test]
fn precise_runs_in_accum_and_restores_dtype() -> Result<()> {
let dev = Device::Cpu;
let ctx = ComputeCtx::new(DType::BF16, dev.clone());
let t = Tensor::zeros((4,), DType::BF16, &dev)?;
let out = ctx.precise(&t, |x| {
assert_eq!(x.dtype(), DType::F32);
x.affine(1.0, 1.0)
})?;
assert_eq!(out.dtype(), DType::BF16);
Ok(())
}
#[test]
fn precise_is_noop_cast_when_already_accum() -> Result<()> {
let dev = Device::Cpu;
let ctx = ComputeCtx::ds4_parity(dev.clone()); let t = Tensor::zeros((4,), DType::F32, &dev)?;
let out = ctx.precise(&t, |x| {
assert_eq!(x.dtype(), DType::F32);
Ok(x.clone())
})?;
assert_eq!(out.dtype(), DType::F32);
Ok(())
}
#[test]
fn residency_auto_resolves_by_fit() {
assert_eq!(Residency::Auto.resolve(80, 100), Residency::Device);
assert_eq!(Residency::Auto.resolve(120, 100), Residency::Mmap);
assert_eq!(Residency::Auto.resolve(100, 100), Residency::Device); assert_eq!(Residency::Device.resolve(999, 1), Residency::Device);
assert_eq!(Residency::Mmap.resolve(1, 999), Residency::Mmap);
}
#[test]
fn policies_set_expected_dtypes() {
let dev = Device::Cpu;
let s = ComputeCtx::serving(dev.clone());
assert_eq!(
(s.compute, s.kv, s.accum),
(DType::BF16, DType::F32, DType::F32)
);
let p = ComputeCtx::ds4_parity(dev);
assert_eq!(
(p.compute, p.kv, p.accum),
(DType::F32, DType::F32, DType::F32)
);
}
}