Summary
burn_pack::Writer requires every tensor's Bytes to already be resident in memory before writing starts. write_to_file / into_bytes / size() all call plan() → build_descriptors() first, which reads tensor.bytes.len() for every Tensor in the Vec<Tensor> to compute the aligned offset table — before any I/O happens. So the caller must materialize every tensor's data up front just to build the Vec<Tensor> that goes into Writer::new.
This is asymmetric with the reader: the README advertises tensor data as "read lazily from files," which is true — Reader::from_file reads only the header+metadata up front, and each tensor's bytes are a Bytes::view window read from disk on first access. There's no equivalent for writing.
It's also a regression from the pre-#5064 burn_store::BurnpackWriter, which held Vec<TensorSnapshot> and called snapshot.to_data() inside the per-tensor write loop — materialize, write, drop, repeat — bounding peak memory to the largest single tensor instead of the whole tensor set.
Impact
BurnpackStore::collect_from has the same shape: it maps every TensorSnapshot through bridge::snapshot_to_tensor (which calls to_data(), materializing) before building Vec<Tensor>. Any caller saving a large Module — or, in our case, burn-onnx, which writes .bpk files during codegen straight from a Vec<TensorSnapshot> — must hold the entire tensor set in host memory at once. burn-onnx's model-check suite includes multi-GB models (stable-diffusion-xl, qwen), so this meaningfully raises peak RSS during codegen.
We worked around it in burn-onnx with a hand-rolled streaming writer. Doing that required duplicating burn-pack's private on-disk layout types (Metadata, TensorDescriptor are pub(crate)) — not something a downstream crate should have to maintain.
Root cause
build_descriptors() reads tensor.bytes.len() for every tensor to compute offsets, before any bytes are written.
Tensor.bytes: Bytes is a concrete, already-resolved field — no lazy-provider variant.
write_tensor_data's WRITE_CHUNK_SIZE streaming only bounds one already-resident tensor's write (useful for device-resident tensors); it doesn't address how many tensors are resident simultaneously going into the write loop.
Suggested fix
A two-phase Writer: register each tensor's metadata (name, dtype, shape, byte length, param_id) without its bytes to build the offset table, then supply bytes per tensor via a callback/lazy provider during the actual write pass — mirroring what the old BurnpackWriter did with TensorSnapshot.
Environment
Confirmed against main @ f2321dde9549f5b9b2296b34876e168ebe4efcaf (2026-07-22) — writer.rs, reader.rs, and base.rs are unchanged since #5064 (2026-06-16).
Summary
burn_pack::Writerrequires every tensor'sBytesto already be resident in memory before writing starts.write_to_file/into_bytes/size()all callplan()→build_descriptors()first, which readstensor.bytes.len()for everyTensorin theVec<Tensor>to compute the aligned offset table — before any I/O happens. So the caller must materialize every tensor's data up front just to build theVec<Tensor>that goes intoWriter::new.This is asymmetric with the reader: the README advertises tensor data as "read lazily from files," which is true —
Reader::from_filereads only the header+metadata up front, and each tensor's bytes are aBytes::viewwindow read from disk on first access. There's no equivalent for writing.It's also a regression from the pre-#5064
burn_store::BurnpackWriter, which heldVec<TensorSnapshot>and calledsnapshot.to_data()inside the per-tensor write loop — materialize, write, drop, repeat — bounding peak memory to the largest single tensor instead of the whole tensor set.Impact
BurnpackStore::collect_fromhas the same shape: it maps everyTensorSnapshotthroughbridge::snapshot_to_tensor(which callsto_data(), materializing) before buildingVec<Tensor>. Any caller saving a largeModule— or, in our case, burn-onnx, which writes.bpkfiles during codegen straight from aVec<TensorSnapshot>— must hold the entire tensor set in host memory at once. burn-onnx's model-check suite includes multi-GB models (stable-diffusion-xl,qwen), so this meaningfully raises peak RSS during codegen.We worked around it in burn-onnx with a hand-rolled streaming writer. Doing that required duplicating
burn-pack's private on-disk layout types (Metadata,TensorDescriptorarepub(crate)) — not something a downstream crate should have to maintain.Root cause
build_descriptors()readstensor.bytes.len()for every tensor to compute offsets, before any bytes are written.Tensor.bytes: Bytesis a concrete, already-resolved field — no lazy-provider variant.write_tensor_data'sWRITE_CHUNK_SIZEstreaming only bounds one already-resident tensor's write (useful for device-resident tensors); it doesn't address how many tensors are resident simultaneously going into the write loop.Suggested fix
A two-phase
Writer: register each tensor's metadata (name, dtype, shape, byte length, param_id) without its bytes to build the offset table, then supply bytes per tensor via a callback/lazy provider during the actual write pass — mirroring what the oldBurnpackWriterdid withTensorSnapshot.Environment
Confirmed against
main@f2321dde9549f5b9b2296b34876e168ebe4efcaf(2026-07-22) —writer.rs,reader.rs, andbase.rsare unchanged since #5064 (2026-06-16).