GEAR-SONIC 架构#

论文 arXiv:2511.07820 · 模型 nvidia/GEAR-SONIC · Isaac Lab 2.3.2

1. 整体架构概览#

GEAR-SONIC 要解决的是一个很具体的接口问题:人类动捕、VR 遥操作、机器人参考轨迹这三种运动来源,格式完全不同,能不能压进同一个空间,让同一个控制器都能跟踪?

答案是 Universal Token(通用运动 token)。三个独立编码器分别吃 G1 参考运动、VR 三点遥操作、SMPL 人体动捕,各自输出到同一个 64 维 FSQ 量化潜空间;下游只有一个动力学解码器,把 token + 本体感知解成 29 维关节动作,50 Hz 跑在真机上。

这个设计带来两个直接后果:

  1. 部署侧只需要一个策略。 换输入模态 = 换编码器,策略权重不变。C++ 部署栈里编码器和解码器是两个独立的 ONNX 文件(model_encoder.onnx / model_decoder.onnx),配一份 observation_config.yaml
  2. token 成了一个通用动作空间。 VLA 模型可以直接预测 64 维 token 而不是关节角 —— 这就是 GR00T N1.7 在这套系统上做全身操作的方式(2.5 Hz 出 token,SONIC 以 50 Hz 解码)。
graph TB subgraph Sources["三种运动来源"] G1REF["G1 参考运动
未来 10 帧 × 0.1 s
关节角 + 关键点"] TELE["VR 遥操作
头 + 双手 3 点位姿"] SMPL["SMPL 人体动捕
未来 10 帧 × 0.02 s"] end subgraph Encoders["编码器 (MLP 2048-1024-512-512, SiLU)"] E1["g1_mf_mlp"] E2["teleop_mlp"] E3["smpl_mlp"] end FSQ["FSQ 量化
2 token × 32 维
每维 32 级
= 64 维 token_flattened"] subgraph Decoders["解码器"] DDYN["g1_dyn_mlp (动力学)
2048-2048-1024-1024-512-512
[token_flattened, proprioception] → action"] DKIN["g1_kin_mf_mlp (运动学)
[token] → 未来指令
⚠️ 仅用于辅助损失"] end ENV["Isaac Lab ManagerBasedRLEnv
4096 并行环境
PPO"] DEPLOY["C++ / TensorRT 部署
50 Hz, ONNX"] G1REF --> E1 --> FSQ TELE --> E2 --> FSQ SMPL --> E3 --> FSQ FSQ --> DDYN FSQ --> DKIN DDYN --> ENV DDYN --> DEPLOY DKIN -.g1_recon 辅助损失.-> ENV style FSQ fill:#e1f5ff style DKIN fill:#fff4e1 style DDYN fill:#e8f5e9

2. 核心组件详解#

2.1 Universal Token:64 维从哪来#

模型卡里写"64 维潜运动 token",训练配置里却找不到 64 这个数。它是算出来的:

self.token_dim = self.num_fsq_levels          # 32
self.token_total_dim = self.token_dim * self.max_num_tokens   # 32 × 2 = 64

(gear_sonic/trl/modules/universal_token_modules.py)。对应配置:

配置项 文件
num_fsq_levels config/actor_critic/universal_token/all_mlp_v1.yaml 32
fsq_level_list 同上 32
max_num_tokens 同上 2
encoder.dimension 部署侧 observation_config.yaml 64

即:2 个 token,每个 32 维,每一维被 FSQ 量化到 32 个离散级别。FSQ(Finite Scalar Quantization)来自 vector_quantize_pytorch,相比 VQ-VAE 的码本查表,它直接对每个标量维度做有界四舍五入,没有码本坍塌问题,也不需要 commitment loss 和 EMA 更新 —— 对 RL 训练特别友好,因为不会出现"码本没更新导致梯度断流"。

模块暴露的特征名有两个:

特征名 维度 用途
token 32 带时间维,给 g1_kin 解码器
token_flattened 64 展平,给 g1_dyn 解码器和部署

UniversalTokenModule 的方法链:_create_frame_and_token_masksparse_tokenizer_obs_encode_single(逐编码器)→ assemble_all_tokensencode / decode / forward(..., latent_residual, latent_residual_mode) / get_token_info

多热编码器掩码。 create_encoder_masks 支持一个环境同时激活多个编码器 —— SMPL 原生的环境会同时打开 SMPL=1 和 G1=1 两个标志。这是"同一段运动、两种表示"的训练信号,也是后面 reencoded_smpl_g1_latent 损失的前提。

2.2 三个编码器#

三者结构完全相同,只有输入维度不同:

编码器 配置文件 输入
g1_mf_mlp config/encoder/g1_mf_mlp.yaml G1 未来参考帧(多帧)
teleop_mlp config/encoder/teleop_mlp.yaml VR 三点位姿
smpl_mlp config/encoder/smpl_mlp.yaml SMPL 未来帧

共同超参:

hidden_dims: [2048, 1024, 512, 512]
activation: SiLU
num_output_temporal_dims: ${max_num_tokens}     # 2

num_output_temporal_dims = max_num_tokens 是关键 —— 编码器直接输出 2 个时间槽位的特征,而不是先出一个大向量再切分。

训练时随机采样编码器:

encoder_sample_probs: {g1: 1.0, teleop: 1.0, smpl: 1.0}

三者等权。加上 teleop_sample_prob_when_smpl: 0.5(SMPL 环境有一半概率同时以遥操作形式呈现),保证任何一个编码器都不会训练不足。

2.3 两个解码器,只有一个上真机#

这是 SONIC 设计里最容易被忽略的一点。

g1_dyn_mlp(动力学解码器)—— 唯一上真机的那个:

inputs: ["token_flattened", "proprioception"]
outputs: ["action"]
has_temporal_dim: False
hidden_dims: [2048, 2048, 1024, 1024, 512, 512]

6 层、最宽 2048 —— 比编码器深得多,因为它要做的事更难:从"想做什么动作"(token)+ "现在什么状态"(本体感知)推出"下一步 29 个关节各转多少"。proprioception_features: ["actor_obs"] 表示本体感知就是 actor 的常规观测。

g1_kin_mf_mlp(运动学解码器)—— 只为损失存在:

inputs: ["token"]
outputs: 未来运动指令
has_temporal_dim: True

它不参与控制。它的唯一职责是把 token 解回"未来运动应该长什么样",与真值比对产生 g1_recon 辅助损失。有它在,token 就不能退化成"只对当前策略有用的黑盒特征",必须真的编码了运动的运动学内容 —— 这才让 token 具备跨模态、跨下游任务的通用性。

2.4 辅助损失:把三个编码器焊在同一个空间#

config/aux_losses/universal_token/g1_recon_and_all_latent.yaml:

损失项 系数 作用
g1_recon 0.01 g1_kin 重建未来运动 → token 保留运动学语义
g1_smpl_latent 1.0 G1 编码 ≈ SMPL 编码
g1_teleop_latent 1.0 G1 编码 ≈ 遥操作编码
teleop_smpl_latent 1.0 遥操作编码 ≈ SMPL 编码
reencoded_smpl_g1_latent 1.0 SMPL→动作→重新编码 后仍 ≈ 原 token

系数对比很说明问题:重建损失 0.01,对齐损失全是 1.0。也就是说训练目标的重心不在"token 能不能还原运动",而在"三个编码器必须收敛到同一个空间"。重建损失只是一个弱正则,防止空间退化。

第四项 reencoded_smpl_g1_latent 需要 reencode_smpl_g1_recon: true(在 sonic_release.yaml 的算法覆写里)。它构成一个闭环一致性约束:SMPL 运动 → SMPL 编码器 → token → 解码出动作 → 用 G1 编码器重新编码 → 应当回到原 token。这直接惩罚了"编码器学会作弊、把模态身份编进 token"的行为。

graph LR S["SMPL 运动"] --> ES["smpl 编码器"] --> T1["token"] G["同一段 G1 运动"] --> EG["g1 编码器"] --> T2["token"] V["同一段 VR 位姿"] --> EV["teleop 编码器"] --> T3["token"] T1 -.g1_smpl_latent 1.0.- T2 T2 -.g1_teleop_latent 1.0.- T3 T1 -.teleop_smpl_latent 1.0.- T3 T1 --> DEC["g1_dyn 解码"] --> A["动作"] --> ROLL["环境 rollout"] --> EG2["g1 编码器
重新编码"] --> T4["token'"] T4 -.reencoded_smpl_g1_latent 1.0.- T1 T2 --> KIN["g1_kin 解码"] --> R["未来运动"] -.g1_recon 0.01.- G style T1 fill:#e1f5ff style T2 fill:#e1f5ff style T3 fill:#e1f5ff

3. 训练流水线#

3.1 环境与算法#

训练跑在 Isaac Lab 的 ManagerBasedRLEnv 上,配置组合由 Hydra 完成:

+exp=manager/universal_token/all_modes/sonic_release

config/base.yaml 基础项:seed: 0,headless: True,num_envs: 4096,base_dir: logs_rl,sim_type: isaacsim,env_spacing: 20

PPO 超参(config/algo/ppo_im_phc.yaml):

epochs 5
mini-batches 4
clip 0.2
γ / λ 0.99 / 0.95
value_loss_coef 1.0
entropy_coef 0.01
actor lr 2e-5
critic lr 1e-3
学习率调度 adaptive,desired_kl: 0.01,范围 [1e-5, 2e-4]
num_steps_per_env 32(实验里覆写为 24)
init_noise_std 0.05
num_learning_iterations 100000
save_interval 500

注意 actor lr 2e-5 比 critic lr 1e-3 小了 50 倍。这在运动跟踪类 RL 里是常见做法:策略网络此时同时承担着"维持潜空间结构"的职责(辅助损失也从这里反传),学得太快会破坏编码器对齐;而 critic 只需要拟合价值函数,可以放开。

sonic_release.yaml 的算法覆写还包括:

use_clampped_std: true
std_clamp_min: 0.001
std_clamp_max: 0.5
max_grad_norm: 0.1

max_grad_norm: 0.1 相当激进(常见值是 0.5~1.0),配合 2e-5 的学习率,整体是一个"小步慢走"的策略 —— 符合"在已经对齐好的潜空间上做精细跟踪"这个目标。

3.2 实验配置要点(sonic_release.yaml)#

说明
num_envs 4096
project_name TRL_G1_Track
本体/动作历史 10 / 10
robot g1_model_12_dex 带灵巧手的 G1
terrain_type trimesh 非平地
num_future_frames 10 G1 参考未来 10 帧
dt_future_ref_frames 0.1 → 1.0 s 前瞻
smpl_num_future_frames 10
smpl_dt_future_ref_frames 0.02 → 0.2 s 前瞻
reward_point_body ["torso_link", "left_wrist_yaw_link", "right_wrist_yaw_link"] 跟踪奖励的参考点
对应偏移 [[0,0,0.5], [0,0,0], [0,0,0]] 躯干点上移 0.5 m,近似头部
feet_acc 权重 -2.5e-6 抑制脚部加速度,减少抖动
cat_upper_body_poses true(prob 0.5) 一半样本拼接上半身姿态指令
freeze_frame_aug true 冻结帧数据增强
teleop_sample_prob_when_smpl 0.5
adp_samp_failure_rate_max_over_mean 200 自适应采样:难样本上限倍率
motion_file data/motion_lib_bones_seed/robot_filtered
smpl_motion_file data/bones_seed_smpl
smpl_y_up true SMPL 数据是 Y-up

reward_point_body 的三点选择值得说一下:躯干 + 双腕。跟踪奖励不是逐关节角度误差,而是这三个点在世界系的位置误差 —— 这让策略有自由度用不同的关节组合达到同一个末端姿态,比逐关节跟踪宽松得多,也更符合"看起来像"的直觉。躯干点加 0.5 m 偏移把参考点抬到接近头部的位置,相当于额外约束了躯干的倾斜。

adp_samp_failure_rate_max_over_mean: 200 是自适应采样:失败率高的运动片段被更频繁地采样,但倍率上限 200 倍,防止某几条极难的片段吃光整个 batch。

3.3 训练五步#

docs/source/references/training_code.md 给出的流程:

graph LR S1["1. 准备运动数据
BONES-SEED → SOMA Retargeter"] --> S2["2. Hydra 组合配置
+exp=.../sonic_release"] S2 --> S3["3. PPO 训练
TRLAuxLossPPOTrainer
4096 env × 100k iter"] S3 --> S4["4. 评估
eval_agent_trl.py / eval_exp.py
CheckpointEvaluator"] S4 --> S5["5. 导出 ONNX
encoder + policy 分离"] S5 --> S6["C++ / TensorRT 部署"]

关键类:

职责
Actor / Critic 策略与价值网络
UniversalTokenModule 编码器/解码器/FSQ 全家桶
PolicyAndValueWrapper 训练时把二者打包
TRLPPOTrainer 标准 PPO
TRLAuxLossPPOTrainer PPO + 辅助损失,SONIC 实际用的
ManagerEnvWrapper Isaac Lab ManagerBasedRLEnv 适配
CheckpointEvaluator 批量评估 checkpoint

4. 部署#

4.1 两个发布模型#

docs/source/model_card.md:

默认 SONIC 低延迟 SONIC
未来帧数 10 4
帧间隔 20 ms 20 ms
前瞻时长 200 ms 80 ms
observation step step5 step1
checkpoint sonic_release/last.pt low_latency/last.pt
部署文件 顶层 model_encoder.onnx / model_decoder.onnx / observation_config.yaml low_latency/ 下同名三件套
token 维度 64 64
控制频率 50 Hz 50 Hz
输入模态 SMPL / G1-ref / VR-3pt

HuggingFace: nvidia/GEAR-SONIC。下载与启动:

python download_from_hf.py                # 默认模型 + 规划器
python download_from_hf.py --low-latency  # 低延迟版本

cd gear_sonic_deploy && ./deploy.sh --input-type zmq_manager real
# 低延迟版需显式指定 checkpoint 前缀与观测配置
./deploy.sh --cp policy/low_latency/model \
            --obs-config policy/low_latency/observation_config.yaml \
            --input-type zmq_manager real

官方特别强调:200 ms / 80 ms 是"参考前瞻窗口",不是端到端遥操作延迟 —— 后者还要加上感知、网络、预处理和推理。

前瞻时长是一个真实的权衡:200 ms 前瞻让动作更平滑、更能提前规划落脚点;80 ms 版本对遥操作的响应更跟手,但动作质量下降。

4.2 观测配置系统#

部署侧用 YAML 声明观测由哪些块拼成,命名规范是 {base}_{N}frame_step{S},其中 S 以 50 Hz 的 tick 计 —— step5 = 5 × 20 ms = 0.1 s。

三个典型配置的规模(docs/source/references/observation_config.md):

配置 策略输入 编码器输入
最小 154 维
token-based 64 + 3 + 29 + 29 + 29 290 + 290 + 60 + 10 = 650
VR 遥操作

token-based 策略输入的读法:64 维 token + 3 维(基座相关)+ 三个 29 维块(关节角、关节速度、上一动作)。这与 Decoupled WBC 的 86 维观测结构其实很像 —— 差别在于前 4 维的显式速度/高度指令被换成了 64 维 token。

C++ 侧每个观测块是一个注册函数:

bool MyObservation(std::vector<double>& target_buffer, size_t offset);

(gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/src/g1_deploy_onnx_ref.cpp)。注册表模式让加新观测不用改主循环。

4.3 运行#

just build
just run g1_deploy_onnx_ref <iface> <model> <motion_dir>
参数 取值
输入接口 keyboard / gamepad / gamepad_manager / zmq / zmq_manager / ros2 / manager
输出 zmq / ros2 / all
--encoder-file 编码器 ONNX
--planner-file 运动学规划器 ONNX(可选)
--policy-precision 推理精度
--disable-crc-check 调试用
--set-compliance 默认 0.5,0.5,0.0
--max-close-ratio 手部闭合上限

CSV 日志以 50 Hz 记录 base_quat / base_ang_vel / torso_quat / torso_ang_vel / q / dq / action

可视化:

python visualize_motion.py --realtime_debug_url tcp://localhost:5557

同屏显示 4 个机器人 —— 目标(彩色)、零平移目标(绿)、实测(红)、温度热力图。

4.4 运动学规划器(Planner ONNX)#

一个可选的上层模块:给定当前状态 + 高层导航指令,输出未来一串 MuJoCo qpos,再交给 SONIC 跟踪。它的接口(docs/source/references/planner_onnx.md)与 MotionBricks 高度同构:

输入 形状 说明
context_mujoco_qpos [1, 4, 36] 最近 4 帧 qpos(3 位置 + 4 四元数 wxyz + 29 关节)
target_vel [1] ≤ 0 用模式默认速度,> 0 覆写(m/s)
mode [1] int64 运动风格索引(idle / slowWalk / ...)
movement_direction [1, 3] 世界系移动方向
facing_direction [1, 3] 世界系朝向
height [1] ≤ 0 关闭高度控制
输出 形状
mujoco_qpos [1, N, 36](padding,只有前 num_pred_frames 帧有效)
num_pred_frames scalar int64

另有 5 个"高级输入"由 C++ 栈管理,不建议手动改。坐标系是 MuJoCo Z-up(X 前、Y 左、Z 上),所有输入都在世界系,规范化由模型内部完成。

规划器的训练代码和技术报告尚未发布,当前只开放 ONNX 接口。

4.5 坐标系约定#

docs/source/references/conventions.md 是踩坑高发区:

空间 Up 轴 四元数序
Isaac Lab / MuJoCo Z wxyz
SMPL / BVH Y
scipy Rotation xyzw

代码里 w_last=False 表示 wxyz。pose_aa 是轴角表示,按 MuJoCo body 顺序排列。两套关节序之间由 G1_ISAACLAB_TO_MUJOCO_DOF / G1_MUJOCO_TO_ISAACLAB_DOF 映射,运动 PKL 文件存的是 MuJoCo 序,由 order_converter.py 转换。


5. 与 VLA 的连接#

docs/source/tutorials/vla_workflow.md 描述的三段式工作流:

graph LR A["1. 采集
VR 遥操作 + 数据导出
→ LeRobot v2.1"] --> B["2. 微调
Isaac-GR00T N1.7"] --> C["3. 部署
PolicyServer + SONIC"]

动作空间就是 SONIC 的 token:

VLA (2.5 Hz) --64 维 × 40--> SONIC 解码器 (C++) --50 Hz--> 机器人

每次推理的完整动作是 78 维 = 64 维运动 token + 7 维左手 + 7 维右手。VLA 只需要决定"做什么",平衡、行走、全身协调这些"怎么做"全部由预训练的 SONIC 承担。2.5 Hz → 50 Hz 的 20 倍频率差,正是靠 40 步的 token chunk 填满的。

数据采集:

python gear_sonic/scripts/launch_data_collection.py \
    --camera-host 192.168.123.164 \
    --task-prompt "pick up the soda can and place it in the bin"

产出标准 LeRobot v2.1 目录(data/*.parquet + videos/observation.images.ego_view/*.mp4 + meta/{info,modality,episodes,tasks})。官方建议单任务 50–100 条演示。采集后必须跑 process_dataset.py 清掉录制时按 x 标记丢弃的 episode 和残留的 SMPL 帧。


6. 关键源文件表#

文件 作用
gear_sonic/trl/modules/universal_token_modules.py UniversalTokenModule:编码/量化/解码、编码器掩码、token 维度推导
gear_sonic/config/exp/manager/universal_token/all_modes/sonic_release.yaml 发布版实验配置(环境/奖励/数据/算法覆写)
gear_sonic/config/actor_critic/universal_token/all_mlp_v1.yaml FSQ 级数、token 数、编码器采样概率、辅助损失开关
gear_sonic/config/encoder/{g1_mf_mlp,teleop_mlp,smpl_mlp}.yaml 三个编码器
gear_sonic/config/decoder/g1_dyn_mlp.yaml 动力学解码器(唯一上真机)
gear_sonic/config/decoder/g1_kin_mf_mlp.yaml 运动学解码器(仅辅助损失)
gear_sonic/config/aux_losses/universal_token/g1_recon_and_all_latent.yaml 5 项辅助损失系数
gear_sonic/config/algo/ppo_im_phc.yaml PPO 超参
gear_sonic/config/base.yaml 全局默认(seed / num_envs / sim_type)
gear_sonic/scripts/launch_data_collection.py VR 遥操作数据采集
gear_sonic/scripts/process_dataset.py 数据集清洗
gear_sonic_deploy/src/g1/g1_deploy_onnx_ref/src/g1_deploy_onnx_ref.cpp C++ 部署主程序、观测注册表
docs/source/model_card.md 两个发布模型的规格
docs/source/references/training_code.md 目录结构、MDP 组件、关键类
docs/source/references/observation_config.md 观测块命名与维度表
docs/source/references/deployment_code.md just 命令、CLI 参数、日志与可视化
docs/source/references/planner_onnx.md 规划器 ONNX 输入输出规格
docs/source/references/conventions.md 坐标系与关节序约定
docs/source/tutorials/vla_workflow.md 采集 → 微调 → 部署

7. 设计取舍小结#

决策 收益 代价
FSQ 而非 VQ-VAE 无码本坍塌、无 commitment loss、RL 友好 表达力受离散级数限制(32 级)
2 × 32 而非 1 × 64 保留时间结构,g1_kin 可按 token 展开 部署侧要展平,多一层约定
对齐损失 1.0 / 重建 0.01 强制跨模态共享空间 token 的可解释性弱
g1_kin 只训不部署 保证 token 有运动学语义 训练开销增加
actor lr 比 critic 小 50 倍 保护已对齐的潜空间 收敛慢,需 100k iter
三点位置奖励而非逐关节 允许多样的关节解 关节层面可能出现奇怪姿态

Decoupled WBC 的路线对比、以及在整个 GR00T-WholeBodyControl 仓库中的定位,见 GR00T-WBC 总览