342 lines
12 KiB
Markdown
342 lines
12 KiB
Markdown
# HUSKY: Humanoid Skateboarding System via Physics-Aware Whole-Body Control
|
||
|
||
基于 [HUSKY](https://arxiv.org/abs/2602.03205) 思路的人形滑板全身控制实验代码:mjlab 训练、`rsl_rl` 与 MuJoCo 评测脚本。本仓库包含个人开发与 **Docker** 封装。
|
||
|
||
**目录:** [`src/mjlab_husky`](src/mjlab_husky) · [`rsl_rl/`](rsl_rl/) · [`dataset/`](dataset/) · [`test_scene/`](test_scene/) · [`ckpts/`](ckpts/)
|
||
|
||
---
|
||
|
||
## 本地安装(Ubuntu 22.04,推荐 `uv`)
|
||
|
||
```bash
|
||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||
git clone https://github.com/<你的用户名>/humanoid_skateboarding.git
|
||
cd humanoid_skateboarding
|
||
uv sync && uv pip install -e .
|
||
```
|
||
|
||
**(可选)LeRobot v3 导出 / 边播边录** 需要额外安装 `lerobot`(不在默认依赖里,以免与现有 PyTorch/CUDA 栈冲突):
|
||
|
||
```bash
|
||
uv pip install lerobot
|
||
```
|
||
|
||
若安装 `lerobot` 等包后出现 `import torch` 报 **NCCL 符号错误**(例如 `undefined symbol: ncclDevCommDestroy`):多为 **`nvidia-nccl-cu12` 与 `torch`(cu13)并存**,二者都往 `site-packages/nvidia/nccl/lib/` 装 `libnccl.so.2`,旧库覆盖了新库。可卸载 cu12 并重装 cu13 的 NCCL:
|
||
|
||
```bash
|
||
uv pip uninstall nvidia-nccl-cu12
|
||
uv pip install --force-reinstall "nvidia-nccl-cu13>=2.29"
|
||
```
|
||
|
||
---
|
||
|
||
## 训练
|
||
|
||
```bash
|
||
cd humanoid_skateboarding
|
||
uv run train Mjlab-Skater-Flat-Unitree-G1 --env.scene.num-envs 4096
|
||
```
|
||
|
||
查看全部参数:
|
||
|
||
```bash
|
||
uv run train Mjlab-Skater-Flat-Unitree-G1 --help
|
||
```
|
||
|
||
---
|
||
|
||
## 回放 `play`
|
||
|
||
任务名固定为 **`Mjlab-Skater-Flat-Unitree-G1`**(注册在 `mjlab_husky.tasks`)。
|
||
|
||
### 通用
|
||
|
||
```bash
|
||
uv run play Mjlab-Skater-Flat-Unitree-G1 --checkpoint_file ckpts/test.pt
|
||
```
|
||
|
||
- **`--viewer auto`**(默认):有 `DISPLAY` / `WAYLAND_DISPLAY` 时用 **native**,否则 **rerun**。
|
||
- **`--viewer native`**:本机有图形界面时使用 MuJoCo 原生窗口。
|
||
- **`--viewer rerun`**:Rerun Web Viewer(无头服务器常用)。
|
||
- **`--viewer rerun_native`**:同一套 mjlab 仿真与策略步进,**同时**打开仓库自带的 **MuJoCo 原生 viewer**(`NativeMujocoViewer` / GLFW)并把离屏相机 / qpos **推到 Rerun**。与「两个进程各跑一套仿真」无关:仍是 **单一 `env`/单一仿真循环**。**不能**与 `--lerobot-record` 共用。
|
||
- **`--viewer viser`**:Viser(浏览器三维面板,另一种自带前端)。
|
||
- **`--viewer rerun_viser`**:**同一进程、单一仿真**,同时在浏览器里打开 **Rerun**(`--rerun-web-port` / `--rerun-grpc-port`)与 **Viser mjlab 面板**(`--viser-port`)。三者各占不同端口;适合 RoboHub 左 Rerun、右 Mujoco 双 iframe。
|
||
- **注意**:`rerun_native` 里的 **native 是 GLFW 桌面窗口,不占用 HTTP 端口**;若你要「两个端口都是网页服务」,用 **`rerun_viser`**,不要用 `rerun_native` 来凑端口。
|
||
|
||
`rerun_native` 示例(端口与 `rerun` 相同,见下节 SSH 转发):
|
||
|
||
```bash
|
||
uv run play Mjlab-Skater-Flat-Unitree-G1 --checkpoint_file ckpts/test.pt \
|
||
--viewer rerun_native --rerun-web-port 18080 --rerun-grpc-port 19876
|
||
```
|
||
|
||
`rerun_viser` 示例(**三个端口**:Rerun Web、Rerun gRPC、Viser,互不重复):
|
||
|
||
```bash
|
||
uv run play Mjlab-Skater-Flat-Unitree-G1 --checkpoint_file ckpts/test.pt \
|
||
--viewer rerun_viser \
|
||
--rerun-web-port 18080 \
|
||
--rerun-grpc-port 19876 \
|
||
--viser-port 19090
|
||
```
|
||
|
||
远程浏览器需 **三个** 本地转发(把示例端口换成你实际用的):
|
||
|
||
```bash
|
||
ssh -N \
|
||
-L 18080:127.0.0.1:18080 \
|
||
-L 19876:127.0.0.1:19876 \
|
||
-L 19090:127.0.0.1:19090 \
|
||
user@云主机
|
||
```
|
||
|
||
**SSH / 无桌面 / RoboHub 技能里 `RuntimeError: … DISPLAY`:**
|
||
`rerun_native` 里的「自带 viewer」是 **本机 X11/Wayland 上的 GLFW 窗口**,不是 Rerun 网页。若 shell 里 **没有** `DISPLAY` 或 `WAYLAND_DISPLAY`(很多容器/编排默认不传),会报错。处理方式:
|
||
|
||
- **只想要浏览器里看 Rerun**(单后端、无 MuJoCo 小窗):用 `--viewer rerun`。
|
||
- **仍要 `rerun_native` 但机器无物理桌面**:可装 `xvfb` 用虚拟显示,例如:
|
||
`xvfb-run -a uv run play Mjlab-Skater-Flat-Unitree-G1 ... --viewer rerun_native`
|
||
(具体以你镜像是否已含 `xvfb` 为准。)
|
||
- **RoboHub 侧**:需在技能/容器环境注入 `DISPLAY` 或把启动命令包在 `xvfb-run` 里,否则与本地终端直跑表现一致。
|
||
|
||
完整参数:
|
||
|
||
```bash
|
||
uv run play Mjlab-Skater-Flat-Unitree-G1 --help
|
||
```
|
||
|
||
### 无头 OpenGL(MuJoCo 离屏相机)
|
||
|
||
在无 `DISPLAY` / `WAYLAND_DISPLAY` 的 Linux 上,`play` 等在 **`import mujoco` 之前** 调用 `mujlab_husky/mujoco_gl.py`:未设置 `MUJOCO_GL` 时默认 **`osmesa`**。仅设 `MUJOCO_GL` 不够:无头时 PyOpenGL 仍可能按 `linux` 选 **GLX**,导致 `glGetError` / `eglQueryString`;因此脚本会同步设置 **`PYOPENGL_PLATFORM=osmesa`**(或在你使用 `MUJOCO_GL=egl` 时为 **`egl`**)。可按需手动指定:
|
||
|
||
```bash
|
||
export MUJOCO_GL=egl # GPU + 可用 NVIDIA EGL 时(更快)
|
||
# 未设置时由 mujoco_gl 默认 osmesa;或显式 CPU 光栅:
|
||
export MUJOCO_GL=osmesa # 需系统已装 libosmesa6(见下)
|
||
```
|
||
|
||
**Ubuntu(OSMesa)**:若仍报 OpenGL / `glGetError`,请先安装运行时:
|
||
|
||
```bash
|
||
sudo apt-get update && sudo apt-get install -y libosmesa6
|
||
```
|
||
|
||
若报错 **`mjENBL_MULTICCD`**:来自 **MuJoCo Python 枚举与 `mujoco-warp` Git 修订不一致**。本项目用 PyPI `mujoco==3.8.x` 时,`uv.lock` 已将 **`mujoco-warp` 固定为上游标签 `v3.8.0`**;若在别处自行 `uv lock --upgrade-package mjlab`,需再次确认锁里两处一致。
|
||
|
||
### Rerun:端口与远程浏览器
|
||
|
||
Rerun 需要 **两个端口**:**Web**(默认 `8080`)+ **gRPC**(默认多为 `9876`,以终端打印为准)。
|
||
|
||
**浏览器与 `play` 在同一台机器**:直接打开终端里 **`http://127.0.0.1:<web_port>/?url=...`** 完整链接(不要只打开无 `?url=` 的首页)。
|
||
|
||
**浏览器在自己电脑、`play` 在云主机**:必须在本机做 **SSH 双端口转发**(把 `user@host` 换成你的登录方式,端口与 `play` 一致):
|
||
|
||
```bash
|
||
ssh -N -L 8080:127.0.0.1:8080 -L 9876:127.0.0.1:9876 user@云主机IP
|
||
```
|
||
|
||
若使用 `~/.ssh/config` 里的 `Host` 别名(例如 `Seoul`):
|
||
|
||
```bash
|
||
ssh -N -L 8080:127.0.0.1:8080 -L 9876:127.0.0.1:9876 Seoul
|
||
```
|
||
|
||
指定密钥时:
|
||
|
||
```bash
|
||
ssh -i ~/.ssh/你的_key -N -L 8080:127.0.0.1:8080 -L 9876:127.0.0.1:9876 ubuntu@云主机IP
|
||
```
|
||
|
||
**本机 8080/9876 已被占用**时,改用空闲本地端口,并同时改 `?url=` 里 gRPC 端口,例如:
|
||
|
||
```bash
|
||
ssh -N -L 18080:127.0.0.1:18080 -L 19876:127.0.0.1:19876 user@云主机IP
|
||
```
|
||
|
||
云主机上 `play` 需一致:
|
||
|
||
```bash
|
||
uv run play Mjlab-Skater-Flat-Unitree-G1 --checkpoint_file ckpts/test.pt \
|
||
--viewer rerun \
|
||
--rerun-web-port 18080 \
|
||
--rerun-grpc-port 19876
|
||
```
|
||
|
||
**不经 SSH、浏览器直连公网**:安全组放行 Web + gRPC 端口,并指定(示例):
|
||
|
||
```bash
|
||
uv run play ... --viewer rerun --rerun-connect-host <云主机公网IP>
|
||
```
|
||
|
||
### Rerun 常用性能参数(可选)
|
||
|
||
```bash
|
||
uv run play Mjlab-Skater-Flat-Unitree-G1 --checkpoint_file ckpts/test.pt \
|
||
--viewer rerun \
|
||
--rerun-viewer-width 640 --rerun-viewer-height 360 \
|
||
--rerun-camera-log-stride 4 --rerun-qpos-log-stride 8 \
|
||
--rerun-camera-max-side 480 \
|
||
--no-rerun-open-browser
|
||
```
|
||
|
||
说明:本项目 CLI 使用 **tyro**,布尔开关一般为 **`--xxx` / `--no-xxx`**(例如 `--lerobot-record`、`--no-rerun-open-browser`),不要写成 `--lerobot-record True`。
|
||
|
||
---
|
||
|
||
## LeRobot v3 数据(`lerobot_data/`)
|
||
|
||
LeRobot **v3** 为 **Parquet + `meta/`**(不是 HDF5)。本仓库提供两种方式写入 **`observation.state`(qpos,float32 向量)**。
|
||
|
||
### 1)离线批量导出(不跑 Rerun)
|
||
|
||
需已 `uv pip install lerobot`。
|
||
|
||
```bash
|
||
uv run python -m mjlab_husky.scripts.export_lerobot_qpos \
|
||
--task-id Mjlab-Skater-Flat-Unitree-G1 \
|
||
--checkpoint-file ckpts/test.pt \
|
||
--out-dir lerobot_data \
|
||
--dataset-name mjlab_husky_skater_qpos \
|
||
--episodes 1 \
|
||
--steps-per-episode 1000 \
|
||
--overwrite
|
||
```
|
||
|
||
### 2)`play` + Rerun 同时边播边录
|
||
|
||
```bash
|
||
uv run play Mjlab-Skater-Flat-Unitree-G1 \
|
||
--checkpoint_file ckpts/test.pt \
|
||
--viewer rerun \
|
||
--rerun-web-port 18080 \
|
||
--rerun-grpc-port 19876 \
|
||
--lerobot-record \
|
||
--lerobot-out-dir lerobot_data \
|
||
--lerobot-dataset-name mjlab_husky_live \
|
||
--lerobot-overwrite
|
||
```
|
||
|
||
要点:
|
||
|
||
- **`--lerobot-overwrite`**:每次启动会 **删除** 同名数据集目录;要 **累积** 多次运行,请 **去掉** 该参数,或换 `--lerobot-dataset-name`。
|
||
- 默认每录满 **`--lerobot-steps-per-episode`**(默认 1000)帧会 `save_episode()` 一次;仿真里多次 `reset` **不会**自动切分,除非打开 **`--lerobot-save-on-env-reset`**。
|
||
- 退出 `play`(如 Ctrl+C)时会 `finalize()`,避免 Parquet 不完整。
|
||
|
||
按仿真每次 `done -> reset` 存成一个 LeRobot episode:
|
||
|
||
```bash
|
||
uv run play Mjlab-Skater-Flat-Unitree-G1 ... --lerobot-record --lerobot-save-on-env-reset
|
||
```
|
||
|
||
### 检查数据集是否可读(行数 / episode)
|
||
|
||
```bash
|
||
uv run python -c "
|
||
from pathlib import Path
|
||
import json
|
||
info = json.loads(Path('lerobot_data/mjlab_husky_live/meta/info.json').read_text())
|
||
print('total_episodes', info.get('total_episodes'), 'total_frames', info.get('total_frames'))
|
||
"
|
||
```
|
||
|
||
---
|
||
|
||
## Docker(推荐)
|
||
|
||
基础环境:Ubuntu 22.04、CUDA 13、`uv` 与项目依赖。镜像 **`MUJOCO_GL=egl`**,默认 **`CMD`** 为 **Rerun** 回放(`--no-rerun-open-browser`)。
|
||
|
||
**构建**
|
||
|
||
```bash
|
||
docker build -t husky-skate:latest .
|
||
```
|
||
|
||
**GPU 运行**(需 [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html))
|
||
|
||
```bash
|
||
docker run --gpus all -it --rm -p 8080:8080 -p 9876:9876 husky-skate:latest
|
||
```
|
||
|
||
在**宿主机浏览器**打开容器日志里打印的 **`http://127.0.0.1:8080/?url=...`**(若浏览器不在宿主机,需自行把对应端口转发到本机)。
|
||
|
||
**宿主机 8080/9876 已被占用**(例如已有其他容器映射):换主机端口 + 覆盖容器内 `play` 端口,例如:
|
||
|
||
```bash
|
||
docker run --gpus all -it --rm -p 18080:18080 -p 19876:19876 husky-skate:latest \
|
||
uv run play Mjlab-Skater-Flat-Unitree-G1 --checkpoint_file ckpts/test.pt \
|
||
--viewer rerun \
|
||
--rerun-web-port 18080 \
|
||
--rerun-grpc-port 19876 \
|
||
--no-rerun-open-browser
|
||
```
|
||
|
||
**仅 CPU**(较慢)
|
||
|
||
```bash
|
||
docker run -it --rm -p 8080:8080 -p 9876:9876 husky-skate:latest
|
||
```
|
||
|
||
**进入容器 Shell**
|
||
|
||
```bash
|
||
docker run --gpus all -it --rm --entrypoint /bin/bash husky-skate:latest
|
||
```
|
||
|
||
**容器内训练**
|
||
|
||
```bash
|
||
docker run --gpus all -it --rm husky-skate:latest \
|
||
uv run train Mjlab-Skater-Flat-Unitree-G1 --env.scene.num-envs 4096
|
||
```
|
||
|
||
**(可选)容器内录 LeRobot**:需先安装 `lerobot`,并把目录挂载出来,例如:
|
||
|
||
```bash
|
||
docker run --gpus all -it --rm \
|
||
-p 18080:18080 -p 19876:19876 \
|
||
-v "$(pwd)/lerobot_data:/app/lerobot_data" \
|
||
husky-skate:latest \
|
||
bash -lc 'uv pip install lerobot && uv run play Mjlab-Skater-Flat-Unitree-G1 \
|
||
--checkpoint_file ckpts/test.pt --viewer rerun \
|
||
--rerun-web-port 18080 --rerun-grpc-port 19876 --no-rerun-open-browser \
|
||
--lerobot-record --lerobot-out-dir lerobot_data --lerobot-dataset-name mjlab_docker_live \
|
||
--lerobot-overwrite'
|
||
```
|
||
|
||
---
|
||
|
||
## PyTorch / CUDA 提示
|
||
|
||
若日志出现 **driver too old(如 12080)** 且 `torch.cuda.is_available()` 为 `False`,多为 **PyTorch cu13x 与当前驱动 API 不匹配**。可选:
|
||
|
||
- 安装与驱动匹配的 **cu12x** 轮子,例如:
|
||
`uv pip install torch torchvision --index-url https://download.pytorch.org/whl/cu124`
|
||
- 或升级宿主机 NVIDIA 驱动以匹配当前 PyTorch 所要求的 CUDA。
|
||
|
||
仿真侧 **Warp/MuJoCo** 仍可能显示 `cpu`,与 **`torch.cuda.is_available()` 为 `play` 选的 device** 一致。
|
||
|
||
---
|
||
|
||
## 轻量 MuJoCo 评测
|
||
|
||
```bash
|
||
bash test_scene/sim.sh your-onnx-path
|
||
```
|
||
|
||
| Viser | MuJoCo |
|
||
|-------|--------|
|
||
|  |  |
|
||
|
||
---
|
||
|
||
## 论文引用(原论文)
|
||
|
||
```bibtex
|
||
@article{han2026husky,
|
||
title={HUSKY: Humanoid Skateboarding System via Physics-Aware Whole-Body Control},
|
||
author={Jinrui Han and Dewei Wang and Chenyun Zhang and Xinzhe Liu and Ping Luo and Chenjia Bai and Xuelong Li},
|
||
journal={arXiv preprint arXiv:2602.03205},
|
||
year={2026}
|
||
}
|
||
```
|