use crate::{DType, HostBuffer, Result, Shape, Storage, WithDType};
use fuel_backend_contract::dyn_backend::DynBackendDevice;
use fuel_cpu_backend::dyn_impl::CpuBackendDevice;
use std::sync::Arc;
pub use fuel_ir::DeviceLocation;
#[derive(Clone, Debug)]
pub struct Device {
pub(crate) inner: Arc<dyn DynBackendDevice>,
}
pub trait NdArray {
fn shape(&self) -> Result<Shape>;
fn to_cpu_storage(&self) -> HostBuffer;
}
impl<S: WithDType> NdArray for S {
fn shape(&self) -> Result<Shape> {
Ok(Shape::from(()))
}
fn to_cpu_storage(&self) -> HostBuffer {
S::to_cpu_storage(&[*self])
}
}
impl<S: WithDType, const N: usize> NdArray for &[S; N] {
fn shape(&self) -> Result<Shape> {
Ok(Shape::from(self.len()))
}
fn to_cpu_storage(&self) -> HostBuffer {
S::to_cpu_storage(self.as_slice())
}
}
impl<S: WithDType> NdArray for &[S] {
fn shape(&self) -> Result<Shape> {
Ok(Shape::from(self.len()))
}
fn to_cpu_storage(&self) -> HostBuffer {
S::to_cpu_storage(self)
}
}
impl<S: WithDType, const N: usize, const M: usize> NdArray for &[[S; N]; M] {
fn shape(&self) -> Result<Shape> {
Ok(Shape::from((M, N)))
}
fn to_cpu_storage(&self) -> HostBuffer {
S::to_cpu_storage_owned(self.concat())
}
}
impl<S: WithDType, const N1: usize, const N2: usize, const N3: usize> NdArray
for &[[[S; N3]; N2]; N1]
{
fn shape(&self) -> Result<Shape> {
Ok(Shape::from((N1, N2, N3)))
}
fn to_cpu_storage(&self) -> HostBuffer {
let mut vec = Vec::with_capacity(N1 * N2 * N3);
for i1 in 0..N1 {
for i2 in 0..N2 {
vec.extend(self[i1][i2])
}
}
S::to_cpu_storage_owned(vec)
}
}
impl<S: WithDType, const N1: usize, const N2: usize, const N3: usize, const N4: usize> NdArray
for &[[[[S; N4]; N3]; N2]; N1]
{
fn shape(&self) -> Result<Shape> {
Ok(Shape::from((N1, N2, N3, N4)))
}
fn to_cpu_storage(&self) -> HostBuffer {
let mut vec = Vec::with_capacity(N1 * N2 * N3 * N4);
for i1 in 0..N1 {
for i2 in 0..N2 {
for i3 in 0..N3 {
vec.extend(self[i1][i2][i3])
}
}
}
S::to_cpu_storage_owned(vec)
}
}
impl<S: WithDType> NdArray for Vec<S> {
fn shape(&self) -> Result<Shape> {
Ok(Shape::from(self.len()))
}
fn to_cpu_storage(&self) -> HostBuffer {
S::to_cpu_storage(self.as_slice())
}
}
impl<S: WithDType> NdArray for Vec<&[S]> {
fn shape(&self) -> Result<Shape> {
if self.is_empty() {
crate::bail!("empty array")
}
let n = self.len();
let m = self[0].len();
for v in self.iter() {
if v.len() != m {
crate::bail!("two elements have different len {m} {}", v.len())
}
}
Ok(Shape::from((n, m)))
}
fn to_cpu_storage(&self) -> HostBuffer {
let data = self.iter().copied().flatten().copied().collect::<Vec<_>>();
S::to_cpu_storage_owned(data)
}
}
impl<S: WithDType> NdArray for Vec<Vec<S>> {
fn shape(&self) -> Result<Shape> {
if self.is_empty() {
crate::bail!("empty array")
}
let n = self.len();
let m = self[0].len();
for v in self.iter() {
if v.len() != m {
crate::bail!("two elements have different len {m} {}", v.len())
}
}
Ok(Shape::from((n, m)))
}
fn to_cpu_storage(&self) -> HostBuffer {
let len: usize = self.iter().map(|v| v.len()).sum();
let mut dst = Vec::with_capacity(len);
for v in self.iter() {
dst.extend(v.iter().copied());
}
S::to_cpu_storage_owned(dst)
}
}
impl<S: WithDType> NdArray for Vec<Vec<Vec<S>>> {
fn shape(&self) -> Result<Shape> {
if self.is_empty() {
crate::bail!("empty array")
}
let shape0 = self[0].shape()?;
let n = self.len();
for v in self.iter() {
let shape = v.shape()?;
if shape != shape0 {
crate::bail!("two elements have different shapes {shape:?} {shape0:?}")
}
}
Ok(Shape::from([[n].as_slice(), shape0.dims()].concat()))
}
fn to_cpu_storage(&self) -> HostBuffer {
if self.is_empty() {
return S::to_cpu_storage_owned(vec![]);
}
let len: usize = self
.iter()
.map(|v| v.iter().map(|v| v.len()).sum::<usize>())
.sum();
let mut dst = Vec::with_capacity(len);
for v1 in self.iter() {
for v2 in v1.iter() {
dst.extend(v2.iter().copied());
}
}
S::to_cpu_storage_owned(dst)
}
}
impl<S: WithDType> NdArray for Vec<Vec<Vec<Vec<S>>>> {
fn shape(&self) -> Result<Shape> {
if self.is_empty() {
crate::bail!("empty array")
}
let shape0 = self[0].shape()?;
let n = self.len();
for v in self.iter() {
let shape = v.shape()?;
if shape != shape0 {
crate::bail!("two elements have different shapes {shape:?} {shape0:?}")
}
}
Ok(Shape::from([[n].as_slice(), shape0.dims()].concat()))
}
fn to_cpu_storage(&self) -> HostBuffer {
let len: usize = self
.iter()
.map(|v| {
v.iter()
.map(|v| v.iter().map(|v| v.len()).sum::<usize>())
.sum::<usize>()
})
.sum();
let mut dst = Vec::with_capacity(len);
for v1 in self.iter() {
for v2 in v1.iter() {
for v3 in v2.iter() {
dst.extend(v3.iter().copied());
}
}
}
S::to_cpu_storage_owned(dst)
}
}
impl Device {
pub fn cpu() -> Self {
Device {
inner: Arc::new(CpuBackendDevice),
}
}
pub fn custom(device: Arc<dyn DynBackendDevice>) -> Self {
Device { inner: device }
}
pub fn as_dyn(&self) -> &Arc<dyn DynBackendDevice> {
&self.inner
}
pub fn is_custom(&self) -> bool {
!self.is_cpu() && !self.is_cuda() && !self.is_metal()
}
pub fn set_seed(&self, seed: u64) -> Result<()> {
self.inner.set_seed_dyn(seed)
}
pub fn get_current_seed(&self) -> Result<u64> {
self.inner.get_current_seed_dyn()
}
pub fn same_device(&self, rhs: &Self) -> bool {
self.inner.same_device_dyn(&*rhs.inner)
}
pub fn location(&self) -> DeviceLocation {
self.inner.location_dyn()
}
pub fn is_cpu(&self) -> bool {
matches!(self.location(), DeviceLocation::Cpu)
}
pub fn is_cuda(&self) -> bool {
matches!(self.location(), DeviceLocation::Cuda { .. })
}
pub fn is_metal(&self) -> bool {
matches!(self.location(), DeviceLocation::Metal { .. })
}
pub fn supports_bf16(&self) -> bool {
self.inner.supports_bf16()
}
pub fn bf16_default_to_f32(&self) -> DType {
if self.supports_bf16() {
DType::BF16
} else {
DType::F32
}
}
#[allow(dead_code)]
pub(crate) fn rand_uniform_f64(
&self,
lo: f64,
up: f64,
shape: &Shape,
dtype: DType,
) -> Result<Storage> {
if self.is_cuda() && (dtype == DType::F16 || dtype == DType::BF16) {
let storage =
Storage::from_dyn(self.inner.rand_uniform_dyn(shape, DType::F32, lo, up)?);
storage.to_dtype(&crate::Layout::contiguous(shape), dtype)
} else {
Ok(Storage::from_dyn(
self.inner.rand_uniform_dyn(shape, dtype, lo, up)?,
))
}
}
#[allow(dead_code)] pub(crate) fn rand_uniform<T: crate::FloatDType>(
&self,
lo: T,
up: T,
shape: &Shape,
) -> Result<Storage> {
self.rand_uniform_f64(lo.to_f64(), up.to_f64(), shape, T::DTYPE)
}
#[allow(dead_code)] pub(crate) fn rand_normal_f64(
&self,
mean: f64,
std: f64,
shape: &Shape,
dtype: DType,
) -> Result<Storage> {
if self.is_cuda() && (dtype == DType::F16 || dtype == DType::BF16) {
let storage =
Storage::from_dyn(self.inner.rand_normal_dyn(shape, DType::F32, mean, std)?);
storage.to_dtype(&crate::Layout::contiguous(shape), dtype)
} else {
Ok(Storage::from_dyn(
self.inner.rand_normal_dyn(shape, dtype, mean, std)?,
))
}
}
#[allow(dead_code)] pub(crate) fn rand_normal<T: crate::FloatDType>(
&self,
mean: T,
std: T,
shape: &Shape,
) -> Result<Storage> {
self.rand_normal_f64(mean.to_f64(), std.to_f64(), shape, T::DTYPE)
}
#[allow(dead_code)] pub(crate) fn zeros(&self, shape: &Shape, dtype: DType) -> Result<Storage> {
Ok(Storage::from_dyn(self.inner.zeros_impl_dyn(shape, dtype)?))
}
pub fn synchronize(&self) -> Result<()> {
self.inner.synchronize_dyn()
}
}