#![allow(clippy::missing_safety_doc)]
#![allow(clippy::missing_transmute_annotations)]
mod lazy;
mod owned;
mod view;
pub use lazy::*;
pub use owned::*;
pub use view::*;
use num_traits::AsPrimitive;
use std::ffi::c_void;
use std::fmt::Display;
use tract_core::internal::*;
use tract_data::itertools::Itertools;
use crate::device::{DeviceBuffer, get_context};
use crate::utils::MAX_COPY_RANK;
use crate::utils::merge_copy_axes;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum DeviceTensor {
Owned(Box<dyn OwnedDeviceTensor>),
View(DeviceView),
}
impl DeviceTensor {
pub const SUPPORTED_DT: [DatumType; 11] = [
DatumType::Bool,
DatumType::F32,
DatumType::F16,
DatumType::I8,
DatumType::U8,
DatumType::I16,
DatumType::U16,
DatumType::I32,
DatumType::U32,
DatumType::I64,
DatumType::U64,
];
pub fn tname(dt: DatumType) -> TractResult<&'static str> {
Ok(match dt {
DatumType::F32 => "f32",
DatumType::F16 => "f16",
DatumType::U8 => "u8",
DatumType::U16 => "u16",
DatumType::U32 => "u32",
DatumType::U64 => "u64",
DatumType::I8 => "i8",
DatumType::I16 => "i16",
DatumType::I32 => "i32",
DatumType::I64 => "i64",
DatumType::Bool => "bool",
_ => bail!("Unsupported dt {:?} for GPU Tensor", dt),
})
}
pub fn uninitialized_dt(dt: DatumType, shape: &[usize]) -> TractResult<DeviceTensor> {
Ok(DeviceTensor::Owned(get_context()?.uninitialized_device_tensor(shape, dt)?))
}
pub fn uninitialized<T: Datum>(shape: &[usize]) -> TractResult<DeviceTensor> {
Self::uninitialized_dt(T::datum_type(), shape)
}
pub fn uninitialized_exotic(exotic_fact: Box<dyn ExoticFact>) -> TractResult<DeviceTensor> {
Ok(DeviceTensor::Owned(get_context()?.uninitialized_device_exotic_tensor(exotic_fact)?))
}
pub fn from_shape<T: Copy + Datum>(shape: &[usize], data: &[T]) -> TractResult<DeviceTensor> {
Tensor::from_shape(shape, data)?.into_device()
}
pub fn is_supported_dt(dt: DatumType) -> bool {
Self::SUPPORTED_DT.contains(&dt)
}
#[inline]
pub fn datum_type(&self) -> DatumType {
match self {
Self::Owned(owned) => owned.datum_type(),
Self::View(view) => view.datum_type(),
}
}
#[inline]
pub fn rank(&self) -> usize {
self.shape().len()
}
#[inline]
pub fn shape(&self) -> &[usize] {
match self {
Self::Owned(t) => t.shape(),
Self::View(t) => t.shape(),
}
}
#[inline]
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
match self {
Self::Owned(t) => t.len(),
Self::View(t) => t.len(),
}
}
#[inline]
pub fn strides(&self) -> &[isize] {
match self {
Self::Owned(t) => t.strides(),
Self::View(t) => t.strides(),
}
}
pub fn device_buffer(&self) -> &dyn DeviceBuffer {
match self {
Self::Owned(t) => t.device_buffer(),
Self::View(t) => t.device_buffer(),
}
}
pub fn buffer_offset<I: Copy + 'static>(&self) -> I
where
usize: AsPrimitive<I>,
{
match self {
Self::Owned(_) => 0.as_(),
Self::View(t) => t.buffer_offset(),
}
}
pub fn device_buffer_ptr(&self) -> *const c_void {
match self {
Self::Owned(t) => t.device_buffer().ptr(),
Self::View(t) => t.device_buffer().ptr(),
}
}
pub fn description(&self) -> String {
format!("|{},{:?}|", self.shape().iter().join(","), self.datum_type(),)
}
pub fn reshaped(&self, shape: TVec<usize>) -> TractResult<Self> {
match self {
Self::Owned(t) => Ok(t.reshaped(shape)?),
Self::View(t) => Ok(Self::View(t.reshaped(shape)?)),
}
}
pub fn restrided(&self, strides: TVec<isize>) -> TractResult<Self> {
match self {
Self::Owned(t) => Ok(t.restrided(strides)?),
Self::View(t) => Ok(Self::View(t.restrided(strides)?)),
}
}
pub fn into_tensor(self) -> Tensor {
let dt = self.datum_type();
let shape: TVec<usize> = self.shape().into();
Tensor::from_storage(dt, &shape, self)
}
pub fn prefix_window(&self, axis: usize, len: usize) -> TractResult<DeviceTensor> {
ensure!(axis < self.rank(), "axis {axis} out of rank {}", self.rank());
ensure!(
len <= self.shape()[axis],
"window of {len} on axis {axis} of extent {}",
self.shape()[axis]
);
ensure!(!self.is_exotic(), "an exotic tensor carries no shape to window");
let mut shape: TVec<usize> = self.shape().into();
shape[axis] = len;
let (buffer, offset_bytes) = match self {
Self::Owned(owned) => (Arc::new(tract_core::dyn_clone::clone_box(&**owned)), 0),
Self::View(view) => (Arc::clone(&view.buffer), view.offset_bytes),
};
Ok(DeviceView {
buffer,
dt: self.datum_type(),
len: shape.iter().product(),
shape,
strides: self.strides().into(),
offset_bytes,
exotic_fact: None,
}
.into())
}
pub(crate) fn slice_on_device(
&self,
dt: DatumType,
shape: &[usize],
axis: usize,
start: usize,
end: usize,
) -> TractResult<Option<DeviceTensor>> {
ensure!(
axis < shape.len() && start < end && end <= shape[axis],
"Invalid slicing range {start}..{end} on axis {axis} of {shape:?}"
);
let mut sliced: TVec<usize> = shape.into();
sliced[axis] = end - start;
let servable = dt == self.datum_type()
&& shape == self.shape()
&& !self.is_exotic()
&& Self::is_supported_dt(dt)
&& self.strides().iter().all(|s| *s > 0)
&& (1..=MAX_COPY_RANK).contains(
&merge_copy_axes(self.strides(), &sliced, &Tensor::natural_strides(&sliced))
.shape
.len(),
);
if !servable {
return Ok(None);
}
let output = DeviceTensor::uninitialized_dt(dt, &sliced)?;
get_context()?.assign_slice(&output, 0..end - start, self, start..end, axis)?;
Ok(Some(output))
}
pub fn to_host(&self) -> TractResult<Arc<Tensor>> {
get_context()?.synchronize()?;
Ok(match self {
Self::Owned(o) => o.to_host()?,
Self::View(v) => v.to_host()?.into(),
})
}
}
impl Display for DeviceTensor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Owned(o) => o.fmt(f),
Self::View(v) => {
let content =
v.to_host().unwrap().dump(false).unwrap_or_else(|e| format!("Error : {e:?}"));
write!(f, "View: {{ {content} }}")
}
}
}
}
pub trait IntoDevice<T> {
fn into_device(self) -> TractResult<T>;
}
impl IntoDevice<DeviceTensor> for Tensor {
fn into_device(self) -> TractResult<DeviceTensor> {
Ok(DeviceTensor::Owned(get_context()?.tensor_to_device(self.into_tvalue())?))
}
}
impl IntoDevice<DeviceTensor> for Arc<Tensor> {
fn into_device(self) -> TractResult<DeviceTensor> {
Ok(DeviceTensor::Owned(get_context()?.tensor_to_device(self.into_tvalue())?))
}
}
impl TensorStorage for DeviceTensor {
fn byte_len(&self) -> usize {
self.len() * self.datum_type().size_of()
}
fn is_empty(&self) -> bool {
self.byte_len() == 0
}
fn deep_clone(&self) -> Box<dyn TensorStorage> {
Box::new(self.clone())
}
fn as_plain_ram(&self) -> Option<&PlainStorage> {
None
}
fn as_plain_ram_mut(&mut self) -> Option<&mut PlainStorage> {
None
}
fn into_plain_ram(self: Box<Self>) -> Option<PlainStorage> {
None
}
fn dyn_hash(&self, _state: &mut dyn std::hash::Hasher) {
}
fn is_exotic(&self) -> bool {
match self {
Self::Owned(owned) => owned.exotic_fact().is_some(),
Self::View(view) => view.exotic_fact().is_some(),
}
}
fn in_ram(&self) -> bool {
false
}
fn slice(
&self,
dt: DatumType,
shape: &[usize],
axis: usize,
start: usize,
end: usize,
) -> TractResult<Option<Tensor>> {
Ok(self.slice_on_device(dt, shape, axis, start, end)?.map(|t| t.into_tensor()))
}
fn exotic_fact(&self, _shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>> {
bail!(
"DeviceTensor cannot reconstruct a DeviceFact: origin (FromHost/FromDevice) is not carried by storage"
)
}
}
impl From<DeviceView> for DeviceTensor {
fn from(view: DeviceView) -> Self {
Self::View(view)
}
}
pub trait DeviceTensorExt {
fn to_device_tensor(&self) -> TractResult<&DeviceTensor>;
fn as_device_tensor(&self) -> Option<&DeviceTensor>;
}
impl DeviceTensorExt for Tensor {
fn to_device_tensor(&self) -> TractResult<&DeviceTensor> {
self.try_storage_as::<DeviceTensor>()
}
fn as_device_tensor(&self) -> Option<&DeviceTensor> {
self.storage_as::<DeviceTensor>()
}
}