Repository navigation
Conversation
360a16d to
4f33675
Compare
1a86f11 to
f87f4a1
Compare
1cb4602 to
5980661
Compare
5980661 to
5e662b3
Compare
5e662b3 to
a4b4fd0
Compare
a4b4fd0 to
7e3a333
Compare
RyanPCo
left a comment
There was a problem hiding this comment.
Automated adversarial review (read-only; findings were checked by a second skeptical pass before posting): 3 low, 2 nit.
| cond_drop: dict | None = None, | ||
| image_history: int = 1, | ||
| image_history_dropout: float = 0.0, | ||
| **kwargs, |
There was a problem hiding this comment.
low (correctness): **kwargs is accepted but never used or forwarded, and RDT._build_policy passes the trunk: config straight in. A typo or an HPT-style key (e.g. +...trunk.num_blocks=12, HPT's name for depth) is silently dropped, and the model trains at depth 28. No RDT config passes extra keys, so remove **kwargs and let unknown keys fail at construction.
| "model.safetensors", | ||
| local_files_only=True, | ||
| ) | ||
| except Exception: # not cached, or not an HF-hosted timm model |
There was a problem hiding this comment.
low (error-handling): except Exception turns any failure, including a bad pretrained_cfg or a coding error, into a silent skip of the pretrained-weight hash check. Catch only the expected cases: huggingface_hub.errors.LocalEntryNotFoundError for a cache miss, and KeyError/TypeError when pretrained_cfg has no hf_hub_id. Log at debug level when skipping.
| def _stream(self, modality, stem) -> str: | ||
| if modality in self.encoders: | ||
| return "img" | ||
| return "lang" if hasattr(stem, "forward_with_mask") else "state" |
There was a problem hiding this comment.
low (correctness): _stream labels a stem as language only if it has forward_with_mask. A text stem without it, such as QwenPooledEncoder (HPT's usual text stem), is therefore routed to state. The prompt then silently becomes a state token, gets cond_drop['state'] instead of the lang dropout, and any token-count error blames proprio history. Detect text input explicitly (e.g. isinstance(data[modality], list)), and raise when such a stem lacks forward_with_mask, pointing the user to QwenPerTokenEncoder.
| assert torch.equal(large[0, D // 2 :], large[25, D // 2 :]) | ||
|
|
||
|
|
||
| def _dino(**kwargs): |
There was a problem hiding this comment.
nit (test-quality): _dino and TINY_TOWER are copied verbatim from tests/unit/test_dinov3_stem.py, also new in this PR, and rdt_small_overrides repeats the same tower kwargs. Move TINY_TOWER and a tiny_dinov3_stem(output_dim, **kw) helper into tests/fixtures and import them in both test files.
| assert (algo.train_image_augs.vectorized is not None) == vectorized | ||
| cam = "observations.images.front_img_1" | ||
| frame = torch.rand(1, 3, 8, 8).expand(B, -1, -1, -1).contiguous() | ||
| batch = { |
There was a problem hiding this comment.
nit (test-quality): This batch dict, and the one in test_algo_keeps_past_and_current_in_order, is exactly what _pair_batch returns a few lines below. Move _pair_batch above both tests and call it from them. Building the key as cam + IMAGE_HISTORY_SUFFIX instead of f"{cam}_hist" also keeps the tests tied to the real constant.
…isode fps in every sample Human.get_keymap(image_history_gap_s=...) adds a <front key>_hist camera entry; ZarrDataset reads it round(gap x fps) frames back, clamped at the episode start. Every sample also carries the episode's fps (zarr attr). The past frame names the front camera's zarr key, so the loader's one-call-per- key read covers both windows. JPEG keys are one frame per chunk, and a plain union span would read every frame in between; the read planner keeps them two rows. Per sample on a synthetic 30 fps episode of 360x640 JPEGs (median of 7x40 samples, login node, noisy): no history 11-12 ms; gap 0.1 s 21-24 -> 19-23 ms (the second decode, which history needs); 0.5 s 30-34 -> 20-21 ms; 2 s 53-61 -> 19 ms, flat in the gap. A test counts the JPEG rows read at 0.1, 0.5 and 2 s. Nothing consumes <front>_hist here; the keymap docstring says so. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_019DbPWJCKtVg5c2PNHs7egw
…spect ratio) Same call convention as ResNet. Loaded through timm: the locked transformers (4.53.2) has no DINOv3 class, and the timm hub repos are ungated. RoPE-only, so image_size may be [H, W]; exposes grid_size. pretrained_reference_state_dict reads the checkpoint back from the local HF cache (never the network), so the pretrained-weight hash check covers this tower as it does the Qwen stems; an uncached checkpoint or a random tower is skipped. Against the real timm/vit_base_patch16_dinov3.lvd1689m file the key sets match (162 tensors) and every value is equal. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_019DbPWJCKtVg5c2PNHs7egw
RDT(HPT) swaps the policy network: no Perceiver pooling; stems hand full token sequences to the head; the DiT blocks live in the trunk slot, shared across embodiments, and cross-attend to language / image tokens in alternating blocks; each FMPolicy head holds one embodiment's action adaptor, position table and output layer. DINOv3 ViT-B/L (fine-tuned at 0.1x) + frozen Qwen3-Embedding, 2-frame image history, ctrl-freq token, per-stream condition dropout. Configs: model/rdt_*; train_zarr_mecka_flagship_6d_rdt. The (past, current) pair goes through the train augs as (B * 2, 3, H, W) with frames=2: one jitter draw per sample covers both frames, and every op runs per frame. Laid side by side instead, ColorJitter's contrast took one mean over both frames (the past frame shifted by up to 0.20 with the current frame's content) and a Resize or CenterCrop would have mixed pixels across the seam. The pair also gets HPT's per-sample absent-camera guard, since both go through _apply_image_augs. compile.enabled=true leaves the trunk off HPT's compile list (no_trunk), but the DiT is still compiled: every head's denoiser calls it, so dynamo traces its blocks into the head's graph (asserted in test_rdt with a capturing backend). Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_019DbPWJCKtVg5c2PNHs7egw
7e3a333 to
3c557cc
Compare
|
Review findings: status after the fixup pass. The fixes are squashed into this PR's commits, and the whole stack was force-pushed. Fixed
Partial
|

Adds RDT (Robotics Diffusion Transformer) as an algo on the HPT chassis.
RDT(HPT)keeps HPT's batch handling, stems, encoders, denoising heads, eval and config layout, and swaps the policy network for RDT's DiT.Behaviour changes
None for existing recipes.
HPTgains a_build_policyhook and aframesargument on its image augs, and at the defaultframes=1both do what they did before.Model
FMPolicyhead holds one embodiment's action adaptor, position table and output layer, so co-training shares the blocks at native action widths.fps;rdt_bc_mecka_6d_dinov3_vitb.yaml. The main ones: flow matching instead of DDPM, DINOv3 instead of SigLIP, Qwen3 instead of T5-XXL, and per-embodiment adaptors instead of the 128-D unified vector.otandfreeze_reprare HPT-only and raise.Configs
model/rdt_bc_mecka_6d_dinov3_vitb.yamlis the default: width 1024 x 28 blocks x 16 heads, with 392.7M trainable (DiT 296.6M, DINOv3 85.6M, plus adaptors). It usesbackbone_lr_scale: 0.1and AdamW 1e-4 with a 1k-step warmup, then a constant LR.model/rdt_bc_mecka_6d_dinov3_vitl.yamlswaps in the ViT-L tower.model/rdt_1b_bc_mecka_6d_dinov3_vitb.yamlis the RDT-1B shape (2048 wide, 32 heads).train_zarr_mecka_flagship_6d_rdt.yamlistrain_zarr_mecka_flagship_6d_hptwith the RDT model. It setsimage_history_gap_s: 0.1andgradient_clip_val: 1.0. It keeps proprio history at K=1 / stride 1, because the RDT state adaptor is declared athistory_len: 1and the two must agree.Prefetch the timm checkpoint on a login node before an offline or multi-rank run (command in the model config).
Step time, as recorded in the model config (H100, batch 32, bf16):
Sampling takes 0.3 s per batch.
Image history through the augs
(B * 2, 3, H, W)withframes=2. One jitter draw per sample covers both frames, and every op runs per frame. Laid side by side instead, ColorJitter's contrast took one mean over both frames, which shifted the past frame by up to 0.20 depending on the current frame's content. A Resize or CenterCrop would also have mixed pixels across the seam.BatchedColorJitterandPerSampleAugstakeframes. The per-image fallback applies the aug once per clip._apply_image_augs.Compile
compile.enabled=trueleaves the trunk off HPT's compile list (no_trunk), but the DiT is still compiled. Every head's denoiser calls it, so dynamo traces its blocks into the head's graph.Tests
tests/unit/test_rdt.pycovers:fpsrequirement;tests/unit/test_train_step.py::test_rdt_train_stepruns a train step on synthetic mecka episodes with a tiny DiT and a random tiny DINOv3 tower (rdt_small_overridesintests/fixtures/recipes.py). It checks that<front>_histandfpsreach the batch.Folded in (to keep the stack under Graphite's 50-branch cap):
🤖 Generated with Claude Code
https://claude.ai/code/session_01HjCQBqQCr2igFEMR7yUovw