一句话认识它: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 真机与遥操作运行时
Topicshumanoid-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 可以预训练出一套行为基础模型。但它留下的问题非常现实:

  1. 训练周期长(45 小时才到稳定部署平台期);
  2. 基础设施围绕单一算法(FB)搭建,换 Representation 等于换代码;
  3. 换机器人 / 换数据源要大量工程改造;
  4. 真机验证不足

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.pyupdate_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 混合两条通道:

  1. 从先验/历史 z buffer 采样;
  2. train_goal_ratio(FB 默认 0.2,即 20%)的概率,把 batch 里另一条轨迹的下一状态B 编码成 goal z——相当于"拿别人的未来当我的目标",制造 goal-conditioned 训练信号。

z 在 rollout 里怎么调度:maybe_update_rollout_contextupdate_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 换了一种组织方式:

  1. 伪目标(temporal rolling):对当前状态 s_t 与下一状态 s_{t+1},从后续轨迹中抽一个未来状态作为伪目标 g;
  2. **共享编码器(backward map / goal encoder)**把三者都映射到潜空间 z;
  3. 对比式约束:时间相隔远的状态在潜空间里拉开、相邻状态保持连续,使潜空间里的距离 ≈ 时间可达性(从当前状态经过多少步能到达目标)。

于是策略的进度奖励写成(项目页/技术报告原式):

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_torque0.00.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 小时",工程上来自三层:

  1. MJLab 后端:轻量 GPU 仿真,替代重型 Isaac 管线。单张 H200 + 1024 envs 下,UFO-FB 吞吐 993.4 FPS,而 BFM-Zero 是 763.6 FPS(提升约 30%);
  2. 同步多卡训练:每张 GPU 独立跑自己的 envs 并维护独立回放池,更新后 all-reduce 梯度,rank0 广播模型;多卡时 global_time = local_time × world_size,等效 batch = batch_size × world_size。更妙的是它不只是"快":因为并行 envs + 回放池 + batch 一起放大,离策略算法能吃到更多样化的状态转移——包括跌倒、地面过渡、扰动恢复这些非标准状态,数据多样性直接提升;
  3. 轨迹式回放 + 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] 为条件 rolloutzs_{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-yraisearms-l-lsitongground…),在重放 buffer 上做 reward relabel 推断出对应 z,再 rolloutreward_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-sizeper-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 类FBcprAuxAgentTldrDistAuxAgent
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-5lr_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 / AuxCriticGoalEncoder / Actor / Critic / Disc / AuxCritic / Dual
discount0.980.98
train_goal_ratio0.20.2
expert_asm_ratio0.6(60% expert batch)0.6
relabel_ratio0.8(80% 样本重贴 z)0.4
ortho_coef100.0—(对比损失替代)
悲观惩罚actor/critic/aux 均 0.5,fb 悲观 0同左
判别器 GP10.010.0
预训练阶段200k env steps 表征预训练
特殊正则scale_reg=Truedual_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 工程,但代码品味在线,几个技巧直接可抄:

  1. pydantic frozen + extra=“forbid” + strict=True 的配置层:配置拼写错误/类型传错在启动即炸,而不是训练 3 小时后才发现;__init_subclass__ 自动给每个子类生成 name: Literal[类名] 判别字段,为多态反序列化铺路;
  2. tensordict/pytree 贯穿:obs 是 dict of tensors,tree_map 批量搬运 device/normalize,省掉大量手写循环;
  3. normalizer 的 eval_mode 纪律:with eval_mode(self._model._obs_normalizer): 在 no_grad 下跑 BatchNorm——rollout/推理时统计量绝不因 BN 更新而漂移;
  4. torch.compiler.cudagraph_mark_step_begin() + compile + CudaGraphModule 可选:同一套代码开关式启用 torch.compile 与 CUDA Graph;
  5. 目标网络 soft update 用 _soft_update_params(...tau),并在分布式下配 sync_floating_buffers 处理 BN running stats 的多卡一致性;
  6. 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.8mjlab==1.4.0mujoco~=3.8.0tensordictpydantichydra-coretorchrunxonnxwandb(可选)、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 envs45 h0.7310.1510763.6 FPS
UFO-FB,1×H200,1024 envs21 h0.6680.1266993.4 FPS
UFO-FB,8×H200,1024 envs/卡6 h0.6390.1178
UFO-FB,8×RTX 4090,512 envs/卡14 h0.6930.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-basedUFO-FBUFO-TeCH
训练信号参考轨迹误差后继特征自监督时间距离对比 + 判别器
是否手写任务奖励否(跟参考即可)无需(任意线性奖励零样本)无需(姿态/轨迹类目标)
抗扰动/跌倒恢复强(演示核心卖点)
技能注入数据里加片段manifest 0.95:0.05 + EMD 加练同左
任意新奖励零样本✓(BFM-Zero 式)✗(只能吃 z 目标)
目标切换平滑性需重定向高(时间距离组织)
训练成本相对低8×4090 约 14h同左

7.3 真机能力展示(定性,来自项目页/技术报告)

跑步、后退、深蹲、跪地、翻滚、快速起身、接箱子、踢箱子;数十次推/拉/踢扰动下保持遥操作跟手;跌倒后可自主站起继续任务;PICO 遥操作延迟在人体可接受范围。官方坦承:恢复时间/成功率/扰动力没有逐次量化统计,适合作为"能力展示"而非 benchmark。


8. 优缺点分析

8.1 优点

  1. 真·双算法同框架:FB 与 TeCH 共享一套 env/buffer/推理/导出代码,--agent 一键切换,横向对比成本极低——这是无监督 RL 方向稀缺的"实验土壤"型基础设施;
  2. 训练效率实打实:MJLab + 同步多卡 + 轨迹回放三板斧,8×H200 六小时、8×4090 十四小时,把"研究门槛"从超大显存集群拉到普通实验室可承担;
  3. 工程完整度罕见:从数据导入(robot_inspect/data_build/data_inspect 三件套)、manifest 多源混合、EMD 课程式优先级采样、到三类零样本推理、ONNX 双导出、真机预检套件与软件安全工程(e-stop/R2 闩锁/看门狗/哈希校验),覆盖"论文代码"几乎不管的全链路;
  4. 机器人感知的设计:robot YAML + RobotState 标准格式 + XML/URDF 推断工具,把"换本体"从改代码变成填配置(虽然仍实验性);
  5. 诚实且克制:README 把"不支持自动重定向、不支持跨本体共享策略、非 G1 局限"写得明明白白,CHANGELOG 也有 Known Limitations 章节,没有学术 repo 常见的夸大。

8.2 缺点 / 局限

  1. 许可证 CC BY-NC 4.0:仅限非商业研究使用,商用要单独评估——比 Apache/MIT 严格得多,企业落地需谨慎;
  2. G1 中心化:训练环境仍是 G1/MJLab 中心;新机器人 bring-up 标注 experimental,可能需要调 env/controller/reward/contact/termination 全套;
  3. 不自动重定向、不跨本体复用:换机器人 = 自己准备重定向数据(hhtools/GMR 等外部工具)+ 重训,checkpoint 维度强绑定;
  4. 数据管线有硬限制:RobotState 导入不做 FPS 重采样、只支持 hinge(铰链)控制关节(球关节/多 DOF 关节支持缺失),v0.1.0 即已知;
  5. 算法层面的明确短板(官方自述):大型反向映射网络(backward encoder)带来可感知延迟;预训练只用 LAFAN1,行走与真实交互动作覆盖不足;后退/侧向移动稳定性弱;无任务专属终止条件导致根节点漂移难约束;
  6. 定量真机证据不足:扰动恢复等以定性演示为主;仓库新(2026-07 创建),生态/issue/社区沉淀还在早期。

9. 高频踩坑清单

  1. --tldr 被弃用:请用 --agent tech,虽然 tldr 仍兼容,但日志会刷 WARNING;
  2. --data-path--data-manifest 二选一:同给直接 parser.error;多 data-path 必须配 --data-mix-weights;
  3. per-GPU 与全局步数别搞混:--num-envs/--buffer-size 按卡算,--num-env-steps 才是全局;扩 envs 时按官方建议调 --num-agent-updates(2048 envs/卡用 32、4096 用 64)对齐更新密度;
  4. resume 不能换卡数:replay buffer 按 rank 分片落盘,checkpoint 的 world_size 与当前不一致直接拒绝恢复;
  5. 推理别用裁剪训练数据:tracking inference 要喂完整动作序列,训练 pkl 是 10s-clipped 的;
  6. 非 G1 目标推理必须自带 goal JSON(默认查找只认 G1 的 29-DoF),reward 推理非 G1 只有三类 locomotion 任务,传别的任务直接 ValueError;
  7. ONNX 维度强绑定:导出 ONNX 只能在原机器人的 action/obs 维度下用,别拿去喂别的机器人;
  8. 真机安全红线:UFO_REAL_ROBOT_OK=1 只在实际下放前设置;任何自由行走前物理 e-stop 实测 + 吊装首跑 + sim2sim 全绿;R2 闩锁与 PICO 断连看门狗必须测;
  9. 商业用途先看许可:CC BY-NC 4.0 非商用,改编/商用需自行评估合规;
  10. 依赖版本别乱动: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。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐