use alloc::string::String;
use core::marker::PhantomData;
use ruda_tensor::{Backend, BackendTypes, DType, DTypeUsage, DTypeUsageSet, DeviceId, DeviceOps};
use ruda_tensor::graph::{BackendIr, HandleKind, TensorHandle};
use ruda_core::device::Device;
use crate::qtensor::HostQTensor;
use crate::tensor::HostTensor;
pub use rurand_host::HostRng as HostRng;
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct HostDevice;
impl Device for HostDevice {
fn to_id(&self) -> DeviceId {
DeviceId::new(0, 0)
}
fn from_id(_id: DeviceId) -> Self {
Self
}
}
impl DeviceOps for HostDevice {}
impl core::fmt::Display for HostDevice {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Cpu")
}
}
impl core::fmt::Debug for HostDevice {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
#[derive(Clone, Copy, Default)]
pub struct Host<E = f32, I = i32> {
_e: PhantomData<E>,
_i: PhantomData<I>,
}
impl<E: core::fmt::Debug, I: core::fmt::Debug> core::fmt::Debug for Host<E, I> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Flex")
.field("_e", &self._e)
.field("_i", &self._i)
.finish()
}
}
impl BackendTypes for Host {
type Device = HostDevice;
type FloatTensorPrimitive = HostTensor;
type FloatElem = f32;
type IntTensorPrimitive = HostTensor;
type IntElem = i32;
type BoolTensorPrimitive = HostTensor;
type BoolElem = bool;
type QuantizedTensorPrimitive = HostQTensor;
}
impl Backend for Host {
fn flush(_device: &Self::Device) -> Result<(), ruda_tensor::ExecutionError> {
Ok(())
}
fn name(_device: &Self::Device) -> String {
"flex".into()
}
fn seed(_device: &Self::Device, seed: u64) {
rurand_host::seed(seed)
}
fn rng_state(_device: &Self::Device) -> Result<alloc::vec::Vec<u8>, ruda_tensor::ExecutionError> {
Ok(rurand_host::rng_state())
}
fn restore_rng_state(_device: &Self::Device, state: &[u8]) -> Result<(), ruda_tensor::ExecutionError> {
rurand_host::restore_rng_state(state)
.map_err(|reason| ruda_tensor::ExecutionError::WithContext { reason: reason.into() })
}
fn device_count(_type_id: u16) -> usize {
1
}
fn dtype_usage(_device: &Self::Device, dtype: DType) -> DTypeUsageSet {
match dtype {
DType::F64 | DType::F32 | DType::F16 | DType::BF16 => {
DTypeUsage::Storage | DTypeUsage::Arithmetic
}
DType::I64 | DType::I32 | DType::I16 | DType::I8 => {
DTypeUsage::Storage | DTypeUsage::Arithmetic
}
DType::U64 | DType::U32 | DType::U16 | DType::U8 => {
DTypeUsage::Storage | DTypeUsage::Arithmetic
}
DType::Bool(ruda_tensor::BoolStore::Native | ruda_tensor::BoolStore::U8) => {
DTypeUsage::Storage | DTypeUsage::Arithmetic
}
DType::Bool(ruda_tensor::BoolStore::U32) => DTypeUsageSet::empty(),
DType::QFloat(_) => DTypeUsage::Storage.into(),
_ => DTypeUsageSet::empty(),
}
}
}
impl BackendIr for Host {
type Handle = HandleKind<Self>;
fn float_tensor(handle: TensorHandle<Self::Handle>) -> HostTensor {
match handle.handle {
HandleKind::Float(t) => t,
_ => panic!("Expected float handle, got {}", handle.handle.name()),
}
}
fn int_tensor(handle: TensorHandle<Self::Handle>) -> HostTensor {
match handle.handle {
HandleKind::Int(t) => t,
_ => panic!("Expected int handle, got {}", handle.handle.name()),
}
}
fn bool_tensor(handle: TensorHandle<Self::Handle>) -> HostTensor {
match handle.handle {
HandleKind::Bool(t) => t,
_ => panic!("Expected bool handle, got {}", handle.handle.name()),
}
}
fn quantized_tensor(handle: TensorHandle<Self::Handle>) -> HostQTensor {
match handle.handle {
HandleKind::Quantized(t) => t,
_ => panic!("Expected quantized handle, got {}", handle.handle.name()),
}
}
fn float_tensor_handle(tensor: HostTensor) -> Self::Handle {
HandleKind::Float(tensor)
}
fn int_tensor_handle(tensor: HostTensor) -> Self::Handle {
HandleKind::Int(tensor)
}
fn bool_tensor_handle(tensor: HostTensor) -> Self::Handle {
HandleKind::Bool(tensor)
}
fn quantized_tensor_handle(tensor: HostQTensor) -> Self::Handle {
HandleKind::Quantized(tensor)
}
}
#[cfg(test)]
mod tests {
use ruda_tensor::{Backend, DType};
use ruda_tensor::BoolStore;
use super::*;
#[test]
fn supports_bool_native() {
let device = HostDevice;
assert!(Host::supports_dtype(
&device,
DType::Bool(BoolStore::Native)
));
}
#[test]
fn supports_bool_u8() {
let device = HostDevice;
assert!(Host::supports_dtype(&device, DType::Bool(BoolStore::U8)));
}
#[test]
fn does_not_support_bool_u32() {
let device = HostDevice;
assert!(
!Host::supports_dtype(&device, DType::Bool(BoolStore::U32)),
"Bool(U32) should not be supported: flex stores bools as 1 byte per element"
);
}
#[test]
fn bool_empty_preserves_native_dtype() {
use ruda_tensor::ops::BoolTensorOps;
let shape = ruda_tensor::Shape::from(alloc::vec![3]);
let t = Host::bool_empty(shape, &HostDevice, ruda_tensor::BoolDType::Native);
assert_eq!(t.dtype(), DType::Bool(BoolStore::Native));
}
#[test]
fn bool_empty_preserves_u8_dtype() {
use ruda_tensor::ops::BoolTensorOps;
let shape = ruda_tensor::Shape::from(alloc::vec![3]);
let t = Host::bool_empty(shape, &HostDevice, ruda_tensor::BoolDType::U8);
assert_eq!(t.dtype(), DType::Bool(BoolStore::U8));
}
#[test]
fn device_prints_as_cpu() {
use alloc::format;
assert_eq!(format!("{:?}", HostDevice), "Cpu");
assert_eq!(format!("{}", HostDevice), "Cpu");
}
#[test]
fn comparison_preserves_out_dtype_native() {
let lhs = HostTensor::from_data(ruda_tensor::TensorData::from([1.0f32, 2.0, 3.0]));
let rhs = HostTensor::from_data(ruda_tensor::TensorData::from([2.0f32, 2.0, 1.0]));
let result = crate::ops::comparison::greater(lhs, rhs, ruda_tensor::BoolDType::Native);
assert_eq!(result.dtype(), DType::Bool(BoolStore::Native));
}
#[test]
fn comparison_preserves_out_dtype_u8() {
let lhs = HostTensor::from_data(ruda_tensor::TensorData::from([1.0f32, 2.0, 3.0]));
let rhs = HostTensor::from_data(ruda_tensor::TensorData::from([2.0f32, 2.0, 1.0]));
let result = crate::ops::comparison::greater(lhs, rhs, ruda_tensor::BoolDType::U8);
assert_eq!(result.dtype(), DType::Bool(BoolStore::U8));
}
#[test]
#[should_panic(expected = "Bool(U32)")]
fn comparison_u32_panics() {
let lhs = HostTensor::from_data(ruda_tensor::TensorData::from([1.0f32, 2.0]));
let rhs = HostTensor::from_data(ruda_tensor::TensorData::from([2.0f32, 1.0]));
let _ = crate::ops::comparison::greater(lhs, rhs, ruda_tensor::BoolDType::U32);
}
#[test]
fn bool_not_preserves_u8_dtype() {
use ruda_tensor::ops::BoolTensorOps;
let t_u8 = crate::ops::comparison::make_bool_tensor(
alloc::vec![1, 0, 1],
ruda_tensor::Shape::from(alloc::vec![3]),
ruda_tensor::BoolDType::U8,
);
let result = Host::bool_not(t_u8);
assert_eq!(result.dtype(), DType::Bool(BoolStore::U8));
let data: &[u8] = result.bytes();
assert_eq!(&data[..3], &[0, 1, 0]);
}
}