12 KiB
HUSKY: Humanoid Skateboarding System via Physics-Aware Whole-Body Control
基于 HUSKY 思路的人形滑板全身控制实验代码:mjlab 训练、rsl_rl 与 MuJoCo 评测脚本。本仓库包含个人开发与 Docker 封装。
目录: src/mjlab_husky · rsl_rl/ · dataset/ · test_scene/ · ckpts/
本地安装(Ubuntu 22.04,推荐 uv)
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 栈冲突):
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:
uv pip uninstall nvidia-nccl-cu12
uv pip install --force-reinstall "nvidia-nccl-cu13>=2.29"
训练
cd humanoid_skateboarding
uv run train Mjlab-Skater-Flat-Unitree-G1 --env.scene.num-envs 4096
查看全部参数:
uv run train Mjlab-Skater-Flat-Unitree-G1 --help
回放 play
任务名固定为 Mjlab-Skater-Flat-Unitree-G1(注册在 mjlab_husky.tasks)。
通用
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 转发):
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,互不重复):
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
远程浏览器需 三个 本地转发(把示例端口换成你实际用的):
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里,否则与本地终端直跑表现一致。
完整参数:
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)。可按需手动指定:
export MUJOCO_GL=egl # GPU + 可用 NVIDIA EGL 时(更快)
# 未设置时由 mujoco_gl 默认 osmesa;或显式 CPU 光栅:
export MUJOCO_GL=osmesa # 需系统已装 libosmesa6(见下)
Ubuntu(OSMesa):若仍报 OpenGL / glGetError,请先安装运行时:
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 一致):
ssh -N -L 8080:127.0.0.1:8080 -L 9876:127.0.0.1:9876 user@云主机IP
若使用 ~/.ssh/config 里的 Host 别名(例如 Seoul):
ssh -N -L 8080:127.0.0.1:8080 -L 9876:127.0.0.1:9876 Seoul
指定密钥时:
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 端口,例如:
ssh -N -L 18080:127.0.0.1:18080 -L 19876:127.0.0.1:19876 user@云主机IP
云主机上 play 需一致:
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 端口,并指定(示例):
uv run play ... --viewer rerun --rerun-connect-host <云主机公网IP>
Rerun 常用性能参数(可选)
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。
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 同时边播边录
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:
uv run play Mjlab-Skater-Flat-Unitree-G1 ... --lerobot-record --lerobot-save-on-env-reset
检查数据集是否可读(行数 / episode)
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)。
构建
docker build -t husky-skate:latest .
GPU 运行(需 NVIDIA Container Toolkit)
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 端口,例如:
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(较慢)
docker run -it --rm -p 8080:8080 -p 9876:9876 husky-skate:latest
进入容器 Shell
docker run --gpus all -it --rm --entrypoint /bin/bash husky-skate:latest
容器内训练
docker run --gpus all -it --rm husky-skate:latest \
uv run train Mjlab-Skater-Flat-Unitree-G1 --env.scene.num-envs 4096
(可选)容器内录 LeRobot:需先安装 lerobot,并把目录挂载出来,例如:
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 test_scene/sim.sh your-onnx-path
| Viser | MuJoCo |
|---|---|
![]() |
![]() |
论文引用(原论文)
@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}
}

