Repository navigation
TensorRT: LeRobot policies that do not run yet, and what blocks them #24353
Description
This issue tracks LeRobot policies exported to ExecuTorch with Torch-TensorRT: which ones run on which GPU, how fast, and what each project still needs to look at. CPU backends are tracked in #24325.
Coverage
| Policy | H100 | A100 | Thor | Orin Nano | Programs |
|---|---|---|---|---|---|
| act |
|
|
|
|
TRT 1 |
| diffusion |
|
|
|
|
TRT 3 |
| dm05 |
|
|
|
➖ | TRT 2 + CUDA 1 |
| eo1 |
|
|
|
➖ | TRT 2 + CUDA 1 |
| evo1 |
|
|
➖ | TRT 4 + CUDA 3 | |
| fastwam |
|
|
|
➖ | TRT 2 + CUDA 1 |
| fineart_vla |
|
|
|
➖ | TRT 1 |
| flux3 | ➖ | ||||
| gaussian_actor |
|
|
|
|
TRT 1 |
| groot |
|
|
|
|
TRT 10 |
| lawam |
|
|
|
➖ | TRT 2 + CUDA 1 |
| lingbot_va | 🔨 | 🔨 | 🔨 | ➖ | |
| lingbot_vla_v2 |
|
|
➖ | TRT 3 + CUDA 2 | |
| molmoact2 |
|
|
|
➖ | TRT 1 |
| multi_task_dit |
|
|
|
|
TRT 3 |
| pi0 |
|
|
|
|
TRT 9 |
| pi05 |
|
|
|
|
TRT 9 |
| pi0_fast |
|
|
|
➖ | TRT 3 |
| smolvla |
|
|
|
|
TRT 4 |
| tdmpc | TRT 1 | ||||
| vla_jepa |
|
|
|
➖ | TRT 3 + CUDA 2 |
| vqbet |
|
|
|
|
TRT 1 |
| wall_oss_05 |
|
|
|
➖ | TRT 2 + CUDA 1 |
| wall_x |
|
|
➖ | TRT 41 + CUDA 40 | |
| xvla |
|
|
|
|
TRT 1 |
Legend:
- The number: milliseconds to produce one action chunk (batch size 1, median) in
lerobot-rollout --bundle.path, with PyTorch never loaded, inputs and outputs on the GPU, and no operator on the CPU. - In brackets: how many times faster than eager PyTorch (
lerobot-rollout --policy.path) on the same device, checkpoint and dtype, with--tf32=false. No brackets: eager does not fit the device. -
$\color{#2da44e}{\checkmark}$ and a number: works with torch 2.15 and the ExecuTorch 1.6 and Torch-TensorRT 2.15 fixes listed under "Built on" (some are still in review). -
$\color{#d4a017}{\checkmark}$ and a number: works only with PyTorch fixes that are not in torch 2.15 (see "PyTorch" below), so it needs the next torch release. Measured with a torch 2.15.0 build that adds them. - ➖: the model is too big for this device's memory.
- 🔨: a fix for this cell is being worked on.
- ⏳: this cell is being measured (running or queued).
- Empty: not measured, and no fix in progress.
"Programs": how many TensorRT engines (TRT) and CUDA backend programs (CUDA) the model is split into.
Notes:
- One stack on all 4 devices (see "Built on"). Every bundle first passed the export's accuracy check against eager PyTorch.
- Orin Nano: xvla runs in bf16, because the float32 export does not fit.
- On the Thor, eager with TF32 on is faster than the strict float32 bundles (pi0: 249 against 820 ms).
- diffusion, eo1, gaussian_actor, multi_task_dit and vqbet use random weights, and so do fastwam, fineart_vla, vla_jepa and wall_x on the Thor.
Built on
One build per architecture from the same sources, used on all 4 devices:
- ExecuTorch: release/1.6, plus these pull requests, at the heads we measured.
- Merged into release/1.6:
- Python runtime: enforce CUDA stream selection rules on the stream itself #24346: CUDA stream rules in the Python runtime
- Cherry-picked into release/1.6:
- Fix mixed-dtype cat and stack in to_edge and the edge check #24381: mixed-dtype
catandstack(pick: Fix mixed-dtype cat and stack in to_edge and the edge check #24450) - Limit the default thread pool to the allowed CPUs on Linux (not Android) #24395: default thread pool size (pick: Limit the default thread pool to the allowed CPUs on Linux (not Android) #24449)
- Keep Half and BFloat16 sums in float in portable matmul, var, cumsum and pooling kernels, and fix portable cat #24401: Half and BFloat16 sums in portable kernels (pick: Keep Half and BFloat16 sums in float in portable matmul, var, cumsum and pooling kernels, and fix portable cat #24443)
- Fix mixed-dtype cat and stack in to_edge and the edge check #24381: mixed-dtype
- Open against release/1.6:
- Fix crashes, wrong outputs and lowering failures in several backends and in exir #24298: crashes, wrong outputs and lowering failures in several backends
- Fix CUDA mask indexing, CUDA graph outputs and the runtime output-buffer check #24320: CUDA mask indexing, CUDA graph outputs, output buffer check
- CUDA backend: per-program weights file names and antialiased resize #24324: CUDA weights file names, antialiased resize
- Fix 0-d tensors, dynamic output sizes and integer reductions, and default to float32 on CPU, in the OpenVINO backend #24377: OpenVINO fixes
- Merged into main, cherry-pick to release/1.6 open:
- Keep ops above 6 dims out of XNNPACK and fix quantized ops it leaves out #24380: XNNPACK, ops above 6 dimensions (pick: Keep ops above 6 dims out of XNNPACK and fix quantized ops it leaves out #24445)
- Support 3-D convolution in the portable convolution kernel #24385: 3-D convolution in the portable kernels (pick: Support 3-D convolution in the portable convolution kernel #24460)
- Add a portable kernel for antialiased bicubic resize #24400: portable antialiased bicubic resize (pick: Add a portable kernel for antialiased bicubic resize #24447)
- Merged into main, not yet picked for release/1.6:
- Open against main:
- Python runtime: stop a backend's log from deadlocking two threads #24393: log deadlock between 2 threads
- CUDA backend: lower rotary embeddings written with complex numbers #24403: CUDA complex rotary embeddings and fused attention; now split, with parts in CUDA backend: delete each output handle once when a program returns one tensor twice #24420 (merged) and CUDA backend: lower fused attention variants #24426 (merged)
- Fix export of zero-stride constants and inputs, and of constants that delegates share #24404: zero-stride constants and shared constants
- Merged into release/1.6:
- Torch-TensorRT: release/2.15, plus these pull requests, at the heads we measured.
- Open against release/2.15:
- [2.15] Pin ExecuTorch 1.6.0 TensorRT#4950: pin ExecuTorch 1.6.0
- Open against main:
- fix(dynamo): save programs with no inputs, keep attention masks that are not plain causal, fix uint8 shape ops and views left to PyTorch, show the runtime TensorRT build number, keep plugins that run PyTorch out of ExecuTorch programs TensorRT#4953: programs with no inputs, attention masks, uint8 shape ops, plugins
- fix(conversion): wrong attention, softmax and integer division results, and attention that fails to build TensorRT#4964: attention, softmax and integer division fixes
- fix(partitioning): resource partitioning RecursionError, tuple outputs and budget TensorRT#4963: resource partitioning
- fix(dynamo): keep, move and refit the tensors that PyTorch nodes read after global partitioning TensorRT#4966: frozen constants after global partitioning
- feat(conversion): optionally add each shared weight to the network once TensorRT#4965: add each shared weight once (opt-in)
- Open against release/2.15:
- LeRobot:
mainplus thelerobot-exportand torch-freelerobot-rollout --bundle.pathwork, on thelerobot-export-stackbranch of shoumikhin/lerobot. The policy fixes in it are also open as LeRobot pull requests. - torch 2.15.0, TensorRT 11.4, transformers 5.5.4. The yellow cells use torch 2.15.0 with the export fixes from [export] Save and load flat_apply calls, such as indexing inside vmap pytorch#202785 applied, as it was on Oct 31. That pull request has since been split into #202785, #202925, #202929 and #202991.
- GPUs: H100, A100, Jetson AGX Thor, Jetson Orin Nano 8 GB. The Jetson builds are for aarch64 and CUDA 13.2.
Known issues
"Fix" is an open pull request unless it says merged. "Needs a decision" is a design question for the maintainers. "Needs an owner" means nobody is fixing it yet. "Verified" means the policy was measured before and after the fix.
ExecuTorch
- Review the open pull requests below. "Built on" shows their release/1.6 picks.
- Merge the release/1.6 picks.
- Decide the GIL question and pick up the scatter kernel (last 2 rows).
| Problem | Policies | Fix | Status |
|---|---|---|---|
| CUDA backend: no kernel for fused attention that Torch-TensorRT leaves to it, or for complex rotary embeddings. | lingbot_va, fastwam | #24403, #24426 | lingbot_va now compiles |
| CUDA backend: the Triton attention kernel computes only the first 256 values for wider heads that are not a power of 2, with no error. | lingbot_va (CUDA only) | #24427 | Verified |
CUDA backend: no kernel for varlen_attn. |
wall_x | #24326 | Verified |
CUDA backend: same random seed in parallel processes, no bf16 scatter, an extra libgomp dependency, attention reading past the last key. |
all with a CUDA program | #24326, pytorch/pytorch#202902 | Verified |
CUDA backend: no kernel for x[mask], stale shapes from CUDA graphs, a too-small output buffer is accepted. |
smolvla (CUDA only) | #24320 | In our build |
| CUDA backend: programs in one folder overwrite each other's weights file; antialiased resize does not lower. | all with 2+ CUDA programs | #24324 | In our build |
| Python runtime: CUDA stream rules are checked on the wrapper, not on the stream. | none (API) | #24346 | In our build |
| Python runtime: a backend's log line can deadlock 2 threads, for example 2 TensorRT bundles in one process. | 2 bundles in one process | #24393 | Verified: 0 of 120 runs hang, 26 of 120 before |
| CUDA backend: no kernel for scatter with a scalar source in deterministic mode. | none yet | needs an owner | Repro |
Python runtime: Method.execute holds the GIL, so other Python threads stall while it runs. |
multi-threaded callers | needs a decision: release the GIL during execute? | Measured |
Torch-TensorRT
- Review the open pull requests below, then pick them into release/2.15 ([2.15] Pin ExecuTorch 1.6.0 TensorRT#4950 pins ExecuTorch 1.6.0 there).
- Land the low-memory Jetson pull requests in main and release/2.15 (row near the end).
- Decide the row marked "needs a decision" and pick up the rows marked "needs an owner".
- 10 policies export only because some of their ops go to the ExecuTorch CUDA backend next to the TensorRT engines (see "Programs"; wall_x has 41 engines and 40 CUDA programs). It would help if Torch-TensorRT could take these ops, so that each policy builds one engine or a few. The 3 rows before the runtime wheel row list what stays out. fastwam is the exception: its own code copies a tensor to the CPU, which LeRobot has to fix.
| Problem | Policies | Fix | Status |
|---|---|---|---|
index_put for the Qwen3-VL mask writes. |
groot | pytorch/TensorRT#4936 | Merged, in release/2.15 |
| A program with no inputs (the noise draw) cannot be saved. | all with noise | pytorch/TensorRT#4953 | Verified |
| A block-causal attention mask next to complex numbers becomes plain causal: wrong results, no error. | fastwam | pytorch/TensorRT#4953 | Verified on a repro |
Views above 8 dimensions reach the CUDA backend as _reshape_copy, which it cannot run. |
lingbot_vla_v2 | pytorch/TensorRT#4953 | Verified |
A shape op on a uint8 input before a cast fails (Could not get tensor shape). |
xvla, fastwam | pytorch/TensorRT#4953 | Verified |
| An engine with the ScatterAdd plugin cannot load without libtorch. | lingbot_vla_v2 | pytorch/TensorRT#4953 | Verified |
| On the Thor, 360 scatter plugin layers end up in one compiler region and the build fills host memory. | lingbot_vla_v2 | pytorch/TensorRT#4953 | Verified: 64 to 13 GiB, 32 to 4 min |
| With CPU offload, complex lowering mixes CPU and CUDA tensors. | fastwam | pytorch/TensorRT#4953 | Verified |
| A float64 output gets a cast with no metadata, so saving the program fails. | lingbot_va | pytorch/TensorRT#4953 | Verified |
| The runtime reads an input that is not row-major in the wrong order, with no error. | none so far | pytorch/TensorRT#4953 | Verified on a repro |
| float32 attention on inputs cast from bf16 does not build. | fastwam | pytorch/TensorRT#4964 | Verified |
| bf16 attention without an explicit scale loses accuracy (error 0.068, 0.0084 after). | eo1, wall_oss_05, wall_x, molmoact2 | pytorch/TensorRT#4964 | Verified on a repro |
| Softmax over a size-1 dimension followed by a matmul returns infinity. | none so far | pytorch/TensorRT#4964 | Verified on a repro |
| Attention with a constant query, key or value does not convert. | flux3 | pytorch/TensorRT#4964 | Verified on a repro |
| Causal attention with different query and key lengths uses a bottom-right mask, not top-left like PyTorch. | none so far | pytorch/TensorRT#4964 | Verified on a repro |
Integer division truncates inside engines (x.shape[0] / 2 at 15 gives 7, not 7.5). |
not known yet | pytorch/TensorRT#4964 | Not verified on LeRobot |
| Each weight read through a transpose is copied once per reader (a TensorRT issue, see below). | xvla, eo1 | pytorch/TensorRT#4965 (opt-in) | A100 compile peak: xvla 20.5 to 11.0 GiB |
| Constant folding keeps one copy of a weight per reader. | evo1 | pytorch/TensorRT#4965 | A100 export peak 9.8 to 3.3 GiB |
| Resource partitioning: recursion error on long chains, split tuple outputs, engines over budget. | xvla | pytorch/TensorRT#4963 | Verified |
After global partitioning, a PyTorch node cannot find a folded constant (_frozen_param0). |
none so far | pytorch/TensorRT#4966 | In our build |
| Tensors above 8 dimensions (the Qwen-VL image patch layout in the preprocessing) go to the CUDA backend. | eo1, lawam, lingbot_vla_v2, vla_jepa, wall_oss_05, wall_x | needs an owner (for example, drop size-1 dimensions first) | Open |
Antialiased resize (_upsample_bilinear2d_aa, _upsample_bicubic2d_aa) has no converter. |
dm05, evo1, lingbot_vla_v2, vla_jepa, wall_x | needs an owner | Open |
PyTorch's packed variable-length attention (varlen_attn) has no converter: 32 of wall_x's 40 CUDA programs. uint8 tensors made inside the graph also stay out. |
wall_x, evo1, lingbot_va | needs an owner | Open |
The torch-tensorrt-executorch-runtime wheel, which every TensorRT bundle needs, is on no index, and pip install torch-tensorrt[executorch] does not install it. |
all | needs a decision: how does it ship? | Open |
| Low-memory compile for Jetsons by @cehongwang is not in main or release/2.15 yet: pytorch/TensorRT#4853 (release weights after their engine is built), with #4854 (compile from memory-mapped weights) and #4942 (compile from a weights file) stacked on it, and the pi0.5 example #4949 for release/2.15. It would let pi0, lawam, vla_jepa and evo1 export on the Orin Nano. | 4 policies on Orin Nano | needs to land in main and release/2.15 | Open, no update since Oct 30 |
| flux3_droid runs out of GPU memory in the compile, even alone on a 95 GiB H100. | flux3 | needs an owner | Open |
NVIDIA TensorRT
These are bugs in TensorRT itself (11.4 unless noted). Each one reproduces with TensorRT alone; we can share the repros.
- Triage the rows below. "Workaround" says where Torch-TensorRT avoids one for now.
| Problem | Policies | Workaround | Status |
|---|---|---|---|
| The builder keeps a host copy of a weight for every layer that reads it through a transpose: 1 GiB of weights with 10 readers costs 10.9 GiB. | xvla, eo1, flow-matching on 8 GB Jetsons | pytorch/TensorRT#4965 (opt-in) | Repro |
| With weight sharing on, dm05's RoPE table is one constant read by 290 matmuls, and the engine returns wrong results (0.246, 0.0039 without sharing). | dm05 | turn sharing off (--backend.deduplicate_weights=false, used for the dm05 cells) |
Repro in progress |
| On the Thor (sm_110) only, the fused attention kernel rounds the scores to bf16 whenever the attention has a mask; without a mask, and on H100, A100 and Orin, it keeps float32. evo1 and lingbot_vla_v2 miss their check there (1.65 and 1.10, limit 0.075). | evo1, lingbot_vla_v2 | none (float32 inputs avoid it but cost 2.2x) | Repro |
| TensorRT 10.16 (JetPack's system TensorRT) returns NaN for bf16 causal attention. | JetPack users | none | Repro ready |
| float32 attention on inputs cast from bf16 fails to build or crashes. | fastwam | pytorch/TensorRT#4964 | Repro |
| On the Thor, newer-interface plugin layers end up in one compiler region, and the build grows host memory by 64 GiB (14 to 16 GiB on H100 and A100). | lingbot_vla_v2 | pytorch/TensorRT#4953 | Repro |
| A constant key read through a transpose is folded wrongly in fp16 and bf16 (error 1.8). | constant-key attention | pytorch/TensorRT#4964 | Repro |
| The fused attention kernel is 12% slower than unfused for evo1's shape (8 heads, 50 queries, 805 keys, head size 112). | evo1 | none | Measured |
PyTorch
These show up only when a program is saved and loaded, or compiled ahead of time.
- Review the pull requests below. None is in torch 2.15, so the yellow cells need a patched torch until a release has them. Could they go into a 2.15.x patch?
- Pick up the row marked "needs an owner".
| Problem | Policies | Fix | Status |
|---|---|---|---|
torch.export.save cannot save flat_apply (transformers' mask functions). |
wall_x, dm05 | pytorch/pytorch#202785 | Verified |
torch.export.load loses metadata on autocast regions that return nothing. |
lawam, molmoact2 | pytorch/pytorch#202925 | Verified |
A program with varlen_attn cannot be loaded after a plain import torch. |
wall_x | pytorch/pytorch#202925 | Verified |
A failing check inside a no_grad region leaves grad mode off. |
none so far | pytorch/pytorch#202898 | Not verified |
| AOTInductor autotuning runs a gather with a bounds check on made-up inputs, and the compile fails. | vla_jepa (CUDA only) | pytorch/pytorch#202931 | Verified |
If PyTorch first lists its device constructors while transformers has torch.linspace swapped, later linspace calls ignore their device. |
models built on the meta device | needs an owner | Found on LeRobot |
LeRobot
- Review the open pull requests:
- Import speed
- perf(imports): import torch and policies only when a command needs them huggingface/lerobot#4693: import torch and policies only when needed
- perf(processor): import torch only when teleoperation needs it huggingface/lerobot#4742: import torch only when teleoperation needs it
- refactor(datasets): import dataset modules on first use huggingface/lerobot#5136: import dataset modules on first use
- refactor(rollout): let the control loop take NumPy actions from the inference engine huggingface/lerobot#4820: the control loop takes NumPy actions
- Loading checkpoints
- feat(policies): load pi0, pi0.5 and pi0-FAST checkpoints without first building random weights huggingface/lerobot#4775: pi0, pi0.5 and pi0-FAST without random init
- feat(policies): load GR00T fine-tunes without NVIDIA's base model huggingface/lerobot#4778: GR00T fine-tunes without NVIDIA's base model
- fix(policies): load FAST action tokenizers saved by transformers 4 huggingface/lerobot#5155: FAST tokenizers saved by transformers 4
- fix(groot): fall back when the processor's saved device is missing huggingface/lerobot#5195: GR00T processor falls back to the CPU
- fix(fastwam): move the device FastWAM records along with the model huggingface/lerobot#5196: FastWAM's device follows its weights
- Policies that pass
torch.export- fix(policies): avoid tensor value reads that block torch.export in eight policies huggingface/lerobot#5150: 8 policies
- fix(molmoact2): let single-example inference export with torch.export huggingface/lerobot#5147: molmoact2
- fix(policies): let EO-1, WALL-X and Wall-OSS 0.5 pass torch.export huggingface/lerobot#5162: eo1, wall_x, wall_oss_05
- fix(policies): let LingBot-VLA v2, the LaWAM VLM stage and the GR00T N1.7 backbone pass torch.export huggingface/lerobot#5163: lingbot_vla_v2, LaWAM, GR00T N1.7
- CPU and kernels
- fix(lawam): let the VLM-to-LAM head take bfloat16 hidden states on the CPU huggingface/lerobot#5214: LaWAM head in bf16 on the CPU
- fix(flux3): hand F3_NATTEN_BACKEND to NATTEN's kernels huggingface/lerobot#5213: flux3 passes its backend to NATTEN
- Import speed
- 7 more are ready, but GitHub's open pull request limit blocks them. Could it be raised?
- diffusion, fastwam, flux3, wall_x, lawam, vla_jepa: one fix each, plus action chunks from frame checkpoints for fastwam and flux3
- Decide the rows marked "needs a decision" and pick up the ones marked "needs an owner".
| Problem | Policies | Fix | Status |
|---|---|---|---|
lerobot-export and the torch-free lerobot-rollout --bundle.path exist only on the lerobot-export-stack branch. |
all | needs a decision: how to bring them upstream in reviewable pieces | Open |
| The export check for lingbot_va cannot work: rounding alone moves it to 0.52 against a limit of 0.2. Checking each program on its own inputs does work (correct programs within 1.85x their noise, broken ones 9x or more). | lingbot_va | needs a decision: check per program | Measured |
| evo1's check uses 1 test frame where 2 gripper choices are nearly tied, so a correct float32 run fails it (1.48). Checking 16 frames and allowing up to 4 gripper flips passed all 30 correct runs and still caught the Thor bundle. | evo1 | needs a decision: check 16 frames | Measured |
lerobot-rollout cannot feed tdmpc its environment state on the test robot, so it has no timing. |
tdmpc | needs a decision | Open |
| The export refuses the flux3 checkpoint, which needs history conditioning. | flux3 | needs a decision | Open |
| On the Orin Nano, the export loads weights the program never reads (vla_jepa 1.8 GiB, lawam 1.6 GiB) and runs out of memory. | lawam, vla_jepa | needs an owner | Open |
evo1 fits the Orin Nano only at TensorRT optimization level 0, which lerobot-export cannot pass yet. |
evo1 | needs an owner | Open |
Thanks to Adrian Wang (@cehongwang) for the low-memory Jetson path: pytorch/TensorRT#4854 compiles from memory-mapped weights, and pytorch/TensorRT#4949 deploys pi0.5 with ExecuTorch and TensorRT.
Activity
docs: bring lerobot-export and bundle-mode docs up to date
docs: bring lerobot-export and bundle-mode docs up to date