UFO:RoboParty 的开源无监督强化学习框架 —— 从 FB 到 TeCH 的人形机器人全身控制深度拆解
一句话认识它:UFO(Unsupervised RL Framework for Humanoid COntrol) 是 RoboParty Lab 开源的一套"以无监督强化学习为核心"的人形机器人运动控制框架。
main分支覆盖 MJLab 训练、机器人感知的动作数据导入、跟踪/目标/奖励推理与 ONNX 导出;deploy分支提供 G1 真机部署与 PICO 遥操作运行时。当前完成度最高、测试最充分的路径是宇树 Unitree G1(29-DoF)。
1. 项目介绍
| 项目 | 信息 |
|---|---|
| 仓库 | github.com/Roboparty/UFO |
| 全称 | UFO: A General Unsupervised Reinforcement Learning Framework for Humanoid COntrol |
| 团队 | RoboParty Lab(项目页 roboparty.github.io/UFO/) |
| 语言 / 规模 | Python 为主,仓库约 280 MB(含 G1 的 MJCF/STL 网格),176 个文件 |
| 热度 | 262 Stars / 28 Forks / 6 open issues(截至 2026-09) |
| 许可证 | CC BY-NC 4.0(非商用研究许可) |
| 创建时间 | 2026-07-13,仓库持续高频迭代 |
| 主要分支 | main = 训练/数据导入/推理/导出;deploy = G1 真机与遥操作运行时 |
| Topics | humanoid-control、mjlab、teleoperation、unsupervised-learning |
UFO 的关键特征可以概括为一套基础设施装下两种算法 + 一套部署运行时:
- UFO-FB:BFM-Zero 主算法在新框架里的重新实现与调优(Forward-Backward 表示学习);
- TeCH:新增的"时间距离对比表征"无监督 RL 算法(早期版本叫 TLDR,
--agent tldr是保留的弃用别名); - 统一支持多源动作数据混合(manifest + 权重)、机器人配置化(非 G1 实验性)、跟踪/目标/奖励三种零样本推理、ONNX 导出 + backward encoder,最终接上真机遥操作。
需要特别强调的是,UFO 的"通用"落在框架与数据接口层面,而非策略层面——它不会自动把人运动或别的机器人运动重定向到新机器人,一个 checkpoint 也不能跨不同形态/动作/观测维度的机器人直接复用。想接新机器人,你需要自己备好:一个 MuJoCo XML、可选的 URDF、以及已经重定向到该机器人的 RobotState 格式动作数据。
2. 为什么需要 UFO:两种人形控制范式的对撞
先厘清一个容易被名字误导的点:UFO 并不是"不需要任何数据的纯无监督"。它的"无监督"是相对"逐任务手写奖励 / 逐任务跟踪"而言的:
- 模仿学习 / Tracking-based(当前主流):给机器人一堆"重定向后的人类动作",训练策略去跟踪参考轨迹。效果直接,但策略被绑死在"跟参考"这件事上,抗干扰、组合与涌现能力弱。
- 无监督 RL(BFM-Zero 开辟的路线):不写"向前走 +1 分、站起来 +1 分"这类任务奖励,而是让机器人在环境里自己折腾,学一个可复用的行为潜空间(z)。之后"走到某处、摆某个姿势、跟某段动作"都变成:把目标翻译成一个 z,再用 z 条件策略输出动作——零样本。
BFM-Zero 已经证明了这条路能跑通:无监督 RL 可以预训练出一套行为基础模型。但它留下的问题非常现实:
- 训练周期长(45 小时才到稳定部署平台期);
- 基础设施围绕单一算法(FB)搭建,换 Representation 等于换代码;
- 换机器人 / 换数据源要大量工程改造;
- 真机验证不足。
RoboParty Lab 的 UFO 正是从这四个痛处下手:用 MJLab + 同步多卡训练重写 FB 管线(把训练时间从 45h 压到 6–8h);把 TeCH(时间距离表征) 接进同一套基础设施;用机器人配置 + RobotState 标准数据接口 + 统一推理流程适配多种本体;最后连到全身遥操作与真机控制(PICO VR 头显 + 追踪器)。
3. 核心原理拆解
3.1 UFO-FB:Forward-Backward 表示学习 + TD3
FB 是这套框架的"地基"算法,思想源头是 Successor Features(后继特征)与 bisimulation 度量学习。框架里学两个映射:
- 前向映射
F(s, z, a):从当前状态出发,在"意图 z"与动作 a 的驱动下,未来状态的某种后继度量。它是 z 的线性函数——这保证了后面可以任意推断线性奖励。 - 后向映射
B(g)(也叫 goal encoder / backward map):把"目标/意图 g"压成一个 z(代码里 z_dim = 256 并做 norm_z 归一化)。
核心回归损失(对应源码 fb/agent.py 的 update_fb):
Fs = F(obs, z, action) # num_parallel × batch × z_dim
B = B(goal) # batch × z_dim
Ms = Fs @ Bᵀ # num_parallel × batch × batch 相似度矩阵
# 对角项: 当前状态要"到达"自己的目标 → 拉大
# 非对角项: 与其它样本的目标要做区分 → 用 TD 误差压制
diff = Ms - γ · target_Ms
fb_offdiag = ½ · Σ (diff · off_diag)² / off_diag_sum
fb_diag = -Σ diag(diff) / batch
fb_loss = fb_offdiag + fb_diag
即:对角位置的相似度要被拉高(状态应匹配它自己的后继目标),非对角位置要用折扣后的 target 做 TD 回归。为了让 B 编码的空间"度量良好",还加了一个正交化损失(ortho_coef),鼓励 Gram 矩阵 Cov = B @ Bᵀ 逼近单位阵——对角为 1、非对角为 0,让不同意图之间尽量"正交可区分":
orth_loss_diag = -Cov.diag().mean()
orth_loss_offdiag = ½·Σ(Cov · off_diag)² / off_diag_sum
fb_loss += ortho_coef · orth_loss
隐式奖励推断(BFM-Zero 式零样本奖励):因为 F 对 z 线性,给定一个新任务奖励 r(在潜空间里),可对 B 求最小二乘恢复隐式奖励权重:
β := (BᵀB)⁻¹ · Bᵀ · z # 最小二乘解出"奖励方向"
implicit_reward = β · z # 即 (B_inv_cov * z).sum(-1)
target_Q = implicit_reward + γ · next_Q
Q_loss = MSE( Q = (F·z).sum(-1), target_Q )
在代码里这正是 q_loss_coef > 0 时开启的路径:用 torch.linalg.solve(cov, B, left=False) 解 BᵀB·x = Bᵀz,从而在不重训的情况下给一个 z 附上可学习的 Q 语义。
悲观集成(uncertainty penalty):网络不是单个 F,而是 num_parallel=2 个并行头(代码 ensemble_mode="batch"),更新时先求集成均值与两两差异:
preds_mean = mean over heads
preds_unc = mean pairwise |head_i - head_j| # 头间分歧 ≈ 不确定性
target = preds_mean - pessimism_penalty · preds_unc
Actor 更新用 TD3 风格:采样动作 → Q = (F·z).sum(-1) → 悲观化 → 最大化 Q(actor_loss = -Q.mean()),并对采样动作做 stddev_clip=0.3 的截断,防止过激动作。
z 从哪来(训练期):sample_mixed_z 混合两条通道:
- 从先验/历史 z buffer 采样;
- 以
train_goal_ratio(FB 默认 0.2,即 20%)的概率,把 batch 里另一条轨迹的下一状态用B编码成 goal z——相当于"拿别人的未来当我的目标",制造 goal-conditioned 训练信号。
z 在 rollout 里怎么调度:maybe_update_rollout_context 每 update_z_every_step(FB 默认 100、TeCH 默认 10)步重新从 z buffer/先验采一个 z 并固定,对应论文里的"gym scheduling horizon τ";同时可按 rollout_expert_trajectories_percentage(默认 50%)让一部分环境切到专家轨迹追踪模式——把参考轨迹未来 seq_length 窗口的 B 均值当作 tracking z,让策略持续被"像人"的意图牵着走,而不是完全放飞。
3.2 TeCH:用"时间距离"组织行为空间(原 TLDR)
TeCH 是 UFO 的真正增量,思想源自 TLDR(Temporal Latent Distance Representation) 的时间距离表征,再针对人形全身控制做了三件事:加入人体动作先验、Sim2Real 辅助约束、真机部署链路。
FB 学的是"后继特征 + 可组合价值结构",TeCH 换了一种组织方式:
- 伪目标(temporal rolling):对当前状态 s_t 与下一状态 s_{t+1},从后续轨迹中抽一个未来状态作为伪目标 g;
- **共享编码器(backward map / goal encoder)**把三者都映射到潜空间 z;
- 对比式约束:时间相隔远的状态在潜空间里拉开、相邻状态保持连续,使潜空间里的距离 ≈ 时间可达性(从当前状态经过多少步能到达目标)。
于是策略的进度奖励写成(项目页/技术报告原式):
r(s_t, s_{t+1}; z) = ‖z − z̄(s_t)‖₂ − ‖z − z̄(s_{t+1})‖₂
直觉:z 是"想要到达的目标"。下一状态离目标潜变量更近 → 第二项更小 → 奖励为正;走远了奖励为负。注意是两个 L2 距离之差,单位是"潜空间步数"。
潜空间由三类数据混合构造:
- 随机先验 → 扩展探索范围(没见过的状态);
- 在线轨迹 → 提供当前可达目标(自己真走过的路);
- 人体动作轨迹(expert) → 给潜空间"人类风格"的稳定锚点(从 expert buffer 切片,
expert_asm_ratio=0.6表示 60% 的 batch 是专家片段)。
Actor 同时吃三个 critic 的信号(TeCH 源码 update_actor):
actor_loss = (
-q.mean() # ① 主 critic:TeCH 进度奖励 Q
- disc_actor_weight * disc_reward.mean() # ② 人体动作判别器(风格奖励)
- aux_actor_weight * q_aux.mean() # ③ aux critic:Sim2Real 辅助奖励 Q
)
- ① 主 Q:对"进度奖励 r"做 TD 学习;
- ② 判别器(DiscriminatorArchi,1024×3 层):把专家片段与在线片段各自编码成
(obs, z)后做正负分类。代码里用的是负 log-sigmoid + softplus 的 WGAN 式损失,并带梯度惩罚 GP=10;其输出被当作"动作像不像人"的奖励喂给 actor(系数disc_reward_coef=0.5)。这正是 TeCH "无监督但有人类先验"的体现——它不跟踪参考轨迹,但奖励风格上往人靠; - ③ aux critic(Sim2Real 辅助):把关节限制、非期望接触、脚朝向、打滑等硬约束惩罚(
aux_rewards列表,见下表)单独学一个 Q 网络,惩罚项不进主进度奖励,避免污染"距离"语义,同时保证学出来的动作可部署(不超关节限位、不磨脚踝)。
TeCH 的 Sim2Real 辅助惩罚项(FB/TeCH 共用):
| 惩罚项 | TeCH 权重 | FB 权重 | 含义 |
|---|---|---|---|
limits_dof_pos | -10.0 | -10.0 | 关节位置超限 |
penalty_ankle_roll | -4.0 | -4.0 | 踝关节内/外翻(防崴脚) |
penalty_slippage | -2.0 | -2.0 | 脚底打滑 |
penalty_undesired_contact | -1.0 | -1.0 | 躯干/肩/髋等非期望接触 |
penalty_feet_ori | -0.4 | -0.4 | 脚姿态偏离 |
penalty_action_rate | -0.1 | -0.1 | 动作变化率过大(抖动) |
penalty_torques / limits_torque | 0.0 | 0.0 | 预留(默认关闭) |
FB 还带一个
cartwheel-aux-safe模式(仅 FB 支持):做侧手翻这类高动态技能时把脚/接触类惩罚清零,只保留关节限位与动作平滑项——防止激烈动作被"防滑/防脚偏"奖励误伤。
训练分两段:先跑 200,000 env steps 的"表征预训练"(tldr_pretrain_env_steps),只练 goal encoder/对比表征(主循环里 _maybe_gcr_pretrain_only 会提前返回);之后进入 RL,tldr_te_during_rl=True 表示RL 过程中表征继续在线更新。另外 TeCH 有很强的正则设计:dual_reg=True 用拉格朗日对偶(初值 dual_lam_init=3000)约束表征尺度,tldr_softplus_scale=500 / beta=0.01 做进度奖励的 softplus 化。
FB vs TeCH 一句话对比:FB 用"后继特征 + 价值组合"统一建模,优点是可做任意线性奖励的零样本推断(奖励推理);TeCH 用"时间距离"组织行为,更擅长目标切换与跌倒恢复,但代价是放弃了 FB 式任意奖励零样本优化(它只能吃姿态目标与参考轨迹,不能接任意新奖励)。
3.3 提速三板斧:MJLab、多卡 all-reduce、轨迹式回放
主页 Highlight 反复强调"训练时间 6–8 小时",工程上来自三层:
- MJLab 后端:轻量 GPU 仿真,替代重型 Isaac 管线。单张 H200 + 1024 envs 下,UFO-FB 吞吐 993.4 FPS,而 BFM-Zero 是 763.6 FPS(提升约 30%);
- 同步多卡训练:每张 GPU 独立跑自己的 envs 并维护独立回放池,更新后 all-reduce 梯度,rank0 广播模型;多卡时
global_time = local_time × world_size,等效 batch =batch_size × world_size。更妙的是它不只是"快":因为并行 envs + 回放池 + batch 一起放大,离策略算法能吃到更多样化的状态转移——包括跌倒、地面过渡、扰动恢复这些非标准状态,数据多样性直接提升; - 轨迹式回放 + expert slicing:回放池不是逐 transition 存,而是
TrajectoryDictBufferMultiDim按整条轨迹组织,expert_slicer从专家动作库切片采样;FB/TeCH 的 expert 分支都能一次抽整段,天然配合"对未来窗口编码 z"这类需要时间上下文的训练目标。
3.4 多源技能注入:0.95 : 0.05 与 EMD 优先级采样
这是 UFO 很实用的一招(README 叫 Multi-source skill injection)。直接做法(把侧手翻等稀有技能并进基础数据再全局优先采样)会在 1400 万 ~ 2800 万环境步后让 Actor/Q 数值发散——因为高难轨迹长期占据高权重,训练不稳定。UFO 的做法是 manifest 固定源比例 + 源内优先采样:
数据集身份按固定比例采样(如基础 LaFAN 0.95 : 侧手翻 0.05)
每个源内部再做优先采样
→ 侧手翻持续获得训练机会,同时基础动作分布不塌
官方跑到了 3.84 亿环境步仍然稳定。代码层面 manifest 就是 configs/data/example_mix.yaml:
datasets:
- name: lafan
format: ufo_pkl
train_path: humanoidverse/data/lafan_29dof_10s-clipped.pkl
weight: 1.0
EMD 优先级采样(PMCP-inspired failed-motion prioritization) 是另一个闭环:每 eval_every_steps 做一次 tracking 评估,把每条动作的 EMD(Earth Mover’s Distance,推土机距离) 拿出来,clamp 到 [0.5, 5] × scale 2,再按 lin / exp / bin 三种模式转成采样权重,同时更新 motion_lib 与环境侧 expert_slicer——跟踪得越差的动作,越要加大采样权重多练,形成"评估 → 加练 → 再评估"的自适应课程。
3.5 推理三件套:Tracking / Goal / Reward
训练好一个 z 条件策略后,三类下游任务全部"零样本"调用(不需微调策略):
| 任务 | 做法 | 产物 |
|---|---|---|
| Tracking 动作跟踪 | 把参考轨迹第 1…T 帧(去掉当前帧)逐帧过 backward_map 得到 z 序列,策略逐帧以 z[step] 为条件 rollout | zs_{motion_id}.pkl、并排 MP4、ONNX |
| Goal 目标到达 | 从目标姿态(来自 goal JSON,如 goal_frames_lafan29dof.json)编码出一个 z;每 goal_switch_interval 步切换目标,演示"连续到达不同姿态" | goal_reaching.pkl、goal.mp4 |
| Reward 奖励/行为 | 把任务写成字符串(move-ego-x-y、raisearms-l-l、sitongground…),在重放 buffer 上做 reward relabel 推断出对应 z,再 rollout | reward_locomotion.pkl、{task}.mp4 |
reward inference 的数学核心(
B的最小二乘 β=(BᵀB)⁻¹Bᵀz)在mjlab_reward_relabel.py内部,入口脚本只做任务编排。注意非 G1 机器人只放行三类 locomotion 任务(move-ego-*、move-ego-low*、rotate-z-*),正则 + 白名单双重校验,其它任务直接ValueError。
3.6 从训练到真机:ONNX + backward encoder
推理/部署的关键是导出两个 ONNX:
- 策略网络
FBcprAuxModel.onnx(meta policy):输入观测 + z,输出 29 维关节目标,并带.meta.json记录 robot name、xml path、num_dof、control_joint_names、actor 输入/输出维度,导出时校验output_action_dim == num_dof; - backward encoder
backward_encoder.onnx:把实时人体姿态编码成 256 维 z。
真机遥操作(deploy 分支)链路因此变得非常干净:
PICO 头显 + 追踪器/手柄
│ WiFi(LAN)
▼
XRoboToolkit 无头服务 ── xrobotoolkit_sdk ──► motion_tracking_retarget(Mink IK 重定向到 G1 位姿)
│ │ 50 Hz
▼ ▼
xrobot_teleop_to_pose_zmq_server(位姿发布 ZMQ)──► realtime_z_server(backward_encoder.onnx → z,50 Hz,端口 28711)
│
▼
UFO 策略(订阅 z,推理动作)→ g1_interface → G1 29-DoF
安全机制是这套系统的重头戏:实时 z 看门狗(200ms 没收到有效 z 就停策略)、--max-retarget-age-ms 200 过期保护、--max-z-delta 0.75 z 突变限制、遥控器 R2 全局停止闩锁(按住即停,须 R1+B 重新武装)、slew-rate 关节限速、有限值检查、UFO_REAL_ROBOT_OK=1 启动保护、release_manifest.yaml 的 SHA256 校验 + preflight 套件(sim2sim 普通/遥操作/诊断三档)。README 更是直接给出真机安全 checklist:物理 e-stop、吊装首跑、Jetson 8 核在线检查……这套"软件安全工程"的认真程度在学术开源仓库里相当少见。
4. 源码结构全景
UFO/
├── run_train.sh # 训练入口(uv 封装 + 缓存目录重定向)
├── pyproject.toml # uv 工程,torch 2.7、mjlab 1.4、mujoco 3.8
├── uv.lock / CHANGELOG.md / LICENSE(CC BY-NC 4.0)
├── configs/
│ ├── robots/g1_29dof.yaml # G1 机器人语义配置(PD/关节/接触)
│ └── data/example_mix.yaml # 多源数据 manifest(权重混合)
├── humanoidverse/
│ ├── train.py # 统一训练入口(CLI 参数、agent 别名、分布式派发)
│ ├── train_mjlab.py / distributed.py
│ ├── tracking_inference.py # 动作跟踪推理 + MP4 + ONNX
│ ├── goal_inference.py # 目标到达推理(目标 → z)
│ ├── reward_inference.py # 奖励/行为推理(任务字符串 → z)
│ ├── mjlab_reward_relabel.py # RewardWrapperHV(最小二乘隐式奖励)
│ ├── mjlab_inference_utils.py / export/backward_encoder.py
│ ├── agents/
│ │ ├── base.py / base_model.py / nn_models.py / nn_filters.py
│ │ ├── normalizers.py / pytree_utils.py / load_utils.py
│ │ ├── buffers/{trajectory,transition}.py
│ │ ├── envs/{expert_motion_loader,humanoidverse_mjlab}.py
│ │ ├── fb/ # FB 基类(agent.py + model.py)
│ │ ├── fb_cpr/ fb_cpr_aux/ # +CPR +Aux critic 的强化版
│ │ ├── gcr_rl/ gcr_rl_dist/ gcr_rl_dist_aux/ # TeCH 对比表征体系
│ │ ├── tldr_dist_aux/ # TeCH(TLDR 兼容命名)
│ │ ├── presets/{fb,tldr}.py # ★ 官方预设(实际被 train.py 调用)
│ │ └── misc/{loggers,zbuffer}.py
│ ├── training/workspace.py # ★ 训练主循环 + 多卡同步 + EMD 优先级
│ ├── envs/g1_env_helper/rewards.py # G1 环境奖励
│ ├── utils/motion_data/ # RobotState 数据导入(CSV/NPZ/pkl/adapters)
│ ├── utils/motion_lib/ # 动作库(采样、切片、骨架、旋转转换)
│ ├── utils/robot_spec/ # 机器人规格解析(XML/URDF → 训练规格)
│ └── tools/ # robot_inspect / data_inspect / data_build
├── scripts/download_data.sh # 下载 G1 LaFAN 数据(SHA256 校验)
├── docs/ tests/ assets/
└── index.html(项目页静态文件)
三个要点先记住:agent 实现与 preset 是分离的(train.py 只认 fb/tech/tldr 别名,真正装配在 presets/);算法血缘可追溯(fb → fb_cpr → fb_cpr_aux 是叠加演进,gcr_rl → gcr_rl_dist → gcr_rl_dist_aux → tldr_dist_aux 是 TeCH 血缘);配置全是 pydantic frozen model,拼写错了直接报错而不是静默吞掉。
5. 源码级拆解(重点)
5.1 训练入口 train.py:参数与别名
DEFAULT_AGENT = "fb"
DEFAULT_NUM_ENVS = 1024 # 每张 GPU 的并行环境数
DEFAULT_NUM_ENV_STEPS = 192000000 # 全局采样预算(1.92 亿步)
DEFAULT_BUFFER_SIZE = 5120000 # 每 rank 回放池容量
DEFAULT_FB_UPDATE_Z_EVERY_STEP = 100 # FB 的 z 重采样间隔
DEFAULT_TECH_UPDATE_Z_EVERY_STEP = 10 # TeCH 的 z 重采样间隔
AGENT_ALIASES = {"fb": "fb", "tech": "tech", "tldr": "tech"} # tldr = tech 弃用别名
--num-envs、--buffer-size是 per-GPU 概念;--num-env-steps才是全局预算;--update-z-every-step默认值跟 agent 走:FB=100 / TeCH=10;--data-path与--data-manifest互斥(parser.error);- 多文件
--data-path必须配--data-mix-weights(如0.95 0.05); --smoke:envs ≤ 16、总步数 ≤ 2048、强制关 W&B、跳过 eval 与 prioritization;- 分布式:不是 Lightning Fabric,而是
torch.distributed(NCCL)+torchrunx.Launcher多进程派发; - robot config 解析优先级:CLI 与 manifest 都有则做
assert_robot_configs_compatible,都没有则落到configs/robots/g1_29dof.yaml; cartwheel_aux_safe(仅 fb)会把 undesired_contact/feet_ori/heading/slippage/ankle_roll 全部清零、action_rate 降到 -0.1。
5.2 presets:FB vs TeCH 官方超参对照(一图看懂两套算法)
| 维度 | UFO-FB(presets/fb.py) | TeCH(presets/tldr.py) |
|---|---|---|
| 实际 agent 类 | FBcprAuxAgent | TldrDistAuxAgent |
| z 维度 | 256(norm) | 256(norm) |
| 网络主力 | Actor/Critic:2048×6 residual,num_parallel=2 | 同左(plus Discriminator 1024×3、aux_critic) |
| 学习率 | lr_f=3e-4、lr_b=1e-5、lr_actor=3e-4、lr_critic=3e-4、lr_disc=1e-5 | lr_goal_encoder=8e-7、lr_actor=5e-5、lr_critic=5e-5、lr_disc=2e-6、lr_aux_critic=2e-5、lr_dual=4e-5 |
| 优化器数量 | F / B / Actor / Critic / Disc / AuxCritic | GoalEncoder / Actor / Critic / Disc / AuxCritic / Dual |
| discount | 0.98 | 0.98 |
| train_goal_ratio | 0.2 | 0.2 |
| expert_asm_ratio | 0.6(60% expert batch) | 0.6 |
| relabel_ratio | 0.8(80% 样本重贴 z) | 0.4 |
| ortho_coef | 100.0 | —(对比损失替代) |
| 悲观惩罚 | actor/critic/aux 均 0.5,fb 悲观 0 | 同左 |
| 判别器 GP | 10.0 | 10.0 |
| 预训练阶段 | 无 | 200k env steps 表征预训练 |
| 特殊正则 | scale_reg=True | dual_reg、softplus(scale 500/β 0.01) |
| 训练节奏 | update_every=1024、updates=16/次 | update_every=1024、updates=128/次 |
| 表现侧重 | 任意线性奖励零样本优化、跟踪更准 | 目标切换/跌倒恢复更强,放弃任意奖励推断 |
注意 FB 版 lr_b 被压到 1e-5、ortho_coef 拉到 100——说明在 BF 目标里,B 的收敛要非常稳(它承担"目标编码"和"隐式奖励方向"双重职责),而 F 与 Actor 可以激进些。TeCH 全线学习率低一个量级,靠长更新步数(128/次)与对偶正则保证稳定。
5.3 FB 的"度量学习 + TD3"是怎么写出来的(fb/agent.py 精髓)
# 主 FB 回归(fb_loss = off-diag TD + diag 拉高)
Fs = self._model._forward_map(obs, z, action) # 并行头
B = self._model._backward_map(goal)
Ms = torch.matmul(Fs, B.T)
with torch.no_grad():
next_action = self.sample_action_from_norm_obs(next_obs, z)
target_Fs = self._model._target_forward_map(next_obs, z, next_action)
target_Ms = torch.matmul(target_Fs, self._model._target_backward_map(goal).T)
_, _, target_M = self.get_targets_uncertainty(target_Ms, fb_pessimism_penalty)
diff = Ms - discount * target_M
fb_loss = 0.5*(diff*self.off_diag).pow(2).sum()/self.off_diag_sum \
- torch.diagonal(diff, dim1=1, dim2=2).mean() * Ms.shape[0]
# TD3 式 Actor:最大化悲观 Q
dist = self._model._actor(obs, z, self._model.cfg.actor_std)
action = dist.sample(clip=self.cfg.train.stddev_clip) # 动作截断
Fs = self._model._forward_map(obs, z, action)
Qs = (Fs * z).sum(-1) # 线性读出 Q
_, _, Q = self.get_targets_uncertainty(Qs, actor_pessimism_penalty)
actor_loss = -Q.mean()
# 训练期 z 混合采样:20% 用"别人的 next_state 编码"当目标
z = self._model.sample_z(batch_size, ...) # 先验/z-buffer
perm_g = tree_map(lambda x: x[torch.randperm(batch_size)], train_goal)
goals = self._model.project_z(self._model._backward_map(perm_g))
mask = torch.rand((batch_size,1)) < train_goal_ratio # 0.2
z = torch.where(mask, goals, z)
5.4 TeCH 的"三路 Actor + 判别器"(gcr_rl_dist_aux/agent.py 精髓)
# Actor 同时被主 Q、人体判别器、aux(Sim2Real)三个信号拉
actor_loss = (
- q.mean() # 主 critic(进度奖励)
- disc_actor_weight * disc_reward.mean() # 判别器风格奖励
- aux_actor_weight * q_aux.mean() # aux critic(Sim2Real 惩罚)
)
# disc_actor_weight = reg_coeff_disc(0.03/0.05) × |Q|.mean() ← 按 Q 尺度自适应缩放
# 判别器:WGAN 式 负log-sigmoid(专家) + softplus(在线) + 梯度惩罚
expert_loss = -F.logsigmoid(expert_logits) # 专家(z 来自 encode_expert)
unlabeled_loss = F.softplus(train_logits) # 在线轨迹
loss = (expert_loss + unlabeled_loss).mean()
if grad_penalty: loss += 10.0 * self.gradient_penalty_wgan(expert_obs, expert_z, train_obs, train_z)
主循环里还有个关键顺序:expert_z 用当前 goal encoder 在线编码(encode_expert),所以判别器与对比表征是联合演化的——人体先验不是死的,而是跟着表征一起更新;随后 relabel_ratio(TeCH 0.4)概率把样本的旧 z 替换成刚混合出的新 z,实现"目标重贴"。
5.5 workspace.py:训练主循环 + EMD 优先级闭环
核心节奏(单卡 1024 envs 场景):
for local_time ...:
├─ [checkpoint_every_steps?] 保存 model + replay buffer + train_status
├─ [eval_every_steps?] rank0 跑 tracking eval → EMD
│ └─ prioritization: clamp(EMD,[0.5,5])×2 → exp/bin/lin → broadcast
│ → motion_lib.update_sampling_weight_by_id + expert_slicer.update_priorities
├─ rollout: obs → agent.act(obs, z, mean=False) → env.step → buffer["train"].extend
└─ [update_agent_every?] for _ in range(num_agent_updates):
agent.update(replay_buffer, local_time) # FB 16 次 / TeCH 128 次
[distributed] sync_floating_buffers + average_metrics(metrics)
多卡一致性被当成一等公民:save 前 module_sync_report 检查各 rank 模型最大偏差 > 1e-5 直接抛 RuntimeError;resume 时 checkpoint 里记录的 world_size 与当前不一致也直接拒绝(回放池是 rank-local 分片,没法自动迁移)。回放池是带时间维的轨迹 buffer(TrajectoryDictBufferMultiDim,n_dim=2,end_key="truncated"),存的是整段轨迹而不是单步 transition——这是 FB/TeCH 的"对未来窗口编码"能吃到时间上下文的前提。
5.6 机器人配置文件:g1_29dof.yaml 在讲什么
name: g1_29dof
xml_path: humanoidverse/data/robots/g1_mjlab/g1_29dof.xml
base_body: pelvis
control_joints: { mode: all_actuated } # 29 个可控关节全部驱动
training:
hydra_robot: g1/g1_29dof_hard_waist # 复用 MJLab 的 G1 硬腰环境
hydra_overrides:
- env.config.lie_down_init=True # 30% 概率躺倒开局!
- env.config.lie_down_init_prob=0.3
semantics: # 接触/语义,奖励推理要用
contact_bodies: [左右踝]
undesired_contact_bodies: [pelvis, shoulder, hip]
control:
action_scale: 0.25; action_clip: 5.0 # 动作 0.25 倍缩放后限幅 ±5
effort_limit_scale: 0.8 # 力矩限幅再打 8 折(安全余量)
stiffness: { hip_pitch/knee: 99.1, ankle: 28.5, waist: 300, shoulder/elbow: 14.25, wrist: 8.6~14.25 }
最有价值的两个细节:lie_down_init_prob=0.3——训练时 30% 概率让机器人躺倒开局,这正是"无监督必须覆盖跌倒/起身等非标准状态"的工程落地,也是后面 TeCH 能"摔倒自起"的关键;以及 PD gains 分组语义(髋/膝最硬、腕最软),机器人配置把"关节顺序/动作维度/执行器限制/接触身体"全部显式化,新机器人就靠这份 YAML + robot_inspect 草稿来对齐。
5.7 值得抄的 Python/PyTorch 工程技巧
UFO 是纯 Python 工程,但代码品味在线,几个技巧直接可抄:
- pydantic frozen + extra=“forbid” + strict=True 的配置层:配置拼写错误/类型传错在启动即炸,而不是训练 3 小时后才发现;
__init_subclass__自动给每个子类生成name: Literal[类名]判别字段,为多态反序列化铺路; - tensordict/pytree 贯穿:obs 是 dict of tensors,
tree_map批量搬运 device/normalize,省掉大量手写循环; - normalizer 的 eval_mode 纪律:
with eval_mode(self._model._obs_normalizer):在 no_grad 下跑 BatchNorm——rollout/推理时统计量绝不因 BN 更新而漂移; torch.compiler.cudagraph_mark_step_begin()+ compile + CudaGraphModule 可选:同一套代码开关式启用 torch.compile 与 CUDA Graph;- 目标网络 soft update 用
_soft_update_params(...tau),并在分布式下配sync_floating_buffers处理 BN running stats 的多卡一致性; average_gradients代替简单 all-reduce loss:保持"各 rank 独立 forward/backward、梯度聚合"的局部损失模式,天然规避 batch norm 跨卡统计的坑。
6. 环境搭建与训练保姆级流程
官方用
uv管理环境。以下命令全部摘自仓库 README / docs,已按 G1 主线整理。
6.1 前置条件
- Linux + NVIDIA GPU(CUDA 可用)。官方报告用 H200 / RTX 4090 做训练,单卡也能训但慢;
deploy真机需要 G1 机载 Jetson(8 核在线); - Python 3.10.x(
pyproject.toml写死requires-python == "3.10.*"); - 依赖摘要:
torch>=2.7,<2.8、mjlab==1.4.0、mujoco~=3.8.0、tensordict、pydantic、hydra-core、torchrunx、onnx、wandb(可选)、pot(EMD)、dm-control等。
6.2 安装
git clone https://github.com/Roboparty/UFO.git && cd UFO
# 装 uv
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.local/bin/env
uv sync # 按 uv.lock 一次性装齐
可选 W&B:先 uv run wandb login,训练命令加 --use-wandb --wandb-run-name ufo_fb_g1。
6.3 下载 G1 动作数据(带 SHA256 校验)
bash scripts/download_data.sh g1_lafan
ls -lh humanoidverse/data/lafan_29dof_10s-clipped.pkl
大动作数据集在仓库外托管,仓库只放轻量元数据。默认训练文件是 LaFAN 处理后的 lafan_29dof_10s-clipped.pkl(10 秒片段裁剪版)。
6.4 Smoke 测试(强烈建议先跑)
./run_train.sh \
--agent fb \
--data-manifest configs/data/example_mix.yaml \
--gpu-ids single \
--smoke \
--work-dir /tmp/ufo_smoke_g1
验证环境、数据、短训练循环三者打通。smoke 自动把 envs 压到 ≤16、步数压到 ≤2048。
6.5 FB 全量训练(G1)
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
./run_train.sh \
--agent fb \
--gpu-ids all \
--num-envs 1024 \
--num-env-steps 192000000 \
--work-dir runs/ufo_fb_g1 \
--data-path humanoidverse/data/lafan_29dof_10s-clipped.pkl \
--update-z-every-step 100 \
--buffer-size 5120000
8×H200 配置约 6 小时到稳定部署平台期;8×RTX 4090(每卡 512 envs)约 14 小时。
6.6 TeCH 训练(G1)
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
./run_train.sh \
--agent tech \
--gpu-ids all \
--num-envs 1024 \
--num-env-steps 192000000 \
--work-dir runs/ufo_tech_g1 \
--data-path humanoidverse/data/lafan_29dof_10s-clipped.pkl \
--update-z-every-step 10 \
--buffer-size 5120000
6.7 跟踪推理 + ONNX 导出
推理要用完整动作序列,不要用裁剪过的训练数据。
CUDA_VISIBLE_DEVICES=0 \
uv run python -m humanoidverse.tracking_inference \
--model-folder runs/ufo_fb_g1 \
--data-path /path/to/full_motions.pkl \
--device cuda:0 \
--headless \
--save-mp4 \
--motion-list 0 \
--export-onnx true
输出到 <model-folder>/tracking_inference/(zs pkl + 专家|策略并排 MP4),<model-folder>/exported/ 下生成策略 ONNX + meta.json,以及 backward_encoder.onnx。ONNX 与 checkpoint 的机器人/动作/观测维度强绑定,别跨机器人复用。
6.8 新机器人 Bring-Up(实验性)
# 1) 从 XML(+可选 URDF)生成草稿配置
uv run python -m humanoidverse.tools.robot_inspect \
--xml /path/to/robot.xml --urdf /path/to/robot.urdf \
--name my_robot \
--out configs/robots/my_robot.yaml \
--hydra-out humanoidverse/config/robot/my_robot/my_robot_auto.yaml
# 2) 人工 review 语义(base/feet/hands/PD/接触体...)
# 3) 用 RobotState CSV 构建数据 manifest(无表头 = root_pos xyz, root_quat xyzw, 然后按 DOF 顺序)
uv run python -m humanoidverse.tools.data_build \
--robot configs/robots/my_robot.yaml \
--source "/path/to/motions/*.csv" \
--format robot_state_csv --name my_motion --fps 50 --clip-seconds 10 \
--out configs/data/my_motion_auto_build.yaml --rebuild-cache
# 4) smoke 训练
./run_train.sh --agent fb --robot-config configs/robots/my_robot.yaml \
--data-manifest configs/data/my_motion_auto_build.yaml \
--gpu-ids single --smoke --work-dir /tmp/ufo_smoke_my_robot
6.9 真机部署 / 遥操作(独立 deploy 分支)
git clone --branch deploy --single-branch https://github.com/Roboparty/UFO.git UFO-Deploy
cd UFO-Deploy && conda create -n ufo-deploy python=3.10 -y && conda activate ufo-deploy
pip install -r requirements/runtime.txt # 遥操作再加 requirements/teleop.txt
# 从 HuggingFace 拉官方 G1 策略 artifact 并做 SHA256 校验
export HF_REPO_ID=xuewang/ufo-g1-policy
python - <<'PY'
from huggingface_hub import snapshot_download
snapshot_download(repo_id=HF_REPO_ID, repo_type="model", local_dir="model")
PY
之后按 README 的 Recommended Order 走:本地普通 sim2sim → 本地遥操作 sim2sim → 机载(吊装)普通 sim2real → 机载遥操作 sim2real → 稳定站立 → R1 启策略 → B 开始跟踪 → 测试 R2/X 停止。机载三/四终端分别起 XRoboToolkit 服务、50Hz 位姿桥、realtime_z_server、策略进程。任何自由行走前,物理 e-stop 必须就位并实测。
7. 效果对比
7.1 训练效率与跟踪精度(对照 BFM-Zero,数值来自 UFO 技术报告/项目页)
| 配置 | 到稳定部署平台期 | 跟踪 EMD ↓ | 关节跟踪 MAE ↓ | 单卡吞吐 |
|---|---|---|---|---|
| BFM-Zero,1×H200,1024 envs | 45 h | 0.731 | 0.1510 | 763.6 FPS |
| UFO-FB,1×H200,1024 envs | 21 h | 0.668 | 0.1266 | 993.4 FPS |
| UFO-FB,8×H200,1024 envs/卡 | 6 h | 0.639 | 0.1178 | — |
| UFO-FB,8×RTX 4090,512 envs/卡 | 14 h | 0.693 | 0.1254 | — |
| TeCH(LAFAN1 全套跟踪) | — | — | 0.1272 | — |
| TeCH(100-Style 风格集) | — | — | 0.1385 | — |
结论很直白:
- 吞吐:同样的 1×H200,FB 重写把 763.6 → 993.4 FPS(约 +30%),这是 MJLab + 训练管线的净收益;
- 收敛速度:同样单卡 H200,稳定平台期 45h → 21h;8×H200 压到 6h,主页另一组演示甚至 3–4h 就能上真机(口径不同:可用动作 vs 稳定平台);
- 质量不降反升:EMD 0.731 → 0.639、MAE 0.1510 → 0.1178,即"更快且更准";
- TeCH 的定位:完整轨迹跟踪 MAE(0.1272)略逊于 UFO-FB(0.1178),但强项在目标切换与跌倒恢复——技术报告展示了外力推倒后自主站起并继续目标动作,且量化上明显优于 BFM-Zero;项目页 Highlights 也明确标注"TeCH 是新算法(Temporal Contrastive Metric Learning),EMD=0.64 级别"。
7.2 与"模仿学习/跟踪式"主流的范式对比(定性)
| 维度 | 模仿学习 / Tracking-based | UFO-FB | UFO-TeCH |
|---|---|---|---|
| 训练信号 | 参考轨迹误差 | 后继特征自监督 | 时间距离对比 + 判别器 |
| 是否手写任务奖励 | 否(跟参考即可) | 无需(任意线性奖励零样本) | 无需(姿态/轨迹类目标) |
| 抗扰动/跌倒恢复 | 弱 | 中 | 强(演示核心卖点) |
| 技能注入 | 数据里加片段 | manifest 0.95:0.05 + EMD 加练 | 同左 |
| 任意新奖励零样本 | ✗ | ✓(BFM-Zero 式) | ✗(只能吃 z 目标) |
| 目标切换平滑性 | 需重定向 | 中 | 高(时间距离组织) |
| 训练成本 | 相对低 | 8×4090 约 14h | 同左 |
7.3 真机能力展示(定性,来自项目页/技术报告)
跑步、后退、深蹲、跪地、翻滚、快速起身、接箱子、踢箱子;数十次推/拉/踢扰动下保持遥操作跟手;跌倒后可自主站起继续任务;PICO 遥操作延迟在人体可接受范围。官方坦承:恢复时间/成功率/扰动力没有逐次量化统计,适合作为"能力展示"而非 benchmark。
8. 优缺点分析
8.1 优点
- 真·双算法同框架:FB 与 TeCH 共享一套 env/buffer/推理/导出代码,
--agent一键切换,横向对比成本极低——这是无监督 RL 方向稀缺的"实验土壤"型基础设施; - 训练效率实打实:MJLab + 同步多卡 + 轨迹回放三板斧,8×H200 六小时、8×4090 十四小时,把"研究门槛"从超大显存集群拉到普通实验室可承担;
- 工程完整度罕见:从数据导入(robot_inspect/data_build/data_inspect 三件套)、manifest 多源混合、EMD 课程式优先级采样、到三类零样本推理、ONNX 双导出、真机预检套件与软件安全工程(e-stop/R2 闩锁/看门狗/哈希校验),覆盖"论文代码"几乎不管的全链路;
- 机器人感知的设计:robot YAML + RobotState 标准格式 + XML/URDF 推断工具,把"换本体"从改代码变成填配置(虽然仍实验性);
- 诚实且克制:README 把"不支持自动重定向、不支持跨本体共享策略、非 G1 局限"写得明明白白,CHANGELOG 也有 Known Limitations 章节,没有学术 repo 常见的夸大。
8.2 缺点 / 局限
- 许可证 CC BY-NC 4.0:仅限非商业研究使用,商用要单独评估——比 Apache/MIT 严格得多,企业落地需谨慎;
- G1 中心化:训练环境仍是 G1/MJLab 中心;新机器人 bring-up 标注 experimental,可能需要调 env/controller/reward/contact/termination 全套;
- 不自动重定向、不跨本体复用:换机器人 = 自己准备重定向数据(hhtools/GMR 等外部工具)+ 重训,checkpoint 维度强绑定;
- 数据管线有硬限制:RobotState 导入不做 FPS 重采样、只支持 hinge(铰链)控制关节(球关节/多 DOF 关节支持缺失),v0.1.0 即已知;
- 算法层面的明确短板(官方自述):大型反向映射网络(backward encoder)带来可感知延迟;预训练只用 LAFAN1,行走与真实交互动作覆盖不足;后退/侧向移动稳定性弱;无任务专属终止条件导致根节点漂移难约束;
- 定量真机证据不足:扰动恢复等以定性演示为主;仓库新(2026-07 创建),生态/issue/社区沉淀还在早期。
9. 高频踩坑清单
--tldr被弃用:请用--agent tech,虽然 tldr 仍兼容,但日志会刷 WARNING;--data-path和--data-manifest二选一:同给直接parser.error;多 data-path 必须配--data-mix-weights;- per-GPU 与全局步数别搞混:
--num-envs/--buffer-size按卡算,--num-env-steps才是全局;扩 envs 时按官方建议调--num-agent-updates(2048 envs/卡用 32、4096 用 64)对齐更新密度; - resume 不能换卡数:replay buffer 按 rank 分片落盘,checkpoint 的 world_size 与当前不一致直接拒绝恢复;
- 推理别用裁剪训练数据:tracking inference 要喂完整动作序列,训练 pkl 是 10s-clipped 的;
- 非 G1 目标推理必须自带 goal JSON(默认查找只认 G1 的 29-DoF),reward 推理非 G1 只有三类 locomotion 任务,传别的任务直接 ValueError;
- ONNX 维度强绑定:导出 ONNX 只能在原机器人的 action/obs 维度下用,别拿去喂别的机器人;
- 真机安全红线:
UFO_REAL_ROBOT_OK=1只在实际下放前设置;任何自由行走前物理 e-stop 实测 + 吊装首跑 + sim2sim 全绿;R2 闩锁与 PICO 断连看门狗必须测; - 商业用途先看许可:CC BY-NC 4.0 非商用,改编/商用需自行评估合规;
- 依赖版本别乱动:Python 锁 3.10、torch <2.8、mjlab==1.4.0,用
uv sync走 lock 文件最稳;想接单位置/复合关节的数据要绕过 RobotState 铰链限制。
10. 总结与展望
UFO 的价值不在"多一个算法复现",而在于它把无监督人形 RL 从论文变成了可重复的研发流程:FB 承接 BFM-Zero 的"后继特征 + 价值组合"路线并把训练时间缩短近一个数量级;TeCH 用时间距离对比表征补上"目标切换与跌倒恢复"这块 FB 不擅长的拼图;manifest 多源混合 + EMD 加练解决了"稀有技能注入导致训练发散"的工程顽疾;RobotState + robot YAML 让"换本体"向填配置演进;deploy 分支则示范了一套带完整软件安全工程的真机遥操作运行时(50Hz、双 ONNX、看门狗/闩锁/哈希校验)。
它同时也很诚实地圈出了边界:G1 是最优路径,其它本体仍实验;不自动重定向、不跨本体复用权重;大型 backward encoder 的延迟、动作数据覆盖、后退/侧移稳定性、根节点漂移约束仍是开放问题;CC BY-NC 4.0 则把商业化留给了"需要单独谈"的领域。
对想入坑无监督人形 RL 的开发者,这是一份难得的"开箱即完整链路"的参考实现;对研究者,它更像一个可以持续往里插新 Representation 的试验台。可以预期后续迭代方向:跨本体共享策略、自动重定向接入、更大的动作数据与更严格的定量真机基准——正如 RoboParty 在官方访谈里说的:UFO 想做的,是"让定义研发方式的人,能够自由探索新的控制方法与行为表示"的那块土壤。
参考链接
- 仓库:https://github.com/Roboparty/UFO
- 项目主页:https://roboparty.github.io/UFO/
- 技术报告 PDF:https://roboparty.github.io/UFO/assets/UFO.pdf
- 仓库中文 README:https://github.com/Roboparty/UFO/blob/main/README_zh-CN.md
- 文档:Import Wizard / Robot-Config Training / TRAIN_INFERENCE(见仓库 docs/)
- Teaser 视频:https://youtu.be/uJPcLdn9sNA
- 团队:RoboParty Lab(https://lab.roboparty.com)
注:文中所引数值(吞吐、EMD/MAE、训练时长)来自 UFO 项目主页与官方技术报告;代码片段均摘自仓库 main 分支实际源码。写作日期:2026-09-09。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)