tensor4all_tensorbackend/
logical_tensor.rs1use num_complex::{Complex32, Complex64};
4use tenferro::{DType, Tensor, TensorRead};
5use tenferro_tensor::TensorDeviceTransfer;
6
7use crate::CpuExecutionContext;
8
9#[derive(Clone, Debug, PartialEq)]
14pub enum LogicalTensorData {
15 F32(Vec<f32>),
17 F64(Vec<f64>),
19 I32(Vec<i32>),
21 I64(Vec<i64>),
23 Bool(Vec<bool>),
25 C32(Vec<Complex32>),
27 C64(Vec<Complex64>),
29}
30
31impl LogicalTensorData {
32 pub fn dtype(&self) -> DType {
34 match self {
35 Self::F32(_) => DType::F32,
36 Self::F64(_) => DType::F64,
37 Self::I32(_) => DType::I32,
38 Self::I64(_) => DType::I64,
39 Self::Bool(_) => DType::Bool,
40 Self::C32(_) => DType::C32,
41 Self::C64(_) => DType::C64,
42 }
43 }
44
45 pub fn len(&self) -> usize {
47 match self {
48 Self::F32(values) => values.len(),
49 Self::F64(values) => values.len(),
50 Self::I32(values) => values.len(),
51 Self::I64(values) => values.len(),
52 Self::Bool(values) => values.len(),
53 Self::C32(values) => values.len(),
54 Self::C64(values) => values.len(),
55 }
56 }
57
58 pub fn is_empty(&self) -> bool {
60 self.len() == 0
61 }
62}
63
64#[derive(Debug, thiserror::Error)]
66pub enum LogicalTensorError {
67 #[error("logical tensor shape product overflows usize: {shape:?}")]
69 ShapeOverflow {
70 shape: Vec<usize>,
72 },
73 #[error(
75 "logical tensor element count mismatch: shape {shape:?} requires {expected}, got {actual}"
76 )]
77 ElementCountMismatch {
78 shape: Vec<usize>,
80 expected: usize,
82 actual: usize,
84 },
85 #[error("logical tensor {operation} failed: {source}")]
87 Tensor {
88 operation: &'static str,
90 #[source]
92 source: tenferro_tensor::Error,
93 },
94}
95
96#[derive(Clone, Debug, PartialEq)]
115pub struct LogicalTensor {
116 shape: Vec<usize>,
117 data: LogicalTensorData,
118}
119
120impl LogicalTensor {
121 pub fn new(shape: Vec<usize>, data: LogicalTensorData) -> Result<Self, LogicalTensorError> {
129 let expected = shape
130 .iter()
131 .try_fold(1_usize, |count, &dim| count.checked_mul(dim).ok_or(()));
132 let expected = expected.map_err(|()| LogicalTensorError::ShapeOverflow {
133 shape: shape.clone(),
134 })?;
135 if data.len() != expected {
136 return Err(LogicalTensorError::ElementCountMismatch {
137 shape,
138 expected,
139 actual: data.len(),
140 });
141 }
142 Ok(Self { shape, data })
143 }
144
145 pub fn from_native(tensor: &Tensor) -> Result<Self, LogicalTensorError> {
152 let dtype = tensor.dtype();
153 let data = match dtype {
154 DType::F32 => LogicalTensorData::F32(
155 tensor
156 .as_slice::<f32>()
157 .map_err(|source| LogicalTensorError::Tensor {
158 operation: "snapshot",
159 source,
160 })?
161 .to_vec(),
162 ),
163 DType::F64 => LogicalTensorData::F64(
164 tensor
165 .as_slice::<f64>()
166 .map_err(|source| LogicalTensorError::Tensor {
167 operation: "snapshot",
168 source,
169 })?
170 .to_vec(),
171 ),
172 DType::I32 => LogicalTensorData::I32(
173 tensor
174 .as_slice::<i32>()
175 .map_err(|source| LogicalTensorError::Tensor {
176 operation: "snapshot",
177 source,
178 })?
179 .to_vec(),
180 ),
181 DType::I64 => LogicalTensorData::I64(
182 tensor
183 .as_slice::<i64>()
184 .map_err(|source| LogicalTensorError::Tensor {
185 operation: "snapshot",
186 source,
187 })?
188 .to_vec(),
189 ),
190 DType::Bool => LogicalTensorData::Bool(
191 tensor
192 .as_slice::<bool>()
193 .map_err(|source| LogicalTensorError::Tensor {
194 operation: "snapshot",
195 source,
196 })?
197 .to_vec(),
198 ),
199 DType::C32 => LogicalTensorData::C32(
200 tensor
201 .as_slice::<Complex32>()
202 .map_err(|source| LogicalTensorError::Tensor {
203 operation: "snapshot",
204 source,
205 })?
206 .to_vec(),
207 ),
208 DType::C64 => LogicalTensorData::C64(
209 tensor
210 .as_slice::<Complex64>()
211 .map_err(|source| LogicalTensorError::Tensor {
212 operation: "snapshot",
213 source,
214 })?
215 .to_vec(),
216 ), DType::External(_) => {
219 return Err(LogicalTensorError::Tensor {
220 operation: "snapshot",
221 source: tenferro_tensor::Error::unsupported(
222 "snapshot",
223 format!("external scalar kind {dtype:?} has no logical-tensor payload"),
224 ),
225 })
226 }
227 };
228 Self::new(tensor.shape().to_vec(), data)
229 }
230
231 pub fn shape(&self) -> &[usize] {
233 &self.shape
234 }
235
236 pub fn dtype(&self) -> DType {
238 self.data.dtype()
239 }
240
241 pub fn data(&self) -> &LogicalTensorData {
243 &self.data
244 }
245
246 fn to_native(&self) -> Result<Tensor, LogicalTensorError> {
247 let result = match &self.data {
248 LogicalTensorData::F32(values) => {
249 Tensor::from_vec_col_major(self.shape.clone(), values.clone())
250 }
251 LogicalTensorData::F64(values) => {
252 Tensor::from_vec_col_major(self.shape.clone(), values.clone())
253 }
254 LogicalTensorData::I32(values) => {
255 Tensor::from_vec_col_major(self.shape.clone(), values.clone())
256 }
257 LogicalTensorData::I64(values) => {
258 Tensor::from_vec_col_major(self.shape.clone(), values.clone())
259 }
260 LogicalTensorData::Bool(values) => {
261 Tensor::from_vec_col_major(self.shape.clone(), values.clone())
262 }
263 LogicalTensorData::C32(values) => {
264 Tensor::from_vec_col_major(self.shape.clone(), values.clone())
265 }
266 LogicalTensorData::C64(values) => {
267 Tensor::from_vec_col_major(self.shape.clone(), values.clone())
268 }
269 };
270 result.map_err(|source| LogicalTensorError::Tensor {
271 operation: "reconstruction",
272 source,
273 })
274 }
275}
276
277impl CpuExecutionContext {
278 pub fn reconstruct(&self, tensor: &LogicalTensor) -> Result<Tensor, LogicalTensorError> {
288 let host = tensor.to_native()?;
289 self.with_session(|engine| engine.upload_host_tensor(TensorRead::from_tensor(&host)))
290 .map_err(|source| LogicalTensorError::Tensor {
291 operation: "target-context upload",
292 source: tenferro_tensor::Error::runtime_state_source(
293 "target-context upload",
294 source,
295 ),
296 })?
297 .map_err(|source| LogicalTensorError::Tensor {
298 operation: "target-context upload",
299 source,
300 })
301 }
302}
303
304#[cfg(test)]
305mod tests;