Skip to main content

burn_pack/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3//! # Burn Pack
4//!
5//! The **burnpack** binary serialization format for the Burn deep learning framework.
6//!
7//! `burn-pack` is intentionally minimal and tensor-library-agnostic: it depends only on
8//! `burn-std` (for [`DType`] / [`Bytes`]), `serde`, and a CBOR codec. It knows how to read
9//! and write the burnpack container format but has no notion of Burn modules or tensors.
10//! Higher layers (e.g. `burn-core`) bridge between [`Tensor`] entries and their own
11//! tensor/snapshot types.
12//!
13//! Write a pack with [`Writer`], read one with [`Reader`]; both operate on [`Tensor`]
14//! entries carrying the format-level metadata plus the raw little-endian bytes.
15//!
16//! A tensor's bytes need not exist yet. A [`Reader`] reads them from the source only when
17//! they are accessed, and a caller that knows a tensor's length without holding its data (a
18//! module snapshot, an ONNX initializer) can build one with [`Tensor::deferred`] to write a
19//! model larger than host memory to a file - see that constructor for the contract.
20//!
21//! ```
22//! use burn_pack::{Bytes, DType, Reader, Tensor, Writer};
23//!
24//! // A 2x2 f32 tensor, as raw little-endian bytes.
25//! let raw: Vec<u8> = [1.0f32, 2.0, 3.0, 4.0]
26//!     .iter()
27//!     .flat_map(|v| v.to_le_bytes())
28//!     .collect();
29//! let tensor = Tensor::new(
30//!     "weight".to_string(),
31//!     DType::F32,
32//!     vec![2, 2],
33//!     Some(42), // optional param id
34//!     Bytes::from_bytes_vec(raw),
35//! );
36//!
37//! // Write to an in-memory buffer ...
38//! let packed = Writer::new(vec![tensor])
39//!     .with_metadata("producer", "burn-pack docs")
40//!     .into_bytes()
41//!     .unwrap();
42//!
43//! // ... and read it back.
44//! let reader = Reader::from_bytes(packed).unwrap();
45//! assert_eq!(reader.metadata()["producer"], "burn-pack docs");
46//! // Consume the reader to get the tensors (zero-copy views into the source).
47//! let tensors = reader.into_tensors().unwrap();
48//! assert_eq!(tensors.len(), 1);
49//! assert_eq!(tensors[0].name, "weight");
50//! assert_eq!(tensors[0].shape.to_vec(), vec![2, 2]);
51//! assert_eq!(tensors[0].param_id, Some(42));
52//! ```
53//!
54//! # File format
55//!
56//! A burnpack file has three parts: a fixed-size header, a CBOR metadata blob, and a
57//! 256-byte-aligned tensor data section. All multi-byte integers are little-endian.
58//!
59//! ```text
60//! ┌──────────────────────────────────────────────────────────────┐
61//! │ Header — 10 bytes ([`HEADER_SIZE`])                           │
62//! │   magic         : u32  — 0x4255524E "BURN" ([`MAGIC_NUMBER`]) │
63//! │   version       : u16  — format version ([`FORMAT_VERSION`])  │
64//! │   metadata_size : u32  — byte length of the CBOR metadata     │
65//! ├──────────────────────────────────────────────────────────────┤
66//! │ Metadata — CBOR, `metadata_size` bytes                       │
67//! │   tensors : map<name, descriptor>                            │
68//! │     dtype        : [`DType`]                                  │
69//! │     shape        : list<u64>                                  │
70//! │     data_offsets : (start, end)  relative to the data section │
71//! │     param_id     : optional u64  (training-state identity)    │
72//! │   metadata : map<string, string>  user key/value pairs       │
73//! ├──────────────────────────────────────────────────────────────┤
74//! │ Padding to the next 256-byte boundary                        │
75//! │   ([`aligned_data_section_start`])                            │
76//! ├──────────────────────────────────────────────────────────────┤
77//! │ Tensor data section                                          │
78//! │   each tensor's bytes start on a 256-byte boundary           │
79//! │   ([`TENSOR_ALIGNMENT`]) for aligned, lazy file-backed       │
80//! │   loading (see [`Bytes::from_file`]).                         │
81//! │   tensors sliced zero-copy.                                   │
82//! └──────────────────────────────────────────────────────────────┘
83//! ```
84//!
85//! ## Why 256-byte alignment
86//!
87//! Aligning every tensor to a 256-byte boundary ([`TENSOR_ALIGNMENT`]) lets a reader
88//! memory-map the file and hand out tensor slices without copying, while satisfying the
89//! alignment requirements of every element type (including 8-byte `f64`), cache lines,
90//! and GPU coalesced access. 256 bytes matches the choice made by GGUF, MLX, ncnn, and
91//! other major formats.
92//!
93//! ## Safety limits
94//!
95//! Reading is hardened against malicious or corrupt inputs. The reader rejects files
96//! that exceed any of the following before allocating for them:
97//!
98//! - [`MAX_METADATA_SIZE`] — largest CBOR metadata blob
99//! - [`MAX_TENSOR_COUNT`] — largest number of tensors
100//! - [`MAX_TENSOR_SIZE`] — largest single tensor
101//! - [`MAX_CBOR_RECURSION_DEPTH`] — deepest CBOR nesting (stack-overflow guard)
102//! - [`MAX_FILE_SIZE`] — largest file accepted by the file loaders (std only)
103//!
104//! It also validates that the file is large enough to contain every tensor it claims,
105//! returning [`Error::ValidationError`] otherwise.
106//!
107//! ## Feature Flags
108//!
109//! - `std`: Enables file I/O ([`Reader::from_file`], [`Reader::from_file_exact`],
110//!   [`Writer::write_to_file`], and [`Writer::write_to_file_in_place`]) (default)
111
112extern crate alloc;
113
114#[cfg(feature = "std")]
115mod atomic;
116mod base;
117mod reader;
118mod tensor;
119mod writer;
120
121#[cfg(feature = "std")]
122pub use atomic::AtomicFile;
123#[cfg(feature = "std")]
124pub use base::MAX_FILE_SIZE;
125pub use base::{
126    Error, FORMAT_VERSION, HEADER_SIZE, Header, MAGIC_NUMBER, MAX_CBOR_RECURSION_DEPTH,
127    MAX_METADATA_SIZE, MAX_TENSOR_COUNT, MAX_TENSOR_SIZE, Scalar, ScalarConversionError,
128    TENSOR_ALIGNMENT, aligned_data_section_start,
129};
130pub use reader::Reader;
131pub use tensor::Tensor;
132pub use writer::Writer;
133
134/// The canonical file extension for burnpack files (without the leading dot).
135pub const EXTENSION: &str = "bpk";
136
137// Re-export the core types so callers can build [`Tensor`] entries and inspect descriptors
138// without depending on `burn-std` directly.
139pub use burn_std::{Bytes, DType, Shape};