Skip to content

[Story] Memory-efficient compilation of pi0.5 (ExecuTorch + TensorRT) for 8 GB devices #4867

Open

Description

@cehongwang

Goal

Compile pi0.5 (lerobot, bf16) to ExecuTorch + TensorRT within the memory of a Jetson-class 8 GB device, where CPU and GPU share one pool. Anthony Shoumikhin reported about 50 GB for the export on Jetson Thor, then about 29 GB after #4774/#4775 and ExecuTorch #23339/#23340. This story tracks the PRs by @cehongwang and @narendasan that bring it down further.

Where it stands (2026-10-30)

Measured on a Jetson AGX Thor. Peak is system used memory plus GPU memory above idle; GPU memory on this board comes from a hugepage pool, which is counted. Latency is the median of 3 × 100 chunks through ExecuTorch's Python runtime with the Torch-TensorRT delegate. Stack: torch 2.15.0.dev20260930, ExecuTorch 1.6.0.dev20261020, TensorRT 11.4, Torch-TensorRT Python-only.

Export time Export peak Run-time memory Time per chunk Max action error
lerobot's pi05_executorch_tensorrt.py, a chain of 9 processes 394 s 11,752 MiB 6,124 MiB 125.9 ms 0.021
One process, main + the PRs below (prefix program of 8 engines, step program with its time table) 233 s 4,983 MiB (812 MiB GPU) 5,908 MiB 117.9 ms 0.029
  • On a Jetson Orin Nano 8 GB (JetPack 7.2, run by @shoumikhin with the reproduction branches below), the export peaks at about 3.3 GB above idle. A chunk takes about 615 ms, and the largest action error is 0.044. It also works on the Torch-TensorRT 2.15 release stack with these PRs.
  • Earlier on one A40 (load, compile and save; peak host RSS + GPU memory), the same flow peaked at 6.59 GiB.
  • The one-time weights dump maps from a bf16 file. It's now written straight from the float32 checkpoint, without loading it, in 0.91 GiB (feat(dynamo): map_weights - compile from memory-mapped weights, and convert a checkpoint without loading it #4854), so it runs on the 8 GB board too.

To reproduce: pytorch/TensorRT:cehongw/pi05-low-memory-repro holds every Torch-TensorRT PR below combined on main. cehongwang/lerobot:pi05-6gb-repro, examples/export/pi05_low_memory/, holds the export script, the versions and the commands.

PRs

Each State badge links to the PR and updates on its own.

Engines and plans

PR Author What Measured on pi0.5 State
#4863 @cehongwang engine_output_dir: write each engine to disk at build time and load it lazily. The ExecuTorch save streams it from the file into the .pte, as FileBackedData, without reading it into memory. compile 14.17 → 5.59 GiB state
#4794 @narendasan Build C++ engines from a view of the plan instead of copies state
#4795 @narendasan Don't keep a host copy of the plan once the engine is set up −8.7 GiB at compile with partitioning state
#4796 @narendasan Hand the built engine's plan to the runtime without copying it state
#4821 @cehongwang ExecuTorch save stores each engine once, as named data export peak 17.8 → 11.6 GiB (13 engines) state

Weights

PR Author What Measured on pi0.5 State
#4853 @cehongwang release_weights_after_build: free a weight once its last engine is built, and never-read weights before the first build 25.7 → 18.9 GiB (9 engines) state
#4854 @cehongwang Weight mapping tools (on #4853):
• map_weights: compile from memory-mapped checkpoint weights, dropping their pages once built; prefix= maps a submodule from a whole-model file.
• save_weights_for_mapping(source=checkpoint_source(...)): write the mapping file from a checkpoint without loading it.
host RSS before compile 8.7 → 2.9 GiB; writing the mapping file 9.3 → 0.91 GiB state
#4942 @cehongwang low_memory, the RFC on #4854 (on #4854):
• one block builds the model on meta, maps its weights and sets compile defaults, also for code you don't own;
• release_weights_after_build defaults to releasing mapped weights only.
the API on top of #4854's measured savings; no new memory measurement state
#4793 @narendasan Offload modules to the CPU without holding both copies not measured alone state

Partitioning

PR Author What Measured on pi0.5 State
#4783 @cehongwang Keep a getitem with its multi-output producer when splitting by size makes small caps possible (prefix split at 0.5 GiB) state
#4855 @cehongwang resource_partition_engine_cap: a fixed per-engine size same split on every run (27 vs 13 engines without it); prefix build 17.6 GiB unsplit → 5.3 state
#4820 @cehongwang Free memory before the resource partitioner reads RSS (on #4783) −2.8 GiB RSS at partitioning after a CPU load state
#4961 @cehongwang The resource partitioner recomputes large input-only values (attention mask, rotary cos/sin) in each engine; values of 64 KiB or less cross between engines. The graph walks are iterative, so chains deeper than Python's recursion limit work. Without it, every Gemma attention engine fails to build on sm_110 (TensorRT 11.0–11.4): "uses too much shared data (0x39174 bytes, 0x38c00 max)" state

Build and save

PR Author What Measured on pi0.5 State
#4792 @narendasan Return the builder heap to the OS after each engine build (malloc_trim) −14.9 GiB state
#4864 @cehongwang The exporter shares the compiled module's tensors instead of deep-copying them −6.74 GiB in the actions program's save state
#4865 @cehongwang Free each saved program at the end of save(output_format="executorch"). ExecuTorch's is_same_node cache and FakeTensorMode's dispatch cache kept every lowered program, engines included, alive. whole export 7.62 → 6.59 GiB (7.73 → 6.59 on ExecuTorch 1.6) state

Docs

PR Author What State
#4858 @cehongwang CPU compilation and resource partitioning in the resource management guide. CPU compilation alone: 41.8 → 34.9 GiB, same engine and latency. state
#4797 @narendasan Compile memory guidance state

Stacks:

#4863, #4864, #4865 and #4961 are each off main, and all are up to date with it (2026-10-30). Each also applies cleanly to release/2.15 on its own. Applied one after another, #4853 and #4863 conflict only where both add a setting at the same spot, and #4863 and #4865 where both add a test; both sides are kept.

Related work by others

PR Author What State
#4774 @shoumikhin Copy the engine once when building the delegate blob state
#4775 @shoumikhin Share tensors when copying a program for preprocess state
ExecuTorch #23339 @shoumikhin Use the backend's copy hook in single-backend to_backend state
ExecuTorch #23340 @shoumikhin Restore program segments from a view of the input state

Together, #4774, #4775 and the two ExecuTorch PRs leave one engine copy in the save: −6.6 GiB.

Still open

Activity

  1. added
    StoryIssues proposing a new Story
    Story: Runtime & Memory & SerializationEngine execution, CUDA graphs, weight streaming, model save/load, refit, memory management, and run
    on Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    StoryIssues proposing a new StoryStory: Runtime & Memory & SerializationEngine execution, CUDA graphs, weight streaming, model save/load, refit, memory management, and run

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions