Uh oh!
There was an error while loading. Please reload this page.
perf: skip the base64 round trip when reading TensorRT engine info - #4502
Conversation
79fce72 to
31dcecbCompareGasoonjia
commented
Aug 18, 2026
thx for the update! What's the major component of |
31dcecb to
19c01abCompareserialize() base64-encodes the engine so the whole record can travel as a vector of strings. Consumers that want only metadata pay that encode for nothing, and consumers that want the engine pay it and then immediately decode it again on the Python side -- an encode plus a decode of a multi-megabyte blob, purely to move bytes through a string. Add the two halves directly: serialize_metadata_only() every field except ENGINE_IDX. All are plain members, so no engine serialization happens at all. serialized_engine_tensor() the engine as a uint8 tensor. Returns at::Tensor rather than std::string because TorchScript maps std::string to a Python str and engine bytes are not valid UTF-8; a tensor is also what the consumer builds anyway. It wraps TensorRT's buffer instead of copying it, so peak host memory is one engine rather than two; the storage is not resizable as a result. serialize() now fills ENGINE_IDX into serialize_metadata_only()'s record instead of carrying a second copy of the field list, so the two cannot drift apart when a field is added. Its output is byte-identical, so existing serialized engines and .pte artifacts are unaffected. Measured through the Python consumers added in the following commit, on a model with a 67MB engine: the ExecuTorch lowering phase drops 54%, 2.35s to 1.08s.
Export resolves engine info for two purposes, and neither wants the base64 form serialize() produces. validate_engine_program reads only flags; replace_execute_engine wants the engine as a byte tensor. Both were paying an encode in C++ and, for the second, a matching decode here. Take each half from the accessor that provides it: metadata_only=True on get_engine_info_from_state and _resolve_engine_info, so a metadata reader never triggers engine serialization. _resolve_engine_tensor returns the engine as a uint8 tensor directly, which also drops the torch.frombuffer rebuild. Both fall back to the old path when the runtime lacks the accessors, so this does not require a matching runtime. They are missing only when the Torch-TensorRT C++ library is older than this Python package -- a source or editable build where only the Python half was rebuilt. The symptom would otherwise be silence, since the fallback is correct and merely slower, so it warns once per accessor naming the cause. _resolve_engine_object is factored out of _resolve_engine_info because the engine arg is a get_attr before ExecuTorch lifts constants and a placeholder after; handling only the first made the tensor accessor silently fall back to base64 on every graph that had been through staging, which is exactly the case that matters. Nothing is cached. With the accessors present a metadata read is a member read away from free, so there is nothing worth memoizing; a runtime without them re-serializes per read, which is the price of keeping this change small. backend.py and partitioner.py are deliberately untouched. Their engine reads go through the no_op_placeholder branch, which already carries the engine as a tensor argument and serializes nothing, so routing them through the accessors would add a parameter that changes no work. Measured on a base carrying pytorch#4473, with pytorch#4489 present in the loaded runtime, exporting a model with a 67MB engine; six runs per configuration, interleaved. The passes that read engine info go from 1.40s to 0.11s and the ExecuTorch lowering phase from 2.35s to 1.08s, a 54% reduction. Engine-state reads are nearly all of that: 1.07s to 0.07s, with the Python-side base64 decode going from 0.26s to nothing. Serializing the engine is only about 0.07s of a 0.54s __getstate__, so most of what disappears is the base64 encode and the string copies feeding it. A metadata-only read costs 10-20us.
19c01ab to
42e9e93CompareReviewed this and found no defects. The tests are unusually good, they assert the performance property itself rather than only correctness. Notes, then two questions. The memory handling in So skipping Having I ran the Python logic directly (loading The third one is the property that makes the fallback safe, so it is good that you have it as a test. One latent footgun. With On CI, the three failures are not yours. Main is already red in every Two questions:
|
Uh oh!
There was an error while loading. Please reload this page.
Description
TRTEngine::serialize()base64-encodes the serialized engine so the whole record can travel as astd::vector<std::string>. That encoding is the only reason the engine is a string, and both ExecuTorch export consumers pay for it without wanting it:validate_engine_programreads only flags and binding names, but reaching them goes through__getstate__, which serializes the entireICudaEngineand base64-encodes it first.replace_execute_enginewants the engine as auint8tensor, so it pays the encode in C++ and then an immediate matching decode in Python — an encode plus a decode of a multi-megabyte blob, purely to move bytes through a string.This adds the two halves as accessors and points the consumers at them.
C++ (
core/runtime)serialize_metadata_only()serialized_infofield exceptENGINE_IDX. All plain members, so no engine serialization happens at all.serialized_engine_tensor()uint8at::Tensor.serialized_engine_tensor()returnsat::Tensorrather thanstd::stringbecause TorchScript mapsstd::stringto a Pythonstrand engine bytes are not valid UTF-8 (UnicodeDecodeError); a tensor is also what the consumer builds anyway.serialize()now fillsENGINE_IDXintoserialize_metadata_only()'s record rather than carrying a second copy of the field list, so the two cannot drift apart when a field is added.Python (
py/torch_tensorrt/executorch/_export_utils.py)A
metadata_only=Truekwarg threaded throughget_engine_info_from_state/_resolve_engine_inforoutes metadata readers to the cheap accessor, and_resolve_engine_tensortakes the engine as a tensor directly (which also drops thetorch.frombufferrebuild).The other engine reader,
_get_engine_info_for_nodeinbackend.py, is left alone. Its one caller (partitioner.py) runs afterreplace_execute_enginehas rewritten those nodes tono_op_placeholder, whose engine argument is already a tensor, so it takes thenode.args[1:]branch and serializes nothing. Theexecute_enginebranch below it would still serialize, but no caller reaches it in the current pass order; threadingmetadata_onlythrough it would change no work today.Results
Exporting a model with a 67 MB engine; six runs per configuration, interleaved, one fresh process per run, medians with min–max. A is this PR's base #4440; B is that base plus these two commits.
Measured against the base (#4440), where the change lands — equivalent to a
main-relative measurement here, since both resolve each engine's info once and pay the single base64 round trip this PR removes. #4440 already includes #4473 (fbb10c9565), whose bulk engine copy sits in both columns and does not move the delta.Validation drops from 0.53 s to about 0.1 ms: it reads only metadata now, and the metadata accessor touches no engine bytes. The rewrite still reads the engine, but as a tensor without the base64 round trip, so its engine read is 0.05 s rather than a 0.53 s
__getstate__plus a 0.24 s decode. The engine's raw serialization is about 0.08 s of a 0.57 s__getstate__, so most of what disappears is the base64 encode and the string copies feeding it. A metadata-only read costs about 10 µs.The separation is clean — every B run is faster than every A run on each metric — so a permutation test on the lowering phase gives p ≈ 0.002, the floor for six-versus-six fully separated samples.
Neither #4440 nor #4473 overlaps in scope with this change.
Compatibility
serialize()output is byte-identical, so existing serialized engines and.pteartifacts are unaffected.serialized_engine_tensor()wraps TensorRT's buffer withat::from_blobrather than copying it, so peak host memory during the call is one engine rather than two — which matters for the multi-GB engines theuntyped_storage()comment inbackend.py'spreprocessalready accounts for. The consequence is that the returned tensor's storage is not resizable; nothing on the export path resizes it.Dependency
Stacked on #4440 (composable Edge export API), which introduces
py/torch_tensorrt/executorch/_export_utils.py. The C++ commit stands alone; the Python commit needs that file to exist.Related work
#4489 is orthogonal and complementary. It stops fakification from serializing the engine through
TRTEngine::__obj_flatten__; this PR removes the base64 round trip in the engine-info reads. Both cut export-time engine serialization, but at different points, and they compose — neither depends on the other.Type of change
Checklist: