ExecuTorch has C++ save_ptd in extension/flat_tensor/serialize/serialize.h,
used by the CIFAR and XOR training examples, but Python has no equivalent.
The only Python entry point is FlatTensorSerializer, which takes a DataPayload
rather than a plain dict[str, torch.Tensor], so Python-side federated learning
servers cannot easily read or write .ptd files.
Add save_ptd and load_ptd to extension/flat_tensor/serialize/serialize.py,
mirroring the C++ argument order and supporting both a path and a binary
file-like object, which covers the two C++ overloads. Both are pure Python
built on the existing FlatTensorSerializer, so there is no new pybind11
surface and no risk of format drift between two implementations.
Details worth noting:
- Tensor bytes go through the existing _tensor_to_bytes helper, which already
handles bfloat16 (no numpy dtype) and non-C-contiguous layouts.
- load_ptd reconstructs the logical shape by reshaping to the physical sizes
implied by dim_order and then inverting that permutation, so channels_last
tensors round-trip exactly rather than coming back transposed.
- Tensors appearing under multiple keys, such as a tied embedding and output
projection, are written once and both keys point at the same buffer.
- Empty tensors are built directly, since torch.frombuffer rejects a
zero-length buffer.
- Named data entries with no tensor layout are not tensors and are skipped.
Tests cover dtype round-trips including bfloat16, scalar and empty tensors,
channels_last, tied-tensor deduplication, the file-object overload, alignment
of segment offsets, and the two error paths.
Also declare the deps serialize.py now imports directly (torch,
exir:tensor, exir:tensor_layout) on the serialize python_library.
Fixes pytorch#8542
Fixes #8542.
ExecuTorch has save_ptd in C++ (extension/flat_tensor/serialize/serialize.h, used by the CIFAR and XOR training examples) but nothing equivalent in Python - grep -rn "save_ptd|load_ptd" --include=*.py comes back empty. The only Python entry point is FlatTensorSerializer, which takes a DataPayload rather than a plain dict[str, torch.Tensor].
Per @JacobSzwejbka's comment on the issue, the ask is round-tripping dict[str, tensor], which #11779 made straightforward.
Added save_ptd and load_ptd in extension/flat_tensor/serialize/serialize.py, mirroring the C++ argument order and taking either a path or a binary file-like object, which covers both C++ overloads in one function. Pure Python on top of the existing FlatTensorSerializer, so no new pybind11 surface and no second implementation to drift.
A few things that needed care:
bfloat16 and non-contiguous layouts - tensor bytes go through the existing _tensor_to_bytes helper rather than a fresh .numpy().tobytes(), since bfloat16 has no numpy dtype.
channels_last - dim_order is a real permutation, so the bytes on disk aren't in logical order. load_ptd reshapes to the physical sizes implied by dim_order and then inverts that permutation. Without it the tensor comes back transposed. There's a test asserting both value equality and that the memory format survives.
Tied weights - a tensor under several keys, like an embedding reused as the output projection, is written once with both keys pointing at one buffer.
Empty tensors are built directly, since torch.frombuffer rejects a zero-length buffer. Named data entries with no tensor layout aren't tensors and get skipped rather than mis-decoded.
Ten new tests: dtype round-trips including bfloat16, scalar and empty tensors, channels_last, tied-tensor dedup, the file-object overload, alignment of segment offsets, and the two error paths. All pass, along with the existing TestSerialize cases in the same file.
ruff isn't used here; ufmt (black 24.4.2 + usort) and flake8 6.1.0 are clean on the changed files.
Note that extension/flat_tensor/test/test_serialize.py is in the --ignore list in pytest.ini (pre-existing, for missing flatc), so these run through the internal python_unittest target rather than OSS pytest. I verified them locally against a flatc-provisioned environment.
Two things I'd like your call on. I mirrored the C++ argument order with path first, since the issue framed this as the Python form of the C++ API - the Python-native alternative would be save_ptd(tensor_map, path, ...) matching torch.save(obj, f). Happy to flip it. And I deliberately left extension/flat_tensor/init.py empty; populating it would give a tidier import, but no python_library target currently covers that file and it would make importing the subpackage pull in torch. Glad to add it if you'd prefer the shorter import.