1use std::path::PathBuf;
4
5use crate::dtype::DataType;
6use crate::shape::Shape;
7
8mod sealed {
9 pub trait Sealed {}
10}
11
12pub trait FromLeBytes: sealed::Sealed + Sized {
14 const BYTE_SIZE: usize;
15
16 fn from_le_bytes(bytes: &[u8]) -> Self;
17}
18
19macro_rules! impl_from_le_bytes {
20 ($($type:ty),+ $(,)?) => {
21 $(
22 impl sealed::Sealed for $type {}
23
24 impl FromLeBytes for $type {
25 const BYTE_SIZE: usize = size_of::<Self>();
26
27 fn from_le_bytes(bytes: &[u8]) -> Self {
28 let mut array = [0_u8; size_of::<Self>()];
29 array.copy_from_slice(bytes);
30 Self::from_le_bytes(array)
31 }
32 }
33 )+
34 };
35}
36
37impl_from_le_bytes!(i32, i64, f32, f64);
38
39#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
41pub enum RawBytesError {
42 #[error(
43 "invalid byte length for little-endian {type_name} scalar: expected {expected}, got {actual}"
44 )]
45 ScalarLength {
46 type_name: &'static str,
47 expected: usize,
48 actual: usize,
49 },
50 #[error(
51 "invalid byte length for little-endian {type_name} vector: {actual} is not a multiple of {element_size}"
52 )]
53 VectorLength {
54 type_name: &'static str,
55 element_size: usize,
56 actual: usize,
57 },
58}
59
60pub fn read_scalar_le<T: FromLeBytes>(bytes: &[u8]) -> Result<T, RawBytesError> {
62 if bytes.len() != T::BYTE_SIZE {
63 return Err(RawBytesError::ScalarLength {
64 type_name: std::any::type_name::<T>(),
65 expected: T::BYTE_SIZE,
66 actual: bytes.len(),
67 });
68 }
69 Ok(T::from_le_bytes(bytes))
70}
71
72#[allow(
78 clippy::chunks_exact_to_as_chunks,
79 reason = "chunk size is an associated const of a generic parameter"
80)]
81pub fn read_vec_le<T: FromLeBytes>(bytes: &[u8]) -> Result<Vec<T>, RawBytesError> {
82 if !bytes.len().is_multiple_of(T::BYTE_SIZE) {
83 return Err(RawBytesError::VectorLength {
84 type_name: std::any::type_name::<T>(),
85 element_size: T::BYTE_SIZE,
86 actual: bytes.len(),
87 });
88 }
89 Ok(bytes
90 .chunks_exact(T::BYTE_SIZE)
91 .map(T::from_le_bytes)
92 .collect())
93}
94
95#[derive(Clone, Debug, PartialEq)]
100pub struct TensorData {
101 pub name: Option<String>,
102 pub dtype: DataType,
103 pub dims: Vec<usize>,
105 pub data: Vec<u8>,
109 pub strings: Vec<String>,
111}
112
113impl TensorData {
114 pub fn from_raw(dtype: DataType, dims: Vec<usize>, data: Vec<u8>) -> Self {
116 Self {
117 name: None,
118 dtype,
119 dims,
120 data,
121 strings: Vec::new(),
122 }
123 }
124
125 pub fn numel(&self) -> usize {
127 self.checked_numel().expect("tensor element count overflow")
128 }
129
130 pub fn checked_numel(&self) -> Option<usize> {
132 checked_numel(&self.dims)
133 }
134
135 pub fn expected_bytes(&self) -> usize {
138 self.checked_expected_bytes()
139 .expect("tensor byte count overflow")
140 }
141
142 pub fn checked_expected_bytes(&self) -> Option<usize> {
144 checked_expected_bytes(self.dtype, &self.dims)
145 }
146}
147
148pub fn checked_numel(dims: &[usize]) -> Option<usize> {
150 dims.iter()
151 .try_fold(1usize, |product, &dimension| product.checked_mul(dimension))
152}
153
154pub fn checked_expected_bytes(dtype: DataType, dims: &[usize]) -> Option<usize> {
156 let element_count = checked_numel(dims)?;
157 if dtype == DataType::Undefined {
158 return None;
159 }
160 if dtype.is_sub_byte() {
161 let elements_per_byte = 8 / dtype.bit_size();
162 return (element_count / elements_per_byte).checked_add(usize::from(
163 !element_count.is_multiple_of(elements_per_byte),
164 ));
165 }
166 element_count.checked_mul(dtype.byte_size())
167}
168
169#[derive(Clone, Debug, PartialEq)]
171pub struct SparseTensorData {
172 pub values: TensorData,
174 pub indices: TensorData,
176 pub dims: Vec<usize>,
178}
179
180#[derive(Clone, Debug, PartialEq)]
183pub enum TypeProto {
184 Tensor {
185 dtype: DataType,
186 shape: Shape,
187 },
188 Sequence(Box<TypeProto>),
189 Optional(Box<TypeProto>),
190 Map {
191 key: DataType,
192 value: Box<TypeProto>,
193 },
194 SparseTensor {
195 dtype: DataType,
196 shape: Shape,
197 },
198}
199
200#[derive(Clone, Debug, PartialEq)]
206pub enum WeightRef {
207 Inline(TensorData),
209 External {
211 path: PathBuf,
212 offset: usize,
213 length: usize,
214 dtype: DataType,
215 dims: Vec<usize>,
216 },
217}
218
219impl WeightRef {
220 pub fn dtype(&self) -> DataType {
222 match self {
223 WeightRef::Inline(t) => t.dtype,
224 WeightRef::External { dtype, .. } => *dtype,
225 }
226 }
227
228 pub fn dims(&self) -> &[usize] {
230 match self {
231 WeightRef::Inline(t) => &t.dims,
232 WeightRef::External { dims, .. } => dims,
233 }
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn tensor_numel_and_bytes() {
243 let t = TensorData::from_raw(DataType::Float32, vec![2, 3], vec![0u8; 24]);
244 assert_eq!(t.numel(), 6);
245 assert_eq!(t.expected_bytes(), 24);
246 }
247
248 #[test]
249 fn sub_byte_expected_bytes() {
250 let t = TensorData::from_raw(DataType::Int4, vec![3], vec![0u8; 2]);
251 assert_eq!(t.numel(), 3);
252 assert_eq!(t.expected_bytes(), 2); }
254
255 #[test]
256 fn checked_geometry_rejects_overflow() {
257 let tensor = TensorData::from_raw(DataType::Float32, vec![usize::MAX, 2], Vec::new());
258 assert_eq!(tensor.checked_numel(), None);
259 assert_eq!(tensor.checked_expected_bytes(), None);
260
261 let byte_overflow =
262 TensorData::from_raw(DataType::Float64, vec![usize::MAX / 4], Vec::new());
263 assert_eq!(byte_overflow.checked_numel(), Some(usize::MAX / 4));
264 assert_eq!(byte_overflow.checked_expected_bytes(), None);
265 }
266
267 #[test]
268 fn weight_ref_accessors() {
269 let w = WeightRef::External {
270 path: PathBuf::from("weights.bin"),
271 offset: 128,
272 length: 4096,
273 dtype: DataType::Float16,
274 dims: vec![64, 32],
275 };
276 assert_eq!(w.dtype(), DataType::Float16);
277 assert_eq!(w.dims(), &[64, 32]);
278 }
279
280 #[test]
281 fn read_little_endian_values() {
282 assert_eq!(read_scalar_le::<i32>(&42_i32.to_le_bytes()), Ok(42));
283
284 let bytes = [1.5_f32.to_le_bytes(), (-2.0_f32).to_le_bytes()].concat();
285 assert_eq!(read_vec_le::<f32>(&bytes), Ok(vec![1.5, -2.0]));
286 assert_eq!(read_vec_le::<i64>(&[]), Ok(Vec::new()));
287 }
288
289 #[test]
290 fn read_little_endian_values_rejects_wrong_lengths() {
291 assert!(matches!(
292 read_scalar_le::<i64>(&[0; 7]),
293 Err(RawBytesError::ScalarLength {
294 expected: 8,
295 actual: 7,
296 ..
297 })
298 ));
299 assert!(matches!(
300 read_vec_le::<i32>(&[0; 5]),
301 Err(RawBytesError::VectorLength {
302 element_size: 4,
303 actual: 5,
304 ..
305 })
306 ));
307 }
308}