1use super::ops::{cast_numeric_slice, expected_byte_len, numeric_to_le_bytes};
2use crate::error::{Error, Result};
3use std::sync::{Arc, OnceLock};
4
5#[cfg(feature = "uring")]
6use wireshift::{Buffer, Completed};
7
8#[derive(Debug, Clone)]
14pub struct Tensor {
15 pub(crate) data: Arc<TensorData>,
17 pub(crate) dtype: DType,
19 pub(crate) shape: Arc<[usize]>,
21}
22
23#[derive(Debug, Default)]
24pub(crate) struct TensorData {
25 pub(crate) bytes: TensorBytes,
26 pub(crate) f32_cache: OnceLock<Vec<f32>>,
27 pub(crate) f64_cache: OnceLock<Vec<f64>>,
28 pub(crate) i32_cache: OnceLock<Vec<i32>>,
29 pub(crate) i64_cache: OnceLock<Vec<i64>>,
30}
31
32#[derive(Debug)]
33pub(crate) enum TensorBytes {
34 Shared(Arc<[u8]>),
35 #[cfg(feature = "uring")]
36 Pooled(Buffer<Completed>),
37}
38
39impl Default for TensorBytes {
40 fn default() -> Self {
41 Self::Shared(Arc::from([]))
42 }
43}
44
45impl TensorBytes {
46 fn as_slice(&self) -> &[u8] {
47 match self {
48 Self::Shared(bytes) => bytes,
49 #[cfg(feature = "uring")]
50 Self::Pooled(buffer) => buffer.filled(),
51 }
52 }
53
54 fn len(&self) -> usize {
55 self.as_slice().len()
56 }
57}
58
59#[non_exhaustive]
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum DType {
63 F32,
65 F64,
67 U8,
69 I32,
71 I64,
73 Bytes,
75}
76
77impl DType {
78 pub fn element_size(self) -> usize {
80 match self {
81 Self::F32 | Self::I32 => 4,
82 Self::F64 | Self::I64 => 8,
83 Self::U8 | Self::Bytes => 1,
84 }
85 }
86
87 pub fn name(self) -> &'static str {
89 match self {
90 Self::F32 => "f32",
91 Self::F64 => "f64",
92 Self::U8 => "u8",
93 Self::I32 => "i32",
94 Self::I64 => "i64",
95 Self::Bytes => "bytes",
96 }
97 }
98}
99
100impl std::fmt::Display for DType {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 f.write_str(self.name())
103 }
104}
105
106impl Tensor {
107 pub fn f32(data: &[f32], shape: Vec<usize>) -> Self {
109 Self::from_bytes_unchecked(numeric_to_le_bytes(data), DType::F32, shape)
110 }
111
112 pub fn f64(data: &[f64], shape: Vec<usize>) -> Self {
114 Self::from_bytes_unchecked(numeric_to_le_bytes(data), DType::F64, shape)
115 }
116
117 pub fn u8(data: Vec<u8>, shape: Vec<usize>) -> Self {
119 Self::from_bytes_unchecked(data, DType::U8, shape)
120 }
121
122 pub fn i32(data: &[i32], shape: Vec<usize>) -> Self {
124 Self::from_bytes_unchecked(numeric_to_le_bytes(data), DType::I32, shape)
125 }
126
127 pub fn i64(data: &[i64], shape: Vec<usize>) -> Self {
129 Self::from_bytes_unchecked(numeric_to_le_bytes(data), DType::I64, shape)
130 }
131
132 pub fn bytes(data: Vec<u8>) -> Self {
134 let len = data.len();
135 Self::from_bytes_unchecked(data, DType::Bytes, vec![len])
136 }
137
138 #[cfg(feature = "uring")]
140 pub fn bytes_from_completed(buffer: Buffer<Completed>) -> Self {
141 let len = buffer.filled_len();
142 Self::from_storage_unchecked(TensorBytes::Pooled(buffer), DType::Bytes, vec![len])
143 }
144
145 pub fn from_bytes(data: Vec<u8>, dtype: DType, shape: Vec<usize>) -> Result<Self> {
151 let expected = expected_byte_len(dtype, &shape)?;
152 if data.len() != expected {
153 return Err(Error::InvalidConfig {
154 reason: format!(
155 "tensor byte length {} does not match dtype {:?} and shape {:?} (expected {expected})",
156 data.len(),
157 dtype,
158 shape
159 ),
160 });
161 }
162
163 Ok(Self::from_bytes_unchecked(data, dtype, shape))
164 }
165
166 pub fn dtype(&self) -> DType {
168 self.dtype
169 }
170
171 pub fn shape(&self) -> &[usize] {
173 &self.shape
174 }
175
176 pub fn num_elements(&self) -> Option<usize> {
180 self.shape
181 .iter()
182 .try_fold(1_usize, |acc, &dim| acc.checked_mul(dim))
183 }
184
185 pub fn as_bytes(&self) -> &[u8] {
187 self.data.bytes.as_slice()
188 }
189
190 pub fn try_as_f32(&self) -> Result<&[f32]> {
195 if self.dtype != DType::F32 {
196 return Err(Error::InvalidConfig {
197 reason: format!("expected dtype f32, got {}", self.dtype),
198 });
199 }
200 cast_numeric_slice(
201 self.data.bytes.as_slice(),
202 &self.data.f32_cache,
203 f32::from_le_bytes,
204 )
205 }
206
207 pub fn try_as_f64(&self) -> Result<&[f64]> {
212 if self.dtype != DType::F64 {
213 return Err(Error::InvalidConfig {
214 reason: format!("expected dtype f64, got {}", self.dtype),
215 });
216 }
217 cast_numeric_slice(
218 self.data.bytes.as_slice(),
219 &self.data.f64_cache,
220 f64::from_le_bytes,
221 )
222 }
223
224 pub fn try_as_i32(&self) -> Result<&[i32]> {
229 if self.dtype != DType::I32 {
230 return Err(Error::InvalidConfig {
231 reason: format!("expected dtype i32, got {}", self.dtype),
232 });
233 }
234 cast_numeric_slice(
235 self.data.bytes.as_slice(),
236 &self.data.i32_cache,
237 i32::from_le_bytes,
238 )
239 }
240
241 pub fn try_as_i64(&self) -> Result<&[i64]> {
246 if self.dtype != DType::I64 {
247 return Err(Error::InvalidConfig {
248 reason: format!("expected dtype i64, got {}", self.dtype),
249 });
250 }
251 cast_numeric_slice(
252 self.data.bytes.as_slice(),
253 &self.data.i64_cache,
254 i64::from_le_bytes,
255 )
256 }
257
258 pub fn byte_len(&self) -> usize {
260 self.data.bytes.len()
261 }
262
263 pub(crate) fn from_bytes_unchecked(data: Vec<u8>, dtype: DType, shape: Vec<usize>) -> Self {
264 Self::from_storage_unchecked(TensorBytes::Shared(Arc::from(data)), dtype, shape)
265 }
266
267 pub(crate) fn from_storage_unchecked(
268 data: TensorBytes,
269 dtype: DType,
270 shape: Vec<usize>,
271 ) -> Self {
272 Self {
273 data: Arc::new(TensorData {
274 bytes: data,
275 f32_cache: OnceLock::new(),
276 f64_cache: OnceLock::new(),
277 i32_cache: OnceLock::new(),
278 i64_cache: OnceLock::new(),
279 }),
280 dtype,
281 shape: Arc::from(shape),
282 }
283 }
284}