Repository navigation
pi05 decomposed: match LeRobot inputs/outputs, fix the TensorRT expert crash and precision - #390
Merged
Merged
Conversation
Four divergences from LeRobot's PI05Policy made Tether's pi05 actions disagree with the reference (mean cosine 0.77 on one L4): - Prompt state: LeRobot's Pi05PrepareStateTokenizerProcessorStep writes "Task: <task>, State: <256-bin ints>;\nAction: " from the normalized state. Tether always dropped the state segment. The exporter now records decomposed.state_in_prompt from the policy (False for state-out students with state_proj and use_proprioceptive_memory configs); the server reads it, and for exports that predate the field derives it from expert_takes_state. A state-in-prompt export refuses requests without state instead of inventing one. - Tokenizer padding: PaliGemma's GemmaTokenizer defaults to left padding; LeRobot pads right. The server now sets padding_side="right". - Image range/resize: the prefix graph feeds images straight into embed_prefix, so SigLIP must get [-1, 1]; Tether sent [0, 1] after a PIL antialiased resize. _prep_image now ports resize_with_pad_torch (bilinear, align_corners=False, centered zero pad) and applies *2-1; max deviation from torch 2.4e-7. - Action dim: the export wrote action_dim=max_action_dim (32) so the server returned 50x32. The exporter now writes the dataset dim from output_features["action"] plus decomposed.max_action_dim; for older exports the server takes the dim from the action normalizer stats and refuses a config that disagrees with them. Also pin the teacher normalizer fetch to the export's model_revision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aphs
Root cause of the FP16 TensorRT pi0.5 failure on L4
("MemcpyFromHost 'Memcpy_token_961': Unsupported type proto value case:0"):
the expert is exported with optimize=False, which leaves 370
SplitToSequence+SequenceAt pairs (torch.split/chunk). Neither the
TensorRT parser nor the CUDA EP takes SplitToSequence, so ORT 1.25 runs it
on the CPU and SequenceAt on CUDA. TensorRT's MemcpyTransformer treats
CUDA nodes as its own device and inserts a TensorRT MemcpyFromHost for
each sequence (INFO log: "Add MemcpyFromHost after split_N for
TensorrtExecutionProvider", 370 times). That kernel copies tensors only;
Get<Tensor> on the TensorSeq formats its error with the generic sequence
type, whose empty TypeProto produces the message. The CUDA-only path ran,
but with 740 host round trips per call.
- _repair_exported_onnx (run by every exporter) now rewrites
SplitToSequence whose consumers are all constant-position SequenceAt
into one Split. Anything it cannot prove equivalent is left alone.
- build_ort_provider_plan refuses TensorRT, with the reason in
trt_disabled_reason and a warning, for graphs that still carry tensor
sequences (exports made before this fix), instead of a 13-minute
engine build followed by a crash.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the sequence fix the default FP16 TensorRT pi0.5 path ran on an L4 but returned wrong actions. Measured on 24 paired chunks vs LeRobot eager (same observations and noise): TRT FP16 (old default) cosine 0.079 (min -0.22), 385 ms p50 TRT BF16 cosine 0.988 (min 0.89), 386 ms p50 TRT FP32/TF32 cosine 0.999992, 442 ms p50 CUDA EP FP32/TF32 cosine 0.999992, 461 ms p50 Reduced-precision TensorRT is not faithful for this bfloat16-trained checkpoint, so the decomposed exporter records the checkpoint dtype as source_dtype and build_ort_provider_plan leaves trt_fp16_enable off for bfloat16 sources (trt_fp16_default). Other exports keep the old default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
On one L4, Tether's pi05 decomposed path disagreed with LeRobot eager (FP32 CUDA: mean cosine 0.77, min -0.33), and the default FP16 TensorRT path crashed on the first predict with
MemcpyFromHost 'Memcpy_token_961': Unsupported type proto value case:0.Fixes
Task: <task>, State: <256-bin ints>;\nAction:from the normalized state. Tether always dropped the state. The exporter now recordsdecomposed.state_in_promptfrom the policy: false for state-out students (state_proj) and foruse_proprioceptive_memoryconfigs. The server reads that field. For older exports it falls back tonot expert_takes_state. An export that puts state in the prompt now refuses a request that has no state. The misleading comment is fixed.embed_prefix, so SigLIP needs the [-1, 1] range. Tether sent [0, 1] images after a PIL antialiased resize._prep_imageis now a NumPy port ofresize_with_pad_torchfollowed by*2-1. Max deviation from torch is 2.4e-7.action_dim=32(the padded size). It now writesoutput_features["action"](7) and also recordsdecomposed.max_action_dim. For older exports the server takes the action dim from the normalizer stats. It refuses a config whose action dim disagrees with the stats.optimize=False, the expert export leaves 370SplitToSequence+SequenceAtpairs. Neither the TensorRT parser nor the CUDA EP takesSplitToSequence, so ORT 1.25 runs it on the CPU and runsSequenceAton CUDA. TensorRT's MemcpyTransformer treats CUDA nodes as its own device. It inserts a TensorRTMemcpyFromHostfor every sequence; the INFO log showsAdd MemcpyFromHost after split_N for TensorrtExecutionProvider370 times. That kernel copies only tensors._repair_exported_onnxnow rewrites each pair to a singleSplit. Outputs are bit-identical on ORT CPU.source_dtype, and bfloat16-trained checkpoints keep TensorRT at FP32/TF32.model_revision.Evidence (one L4, 24 paired observations and noise vs LeRobot eager)
Full write-up: tether-studio-context
03_experiments/2026-10-27-pi05-l4-instinctflash-vs-tether/RESULTS.md, section "Rerun after fixes".Not done here
trt_load_user_initializermay remove that limit. It is untested here.Tests
uv run --no-sync pytest tests/ -q -p no:cacheprovider: 4702 passed, 121 skipped (main baseline: 4679 passed). Each new test fails on main.🤖 Generated with Claude Code