1use serde::{Deserialize, Serialize};
9
10use crate::error::{ModelError, Result};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum DataType {
15 Fp32,
16 Int64,
17 Int32,
18}
19
20impl DataType {
21 pub fn v2_name(self) -> &'static str {
23 match self {
24 Self::Fp32 => "FP32",
25 Self::Int64 => "INT64",
26 Self::Int32 => "INT32",
27 }
28 }
29
30 pub fn from_v2_name(name: &str) -> Option<Self> {
33 match name {
34 "FP32" => Some(Self::Fp32),
35 "INT64" => Some(Self::Int64),
36 "INT32" => Some(Self::Int32),
37 _ => None,
38 }
39 }
40
41 pub fn size(self) -> usize {
43 match self {
44 Self::Fp32 | Self::Int32 => 4,
45 Self::Int64 => 8,
46 }
47 }
48
49 pub fn is_int(self) -> bool {
51 matches!(self, Self::Int64 | Self::Int32)
52 }
53}
54
55#[derive(Debug, Clone, PartialEq)]
58pub struct IoSpec {
59 pub name: String,
60 pub dtype: DataType,
61 pub shape: Vec<Option<usize>>,
62}
63
64impl IoSpec {
65 pub fn new(name: &str, dtype: DataType, shape: &[Option<usize>]) -> Self {
67 Self {
68 name: name.to_string(),
69 dtype,
70 shape: shape.to_vec(),
71 }
72 }
73
74 pub fn fp32(name: &str, shape: &[usize]) -> Self {
76 Self {
77 name: name.to_string(),
78 dtype: DataType::Fp32,
79 shape: shape.iter().map(|&d| Some(d)).collect(),
80 }
81 }
82
83 pub fn static_shape(&self) -> Option<Vec<usize>> {
85 self.shape.iter().copied().collect()
86 }
87
88 pub fn v2_shape(&self) -> Vec<i64> {
90 std::iter::once(-1)
91 .chain(self.shape.iter().map(|d| d.map_or(-1, |d| d as i64)))
92 .collect()
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct TensorSpec {
100 pub shape: Vec<usize>,
101 pub dtype: String,
103 pub min_value: Option<f32>,
104 pub max_value: Option<f32>,
105}
106
107#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct TensorInfo {
110 pub name: String,
111 pub datatype: String,
113 pub shape: Vec<i64>,
115}
116
117impl From<&IoSpec> for TensorInfo {
118 fn from(spec: &IoSpec) -> Self {
119 Self {
120 name: spec.name.clone(),
121 datatype: spec.dtype.v2_name().to_string(),
122 shape: spec.v2_shape(),
123 }
124 }
125}
126
127#[derive(Debug, Clone, PartialEq)]
129pub enum Values {
130 F32(Vec<f32>),
131 I64(Vec<i64>),
132}
133
134impl Values {
135 pub fn len(&self) -> usize {
137 match self {
138 Self::F32(v) => v.len(),
139 Self::I64(v) => v.len(),
140 }
141 }
142
143 pub fn is_empty(&self) -> bool {
145 self.len() == 0
146 }
147}
148
149#[derive(Debug, Clone, PartialEq)]
154pub struct HostTensor {
155 pub shape: Vec<usize>,
156 pub values: Values,
157}
158
159impl HostTensor {
160 pub fn batch(&self) -> usize {
163 self.shape.first().copied().unwrap_or(0)
164 }
165
166 pub fn slice_batch(&self, start: usize, end: usize) -> Self {
168 let per_item: usize = self.shape[1..].iter().product();
169 let range = start * per_item..end * per_item;
170 let mut shape = self.shape.clone();
171 shape[0] = end - start;
172 Self {
173 shape,
174 values: match &self.values {
175 Values::F32(v) => Values::F32(v[range].to_vec()),
176 Values::I64(v) => Values::I64(v[range].to_vec()),
177 },
178 }
179 }
180
181 pub fn concat_batch(parts: Vec<Self>) -> Result<Self> {
183 let mut parts = parts.into_iter();
184 let mut joined = parts
185 .next()
186 .ok_or_else(|| ModelError::InferenceFailed("nothing to join".to_string()))?;
187 for part in parts {
188 if part.shape[1..] != joined.shape[1..] {
189 return Err(ModelError::InferenceFailed(format!(
190 "chunks of one batch returned shapes {:?} and {:?}",
191 joined.shape, part.shape
192 ))
193 .into());
194 }
195 joined.shape[0] += part.shape[0];
196 match (&mut joined.values, part.values) {
197 (Values::F32(a), Values::F32(b)) => a.extend(b),
198 (Values::I64(a), Values::I64(b)) => a.extend(b),
199 _ => {
200 return Err(ModelError::InferenceFailed(
201 "chunks of one batch returned different types".to_string(),
202 )
203 .into());
204 }
205 }
206 }
207 Ok(joined)
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn batches_split_and_join() {
217 let tensor = HostTensor {
218 shape: vec![3, 2],
219 values: Values::I64(vec![1, 2, 3, 4, 5, 6]),
220 };
221 let parts = vec![tensor.slice_batch(0, 1), tensor.slice_batch(1, 3)];
222 assert_eq!(parts[1].shape, [2, 2]);
223 assert_eq!(HostTensor::concat_batch(parts).unwrap(), tensor);
224 }
225
226 #[test]
227 fn metadata_shapes() {
228 let spec = IoSpec {
229 name: "ids".to_string(),
230 dtype: DataType::Int64,
231 shape: vec![Some(256), None],
232 };
233 assert_eq!(spec.v2_shape(), [-1, 256, -1]);
234 assert_eq!(spec.static_shape(), None);
235 assert_eq!(TensorInfo::from(&spec).datatype, "INT64");
236 assert_eq!(DataType::from_v2_name("INT32"), Some(DataType::Int32));
237 assert_eq!(DataType::from_v2_name("FP16"), None);
238 }
239}