Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions conf/ppo/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ training:
cam_tracking: false
cam_tracking_env_idx: 0
cam_tracking_extra_envs: 2
# MuJoCo geom groups to hide during offline play/video (0..5); e.g. OpenArm cell shell uses group 1.
play_hide_geom_groups: []
# MP4 playback fps; null = match physics (1/ctrl_dt). Lower = slower on-screen motion.
play_video_fps: null
# If true, play/export uses stochastic policy samples (more motion when the mean is near zero).
play_stochastic: false
log_root: null
num_timesteps: null
log_dir: null
Expand Down
63 changes: 63 additions & 0 deletions conf/ppo/task/openarm_demo_pick/mujoco.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# @package _global_
# Base OpenArm demo pick task (MuJoCo). The cube pick/place bring-up on the vendored
# ``demo.xml`` cell. Variants under this folder (e.g. mujoco_lift3d) override the scene,
# reward shaping and goal for the 3D lift task.
training:
task_name: OpenArmDemoPick
sim_backend: mujoco
no_play: true
render_spacing: 2.0
cam_distance: 2.75
cam_elevation: -30.0
cam_azimuth: 222.0
cam_lookat: [0.34, -0.02, 1.05]
# Hide only the cell enclosure mesh (group 3) so the work surface (sheet) and the
# lifter support block stay visible; the closed cabinet would otherwise occlude the arm.
play_hide_geom_groups: [3]
# 5 fps over 50 Hz physics = 10x slow motion, easier to inspect the grasp.
play_video_fps: 5
play_steps: 200
algo:
num_envs: 4096
num_steps_per_env: 24
max_iterations: 1500
save_interval: 500
empirical_normalization: true
obs_groups:
actor: [policy]
critic: [policy]
policy:
init_noise_std: 1.0
actor_hidden_dims: [512, 256, 128]
critic_hidden_dims: [512, 256, 128]
activation: elu
class_name: ActorCritic
algorithm:
value_loss_coef: 1.0
clip_param: 0.2
entropy_coef: 0.01
num_learning_epochs: 4
num_mini_batches: 8
learning_rate: 0.0003
schedule: adaptive
gamma: 0.99
lam: 0.95
desired_kl: 0.015
max_grad_norm: 0.5
env:
action_scale: 0.04
fix_lifter: true
left_arm_only: true
rigid_freeze_right_arm: true
binary_gripper: false
playback_hide_geom_groups: [3]
playback_video_fps: 5
reward:
scales:
reach: 1.0
place: 0.8
lift: 0.35
grasp: 0.22
success: 25.0
action: -0.01
drop: -1.0
45 changes: 45 additions & 0 deletions conf/ppo/task/openarm_demo_pick/mujoco_lift3d.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# @package _global_
# Lift line (hard, in-air goal). Builds on the base demo pick task with:
# * graspability-tuned scene (demo_lift3d.xml: 3cm cube, higher friction, no black_frame),
# * fingertip TCP reach (reach_use_tcp + reach_tcp_offset, the grasp-alignment fix),
# * 3D lift goal (goal_success_mode=lift3d) ~0.12 m above the spawn band,
# * binary gripper, and proximity-gated dense lift (lift_gate_mode=proximity)
# to escape the "jaws-on-cube, never lift" local optimum.
defaults:
- /task/openarm_demo_pick/mujoco
- _self_
env:
action_scale: 0.055
binary_gripper: true
scene:
model_file: src/unilab/assets/robots/openarm_mujoco_v2/demo_lift3d.xml
# In-air goal above the (narrowed) spawn band center: lift the cube ~0.12 m straight up.
goal_pos: [0.46, 0.0, 1.13]
cube_xy_range: [0.44, 0.48, -0.04, 0.04]
reward:
scales:
reach: 1.0
place: 0.0
lift: 12.0
grasp: 0.6
success: 25.0
action: -0.01
drop: -1.0
lift_bonus: 10.0
goal_track_coarse: 16.0
goal_track_fine: 5.0
fall_z: 0.86
table_z: 1.008
lift_shaping_span: 0.12
grasp_proximity_decay: 6.0
place_use_grasp_gate: true
# Proximity-only soft gate on dense lift: robust to closure wobble, monotonic rest->goal gradient.
lift_gate_mode: proximity
lift_use_grasp_gate: true
reach_use_tcp: true
reach_tcp_offset: [-0.0025, 0.0, -0.0669]
goal_success_mode: lift3d
success_dist: 0.05
lift_margin: 0.05
goal_track_coarse_std: 0.1
goal_track_fine_std: 0.03
48 changes: 48 additions & 0 deletions conf/ppo/task/openarm_demo_pick/mujoco_lift3d_contgrip.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# @package _global_
# Continuous-gripper lift3d pick: policy-controlled (action_scale-limited) finger
# closing instead of the binary snap-close, with staged + firm grasp shaping.
#
# Same task as mujoco_lift3d (3D in-air goal) but the gripper is closed
# continuously by the policy, which is more physical but harder to learn. The
# reward shaping below is what makes a deliberate, goal-accurate, sustained pick
# emerge:
# * terminate_on_success=false: keep the episode alive after success so the
# continuous gripper is actually paid to *hold* the cube at the goal
# (hold_success / goal_track accrue per step); otherwise the cube slips right
# after the success latches.
# * grasp 0.4, drop -4.0, hold_success 8.0, goal_track_coarse 24.0: reward
# sustained closure x proximity and holding *at the goal*, punish drops, and
# pull hard toward the in-air goal to escape the "grab and hold low" optimum.
# * approach 1.0 / premature_close -0.5 / action_rate -0.02: staged-grasp
# terms -- a gentle nudge to go above the cube with the gripper open before
# closing, a mild penalty for closing far (xy) from the cube, and a
# smoothness penalty -- so the pick is segmented (above-open -> close ->
# lift) rather than a single swipe.
# * firm_grasp 8.0: closure x lift_gate x proximity, paid only once the cube is
# lifted and the TCP is on it, encouraging a real grip while carrying.
#
# Result (PPO, 5 seeds x 512 envs = 2560 episodes): ever-success 100%,
# final-success 92.3% (90.8-93.4), drop rate 0%, mean goal dist ~0.02 m. The
# learned grasp is a stable open fingertip-cradle (closure ~0); for this
# arm/gripper/high-friction-cube geometry the open cradle is the robust optimum
# and forcing closure hurts the primary objective.
defaults:
- /task/openarm_demo_pick/mujoco_lift3d
- _self_
env:
binary_gripper: false
reward:
terminate_on_success: false
scales:
grasp: 0.4
drop: -4.0
hold_success: 8.0
goal_track_coarse: 24.0
approach: 1.0
premature_close: -0.5
action_rate: -0.02
firm_grasp: 8.0
pregrasp_h: 0.10
approach_xy_decay: 8.0
approach_z_decay: 8.0
premature_close_decay: 8.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# @package _global_
# Low-entropy continuous-gripper variant: identical task/reward to
# mujoco_lift3d_contgrip, but lowers the PPO entropy bonus from 0.01 to 0.003.
#
# Why: with entropy_coef=0.01 the task is solved by iter ~600, after which the
# mean-action gradient flattens while the entropy bonus keeps paying for larger
# pre-squash action std. Because actions are tanh/action_scale-saturated, a huge
# std barely changes the executed action, so PPO inflates Policy/mean_std and
# Loss/entropy unboundedly (std -> ~39, entropy -> ~40 by iter 1500). This is a
# cosmetic exploration-curve artifact, not a control problem -- deterministic
# eval is unaffected.
#
# Dropping entropy_coef to 0.003 removes that free-entropy incentive and keeps
# the stochastic curves flat without hurting the policy.
#
# Control run (PPO, seed=1, 512-env eval, 1500 iter, same machine as the
# baseline contgrip run):
# * Policy/mean_std: final 1.35 (vs baseline 39.08); Loss/entropy: ~12 flat
# (vs baseline ~40 monotonic climb).
# * ever-success 100.0% (vs 98.8%), final-success 87.9% (vs 86.3%), drop 0%.
# * mean reward final 2800 / max 2971 (vs 2580 / 2903) -- slightly slower to
# reach the plateau (~150 iter later) but a flatter, less noisy plateau.
defaults:
- /task/openarm_demo_pick/mujoco_lift3d_contgrip
- _self_
algo:
algorithm:
entropy_coef: 0.003
11 changes: 11 additions & 0 deletions conf/ppo/task/openarm_demo_pick/mujoco_lift3d_easy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# @package _global_
# Lift line, easy: same proximity-gated lift fix as mujoco_lift3d but with a lower /
# wider success criterion (goal z 1.13 -> 1.09, success_dist 0.05 -> 0.06) to make the
# first non-zero successes easier to reach (curriculum / debugging aid).
defaults:
- /task/openarm_demo_pick/mujoco_lift3d
- _self_
env:
goal_pos: [0.46, 0.0, 1.09]
reward:
success_dist: 0.06
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 12 additions & 2 deletions docs/sphinx/source/en/2-user_guide/4-tasks/3-manipulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,15 @@ uv run train --algo ppo --task stewart_balance --sim motrix training.no_play=tru
uv run train --algo ppo --task go2_arm_manip_loco --sim mujoco training.no_play=true
```

See {doc}`../8-manipulation/1-dexterous_inhand` and
{doc}`../8-manipulation/2-manip_loco` for task-specific notes.
## Single-Arm Pick

`openarm_demo_pick` is the committed OpenArm single-arm pick owner path (MuJoCo
only); select the lift3d / lift3d_contgrip variants with `--profile`:

```bash
uv run train --algo ppo --task openarm_demo_pick --sim mujoco --profile lift3d_contgrip training.no_play=true
```

See {doc}`../8-manipulation/1-dexterous_inhand`,
{doc}`../8-manipulation/2-manip_loco`, and
{doc}`../8-manipulation/3-openarm_pick` for task-specific notes.
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,18 @@ Allegro and Sharpa owner YAMLs, grasp caches, and train commands.
Go2 plus Airbot locomotion/manipulation owner paths.
:::

:::{grid-item-card} OpenArm Pick
:link: 3-openarm_pick
:link-type: doc
Training, evaluation, and video recording for the OpenArm single-arm pick.
:::

::::

```{toctree}
:hidden:

1-dexterous_inhand
2-manip_loco
3-openarm_pick
```
137 changes: 137 additions & 0 deletions docs/sphinx/source/en/2-user_guide/8-manipulation/3-openarm_pick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# OpenArm Pick

This page covers the checked-in OpenArm single-arm pick task: how to train, how
to evaluate, and how to record playback video. Select backends with `--task` and
`--sim`; do not override `training.sim_backend` alone. The owner YAMLs remain the
internal evidence for which combinations are configured.

The task uses the registered env `OpenArmDemoPick`
(`@registry.env("OpenArmDemoPick", sim_backend="mujoco")`, see
`src/unilab/envs/manipulation/openarm/pick_place_demo.py`). Only the MuJoCo owner
path is checked in; there is no Motrix owner. The left arm is the only movable
arm (`left_arm_only`, `rigid_freeze_right_arm`, `fix_lifter`); the policy controls
the 7 left-arm joints plus the gripper.

## Owner configs

The config group is `openarm_demo_pick`; all owner YAMLs live under
`conf/ppo/task/openarm_demo_pick/`:

| File | `--profile` | Notes |
| --- | --- | --- |
| `mujoco.yaml` | (none) | Base pick task inherited by the variants; not meant to train on its own |
| `mujoco_lift3d.yaml` | `lift3d` | Main task: 3 cm cube, in-air 3D goal, binary gripper, proximity-gated dense lift |
| `mujoco_lift3d_easy.yaml` | `lift3d_easy` | Curriculum/debug aid: lower, wider goal (`goal z 1.13→1.09`, `success_dist 0.05→0.06`) |
| `mujoco_lift3d_contgrip.yaml` | `lift3d_contgrip` | Continuous-gripper variant: policy closes the gripper continuously, with staged + firm grasp shaping |
| `mujoco_lift3d_contgrip_lowent.yaml` | `lift3d_contgrip_lowent` | Same task/reward as contgrip, only lowers `entropy_coef` 0.01→0.003: removes the late-training `action std`/`entropy` drift with equal/slightly-better success (see the control run notes in the file) |

The top-level CLI selects a variant with `--profile`: `--task openarm_demo_pick
--sim mujoco --profile lift3d_contgrip` composes the owner
`openarm_demo_pick/mujoco_lift3d_contgrip`.

## Training

Train the continuous-gripper main variant (no rendering):

```bash
uv run train --algo ppo --task openarm_demo_pick --sim mujoco \
--profile lift3d_contgrip training.no_play=true
```

The binary-gripper lift3d main task and the easier curriculum variant:

```bash
uv run train --algo ppo --task openarm_demo_pick --sim mujoco \
--profile lift3d training.no_play=true
uv run train --algo ppo --task openarm_demo_pick --sim mujoco \
--profile lift3d_easy training.no_play=true
```

The owner defaults to `max_iterations: 1500` and `num_envs: 4096`. Runs land in
`logs/rsl_rl_ppo/OpenArmDemoPick/<timestamp>_mujoco/`.

## Recording playback video

The `eval` route enters playback with `play_only=true`; on a headless machine the
default `play_render_mode` resolves to recording, writing `play_video.mp4` into
the run directory. `--load-run -1` picks the latest run:

```bash
uv run eval --algo ppo --task openarm_demo_pick --sim mujoco \
--profile lift3d_contgrip --load-run -1
```

The owner YAML already sets playback parameters for this task: `play_video_fps: 5`
(~10x slow motion over 50 Hz physics, easier to inspect the grasp),
`play_hide_geom_groups: [3]` (hide the cell enclosure mesh so it does not occlude
the arm), `play_steps: 200`, and the camera pose. Override on the command line
with `training.play_steps=300` etc.

## Evaluation metrics

The top-level `eval` only plays back the single on-camera env in the video. For
objective pick metrics across many parallel envs, use
`scripts/eval_openarm_success.py`: it loads a checkpoint, rolls the deterministic
policy out over many envs (no rendering), and reports ever-success, final hold
success, drop rate, final cube height, grasp firmness, and staged-grasp
adherence. `eval_envs` is not in the schema, so append it with `+`:

```bash
HIP_VISIBLE_DEVICES=0 uv run scripts/eval_openarm_success.py \
task=openarm_demo_pick/mujoco_lift3d_contgrip \
algo.load_run=logs/rsl_rl_ppo/OpenArmDemoPick/<run>_mujoco \
+training.eval_envs=512 training.play_steps=200
```

## Helper scripts

- `scripts/openarm_scripted_pick.py`: a non-RL scripted staged pick (APPROACH →
DESCEND → CLOSE → LIFT) using a damped least-squares IK delta on the left-arm
Jacobian (`openarm_left_tcp` site). It runs through the same
`run_playback_mode` pipeline as the policy and needs no checkpoint, producing a
clean reference demo video.

```bash
MUJOCO_GL=egl HIP_VISIBLE_DEVICES=0 uv run scripts/openarm_scripted_pick.py \
task=openarm_demo_pick/mujoco_lift3d_contgrip training.play_steps=260
```

- `scripts/verify_openarm_play_motion.py`: checks that the left-arm joints
actually move in a physics rollout (env 0), a regression guard against the
"policy never moves the arm" degeneration.

```bash
HIP_VISIBLE_DEVICES=0 uv run scripts/verify_openarm_play_motion.py \
--run-dir logs/rsl_rl_ppo/OpenArmDemoPick/<run>_mujoco --steps 120
```

## Training results and curve comparison

`lift3d_contgrip_lowent` (`entropy_coef=0.003`) vs the baseline
`lift3d_contgrip` (`entropy_coef=0.01`) (PPO, seed=1, 4096 env x 24 steps x 1500
iter ~= 147.5M steps):

| Metric | baseline 0.01 | lowent 0.003 |
| --- | --- | --- |
| ever success (512-env deterministic eval) | 98.8% | 100.0% |
| final success | 86.3% | 87.9% |
| drop rate | 0% | 0% |
| final reward | 2580 | 2800 |
| final `action std` | 39.08 | 1.35 |
| final `entropy loss` | ~40 (monotonic climb) | ~12 (flat) |

Lowering `entropy_coef` from 0.01 to 0.003 removes the `action std` / `entropy`
drift that PPO accrues after the task is solved (iter ~600): tanh saturation lets
the policy inflate the pre-squash std for "free" entropy bonus without changing
the executed action. The result is a cleaner training curve with equal/slightly
better deterministic success (eval uses the policy mean, so it is unaffected by
exploration noise). The only cost is reaching the plateau ~150 iter later.

![entropy_coef 0.01 vs 0.003 training curves](../../../_static/images/openarm_pick_lowent_curves.png)

The learned grasp is a stable open fingertip-cradle (closure ~0); for this
high-friction small-cube geometry the open cradle is the more robust optimum:

![gripper cradling the cube, left-arm right-front close-up](../../../_static/images/openarm_pick_grasp_closeup.png)

For the category-level task page, see {doc}`../4-tasks/3-manipulation`.
Loading
Loading