1use alloc::collections::BTreeMap;
6use alloc::format;
7use alloc::string::String;
8use alloc::vec::Vec;
9use burn_std::DType;
10use byteorder::{ByteOrder, LittleEndian};
11use serde::{Deserialize, Serialize};
12
13pub const MAGIC_NUMBER: u32 = 0x4255524E;
16
17pub const FORMAT_VERSION: u16 = 0x0001;
19
20pub const MAGIC_SIZE: usize = 4;
22
23pub const VERSION_SIZE: usize = 2;
25
26pub const METADATA_SIZE_FIELD_SIZE: usize = 4;
28
29pub const HEADER_SIZE: usize = MAGIC_SIZE + VERSION_SIZE + METADATA_SIZE_FIELD_SIZE;
31
32pub const TENSOR_ALIGNMENT: u64 = 256;
49
50#[inline]
58pub fn aligned_data_section_start(metadata_size: usize) -> usize {
59 let unaligned_start = (HEADER_SIZE + metadata_size) as u64;
60 (unaligned_start.div_ceil(TENSOR_ALIGNMENT) * TENSOR_ALIGNMENT) as usize
62}
63
64pub const MAX_METADATA_SIZE: u32 = 100 * 1024 * 1024;
70
71#[cfg(target_pointer_width = "32")]
76pub const MAX_TENSOR_SIZE: usize = 2 * 1024 * 1024 * 1024;
77#[cfg(not(target_pointer_width = "32"))]
78pub const MAX_TENSOR_SIZE: usize = 10 * 1024 * 1024 * 1024;
79
80pub const MAX_TENSOR_COUNT: usize = 100_000;
83
84pub const MAX_CBOR_RECURSION_DEPTH: usize = 128;
87
88#[cfg(feature = "std")]
92pub const MAX_FILE_SIZE: u64 = 100 * 1024 * 1024 * 1024;
93
94pub const fn magic_range() -> core::ops::Range<usize> {
96 let start = 0;
97 let end = start + MAGIC_SIZE;
98 start..end
99}
100
101pub const fn version_range() -> core::ops::Range<usize> {
103 let start = MAGIC_SIZE;
104 let end = start + VERSION_SIZE;
105 start..end
106}
107
108pub const fn metadata_size_range() -> core::ops::Range<usize> {
110 let start = MAGIC_SIZE + VERSION_SIZE;
111 let end = start + METADATA_SIZE_FIELD_SIZE;
112 start..end
113}
114
115const _: () = assert!(MAGIC_SIZE + VERSION_SIZE + METADATA_SIZE_FIELD_SIZE == HEADER_SIZE);
117
118#[derive(Debug, Clone, Copy)]
120pub struct Header {
121 pub magic: u32,
123 pub version: u16,
125 pub metadata_size: u32,
127}
128
129impl Header {
130 pub fn new(metadata_size: u32) -> Self {
132 Self {
133 magic: MAGIC_NUMBER,
134 version: FORMAT_VERSION,
135 metadata_size,
136 }
137 }
138
139 pub fn into_bytes(self) -> [u8; HEADER_SIZE] {
141 let mut bytes = [0u8; HEADER_SIZE];
142 LittleEndian::write_u32(&mut bytes[magic_range()], self.magic);
143 LittleEndian::write_u16(&mut bytes[version_range()], self.version);
144 LittleEndian::write_u32(&mut bytes[metadata_size_range()], self.metadata_size);
145 bytes
146 }
147
148 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
150 if bytes.len() < HEADER_SIZE {
151 return Err(Error::InvalidHeader);
152 }
153
154 let magic = LittleEndian::read_u32(&bytes[magic_range()]);
155 if magic != MAGIC_NUMBER {
156 return Err(Error::InvalidMagicNumber);
157 }
158
159 let version = LittleEndian::read_u16(&bytes[version_range()]);
160 let metadata_size = LittleEndian::read_u32(&bytes[metadata_size_range()]);
161
162 Ok(Self {
163 magic,
164 version,
165 metadata_size,
166 })
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
179pub enum Scalar {
180 Int(i64),
182 UInt(u64),
184 Float(f64),
186 Bool(bool),
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub struct ScalarConversionError;
194
195impl core::fmt::Display for ScalarConversionError {
196 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
197 write!(f, "scalar value does not fit the requested type")
198 }
199}
200
201impl core::error::Error for ScalarConversionError {}
202
203macro_rules! impl_scalar_int {
204 ($($t:ty => $variant:ident),* $(,)?) => {
205 $(
206 impl From<$t> for Scalar {
207 fn from(value: $t) -> Self {
208 Scalar::$variant(value as _)
209 }
210 }
211
212 impl TryFrom<Scalar> for $t {
213 type Error = ScalarConversionError;
214 fn try_from(scalar: Scalar) -> Result<Self, Self::Error> {
215 match scalar {
216 Scalar::Int(v) => v.try_into().map_err(|_| ScalarConversionError),
217 Scalar::UInt(v) => v.try_into().map_err(|_| ScalarConversionError),
218 _ => Err(ScalarConversionError),
219 }
220 }
221 }
222 )*
223 };
224}
225
226impl_scalar_int!(
227 i8 => Int, i16 => Int, i32 => Int, i64 => Int, isize => Int,
228 u8 => UInt, u16 => UInt, u32 => UInt, u64 => UInt, usize => UInt,
229);
230
231impl From<f64> for Scalar {
232 fn from(value: f64) -> Self {
233 Scalar::Float(value)
234 }
235}
236
237impl From<f32> for Scalar {
238 fn from(value: f32) -> Self {
239 Scalar::Float(value as f64)
240 }
241}
242
243impl From<bool> for Scalar {
244 fn from(value: bool) -> Self {
245 Scalar::Bool(value)
246 }
247}
248
249impl TryFrom<Scalar> for f64 {
250 type Error = ScalarConversionError;
251 fn try_from(scalar: Scalar) -> Result<Self, Self::Error> {
252 match scalar {
253 Scalar::Float(v) => Ok(v),
254 Scalar::Int(v) => Ok(v as f64),
255 Scalar::UInt(v) => Ok(v as f64),
256 _ => Err(ScalarConversionError),
257 }
258 }
259}
260
261impl TryFrom<Scalar> for f32 {
262 type Error = ScalarConversionError;
263 fn try_from(scalar: Scalar) -> Result<Self, Self::Error> {
264 match scalar {
266 Scalar::Float(v) => Ok(v as f32),
267 Scalar::Int(v) => Ok(v as f32),
268 Scalar::UInt(v) => Ok(v as f32),
269 _ => Err(ScalarConversionError),
270 }
271 }
272}
273
274impl TryFrom<Scalar> for bool {
275 type Error = ScalarConversionError;
276 fn try_from(scalar: Scalar) -> Result<Self, Self::Error> {
277 match scalar {
278 Scalar::Bool(v) => Ok(v),
279 _ => Err(ScalarConversionError),
280 }
281 }
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
286pub(crate) struct Metadata {
287 pub tensors: BTreeMap<String, TensorDescriptor>,
289 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
291 pub metadata: BTreeMap<String, String>,
292 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
296 pub scalars: BTreeMap<String, Scalar>,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301pub(crate) struct TensorDescriptor {
302 pub dtype: DType,
304 pub shape: Vec<u64>,
306 pub data_offsets: (u64, u64),
308 #[serde(default, skip_serializing_if = "Option::is_none")]
311 pub param_id: Option<u64>,
312}
313
314pub(crate) fn validate_tensor_byte_len(
319 name: &str,
320 dtype: DType,
321 shape: &[u64],
322 data_len: u64,
323) -> Result<(), Error> {
324 if matches!(dtype, DType::QFloat(_)) {
325 return Ok(());
326 }
327
328 let num_elements = shape
329 .iter()
330 .try_fold(1u64, |total, &dimension| total.checked_mul(dimension));
331 let expected = num_elements
332 .and_then(|num_elements| num_elements.checked_mul(dtype.size() as u64))
333 .ok_or_else(|| {
334 Error::ValidationError(format!(
335 "tensor '{name}' byte length calculation overflows u64 for shape {shape:?} and dtype {dtype:?}"
336 ))
337 })?;
338
339 if data_len != expected {
340 return Err(Error::ValidationError(format!(
341 "tensor '{name}' declares {data_len} bytes but its shape {shape:?} and dtype {dtype:?} need {expected}"
342 )));
343 }
344
345 Ok(())
346}
347
348#[derive(Debug)]
350pub enum Error {
351 InvalidHeader,
352 InvalidMagicNumber,
353 InvalidVersion,
354 MetadataSerializationError(String),
355 MetadataDeserializationError(String),
356 IoError(String),
357 TensorNotFound(String),
358 TensorBytesSizeMismatch(String),
359 ValidationError(String),
360 AlreadyExists(String),
362}
363
364impl core::fmt::Display for Error {
365 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
366 match self {
367 Error::InvalidHeader => write!(f, "Invalid header: insufficient bytes"),
368 Error::InvalidMagicNumber => write!(f, "Invalid magic number"),
369 Error::InvalidVersion => write!(f, "Unsupported version"),
370 Error::MetadataSerializationError(e) => {
371 write!(f, "Metadata serialization error: {}", e)
372 }
373 Error::MetadataDeserializationError(e) => {
374 write!(f, "Metadata deserialization error: {}", e)
375 }
376 Error::IoError(e) => write!(f, "I/O error: {}", e),
377 Error::TensorNotFound(name) => write!(f, "Tensor not found: {}", name),
378 Error::TensorBytesSizeMismatch(e) => {
379 write!(f, "Tensor bytes size mismatch: {}", e)
380 }
381 Error::ValidationError(e) => write!(f, "Validation error: {}", e),
382 Error::AlreadyExists(path) => write!(
383 f,
384 "File already exists: {}. To replace it, use .overwrite(true), or \
385 AtomicFile::commit instead of commit_new.",
386 path
387 ),
388 }
389 }
390}
391
392impl Error {
393 pub(crate) fn in_tensor(mut self, name: &str) -> Self {
399 match &mut self {
400 Error::MetadataSerializationError(message)
401 | Error::MetadataDeserializationError(message)
402 | Error::IoError(message)
403 | Error::TensorBytesSizeMismatch(message)
404 | Error::ValidationError(message) => *message = format!("tensor '{name}': {message}"),
405 Error::InvalidHeader
409 | Error::InvalidMagicNumber
410 | Error::InvalidVersion
411 | Error::TensorNotFound(_)
412 | Error::AlreadyExists(_) => {}
413 }
414 self
415 }
416}
417
418impl core::error::Error for Error {}
419
420#[cfg(test)]
421mod scalar_tests {
422 use super::*;
423
424 #[test]
425 fn int_round_trips_through_checked_conversion() {
426 assert_eq!(i32::try_from(Scalar::from(-5i32)).unwrap(), -5);
427 assert_eq!(u8::try_from(Scalar::from(200u8)).unwrap(), 200);
428 assert_eq!(usize::try_from(Scalar::from(42usize)).unwrap(), 42);
429 }
430
431 #[test]
432 fn out_of_range_int_conversion_is_rejected() {
433 let big = Scalar::from(5_000_000_000u64);
435 assert!(i32::try_from(big).is_err());
436 assert!(u32::try_from(Scalar::from(-1i32)).is_err());
438 assert!(u8::try_from(Scalar::from(300u32)).is_err());
440 }
441
442 #[test]
443 fn float_and_bool_variant_mismatches_are_rejected() {
444 assert!(i64::try_from(Scalar::Float(1.5)).is_err());
446 assert!(bool::try_from(Scalar::Int(1)).is_err());
448 assert!(f64::try_from(Scalar::Bool(true)).is_err());
450 }
451
452 #[test]
453 fn float_accepts_int_variants_symmetrically() {
454 assert_eq!(f64::try_from(Scalar::Int(3)).unwrap(), 3.0);
455 assert_eq!(f32::try_from(Scalar::Int(3)).unwrap(), 3.0);
456 assert_eq!(f64::try_from(Scalar::Float(2.5)).unwrap(), 2.5);
457 assert_eq!(f32::try_from(Scalar::Float(2.5)).unwrap(), 2.5);
458 }
459}