use candle_core::DType;
use super::*;
const V: Support = Support::Verified;
const U: Support = Support::Unsupported;
const T: Support = Support::Untested;
fn row(cpu: Support, metal: Support, cuda: Support) -> BackendSupport {
BackendSupport { cpu, metal, cuda }
}
#[test]
fn an_accelerator_only_dtype_is_unsupported_on_the_processor_and_keeps_its_flat_status_elsewhere() {
let derived = DtypeBackendSupport::derive(row(V, V, V), DtypeSupport::verified(), &[DType::F16]);
assert_eq!(derived.f16, row(U, V, V));
assert_eq!(derived.f32, row(V, V, V));
let pending = DtypeSupport {
f16: Support::Pending,
..DtypeSupport::verified()
};
let derived = DtypeBackendSupport::derive(row(V, V, T), pending, &[DType::F16]);
assert_eq!(
derived.f16,
row(U, Support::Pending, T),
"metal carries the flat status, whatever it is"
);
}
#[test]
fn a_flatly_unsupported_dtype_is_unsupported_on_every_device() {
let dtypes = DtypeSupport {
f16: U,
..DtypeSupport::verified()
};
let derived = DtypeBackendSupport::derive(row(V, V, T), dtypes, &[]);
assert_eq!(derived.f16, row(U, U, U));
assert_eq!(derived.f32, row(V, V, T));
}
#[test]
fn a_device_untested_at_the_model_level_is_untested_for_every_dtype_it_can_run() {
let derived = DtypeBackendSupport::derive(row(T, V, T), DtypeSupport::verified(), &[]);
assert_eq!(derived.f32, row(T, V, T));
assert_eq!(derived.f16, row(T, V, T));
assert_eq!(derived.bf16, row(U, V, T));
}
#[test]
fn a_device_pending_at_the_model_level_is_pending_for_every_dtype_it_does_not_refuse() {
let pending = Support::Pending;
let derived =
DtypeBackendSupport::derive(row(pending, V, T), DtypeSupport::verified(), &[DType::F16]);
assert_eq!(derived.f32, row(pending, V, T));
assert_eq!(derived.f16, row(U, V, T));
assert_eq!(derived.bf16, row(U, V, T));
}
#[test]
fn bf16_is_unsupported_on_the_processor_even_when_the_flat_and_model_wide_statuses_are_verified() {
let derived = DtypeBackendSupport::derive(row(V, V, V), DtypeSupport::verified(), &[]);
assert_eq!(derived.bf16, row(U, V, V));
assert_eq!(derived.f32, row(V, V, V));
assert_eq!(derived.f16, row(V, V, V));
}
#[test]
fn not_applicable_survives_in_all_nine_cells() {
let derived = DtypeBackendSupport::derive(
BackendSupport::not_applicable(),
DtypeSupport::not_applicable(),
&[],
);
let everywhere = BackendSupport::not_applicable();
assert_eq!(
derived,
DtypeBackendSupport {
f32: everywhere,
f16: everywhere,
bf16: everywhere,
}
);
}
const P: Support = Support::Pending;
const STATUSES: [Support; 5] = [V, P, T, U, Support::NotApplicable];
fn derivable(wanted: impl Fn(&DtypeBackendSupport) -> bool) -> bool {
let subsets: Vec<Vec<DType>> = (0..8)
.map(|bits| {
[DType::F32, DType::F16, DType::BF16]
.into_iter()
.enumerate()
.filter(|(i, _)| bits & (1 << i) != 0)
.map(|(_, dtype)| dtype)
.collect()
})
.collect();
STATUSES.iter().any(|&cpu| {
STATUSES.iter().any(|&metal| {
STATUSES.iter().any(|&cuda| {
STATUSES.iter().any(|&f32| {
STATUSES.iter().any(|&f16| {
STATUSES.iter().any(|&bf16| {
subsets.iter().any(|accel| {
let dtypes = DtypeSupport { f32, f16, bf16 };
wanted(&DtypeBackendSupport::derive(
row(cpu, metal, cuda),
dtypes,
accel,
))
})
})
})
})
})
})
})
}
fn exception(dtype: DType, backend: Backend, status: Support) -> CellException {
CellException {
dtype,
backend,
status,
reason: "a run that decided this cell",
}
}
#[test]
fn a_cpu_f32_pass_beside_an_unrun_metal_f32_needs_one_exception() {
let wanted = |r: &DtypeBackendSupport| r.f32.cpu == V && r.f32.metal == T && r.bf16.metal == V;
assert!(!derivable(wanted));
let dtypes = DtypeSupport {
f32: V,
f16: U,
bf16: V,
};
let derived = DtypeBackendSupport::derive(row(V, V, T), dtypes, &[]);
assert_eq!(derived.f32, row(V, V, T), "Metal F32 verified with no run");
let exact = derived.with_exceptions(&[exception(DType::F32, Backend::Metal, T)]);
assert!(wanted(&exact));
assert_eq!(exact.f32, row(V, T, T));
assert_eq!((exact.f16, exact.bf16), (derived.f16, derived.bf16));
}
#[test]
fn a_metal_f32_miss_beside_a_pending_cpu_f32_needs_one_exception() {
let wanted = |r: &DtypeBackendSupport| r.f32.cpu == P && r.f32.metal == U && r.bf16.metal == V;
assert!(!derivable(wanted));
let dtypes = DtypeSupport {
f32: T,
f16: U,
bf16: V,
};
let derived = DtypeBackendSupport::derive(row(P, V, T), dtypes, &[]);
let exact = derived.with_exceptions(&[exception(DType::F32, Backend::Metal, U)]);
assert!(wanted(&exact));
assert_eq!(exact.f32, row(P, U, T));
assert_eq!((exact.f16, exact.bf16), (derived.f16, derived.bf16));
}
#[test]
fn an_exception_replaces_exactly_its_own_cell() {
let derived = DtypeBackendSupport::derive(row(V, V, V), DtypeSupport::verified(), &[]);
for dtype in [DType::F32, DType::F16, DType::BF16] {
for backend in [Backend::Cpu, Backend::Metal, Backend::Cuda] {
let exact = derived.with_exceptions(&[exception(dtype, backend, P)]);
for other_dtype in [DType::F32, DType::F16, DType::BF16] {
for other in [Backend::Cpu, Backend::Metal, Backend::Cuda] {
let expected = if (other_dtype, other) == (dtype, backend) {
P
} else {
derived.row(other_dtype).get(other)
};
assert_eq!(
exact.row(other_dtype).get(other),
expected,
"{dtype:?} on {backend:?}"
);
}
}
}
}
}