Skip to content

burn_pack::Writer requires eager materialization of all tensor bytes before writing #5219

Description

@antimora

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions