paper_rob__3d_diffusion_policy
3D Diffusion Policy (DP3) combines compact 3D visual representations from sparse point clouds with diffusion-based action generation for robotic imitation learning. It achieves 24.2% improvement over baselines across 72 simulation tasks with only 10 demonstrations, and 85% success on real robot tasks.
Paper Info
Method Overview
- Point cloud observation: Sparse point clouds (512 or 1024 points) are extracted from depth cameras. Only XYZ coordinates are used by default (no color), providing appearance invariance.
- DP3Encoder: A PointNet-based encoder processes the point cloud via a 3-layer MLP [64, 128, 256] with max-pooling and projection to a compact 64-dim feature. A separate 2-layer MLP encodes the robot's proprioceptive state (agent_pos) into 64 dims. These are concatenated into a 128-dim observation feature.
- Conditional UNet1D diffusion: The observation feature (flattened across n_obs_steps to 256-dim) conditions a 1D UNet via FiLM modulation to iteratively denoise a random trajectory into an action chunk. DDIM scheduler with 100 training / 10 inference steps, predicting the clean sample directly.
- Action chunking: The model predicts a horizon of 16 actions but executes only 8 (n_action_steps), starting from timestep offset
n_obs_steps - 1 = 1.
Key insight: A simple PointNet encoder producing a compact 64-dim 3D feature is sufficient for diffusion policy conditioning -- no need for complex 3D backbones, NeRFs, or dense representations.
Paper-Code Mapping
| Paper Concept |
Code Location |
Notes |
| DP3 policy (Sec 3) |
diffusion_policy_3d/policy/dp3.py:DP3 |
Main policy: predict_action(), compute_loss(), conditional_sample() |
| Simple DP3 variant |
diffusion_policy_3d/policy/simple_dp3.py:SimpleDP3 |
Lighter UNet (1 resblock/level, 1 mid block), 25 FPS inference |
| DP3Encoder (Sec 3.1) |
diffusion_policy_3d/model/vision/pointnet_extractor.py:DP3Encoder |
Wraps PointNet + state MLP, output_shape() returns 128 (64+64) |
| PointNet encoder (XYZ) |
pointnet_extractor.py:PointNetEncoderXYZ |
MLP [3->64->128->256], LayerNorm, max-pool, Linear(256->64)+LayerNorm |
| PointNet encoder (XYZRGB) |
pointnet_extractor.py:PointNetEncoderXYZRGB |
MLP [6->64->128->256->512], max-pool, Linear(512->64)+LayerNorm |
| Conditional UNet1D (Sec 3.2) |
diffusion_policy_3d/model/diffusion/conditional_unet1d.py:ConditionalUnet1D |
FiLM-conditioned 1D UNet, 2 resblocks/level, 2 mid blocks |
| Simple UNet1D |
diffusion_policy_3d/model/diffusion/simple_conditional_unet1d.py:ConditionalUnet1D |
1 resblock/level, 1 mid block |
| FiLM conditioning (Eq 4) |
conditional_unet1d.py:ConditionalResidualBlock1D |
condition_type='film': scale/bias modulation; also supports add, cross_attention_add/film, mlp_film |
| DDIM noise schedule |
dp3.yaml:noise_scheduler |
DDIMScheduler, 100 train / 10 inference steps, prediction_type: sample |
| Diffusion timestep embed |
diffusion_policy_3d/model/diffusion/positional_embedding.py:SinusoidalPosEmb |
Sinusoidal -> Linear(d, 4d) -> Mish -> Linear(4d, d) |
| Normalizer |
diffusion_policy_3d/model/common/normalizer.py:LinearNormalizer |
Per-field linear normalization fitted from dataset |
| EMA model |
diffusion_policy_3d/model/diffusion/ema_model.py:EMAModel |
Exponential moving average for stable eval |
| Action masking |
diffusion_policy_3d/model/diffusion/mask_generator.py:LowdimMaskGenerator |
Inpainting mask: actions invisible, obs visible (when not global_cond) |
| Zarr dataset |
diffusion_policy_3d/dataset/adroit_dataset.py:AdroitDataset |
Loads .zarr, returns {obs: {point_cloud, agent_pos}, action} |
| Training workspace |
train.py:TrainDP3Workspace |
Hydra-based: model init, training loop, WandB, checkpointing |
Setup
Dependencies
- Python 3.8
- PyTorch (CUDA 11.7+ or 12.1+)
- Hydra 1.2.0
- Key packages:
diffusers==0.11.1, zarr==2.12.0, einops==0.4.1, dill==0.3.5.1, numba==0.56.4, wandb, pytorch3d (simplified version in repo)
- Simulation:
gym==0.21.0 (pinned, from third_party/), mujoco-py==2.1.2.14 (from third_party/), MuJoCo 2.1.0
Installation
git clone https://github.com/YanjieZe/3D-Diffusion-Policy.git
cd 3D-Diffusion-Policy
# 1. Create conda env
conda create -n dp3 python=3.8 && conda activate dp3
# 2. Install PyTorch (match your CUDA)
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
# 3. Install DP3 package
cd 3D-Diffusion-Policy && pip install -e . && cd ..
# 4. Install MuJoCo 2.1.0
mkdir -p ~/.mujoco && cd ~/.mujoco
wget https://github.com/deepmind/mujoco/releases/download/2.1.0/mujoco210-linux-x86_64.tar.gz -O mujoco210.tar.gz
tar -xvzf mujoco210.tar.gz
# Add to ~/.bashrc:
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${HOME}/.mujoco/mujoco210/bin:/usr/lib/nvidia:/usr/local/cuda/lib64
# export MUJOCO_GL=egl
# 5. Install third-party deps (order matters)
pip install setuptools==59.5.0 Cython==0.29.35 patchelf==0.17.2.0
cd third_party
cd mujoco-py-2.1.2.14 && pip install -e . && cd ..
cd gym-0.21.0 && pip install -e . && cd ..
cd dexart-release && pip install -e . && cd ..
cd Metaworld && pip install -e . && cd ..
cd rrl-dependencies && pip install -e mj_envs/. && pip install -e mjrl/. && cd ..
cd pytorch3d_simplified && pip install -e . && cd ../..
# 6. Install remaining packages
pip install zarr==2.12.0 wandb ipdb gpustat dm_control omegaconf hydra-core==1.2.0 \
dill==0.3.5.1 einops==0.4.1 diffusers==0.11.1 numba==0.56.4 moviepy imageio av \
matplotlib termcolor
External Assets
- Adroit RL experts: Download from OneDrive or Google Drive -> unzip
ckpts/ into third_party/VRL3/
- DexArt assets: Download from Google Drive -> unzip
assets/ into third_party/dexart-release/
- Real robot data: Download from Google Drive -> place zarr files into
3D-Diffusion-Policy/data/
Usage Scenarios
Generate Demonstrations
# Adroit (door, hammer, pen) -- 10 episodes via VRL3 expert
bash scripts/gen_demonstration_adroit.sh hammer
# DexArt (laptop, faucet, bucket, toilet) -- 100 episodes via RL checkpoint
bash scripts/gen_demonstration_dexart.sh laptop
# MetaWorld (50 tasks) -- 10 episodes via built-in expert
bash scripts/gen_demonstration_metaworld.sh basketball
Data saved to 3D-Diffusion-Policy/data/<env>_<task>_expert.zarr.
Train a Policy
# Usage: bash scripts/train_policy.sh <alg> <task> <tag> <seed> <gpu_id>
bash scripts/train_policy.sh dp3 adroit_hammer 0322 0 0
bash scripts/train_policy.sh simple_dp3 adroit_hammer 0322 0 0
bash scripts/train_policy.sh dp3 metaworld_basketball 0602 0 0
bash scripts/train_policy.sh dp3 realdex_drill 0112 0 0
Internally runs: cd 3D-Diffusion-Policy && python train.py --config-name=dp3.yaml task=adroit_hammer ...
Evaluate a Saved Policy
# Same args as training
bash scripts/eval_policy.sh dp3 adroit_hammer 0322 0 0
Loads latest.ckpt from the output directory and runs rollouts. For benchmarking use WandB metrics from training, not this script.
Key Config Flags
| Flag / Override |
Default |
Description |
horizon |
16 |
Total prediction horizon (action chunk length) |
n_obs_steps |
2 |
Number of observation steps fed to encoder |
n_action_steps |
8 |
Number of actions executed per inference call |
policy.encoder_output_dim |
64 |
PointNet output dimensionality |
policy.down_dims |
[512,1024,2048] (dp3) / [128,256,384] (simple) |
UNet channel widths per level |
policy.diffusion_step_embed_dim |
128 |
Diffusion timestep embedding dim |
policy.num_inference_steps |
10 |
DDIM denoising steps at inference |
policy.condition_type |
film |
Options: film, add, cross_attention_add, cross_attention_film, mlp_film |
policy.use_pc_color |
false |
Use XYZRGB (6-ch) vs XYZ (3-ch) point clouds |
training.num_epochs |
3000 |
Training epochs |
training.use_ema |
true |
Use EMA model for evaluation |
training.lr_scheduler |
cosine |
LR schedule with 500-step warmup |
training.rollout_every |
200 |
Epochs between evaluation rollouts |
dataloader.batch_size |
128 |
Batch size |
optimizer.lr |
1e-4 |
AdamW learning rate |
checkpoint.save_ckpt |
false |
Set true to persist checkpoints |
Code Integration Guide
Minimal Imports
import sys
sys.path.append("/path/to/3D-Diffusion-Policy/3D-Diffusion-Policy")
from diffusion_policy_3d.policy.dp3 import DP3
from diffusion_policy_3d.model.vision.pointnet_extractor import DP3Encoder, PointNetEncoderXYZ
from diffusion_policy_3d.model.diffusion.conditional_unet1d import ConditionalUnet1D
from diffusion_policy_3d.model.common.normalizer import LinearNormalizer
Model Instantiation & Inference
import torch
import dill
from omegaconf import OmegaConf
from diffusers.schedulers.scheduling_ddim import DDIMScheduler
# Define shape metadata (must match your task)
shape_meta = {
'obs': {
'point_cloud': {'shape': [512, 3], 'type': 'point_cloud'},
'agent_pos': {'shape': [24], 'type': 'low_dim'},
},
'action': {'shape': [26]}
}
noise_scheduler = DDIMScheduler(
num_train_timesteps=100,
beta_start=0.0001, beta_end=0.02,
beta_schedule='squaredcos_cap_v2',
clip_sample=True, set_alpha_to_one=True,
prediction_type='sample'
)
pc_cfg = OmegaConf.create({
'in_channels': 3,
'out_channels': 64,
'use_layernorm': True,
'final_norm': 'layernorm',
'normal_channel': False,
})
policy = DP3(
shape_meta=shape_meta,
noise_scheduler=noise_scheduler,
horizon=16,
n_action_steps=8,
n_obs_steps=2,
num_inference_steps=10,
obs_as_global_cond=True,
diffusion_step_embed_dim=128,
down_dims=[512, 1024, 2048],
kernel_size=5,
n_groups=8,
condition_type='film',
encoder_output_dim=64,
use_pc_color=False,
pointnet_type='pointnet',
pointcloud_encoder_cfg=pc_cfg,
)
# Load checkpoint (dill required)
ckpt = torch.load("path/to/latest.ckpt", pickle_module=dill, map_location='cpu')
policy.load_state_dict(ckpt['state_dicts']['model'])
# Also load EMA model if available:
# ema_policy.load_state_dict(ckpt['state_dicts']['ema_model'])
policy.eval().cuda()
# Inference -- obs_dict values must have shape (B, T=n_obs_steps, ...)
obs_dict = {
'point_cloud': torch.randn(1, 2, 512, 3).cuda(), # (B, T, N_pts, 3)
'agent_pos': torch.randn(1, 2, 24).cuda(), # (B, T, D_state)
}
result = policy.predict_action(obs_dict)
action = result['action'] # (B, n_action_steps, D_action) = (1, 8, 26)
action_pred = result['action_pred'] # (B, horizon, D_action) = (1, 16, 26)
Data Format
| Field |
Shape / Type |
Description |
obs.point_cloud |
(B, T, N_pts, 3) float32 |
XYZ point cloud; N_pts=512 (sim) or 1024 (real) |
obs.agent_pos |
(B, T, D_state) float32 |
Robot proprioceptive state |
obs.imagin_robot |
(B, T, N_imag, 3) float32 |
Optional imagined robot points (concatenated with point_cloud in encoder) |
action |
(B, T, D_action) float32 |
Action trajectory |
Zarr Archive Structure
data/<task>.zarr/
data/
state (N_total, D_state) float32 -- robot state (mapped to obs.agent_pos)
action (N_total, D_action) float32 -- actions
point_cloud (N_total, N_pts, 3+) float64 -- point clouds
img (N_total, H, W, 3) uint8 -- images (optional)
meta/
episode_ends (N_episodes,) int64 -- cumulative step indices marking episode boundaries
Integration Notes
- The repo uses a nested directory layout: the outer
3D-Diffusion-Policy/ is the repo root, the inner 3D-Diffusion-Policy/ is the Python package. train.py and eval.py live in the inner directory. Scripts run from the outer root; the entry points do sys.path.append(ROOT_DIR) where ROOT_DIR is the outer directory.
- Hydra config resolution requires
OmegaConf.register_new_resolver("eval", eval, replace=True) for expressions like ${eval:'${n_obs_steps}-1'}.
DP3Encoder.forward() expects a dict with keys point_cloud (required) and agent_pos (required). If imagin_robot key exists in observation_space, those points are concatenated with point_cloud before encoding.
- Point clouds are NOT normalized by the PointNet encoder. Normalization happens via
LinearNormalizer set on the policy via policy.set_normalizer(). The normalizer is fitted from the dataset and saved inside the checkpoint.
- Checkpoints are saved via
dill (not plain pickle). Loading requires pickle_module=dill in torch.load().
- The
DP3 constructor accepts **kwargs which are forwarded to noise_scheduler.step() during inference.
Core Architecture
Point Cloud (B, N, 3)
|
PointNetEncoderXYZ
[Linear 3->64, LN, ReLU]
[Linear 64->128, LN, ReLU]
[Linear 128->256, LN, ReLU]
[max-pool over N points]
[Linear 256->64, LN]
|
pn_feat (B, 64)
|
Agent State (B, D) --> StateMLP [D->64, ReLU, 64->64] --> state_feat (B, 64)
| |
+-------------concat-----------------+
|
obs_feature (B, 128) <-- computed per obs step
|
[flatten n_obs_steps=2 -> 256]
|
global_cond (B, 256)
|
+----------+----------+
| |
timestep_embed global_cond
SinusoidalPosEmb(128) |
-> MLP -> 128-dim |
| |
+------concat---------+
|
cond_feature (B, 128+256=384)
|
ConditionalUnet1D (dp3 variant)
[Down: action_dim->512->1024->2048, 2 ResBlocks/level, FiLM]
[Mid: 2x ResBlock at 2048]
[Up: 2048->1024->512, skip connections, 2 ResBlocks/level]
[Final: Conv1dBlock + Conv1d -> action_dim]
|
denoised actions (B, horizon=16, D_action)
|
take steps [1:9] -> executed actions (B, 8, D_action)
Repo Structure
| Path |
Purpose |
3D-Diffusion-Policy/train.py |
Training entry with TrainDP3Workspace (model init, training loop, checkpointing) |
3D-Diffusion-Policy/eval.py |
Evaluation entry (loads latest.ckpt, runs env rollouts) |
diffusion_policy_3d/policy/dp3.py |
DP3 policy class: encoder + UNet diffusion + action chunking |
diffusion_policy_3d/policy/simple_dp3.py |
SimpleDP3: same API, uses lighter simple UNet |
diffusion_policy_3d/policy/base_policy.py |
BasePolicy base class (extends ModuleAttrMixin) |
diffusion_policy_3d/model/vision/pointnet_extractor.py |
DP3Encoder, PointNetEncoderXYZ, PointNetEncoderXYZRGB |
diffusion_policy_3d/model/diffusion/conditional_unet1d.py |
ConditionalUnet1D (full), ConditionalResidualBlock1D, CrossAttention |
diffusion_policy_3d/model/diffusion/simple_conditional_unet1d.py |
ConditionalUnet1D (simple: 1 resblock/level) |
diffusion_policy_3d/model/diffusion/conv1d_components.py |
Conv1dBlock, Downsample1d, Upsample1d |
diffusion_policy_3d/model/diffusion/positional_embedding.py |
SinusoidalPosEmb |
diffusion_policy_3d/model/diffusion/ema_model.py |
EMAModel for exponential moving average |
diffusion_policy_3d/model/diffusion/mask_generator.py |
LowdimMaskGenerator for inpainting-style action masking |
diffusion_policy_3d/model/common/normalizer.py |
LinearNormalizer, SingleFieldLinearNormalizer |
diffusion_policy_3d/model/common/lr_scheduler.py |
get_scheduler() (cosine, linear, constant) |
diffusion_policy_3d/config/dp3.yaml |
Main Hydra config (dp3 variant) |
diffusion_policy_3d/config/simple_dp3.yaml |
Simple DP3 Hydra config |
diffusion_policy_3d/config/task/ |
61 task configs: 3 Adroit + 4 DexArt + 50 MetaWorld + 4 RealDex |
diffusion_policy_3d/dataset/adroit_dataset.py |
AdroitDataset(BaseDataset) -- zarr-backed |
diffusion_policy_3d/dataset/dexart_dataset.py |
DexArtDataset(BaseDataset) |
diffusion_policy_3d/dataset/metaworld_dataset.py |
MetaworldDataset(BaseDataset) |
diffusion_policy_3d/dataset/realdex_dataset.py |
RealDexDataset(BaseDataset) -- real robot data |
diffusion_policy_3d/dataset/base_dataset.py |
BaseDataset interface: get_normalizer(), __getitem__() |
diffusion_policy_3d/env_runner/adroit_runner.py |
AdroitRunner(BaseRunner) -- rollout evaluation |
diffusion_policy_3d/env_runner/dexart_runner.py |
DexArtRunner(BaseRunner) |
diffusion_policy_3d/env_runner/metaworld_runner.py |
MetaworldRunner(BaseRunner) |
diffusion_policy_3d/common/replay_buffer.py |
Zarr-backed ReplayBuffer |
diffusion_policy_3d/common/sampler.py |
SequenceSampler, get_val_mask, downsample_mask |
diffusion_policy_3d/common/pytorch_util.py |
dict_apply, optimizer_to utilities |
diffusion_policy_3d/common/checkpoint_util.py |
TopKCheckpointManager |
scripts/train_policy.sh |
Training launcher (Hydra overrides) |
scripts/eval_policy.sh |
Evaluation launcher |
scripts/gen_demonstration_adroit.sh |
Adroit demo generation (VRL3 expert, 10 episodes) |
scripts/gen_demonstration_dexart.sh |
DexArt demo generation (RL checkpoint, 100 episodes) |
scripts/gen_demonstration_metaworld.sh |
MetaWorld demo generation (built-in expert, 10 episodes) |
scripts/convert_real_robot_data.py |
Convert raw real robot data (pickle) to zarr format with FPS + cropping |
third_party/ |
Pinned deps: gym-0.21.0, mujoco-py-2.1.2.14, Metaworld, dexart-release, VRL3, pytorch3d_simplified |
visualizer/ |
Optional plotly-based point cloud visualizer (pip install -e .) |
Supported Environments (61 task configs)
| Suite |
Tasks |
Point Cloud Shape |
Action Dim |
State Dim |
| Adroit |
door, hammer, pen |
(512, 3) |
26-28 |
24-30 |
| DexArt |
bucket, faucet, laptop, toilet |
(512, 3) |
varies |
varies |
| MetaWorld |
50 tasks (assembly, basketball, pick-place, etc.) |
(512, 3) |
varies |
varies |
| RealDex |
drill, dumpling, pour, roll |
(1024, 3) |
22 |
22 |
Tips & Gotchas
- GPU memory: DP3 uses ~10 GB GPU memory; training takes ~3 hours on A40. Simple DP3 is faster (1-2 hours) with comparable performance.
- simple_dp3 vs dp3: Simple DP3 uses UNet channels [128,256,384] with 1 resblock per level (vs [512,1024,2048] with 2). Recommended for real robot work due to 25 FPS inference speed.
- Longer horizons help: The authors recommend trying horizon=8/16/32 and n_action_steps=8/16 for better results on custom tasks.
- Use global position actions: Absolute end-effector position as action space works better than relative position.
- Point cloud cropping is critical: For real robot, crop out the table/background -- keep only task-relevant points. Use bounding box cropping + FPS downsampling (see
scripts/convert_real_robot_data.py).
- Camera quality: RealSense L515 is recommended; D435 produces poor point clouds that cause DP3 to fail.
- gym version is critical: Must use the pinned
gym==0.21.0 from third_party/. Other versions break environment wrappers.
- pip version: If gym-0.21.0 fails to install with pip>=24, downgrade to
pip install pip==21.
- opencv-python spec: The gym-0.21.0
setup.py line 20 has opencv-python>=3. (missing minor version). Edit to opencv-python>=3.0 if installation fails.
- pytorch3d CUDA errors: If you get "no kernel image is available", reinstall from
third_party/pytorch3d_simplified.
- huggingface_hub: Use version <= 0.25.2 (
pip install huggingface_hub==0.25.2) to avoid cached_download import error from diffusers.
- OpenGL/rendering errors: Run
unset LD_PRELOAD and set export MUJOCO_GL=egl for headless rendering.
- Demonstration quality matters: Results depend heavily on demo quality. Re-generate if you get bad demonstrations rather than adding more.
- WandB: Results are logged to WandB; run
wandb login before training. Use logging.mode=offline for debugging.
- Checkpoints use dill: All checkpoints are saved with
pickle_module=dill. Use torch.load(path, pickle_module=dill) to load.
- Real robot deployment: For deployment inference loop code, refer to iDP3.
- Custom tasks: Need to implement: (1) env wrapper in
env/, (2) env runner in env_runner/, (3) dataset class in dataset/, (4) task config YAML in config/task/. See Adroit implementations as reference.
- Nested repo layout: The inner
3D-Diffusion-Policy/ is the package directory. Scripts in scripts/ do cd 3D-Diffusion-Policy internally. Running train.py directly requires being in the inner dir or adjusting sys.path.
1---2name: paper-rob-3d-diffusion-policy3description: 3D Diffusion Policy (DP3) -- visuomotor imitation learning via sparse point cloud representations and diffusion-based action generation4---56# paper_rob__3d_diffusion_policy783D Diffusion Policy (DP3) combines compact 3D visual representations from sparse point clouds with diffusion-based action generation for robotic imitation learning. It achieves 24.2% improvement over baselines across 72 simulation tasks with only 10 demonstrations, and 85% success on real robot tasks.910## Paper Info1112| Field | Value |13|-------|-------|14| Title | 3D Diffusion Policy: Generalizable Visuomotor Policy Learning via Simple 3D Representations |15| Authors | Yanjie Ze et al. |16| Year | 2024 |17| Venue | RSS 2024 |18| Paper | https://arxiv.org/abs/2403.03954 |19| Code | https://github.com/YanjieZe/3D-Diffusion-Policy |20| Data | https://drive.google.com/file/d/1G5MP6Nzykku9sDDdzy7tlRqMBnKb253O (real robot data) |2122## Method Overview23241. **Point cloud observation**: Sparse point clouds (512 or 1024 points) are extracted from depth cameras. Only XYZ coordinates are used by default (no color), providing appearance invariance.252. **DP3Encoder**: A PointNet-based encoder processes the point cloud via a 3-layer MLP [64, 128, 256] with max-pooling and projection to a compact 64-dim feature. A separate 2-layer MLP encodes the robot's proprioceptive state (agent_pos) into 64 dims. These are concatenated into a 128-dim observation feature.263. **Conditional UNet1D diffusion**: The observation feature (flattened across n_obs_steps to 256-dim) conditions a 1D UNet via FiLM modulation to iteratively denoise a random trajectory into an action chunk. DDIM scheduler with 100 training / 10 inference steps, predicting the clean sample directly.274. **Action chunking**: The model predicts a horizon of 16 actions but executes only 8 (n_action_steps), starting from timestep offset `n_obs_steps - 1 = 1`.2829Key insight: A simple PointNet encoder producing a compact 64-dim 3D feature is sufficient for diffusion policy conditioning -- no need for complex 3D backbones, NeRFs, or dense representations.3031## Paper-Code Mapping3233| Paper Concept | Code Location | Notes |34|---------------|---------------|-------|35| DP3 policy (Sec 3) | `diffusion_policy_3d/policy/dp3.py:DP3` | Main policy: `predict_action()`, `compute_loss()`, `conditional_sample()` |36| Simple DP3 variant | `diffusion_policy_3d/policy/simple_dp3.py:SimpleDP3` | Lighter UNet (1 resblock/level, 1 mid block), 25 FPS inference |37| DP3Encoder (Sec 3.1) | `diffusion_policy_3d/model/vision/pointnet_extractor.py:DP3Encoder` | Wraps PointNet + state MLP, output_shape() returns 128 (64+64) |38| PointNet encoder (XYZ) | `pointnet_extractor.py:PointNetEncoderXYZ` | MLP [3->64->128->256], LayerNorm, max-pool, Linear(256->64)+LayerNorm |39| PointNet encoder (XYZRGB) | `pointnet_extractor.py:PointNetEncoderXYZRGB` | MLP [6->64->128->256->512], max-pool, Linear(512->64)+LayerNorm |40| Conditional UNet1D (Sec 3.2) | `diffusion_policy_3d/model/diffusion/conditional_unet1d.py:ConditionalUnet1D` | FiLM-conditioned 1D UNet, 2 resblocks/level, 2 mid blocks |41| Simple UNet1D | `diffusion_policy_3d/model/diffusion/simple_conditional_unet1d.py:ConditionalUnet1D` | 1 resblock/level, 1 mid block |42| FiLM conditioning (Eq 4) | `conditional_unet1d.py:ConditionalResidualBlock1D` | `condition_type='film'`: scale/bias modulation; also supports add, cross_attention_add/film, mlp_film |43| DDIM noise schedule | `dp3.yaml:noise_scheduler` | `DDIMScheduler`, 100 train / 10 inference steps, `prediction_type: sample` |44| Diffusion timestep embed | `diffusion_policy_3d/model/diffusion/positional_embedding.py:SinusoidalPosEmb` | Sinusoidal -> Linear(d, 4d) -> Mish -> Linear(4d, d) |45| Normalizer | `diffusion_policy_3d/model/common/normalizer.py:LinearNormalizer` | Per-field linear normalization fitted from dataset |46| EMA model | `diffusion_policy_3d/model/diffusion/ema_model.py:EMAModel` | Exponential moving average for stable eval |47| Action masking | `diffusion_policy_3d/model/diffusion/mask_generator.py:LowdimMaskGenerator` | Inpainting mask: actions invisible, obs visible (when not global_cond) |48| Zarr dataset | `diffusion_policy_3d/dataset/adroit_dataset.py:AdroitDataset` | Loads `.zarr`, returns `{obs: {point_cloud, agent_pos}, action}` |49| Training workspace | `train.py:TrainDP3Workspace` | Hydra-based: model init, training loop, WandB, checkpointing |5051## Setup5253### Dependencies5455- Python 3.856- PyTorch (CUDA 11.7+ or 12.1+)57- Hydra 1.2.058- Key packages: `diffusers==0.11.1`, `zarr==2.12.0`, `einops==0.4.1`, `dill==0.3.5.1`, `numba==0.56.4`, `wandb`, `pytorch3d` (simplified version in repo)59- Simulation: `gym==0.21.0` (pinned, from `third_party/`), `mujoco-py==2.1.2.14` (from `third_party/`), MuJoCo 2.1.06061### Installation6263```bash64git clone https://github.com/YanjieZe/3D-Diffusion-Policy.git65cd 3D-Diffusion-Policy6667# 1. Create conda env68conda create -n dp3 python=3.8 && conda activate dp36970# 2. Install PyTorch (match your CUDA)71pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu1217273# 3. Install DP3 package74cd 3D-Diffusion-Policy && pip install -e . && cd ..7576# 4. Install MuJoCo 2.1.077mkdir -p ~/.mujoco && cd ~/.mujoco78wget https://github.com/deepmind/mujoco/releases/download/2.1.0/mujoco210-linux-x86_64.tar.gz -O mujoco210.tar.gz79tar -xvzf mujoco210.tar.gz80# Add to ~/.bashrc:81# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${HOME}/.mujoco/mujoco210/bin:/usr/lib/nvidia:/usr/local/cuda/lib6482# export MUJOCO_GL=egl8384# 5. Install third-party deps (order matters)85pip install setuptools==59.5.0 Cython==0.29.35 patchelf==0.17.2.086cd third_party87cd mujoco-py-2.1.2.14 && pip install -e . && cd ..88cd gym-0.21.0 && pip install -e . && cd ..89cd dexart-release && pip install -e . && cd ..90cd Metaworld && pip install -e . && cd ..91cd rrl-dependencies && pip install -e mj_envs/. && pip install -e mjrl/. && cd ..92cd pytorch3d_simplified && pip install -e . && cd ../..9394# 6. Install remaining packages95pip install zarr==2.12.0 wandb ipdb gpustat dm_control omegaconf hydra-core==1.2.0 \96 dill==0.3.5.1 einops==0.4.1 diffusers==0.11.1 numba==0.56.4 moviepy imageio av \97 matplotlib termcolor98```99100### External Assets101102- **Adroit RL experts**: Download from [OneDrive](https://1drv.ms/u/s!Ag5QsBIFtRnTlFWqYWtS2wMMPKNX) or [Google Drive](https://drive.google.com/file/d/1iNkSrLD_N4NrezLx58L1YoBBqYYg-33u) -> unzip `ckpts/` into `third_party/VRL3/`103- **DexArt assets**: Download from [Google Drive](https://drive.google.com/file/d/1DxRfB4087PeM3Aejd6cR-RQVgOKdNrL4) -> unzip `assets/` into `third_party/dexart-release/`104- **Real robot data**: Download from [Google Drive](https://drive.google.com/file/d/1G5MP6Nzykku9sDDdzy7tlRqMBnKb253O) -> place zarr files into `3D-Diffusion-Policy/data/`105106## Usage Scenarios107108### Generate Demonstrations109110```bash111# Adroit (door, hammer, pen) -- 10 episodes via VRL3 expert112bash scripts/gen_demonstration_adroit.sh hammer113114# DexArt (laptop, faucet, bucket, toilet) -- 100 episodes via RL checkpoint115bash scripts/gen_demonstration_dexart.sh laptop116117# MetaWorld (50 tasks) -- 10 episodes via built-in expert118bash scripts/gen_demonstration_metaworld.sh basketball119```120121Data saved to `3D-Diffusion-Policy/data/<env>_<task>_expert.zarr`.122123### Train a Policy124125```bash126# Usage: bash scripts/train_policy.sh <alg> <task> <tag> <seed> <gpu_id>127bash scripts/train_policy.sh dp3 adroit_hammer 0322 0 0128bash scripts/train_policy.sh simple_dp3 adroit_hammer 0322 0 0129bash scripts/train_policy.sh dp3 metaworld_basketball 0602 0 0130bash scripts/train_policy.sh dp3 realdex_drill 0112 0 0131```132133Internally runs: `cd 3D-Diffusion-Policy && python train.py --config-name=dp3.yaml task=adroit_hammer ...`134135### Evaluate a Saved Policy136137```bash138# Same args as training139bash scripts/eval_policy.sh dp3 adroit_hammer 0322 0 0140```141142Loads `latest.ckpt` from the output directory and runs rollouts. For benchmarking use WandB metrics from training, not this script.143144### Key Config Flags145146| Flag / Override | Default | Description |147|------|---------|-------------|148| `horizon` | 16 | Total prediction horizon (action chunk length) |149| `n_obs_steps` | 2 | Number of observation steps fed to encoder |150| `n_action_steps` | 8 | Number of actions executed per inference call |151| `policy.encoder_output_dim` | 64 | PointNet output dimensionality |152| `policy.down_dims` | [512,1024,2048] (dp3) / [128,256,384] (simple) | UNet channel widths per level |153| `policy.diffusion_step_embed_dim` | 128 | Diffusion timestep embedding dim |154| `policy.num_inference_steps` | 10 | DDIM denoising steps at inference |155| `policy.condition_type` | `film` | Options: film, add, cross_attention_add, cross_attention_film, mlp_film |156| `policy.use_pc_color` | false | Use XYZRGB (6-ch) vs XYZ (3-ch) point clouds |157| `training.num_epochs` | 3000 | Training epochs |158| `training.use_ema` | true | Use EMA model for evaluation |159| `training.lr_scheduler` | cosine | LR schedule with 500-step warmup |160| `training.rollout_every` | 200 | Epochs between evaluation rollouts |161| `dataloader.batch_size` | 128 | Batch size |162| `optimizer.lr` | 1e-4 | AdamW learning rate |163| `checkpoint.save_ckpt` | false | Set true to persist checkpoints |164165## Code Integration Guide166167### Minimal Imports168169```python170import sys171sys.path.append("/path/to/3D-Diffusion-Policy/3D-Diffusion-Policy")172173from diffusion_policy_3d.policy.dp3 import DP3174from diffusion_policy_3d.model.vision.pointnet_extractor import DP3Encoder, PointNetEncoderXYZ175from diffusion_policy_3d.model.diffusion.conditional_unet1d import ConditionalUnet1D176from diffusion_policy_3d.model.common.normalizer import LinearNormalizer177```178179### Model Instantiation & Inference180181```python182import torch183import dill184from omegaconf import OmegaConf185from diffusers.schedulers.scheduling_ddim import DDIMScheduler186187# Define shape metadata (must match your task)188shape_meta = {189 'obs': {190 'point_cloud': {'shape': [512, 3], 'type': 'point_cloud'},191 'agent_pos': {'shape': [24], 'type': 'low_dim'},192 },193 'action': {'shape': [26]}194}195196noise_scheduler = DDIMScheduler(197 num_train_timesteps=100,198 beta_start=0.0001, beta_end=0.02,199 beta_schedule='squaredcos_cap_v2',200 clip_sample=True, set_alpha_to_one=True,201 prediction_type='sample'202)203204pc_cfg = OmegaConf.create({205 'in_channels': 3,206 'out_channels': 64,207 'use_layernorm': True,208 'final_norm': 'layernorm',209 'normal_channel': False,210})211212policy = DP3(213 shape_meta=shape_meta,214 noise_scheduler=noise_scheduler,215 horizon=16,216 n_action_steps=8,217 n_obs_steps=2,218 num_inference_steps=10,219 obs_as_global_cond=True,220 diffusion_step_embed_dim=128,221 down_dims=[512, 1024, 2048],222 kernel_size=5,223 n_groups=8,224 condition_type='film',225 encoder_output_dim=64,226 use_pc_color=False,227 pointnet_type='pointnet',228 pointcloud_encoder_cfg=pc_cfg,229)230231# Load checkpoint (dill required)232ckpt = torch.load("path/to/latest.ckpt", pickle_module=dill, map_location='cpu')233policy.load_state_dict(ckpt['state_dicts']['model'])234# Also load EMA model if available:235# ema_policy.load_state_dict(ckpt['state_dicts']['ema_model'])236policy.eval().cuda()237238# Inference -- obs_dict values must have shape (B, T=n_obs_steps, ...)239obs_dict = {240 'point_cloud': torch.randn(1, 2, 512, 3).cuda(), # (B, T, N_pts, 3)241 'agent_pos': torch.randn(1, 2, 24).cuda(), # (B, T, D_state)242}243result = policy.predict_action(obs_dict)244action = result['action'] # (B, n_action_steps, D_action) = (1, 8, 26)245action_pred = result['action_pred'] # (B, horizon, D_action) = (1, 16, 26)246```247248### Data Format249250| Field | Shape / Type | Description |251|-------|-------------|-------------|252| `obs.point_cloud` | `(B, T, N_pts, 3)` float32 | XYZ point cloud; N_pts=512 (sim) or 1024 (real) |253| `obs.agent_pos` | `(B, T, D_state)` float32 | Robot proprioceptive state |254| `obs.imagin_robot` | `(B, T, N_imag, 3)` float32 | Optional imagined robot points (concatenated with point_cloud in encoder) |255| `action` | `(B, T, D_action)` float32 | Action trajectory |256257#### Zarr Archive Structure258259```260data/<task>.zarr/261 data/262 state (N_total, D_state) float32 -- robot state (mapped to obs.agent_pos)263 action (N_total, D_action) float32 -- actions264 point_cloud (N_total, N_pts, 3+) float64 -- point clouds265 img (N_total, H, W, 3) uint8 -- images (optional)266 meta/267 episode_ends (N_episodes,) int64 -- cumulative step indices marking episode boundaries268```269270### Integration Notes271272- The repo uses a nested directory layout: the outer `3D-Diffusion-Policy/` is the repo root, the inner `3D-Diffusion-Policy/` is the Python package. `train.py` and `eval.py` live in the inner directory. Scripts run from the outer root; the entry points do `sys.path.append(ROOT_DIR)` where ROOT_DIR is the outer directory.273- Hydra config resolution requires `OmegaConf.register_new_resolver("eval", eval, replace=True)` for expressions like `${eval:'${n_obs_steps}-1'}`.274- `DP3Encoder.forward()` expects a dict with keys `point_cloud` (required) and `agent_pos` (required). If `imagin_robot` key exists in `observation_space`, those points are concatenated with `point_cloud` before encoding.275- Point clouds are NOT normalized by the PointNet encoder. Normalization happens via `LinearNormalizer` set on the policy via `policy.set_normalizer()`. The normalizer is fitted from the dataset and saved inside the checkpoint.276- Checkpoints are saved via `dill` (not plain pickle). Loading requires `pickle_module=dill` in `torch.load()`.277- The `DP3` constructor accepts `**kwargs` which are forwarded to `noise_scheduler.step()` during inference.278279## Core Architecture280281```282 Point Cloud (B, N, 3)283 |284 PointNetEncoderXYZ285 [Linear 3->64, LN, ReLU]286 [Linear 64->128, LN, ReLU]287 [Linear 128->256, LN, ReLU]288 [max-pool over N points]289 [Linear 256->64, LN]290 |291 pn_feat (B, 64)292 |293 Agent State (B, D) --> StateMLP [D->64, ReLU, 64->64] --> state_feat (B, 64)294 | |295 +-------------concat-----------------+296 |297 obs_feature (B, 128) <-- computed per obs step298 |299 [flatten n_obs_steps=2 -> 256]300 |301 global_cond (B, 256)302 |303 +----------+----------+304 | |305 timestep_embed global_cond306 SinusoidalPosEmb(128) |307 -> MLP -> 128-dim |308 | |309 +------concat---------+310 |311 cond_feature (B, 128+256=384)312 |313 ConditionalUnet1D (dp3 variant)314 [Down: action_dim->512->1024->2048, 2 ResBlocks/level, FiLM]315 [Mid: 2x ResBlock at 2048]316 [Up: 2048->1024->512, skip connections, 2 ResBlocks/level]317 [Final: Conv1dBlock + Conv1d -> action_dim]318 |319 denoised actions (B, horizon=16, D_action)320 |321 take steps [1:9] -> executed actions (B, 8, D_action)322```323324## Repo Structure325326| Path | Purpose |327|------|---------|328| `3D-Diffusion-Policy/train.py` | Training entry with `TrainDP3Workspace` (model init, training loop, checkpointing) |329| `3D-Diffusion-Policy/eval.py` | Evaluation entry (loads latest.ckpt, runs env rollouts) |330| `diffusion_policy_3d/policy/dp3.py` | `DP3` policy class: encoder + UNet diffusion + action chunking |331| `diffusion_policy_3d/policy/simple_dp3.py` | `SimpleDP3`: same API, uses lighter simple UNet |332| `diffusion_policy_3d/policy/base_policy.py` | `BasePolicy` base class (extends `ModuleAttrMixin`) |333| `diffusion_policy_3d/model/vision/pointnet_extractor.py` | `DP3Encoder`, `PointNetEncoderXYZ`, `PointNetEncoderXYZRGB` |334| `diffusion_policy_3d/model/diffusion/conditional_unet1d.py` | `ConditionalUnet1D` (full), `ConditionalResidualBlock1D`, `CrossAttention` |335| `diffusion_policy_3d/model/diffusion/simple_conditional_unet1d.py` | `ConditionalUnet1D` (simple: 1 resblock/level) |336| `diffusion_policy_3d/model/diffusion/conv1d_components.py` | `Conv1dBlock`, `Downsample1d`, `Upsample1d` |337| `diffusion_policy_3d/model/diffusion/positional_embedding.py` | `SinusoidalPosEmb` |338| `diffusion_policy_3d/model/diffusion/ema_model.py` | `EMAModel` for exponential moving average |339| `diffusion_policy_3d/model/diffusion/mask_generator.py` | `LowdimMaskGenerator` for inpainting-style action masking |340| `diffusion_policy_3d/model/common/normalizer.py` | `LinearNormalizer`, `SingleFieldLinearNormalizer` |341| `diffusion_policy_3d/model/common/lr_scheduler.py` | `get_scheduler()` (cosine, linear, constant) |342| `diffusion_policy_3d/config/dp3.yaml` | Main Hydra config (dp3 variant) |343| `diffusion_policy_3d/config/simple_dp3.yaml` | Simple DP3 Hydra config |344| `diffusion_policy_3d/config/task/` | 61 task configs: 3 Adroit + 4 DexArt + 50 MetaWorld + 4 RealDex |345| `diffusion_policy_3d/dataset/adroit_dataset.py` | `AdroitDataset(BaseDataset)` -- zarr-backed |346| `diffusion_policy_3d/dataset/dexart_dataset.py` | `DexArtDataset(BaseDataset)` |347| `diffusion_policy_3d/dataset/metaworld_dataset.py` | `MetaworldDataset(BaseDataset)` |348| `diffusion_policy_3d/dataset/realdex_dataset.py` | `RealDexDataset(BaseDataset)` -- real robot data |349| `diffusion_policy_3d/dataset/base_dataset.py` | `BaseDataset` interface: `get_normalizer()`, `__getitem__()` |350| `diffusion_policy_3d/env_runner/adroit_runner.py` | `AdroitRunner(BaseRunner)` -- rollout evaluation |351| `diffusion_policy_3d/env_runner/dexart_runner.py` | `DexArtRunner(BaseRunner)` |352| `diffusion_policy_3d/env_runner/metaworld_runner.py` | `MetaworldRunner(BaseRunner)` |353| `diffusion_policy_3d/common/replay_buffer.py` | Zarr-backed `ReplayBuffer` |354| `diffusion_policy_3d/common/sampler.py` | `SequenceSampler`, `get_val_mask`, `downsample_mask` |355| `diffusion_policy_3d/common/pytorch_util.py` | `dict_apply`, `optimizer_to` utilities |356| `diffusion_policy_3d/common/checkpoint_util.py` | `TopKCheckpointManager` |357| `scripts/train_policy.sh` | Training launcher (Hydra overrides) |358| `scripts/eval_policy.sh` | Evaluation launcher |359| `scripts/gen_demonstration_adroit.sh` | Adroit demo generation (VRL3 expert, 10 episodes) |360| `scripts/gen_demonstration_dexart.sh` | DexArt demo generation (RL checkpoint, 100 episodes) |361| `scripts/gen_demonstration_metaworld.sh` | MetaWorld demo generation (built-in expert, 10 episodes) |362| `scripts/convert_real_robot_data.py` | Convert raw real robot data (pickle) to zarr format with FPS + cropping |363| `third_party/` | Pinned deps: gym-0.21.0, mujoco-py-2.1.2.14, Metaworld, dexart-release, VRL3, pytorch3d_simplified |364| `visualizer/` | Optional plotly-based point cloud visualizer (`pip install -e .`) |365366## Supported Environments (61 task configs)367368| Suite | Tasks | Point Cloud Shape | Action Dim | State Dim |369|-------|-------|------------------|------------|-----------|370| Adroit | door, hammer, pen | (512, 3) | 26-28 | 24-30 |371| DexArt | bucket, faucet, laptop, toilet | (512, 3) | varies | varies |372| MetaWorld | 50 tasks (assembly, basketball, pick-place, etc.) | (512, 3) | varies | varies |373| RealDex | drill, dumpling, pour, roll | (1024, 3) | 22 | 22 |374375## Tips & Gotchas376377- **GPU memory**: DP3 uses ~10 GB GPU memory; training takes ~3 hours on A40. Simple DP3 is faster (1-2 hours) with comparable performance.378- **simple_dp3 vs dp3**: Simple DP3 uses UNet channels [128,256,384] with 1 resblock per level (vs [512,1024,2048] with 2). Recommended for real robot work due to 25 FPS inference speed.379- **Longer horizons help**: The authors recommend trying horizon=8/16/32 and n_action_steps=8/16 for better results on custom tasks.380- **Use global position actions**: Absolute end-effector position as action space works better than relative position.381- **Point cloud cropping is critical**: For real robot, crop out the table/background -- keep only task-relevant points. Use bounding box cropping + FPS downsampling (see `scripts/convert_real_robot_data.py`).382- **Camera quality**: RealSense L515 is recommended; D435 produces poor point clouds that cause DP3 to fail.383- **gym version is critical**: Must use the pinned `gym==0.21.0` from `third_party/`. Other versions break environment wrappers.384- **pip version**: If gym-0.21.0 fails to install with pip>=24, downgrade to `pip install pip==21`.385- **opencv-python spec**: The gym-0.21.0 `setup.py` line 20 has `opencv-python>=3.` (missing minor version). Edit to `opencv-python>=3.0` if installation fails.386- **pytorch3d CUDA errors**: If you get "no kernel image is available", reinstall from `third_party/pytorch3d_simplified`.387- **huggingface_hub**: Use version <= 0.25.2 (`pip install huggingface_hub==0.25.2`) to avoid `cached_download` import error from diffusers.388- **OpenGL/rendering errors**: Run `unset LD_PRELOAD` and set `export MUJOCO_GL=egl` for headless rendering.389- **Demonstration quality matters**: Results depend heavily on demo quality. Re-generate if you get bad demonstrations rather than adding more.390- **WandB**: Results are logged to WandB; run `wandb login` before training. Use `logging.mode=offline` for debugging.391- **Checkpoints use dill**: All checkpoints are saved with `pickle_module=dill`. Use `torch.load(path, pickle_module=dill)` to load.392- **Real robot deployment**: For deployment inference loop code, refer to [iDP3](https://github.com/YanjieZe/Improved-3D-Diffusion-Policy).393- **Custom tasks**: Need to implement: (1) env wrapper in `env/`, (2) env runner in `env_runner/`, (3) dataset class in `dataset/`, (4) task config YAML in `config/task/`. See Adroit implementations as reference.394- **Nested repo layout**: The inner `3D-Diffusion-Policy/` is the package directory. Scripts in `scripts/` do `cd 3D-Diffusion-Policy` internally. Running `train.py` directly requires being in the inner dir or adjusting sys.path.