Skip to content

TensorRT: LeRobot policies that do not run yet, and what blocks them #24353

Open

Description

@shoumikhin

This issue tracks LeRobot policies exported to ExecuTorch with the Torch-TensorRT backend and run with lerobot-rollout on a fake robot: which policies work, how fast they are, and each known problem with the pull request that fixes it. #24325 is the companion issue for the CPU backends and the general export flow.

Coverage

Each cell is the median milliseconds per action chunk (batch size 1) inside lerobot-rollout. In brackets: the speed-up over eager PyTorch (--policy.path) on the same device, checkpoint and dtype, with --tf32=false. In every run with a number, PyTorch was never loaded, program inputs and outputs stayed on the GPU, and no operator ran on the CPU.

What the marks mean:

  • $\color{#2da44e}{\checkmark}$ and a number: works with torch 2.15 and the ExecuTorch 1.6 and Torch-TensorRT 2.15 fixes listed under "Stack" (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" under "Known issues"), 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.

Policy H100 A100 Thor Orin Nano Programs
act $\color{#2da44e}{\checkmark}$ 2.88 (2.6x) $\color{#2da44e}{\checkmark}$ 4.48 (3.3x) $\color{#2da44e}{\checkmark}$ 8.51 (4.2x) $\color{#2da44e}{\checkmark}$ 26.1 (1.6x) TRT 1
diffusion $\color{#2da44e}{\checkmark}$ 531 (1.6x) $\color{#2da44e}{\checkmark}$ 747 (3.2x) $\color{#2da44e}{\checkmark}$ 1268 (3.5x) $\color{#2da44e}{\checkmark}$ 3107 (1.7x) TRT 3
dm05 🔨 🔨 🔨 ➖ TRT 2 + CUDA 1
eo1 $\color{#2da44e}{\checkmark}$ 58.0 (5.3x) $\color{#2da44e}{\checkmark}$ 83.0 (9.3x) $\color{#2da44e}{\checkmark}$ 286 (1.4x) ➖ TRT 2 + CUDA 1
evo1 $\color{#2da44e}{\checkmark}$ 28.8 (6.7x) $\color{#2da44e}{\checkmark}$ 57.1 (7.9x) 🔨 ➖ TRT 4 + CUDA 3
fastwam $\color{#2da44e}{\checkmark}$ 53.7 (8.2x) $\color{#2da44e}{\checkmark}$ 83.9 (12.2x) ⏳ ➖ TRT 2 + CUDA 1
fineart_vla $\color{#2da44e}{\checkmark}$ 105 (2.3x) $\color{#2da44e}{\checkmark}$ 243 (2.6x) $\color{#2da44e}{\checkmark}$ 693 (1.4x) ➖ TRT 1
flux3 ➖
gaussian_actor $\color{#2da44e}{\checkmark}$ 0.57 (4.1x) $\color{#2da44e}{\checkmark}$ 1.14 (4.4x) $\color{#2da44e}{\checkmark}$ 0.62 (3.1x) $\color{#2da44e}{\checkmark}$ 1.38 (4.6x) TRT 1
groot $\color{#2da44e}{\checkmark}$ 19.3 (6.9x) $\color{#2da44e}{\checkmark}$ 28.9 (9.3x) $\color{#2da44e}{\checkmark}$ 58.4 (3.6x) $\color{#2da44e}{\checkmark}$ 200 TRT 10
lawam $\color{#d4a017}{\checkmark}$ 35.8 (5.1x) $\color{#d4a017}{\checkmark}$ 64.5 (9.0x) $\color{#d4a017}{\checkmark}$ 92.2 (2.9x) ➖ TRT 2 + CUDA 1
lingbot_va 🔨 🔨 🔨 ➖
lingbot_vla_v2 $\color{#2da44e}{\checkmark}$ 76.2 (7.3x) $\color{#2da44e}{\checkmark}$ 99.8 (11.3x) 🔨 ➖ TRT 3 + CUDA 2
molmoact2 $\color{#d4a017}{\checkmark}$ 48.6 (4.3x) $\color{#d4a017}{\checkmark}$ 88.0 (7.1x) ⏳ ➖ TRT 1
multi_task_dit $\color{#2da44e}{\checkmark}$ 79.5 (3.7x) $\color{#2da44e}{\checkmark}$ 98.4 (6.3x) $\color{#2da44e}{\checkmark}$ 195 (1.4x) $\color{#2da44e}{\checkmark}$ 484 (2.5x) TRT 3
pi0 ⏳ ⏳ ⏳ $\color{#2da44e}{\checkmark}$ 543 TRT 9 + CUDA 1
pi05 $\color{#2da44e}{\checkmark}$ 34.1 (5.7x) $\color{#2da44e}{\checkmark}$ 56.3 (7.7x) $\color{#2da44e}{\checkmark}$ 90.0 (2.7x) $\color{#2da44e}{\checkmark}$ 447 TRT 9
pi0_fast $\color{#2da44e}{\checkmark}$ 1589 (1.8x) $\color{#2da44e}{\checkmark}$ 2337 (2.6x) $\color{#2da44e}{\checkmark}$ 11538 (1.1x) ➖ TRT 3
smolvla $\color{#2da44e}{\checkmark}$ 17.6 (10.9x) $\color{#2da44e}{\checkmark}$ 28.5 (14.7x) $\color{#2da44e}{\checkmark}$ 37.8 (4.6x) $\color{#2da44e}{\checkmark}$ 176 (4.3x) TRT 4
tdmpc 🔨 🔨 🔨 🔨 TRT 1
vla_jepa $\color{#2da44e}{\checkmark}$ 16.9 (7.5x) $\color{#2da44e}{\checkmark}$ 27.9 (12.5x) $\color{#2da44e}{\checkmark}$ 40.0 (2.5x) ➖ TRT 3 + CUDA 2
vqbet $\color{#2da44e}{\checkmark}$ 2.26 (3.3x) $\color{#2da44e}{\checkmark}$ 3.75 (3.7x) $\color{#2da44e}{\checkmark}$ 3.50 (2.0x) $\color{#2da44e}{\checkmark}$ 7.56 (2.8x) TRT 1
wall_oss_05 $\color{#2da44e}{\checkmark}$ 67.3 (5.2x) $\color{#2da44e}{\checkmark}$ 105 (6.8x) $\color{#2da44e}{\checkmark}$ 152 (2.9x) ➖ TRT 2 + CUDA 1
wall_x $\color{#d4a017}{\checkmark}$ 93.5 (5.6x) 🔨 $\color{#d4a017}{\checkmark}$ 471 (1.4x) ➖ TRT 41 + CUDA 40
xvla $\color{#2da44e}{\checkmark}$ 66.6 (1.6x) $\color{#2da44e}{\checkmark}$ 134 (1.5x) $\color{#2da44e}{\checkmark}$ 402 (1.2x) $\color{#2da44e}{\checkmark}$ 245 (1.3x) TRT 1

What the cells do not show:

  • All four devices now use one stack: the same ExecuTorch, Torch-TensorRT, torch and LeRobot (see "Stack"). Each number was measured with lerobot-rollout --bundle.path, and each bundle first passed the export's own accuracy check against eager PyTorch.
  • On the Orin Nano, most ➖ policies are bigger than the 8 GB board. lawam, vla_jepa and evo1 would fit at run time, but their export does not (see "LeRobot" below).
  • The Orin Nano groot, pi05 and pi0 cells have no speed-up, because eager PyTorch does not fit the board. The Orin Nano xvla cell runs in bf16, because the float32 export does not fit.
  • pi0 is now exported as a chain of smaller programs, like pi05, so that it fits the Orin Nano. Its H100, A100 and Thor cells are being measured again with that export.
  • diffusion, eo1, gaussian_actor, multi_task_dit and vqbet ran with random weights, and so did fineart_vla, vla_jepa and wall_x on the Thor. That tests the compiler and the runtime, not a trained policy.

Stack

One build per architecture from the same sources, used on all four devices:

Known issues

Every fix pull request below is still in review unless it says Merged. SHAs are the current heads. "Verified on LeRobot": the policy was measured before and after the fix.

Torch-TensorRT

Issue Policies hit Fixed by Status
index_put for the Qwen3-VL DeepStack mask writes. groot pytorch/TensorRT#4936 Merged, in release/2.15
A program with no inputs (the noise draw) cannot be saved (assert fake_mode is not None). every policy with a noise program pytorch/TensorRT#4953 676f9433 Verified on LeRobot
With complex numbers in the graph, a block-causal attention mask is compiled as plain causal: wrong results, no error. fastwam pytorch/TensorRT#4953 Verified on a repro (largest difference 1.08 before, 1.55e-6 after)
Views above 8 dimensions reach the CUDA backend as aten._reshape_copy, which it cannot run. lingbot_vla_v2 pytorch/TensorRT#4953 Verified on LeRobot
A shape op on a uint8 input before a cast (img.permute(2, 0, 1).float()) fails: Could not get tensor shape. xvla, fastwam pytorch/TensorRT#4953 Verified on LeRobot
An engine with Torch-TensorRT's ScatterAdd plugin cannot load without libtorch: Cannot find plugin: ScatterAddtorch_tensorrt. lingbot_vla_v2 pytorch/TensorRT#4953 (fallback_pytorch_plugin_ops=True) Verified on LeRobot (H100, A100)
On the Thor, TensorRT 11.4 merges the 360 scatter plugin layers into one compiler region, and the engine build fills host memory. lingbot_vla_v2 pytorch/TensorRT#4953 Verified on the Thor: build growth 64.2 to 12.7 GiB, 1,920 to 262 s
With offload_module_to_cpu=True, complex lowering mixes a CPU constant with CUDA scalars: Expected all tensors to be on the same device. fastwam pytorch/TensorRT#4953 Verified on LeRobot
The ExecuTorch TensorRT runtime reads an input that is not in row-major dim order (for example an attention output from the CUDA backend) in the wrong order, with no error. none in the table so far pytorch/TensorRT#4953 (newest head) Verified on a repro. No change on LeRobot (wall_oss_05, lingbot_vla_v2 on the H100)
TensorRT 11.4 fails to build float32 attention on inputs cast from bf16: Input 0's element type (bf16) differs from input 1's element type (float). The fix decomposes it. fastwam pytorch/TensorRT#4964 9696b391 Verified on LeRobot (H100, A100, Thor)
BF16 attention without an explicit scale loses accuracy (0.068 before, 0.0084 after). eo1, wall_oss_05, wall_x, molmoact2 pytorch/TensorRT#4964 Verified on a repro. No policy got worse on the H100
Attention with a constant query, key or value fails to convert (add_shape() / add_attention_v2(): incompatible function arguments). flux3 pytorch/TensorRT#4964 Verified on a repro. flux3 not measured (see "LeRobot")
Causal attention with different query and key lengths uses a bottom-right mask, not top-left as in PyTorch. none in the table pytorch/TensorRT#4964 Verified on a repro (15 of 15 cases)
Integer true division truncates inside engines: x * (x.shape[0] / 2) at size 15 scales by 7, not 7.5. not known yet pytorch/TensorRT#4964 (newest head) Not yet verified on LeRobot
TensorRT keeps a host copy of a weight for every layer that reads it transposed (details below). xvla, eo1 pytorch/TensorRT#4965 d40715b7 (opt-in deduplicate_weights) A100 compile peak: xvla 20.5 to 11.0 GiB, eo1 74.8 to 60.6 GiB
Constant folding keeps one copy of a weight per reader (one bf16 cast per denoising step under autocast). evo1 pytorch/TensorRT#4965 A100 export GPU peak 9,750 to 3,292 MiB. Still does not fit the Orin Nano
With #4965's constant folding change, dm05's bundle misses the export's accuracy check: 0.246 against a tolerance of 0.2 on the H100, A100 and Thor. Without that commit it is 0.0039. wall_x on the A100 also got worse (0.077 against 0.075, 0.055 before). dm05, maybe wall_x pytorch/TensorRT#4965 (being fixed) Found on LeRobot. wall_x is being checked with and without the commit
Resource partitioning: RecursionError on long chains, tuple outputs split from their getitems, engines over the memory budget. xvla (with a memory budget) pytorch/TensorRT#4963 ef8f3024 Verified on a repro and on xvla (H100, A100)
After global partitioning, a PyTorch node that reads a folded constant fails: 'GraphModule' object has no attribute '_frozen_param0'. repro only pytorch/TensorRT#4966 ba98542c In the tested stack
release/2.15 needs to pin ExecuTorch 1.6.0. all pytorch/TensorRT#4950 337b2212 In the tested stack
On the Thor only, TensorRT computes a different result for the same network (details below). evo1, lingbot_vla_v2 no fix yet Being reduced to a small TensorRT repro

ExecuTorch

Issue Policies hit Fixed by Status
The CUDA backend has no kernel for fused attention that Torch-TensorRT leaves to it (missing fallback kernels: _scaled_dot_product_flash_attention), or for complex rotary embeddings. lingbot_va; fastwam (CUDA-only) #24403 bbe873c6ab lingbot_va's 19 programs now compile
The CUDA backend's Triton attention kernel is wrong for head sizes above 256 that are not powers of two: it only computes the first 256 values, with no error (details below). lingbot_va fix in progress Found on a repro
torch_attn._varlen_attn has no CUDA kernel. wall_x #24326 bdea161d34 Verified on LeRobot
CUDA programs: the same random seed in processes started together, no kernel for scatter with a bf16 source, a libgomp dependency, Triton attention counting keys past the end. every bundle with a CUDA program #24326 Verified on repros and on LeRobot
CUDA mask indexing (x[mask]) has no kernel, CUDA graphs return stale shapes, and a too-small output buffer is accepted. smolvla in a CUDA-only bundle #24320 b1f5f7e8af In the tested stack
CUDA programs exported into one folder overwrite each other's weights file. Antialiased resize does not lower. any bundle with more than one CUDA program #24324 f714c0871e In the tested stack
The Python runtime checks some CUDA stream rules on the wrapper, not on the stream. none (API) #24346 e3e0eb85be In the tested stack
A backend's log line can deadlock two threads, for example two TensorRT bundles in one process. any two bundles in one process #24393 4ca018cb17 Verified: 0 of 120 runs hang, 36 of 120 before

#24320 and #24324 sit on top of #24298 (base AOTInductor and CUDA fixes). The other ExecuTorch fixes in the stack are for CPU backends and are tracked in #24325.

PyTorch

These show up only because the program is saved and loaded. pytorch/pytorch#202785 (f63b88cc97) fixes both. It is not in torch 2.15, so the yellow cells use a torch 2.15.0 build that adds it.

Issue Policies hit Fixed by Status
torch.export.save cannot serialize flat_apply over ModIndex (transformers' or_mask_function masks). wall_x, dm05 pytorch/pytorch#202785 Verified on LeRobot
torch.export.load loses val on autocast regions that return nothing. lawam, molmoact2 pytorch/pytorch#202785 Verified on LeRobot

LeRobot

Issue Policies hit Fixed by Status
lerobot-rollout cannot feed the PushT TD-MPC its environment state, so it has no timed cell. Its export and bundle check pass. tdmpc no fix yet Open
pi0_fast turns its action tokens into actions with NumPy on the host, outside the ExecuTorch programs. pi0_fast no fix yet Works, as measured
The pi0 export loaded the whole policy (8.3 GiB in bf16, 1.5 GiB of it never read) and built all ten denoising steps as one engine, so it ran out of memory on the Orin Nano. It now exports a chain of parts, each with only its own weights. pi0 LeRobot export Fixed: Orin Nano 543 ms. A100 compile peak 32.6 to 5.1 GB
evo1 fits the Orin Nano only at TensorRT optimization level 0, which lerobot-export cannot pass yet (then 1,488 ms per chunk, 1.0x). evo1 no fix yet Open
On the Orin Nano, the export loads every weight, also ones the program never reads (vla_jepa 1.8 GiB, lawam 1.6 GiB), and runs out of memory. lawam, vla_jepa no fix yet Open
The export refuses the flux3 checkpoint (it needs frame conditioning). The frame-conditioned flux3_droid runs out of GPU memory in the TensorRT compile, even alone on a 95 GiB H100. flux3 no fix yet Open

lingbot_va: wrong attention in the CUDA backend

With #24403, all 19 programs of lingbot_va compile, but the bundle does not match the policy, so it is not written:

RuntimeError: The exported policy does not reproduce its test case (4.37e-01 > 0.2).

The largest difference is 0.30 on the H100, 0.44 on the A100 and 0.48 on the Thor (tolerance 0.2). The first program that drifts is the encoder. Its attention has a head size of 640. Called directly, the CUDA backend's Triton attention kernel gives wrong results (relative error 0.9 to 1.15, or NaN) for head sizes 288, 320, 384, 640 and 768, and matches PyTorch for 96, 192, 256, 512 and 1024. So it covers only the first 256 values of a head unless the size is a power of two.

Thor only: TensorRT computes a different result

The bf16 bundles of evo1 and lingbot_vla_v2 fail the export's accuracy check on the Thor only: 1.65 and 1.10, against a tolerance of 0.075. On the H100 and A100 both pass. The cause is inside one TensorRT engine:

  • The C++ and the Python Torch-TensorRT runtimes both give the same wrong actions (largest difference 1.62).
  • Replaying the engine's plan with plain TensorRT on the same inputs reproduces it exactly.
  • With every input to that engine bit-exact to eager PyTorch, it still drifts (1.62).

So TensorRT 11.4 on sm_110 computes this network differently. Compiled as two engines, split at the vision-language model's output, evo1 is off by only 0.021. The TensorRT optimization level does not change it.

TensorRT keeps a host copy of a weight per transposed reader

TensorRT's builder (11.4.0.106) holds one host copy of a constant weight for every layer that reads it through a transpose, even when all the constant layers point at the same host array. Its [MemUsageStats] does not show this. In a repro with only TensorRT and NumPy (about 12 s), 1 GiB of weights read by 10 transposed matmuls raises host memory by 10.9 GiB (about 11x the weights), against 2.1x when the 10 readers share one shuffle. Flow-matching policies read their action network once per denoising step, so each weight gets 10 or 11 readers, which costs several GiB on an 8 GB Jetson. The opt-in deduplicate_weights in pytorch/TensorRT#4965 converts each weight once and shares one transpose.

Adrian Wang (@cehongwang) made the low-memory Jetson path possible. pytorch/TensorRT#4854 compiles from memory-mapped weights, and pytorch/TensorRT#4949 is a standalone example that deploys pi0.5 with ExecuTorch and TensorRT. His one-process pi0.5 on the Orin Nano ran at about 615 ms per chunk.

cc @Gasoonjia @digantdesai

Activity

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

    module: cudaIssues related to the AOTI CUDA backend

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions