三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

SONIC 核心原理讲解 - S-X

SONIC 核心原理讲解 - S-X

SONIC 核心原理讲解:让人形机器人自然地完成全身动作

1. 先用一张图看懂 SONIC

SONIC 不是“输入一句话,直接输出电机力矩”的端到端大模型。

它更像机器人身体里的一个通用动作执行器:上层告诉它“接下来想怎么动”,它负责结合机器人的真实状态,把动作稳定地做出来。

flowchart LRA["上层给出参考动作<br/>规划器 / VR / 人体动作 / VLA"]B["动作编码器<br/>把不同格式翻译成同一种语言"]C["统一运动 token<br/>描述想做什么"]D["控制解码器"]E["机器人本体反馈<br/>描述现在怎么样"]F["目标关节角"]G["PD 控制器"]H["真实机器人"]A --> B --> C --> D --> F --> G --> HH -- "IMU 与关节反馈" --> E --> D

一句话概括:

SONIC 把各种参考动作翻译成统一的运动意图,再结合机器人当前状态,每个控制周期重新决定下一步关节该怎么动。

这里有两个信息不能混在一起:

信息 回答的问题 在 SONIC 中的形式
参考动作 “我想做什么?” 统一运动 token
本体反馈 “我现在是什么状态?” IMU、关节状态和历史动作

token 不是完整控制命令。它只表达动作意图;真正的控制动作必须结合实时反馈才能得到。


2. SONIC 到底解决了什么问题

2.1 传统做法为什么难以扩展

过去常见的方式是:每增加一种能力,就重新设计一套任务、奖励和控制器。

flowchart TBsubgraph OLD["传统方式"]W["走路奖励"] --> WP["走路策略"]J["跳跃奖励"] --> JP["跳跃策略"]D["跳舞奖励"] --> DP["跳舞策略"]C["爬行奖励"] --> CP["爬行策略"]endsubgraph SONIC["SONIC 的方式"]M["大量跑、跳、舞蹈、爬行等动作"] --> T["统一任务:跟住参考动作"]T --> P["一个通用策略"]end

传统路线的问题不是单个动作做不好,而是每个新动作都要重新开发,因此很难随着数据和算力持续扩大能力。

2.2 SONIC 的第一个核心判断

SONIC 把运动跟踪选作人形机器人的基础任务。

所谓运动跟踪,就是:

给机器人一段目标动作,让它在物理世界中尽量跟上,同时保持平衡、接触合理、动作平滑。

为什么这个任务适合扩大规模?

flowchart LRA["海量人体动作数据"] --> B["每一帧都有目标姿态"]B --> C["无需为每个动作单独写奖励"]C --> D["同一个训练目标可覆盖大量动作"]D --> E["数据、模型和算力可以一起扩展"]

动作捕捉数据中,每一帧都告诉系统头、手、脚和身体应该位于哪里。与只有“任务成功或失败”的稀疏信号相比,这是一种非常密集的监督。

因此,SONIC 可以把走路、跑步、下蹲、舞蹈、拳击等动作放进同一套训练流程,而不是把它们当成互不相关的任务。

论文把这一思路扩展到超过 \(1\) 亿帧动作、最大约 \(42\mathrm{M}\) 参数的控制模型和 \(128\) 张 GPU 的并行训练。规模本身不是方法的全部,但它验证了:运动跟踪确实能成为可扩展的基础任务。


3. 三个最核心的设计

SONIC 的方法可以拆成三个相互配合的设计。

flowchart TBA["核心一<br/>用大规模运动跟踪学习通用身体能力"]B["核心二<br/>把不同动作格式对齐到统一 token"]C["核心三<br/>token 与实时本体反馈共同形成闭环控制"]A --> B --> C

3.1 核心一:先学会“执行任何参考动作”

SONIC 不直接学习“拿苹果”“走到门口”这些高级任务。

它先学习一个更基础、更通用的能力:

只要有人给出合理的参考动作,我就尽量在真实动力学约束下把它执行出来。

训练不是简单播放关节轨迹,因为机器人还必须处理:

  • 重力、惯性和碰撞;
  • 单脚支撑时的平衡;
  • 脚与地面的接触;
  • 参考动作与机器人身体结构之间的差异;
  • 受到扰动后如何恢复。

这些能力通过物理仿真中的强化学习获得。

3.2 核心二:不同动作格式共用一种“运动语言”

上层系统给出的动作可能长得完全不同:

  • 机器人规划器给出 G1 的未来关节动作;
  • 人体动作估计器给出 SMPL 人体骨架;
  • VR 只给出头和双手,再搭配下半身规划结果。

如果分别训练三个控制器,它们的数据和能力无法充分共享。SONIC 的做法是给每种输入配一个“翻译器”,但让它们翻译到同一个运动空间。

flowchart LRR["G1 参考动作"] --> ER["机器人动作编码器"]H["SMPL 人体动作"] --> EH["人体动作编码器"]V["头、手与下身混合命令"] --> EV["遥操作编码器"]ER --> Z["统一运动 token"]EH --> ZEV --> ZZ --> D["共享控制解码器"]

这可以类比成三个人说三种语言:

flowchart LRA["机器人动作语言"] --> T["翻译成共同语义"]B["人体骨架语言"] --> TC["VR 稀疏关键点语言"] --> TT --> K["同一个机器人控制器听懂并执行"]

同一个动作,例如“向前迈左脚并抬起右手”,无论来自 G1 轨迹、人体骨架还是 VR,都应该被编码成相近的运动语义。

token 在这里起什么作用

动作编码器先得到连续特征,再用有限标量量化(FSQ)压成统一 token:

\[z_t=Q_{\mathrm{FSQ}}\!\left(E_i(g_t^{(i)})\right) \]

其中:

  • \(g_t^{(i)}\) 是第 \(i\) 种格式的参考动作;
  • \(E_i\) 是对应的动作编码器;
  • \(Q_{\mathrm{FSQ}}\) 是量化器;
  • \(z_t\) 是共享的运动 token。

论文使用两个 token,每个 token 有 \(32\) 个量化标量,控制时展平为 \(64\) 维。理解方法时不必记住这个数字,只需要记住:

token 是紧凑的动作意图,不是某个具体机器人的完整关节命令。

FSQ 的主要价值是提供一个稳定、有限、容易被其他模型预测的接口,同时避免普通可学习码本可能出现的码本坍塌。

3.3 核心三:动作意图与真实状态必须闭环结合

只有参考动作是不够的。

假设 token 表示“继续向前迈步”,机器人可能处于完全不同的状态:

flowchart TBZ["相同意图:继续向前迈步"]S1["身体直立"] --> A1["正常落脚"]S2["身体向左倾斜"] --> A2["腿和躯干向右补偿"]S3["右脚刚被撞到"] --> A3["重新调整支撑与步幅"]Z --> S1Z --> S2Z --> S3

因此,控制解码器同时读取:

  1. token:希望执行什么动作;
  2. 本体状态历史:机器人实际处于什么状态。

控制策略可以简写为:

\[a_t=D_c\!\left(z_t,\,o_{t-k:t}^{\mathrm{prop}}\right) \]

其中 \(o_{t-k:t}^{\mathrm{prop}}\) 包含最近若干帧的 IMU、关节角、关节速度和历史动作。使用一小段历史而不是只看当前帧,是为了判断机器人正在向哪边倒、正在加速还是减速。

模型还会读取短期的未来参考动作,而不仅是当前目标。这样它能提前准备抬脚、转身和落地,而不是等动作发生后再追赶。

flowchart LRP["过去几帧本体状态<br/>身体正在怎样运动"] --> D["控制解码器"]F["未来几帧参考动作<br/>接下来想怎样运动"] --> E["动作编码器"]E --> Z["运动 token"] --> DD --> A["下一拍目标关节角"]

4. 模型结构:其实只有两条主支路

从顶层看,SONIC 的 Actor 并不复杂。

flowchart TBsubgraph COMMAND["动作意图支路"]G["短期参考动作"] --> E["对应的 MLP 编码器"]E --> Q["FSQ 量化"] --> Z["统一运动 token"]endsubgraph FEEDBACK["机器人反馈支路"]I["IMU"] --> H["本体状态历史"]J["关节角与速度"] --> HA0["上一拍动作"] --> HendZ --> C["共享控制解码器"]H --> CC --> A["目标关节位置"]

4.1 输入是什么

输入只有两大类:

输入 内容 作用
参考动作 一小段当前及未来动作 告诉策略目标与运动趋势
本体历史 IMU、关节角、关节速度、上一动作 告诉策略真实状态与短期动态

参考动作会经过对应编码器;本体状态不经过动作编码器,而是直接送入共享控制解码器。

4.2 模型输出是什么

对于论文中的 Unitree G1,Actor 输出 \(29\) 个关节的位置目标,而不是直接输出电机力矩。

可以把它理解为:

\[q_t^{\mathrm{target}}=q^{\mathrm{default}}+a_t \]

底层 PD 控制器再根据目标关节角和当前关节状态计算力矩:

\[\tau_t=K_p\left(q_t^{\mathrm{target}}-q_t\right)-K_d\dot q_t \]

flowchart LRA["SONIC 输出关节位置目标"] --> P["PD 控制器"]S["当前关节角与速度"] --> PP --> T["电机力矩"] --> R["机器人运动"]

这种分工让神经网络负责全身动作与平衡,让成熟的关节控制器负责快速、稳定的电机执行。

4.3 Actor 与 Critic 为什么看到的信息不同

训练采用不对称 Actor-Critic:

  • Actor 只能看真机上能获得的信息,因此可以直接部署;
  • Critic 在训练时可以看无噪声的完整仿真状态,从而更准确地评价动作好坏;
  • 部署时只保留 Actor,不需要 Critic。
flowchart LRO["真机可获得的带噪观测"] --> A["Actor"] --> ACT["关节目标"]O --> C["Critic"]X["仅仿真可见的完整状态"] --> C --> V["价值估计"]

5. 训练:同时学会“做得出来”和“说同一种动作语言”

这是理解 SONIC 最关键的部分。

SONIC 不是只靠一个损失训练,而是同时解决两个问题:

flowchart TBL["联合训练目标"]P["PPO<br/>动作在物理世界里能否稳定执行"]S["表征学习<br/>不同输入是否表达同一个动作语义"]R["重建损失"]A["对齐损失"]C["循环一致性损失"]L --> PL --> SS --> RS --> AS --> C

5.1 PPO:教会机器人真正把动作做出来

在物理仿真中,Actor 输出关节目标,机器人执行后获得奖励。

奖励的核心是让真实状态接近参考动作,主要比较:

  • 根节点的位置和朝向;
  • 全身各刚体的位置和朝向;
  • 身体的线速度和角速度;
  • 头、双手和双脚等关键末端的位置。

同时加入平滑与安全惩罚,例如动作突变、关节超限、不合理接触、头手抖动和脚部冲击。

概念上可以写成:

\[r_t=r_{\mathrm{track}}-p_{\mathrm{unsafe}}-p_{\mathrm{jitter}} \]

PPO 根据长期累计奖励更新策略:

\[\max_\pi\;\mathbb E\!\left[\sum_t\gamma^t r_t\right] \]

它真正教会模型的不是“姿态长得像”,而是:在接触、惯性、重力和平衡约束下,怎样把姿态做出来。

5.2 重建损失:保证 token 里真的有动作信息

SONIC 额外训练一个辅助运动解码器,让任何一种输入产生的 token 都能重建对应的 G1 参考动作。

flowchart LRH["人体动作"] --> E["人体编码器"] --> Z["统一 token"]Z --> D["辅助运动解码器"] --> G["预测的 G1 参考动作"]T["同步的 G1 参考真值"] --> M["比较误差"]G --> M

这一步有两个作用:

  1. 防止 token 变成难以解释的控制捷径;
  2. 让人体动作到机器人动作的转换被隐式学进网络,运行时不必再做显式在线重定向。

5.3 对齐损失:同一个动作应得到相近表示

训练数据会把同一段动作同步表示成 G1、SMPL 和混合遥操作格式。

三个编码器看到的是不同数据,但它们的潜变量应该彼此接近:

\[\mathcal L_{\mathrm{align}}= \lVert u_r-u_h\rVert_2^2+ \lVert u_r-u_m\rVert_2^2+ \lVert u_h-u_m\rVert_2^2 \]

这里 \(u_r\)\(u_h\)\(u_m\) 分别是机器人、人体和混合输入的连续潜变量。

直觉上,这个损失是在要求三个“翻译器”对同一个动作给出相近译文。

5.4 循环一致性:翻译过去再翻译回来,含义不能变

人体动作先编码为 token,再解码为 G1 动作,最后重新用 G1 编码器编码。得到的运动语义应该与原本的 G1 语义一致。

flowchart LRH["人体动作"] --> EH["人体编码器"] --> ZH["人体 token"]ZH --> D["解码成 G1 动作"] --> ER["G1 编码器"] --> ZB["回译后的 token"]ZR["同步 G1 的原始 token"] --> C["检查是否一致"]ZB --> C

它约束的是“动作含义”在跨身体表示的转换中不要丢失。

5.5 总训练目标

把上述目标合在一起:

\[\mathcal L= \mathcal L_{\mathrm{PPO}}+ \lambda_{\mathrm{rec}}\mathcal L_{\mathrm{recon}}+ \lambda_{\mathrm{align}}\mathcal L_{\mathrm{align}}+ \lambda_{\mathrm{cycle}}\mathcal L_{\mathrm{cycle}} \]

最重要的理解是:

学习信号 教会模型什么
PPO 动作在真实物理中可执行、可平衡
重建 token 保留完整动作信息
对齐 不同输入格式共享同一运动语义
循环一致性 跨人体与机器人表示转换时语义不漂移

5.6 一次训练迭代

flowchart TDA["采样一段参考动作"] --> B["构造 G1、人体或混合命令"]B --> C["编码并量化成统一 token"]C --> D["结合本体状态输出关节目标"]D --> E["物理仿真执行"]E --> F["计算跟踪奖励与安全惩罚"]F --> G["收集一批轨迹"]G --> H["计算 PPO 损失"]B --> I["计算重建、对齐与循环损失"]H --> J["联合反向传播"]I --> JJ --> A

训练伪代码如下:

# 伪代码:只保留 SONIC 的核心训练逻辑
初始化 三个动作编码器
初始化 FSQ量化器、控制解码器、辅助运动解码器
初始化 只在训练中使用的Criticfor 每次训练迭代:轨迹批次 = []for 每个仿真控制步:同步动作 = 采样动作片段()输入类型 = 随机选择(G1, SMPL, 混合遥操作)参考命令 = 按输入类型构造短期动作窗口(同步动作)本体历史 = 读取机器人最近状态()潜变量 = 对应编码器(参考命令)运动token = FSQ量化(潜变量)关节目标 = 控制解码器(运动token, 本体历史)下一状态, 奖励 = 物理仿真执行(关节目标)保存到轨迹批次(下一状态, 奖励, 关节目标)PPO损失 = 根据轨迹批次计算PPO()# 同一动作的三种表示应说同一种“运动语言”机器人潜变量 = 机器人编码器(同步动作.G1表示)人体潜变量 = 人体编码器(同步动作.SMPL表示)混合潜变量 = 混合编码器(同步动作.遥操作表示)重建损失 = 计算G1动作重建误差()对齐损失 = 计算三种潜变量的两两距离()循环损失 = 计算人体到G1再编码的一致性()总损失 = PPO损失 + 重建损失 + 对齐损失 + 循环损失反向传播并更新网络(总损失)

5.7 怎样让仿真训练能迁移到真机

仅在固定仿真条件下训练,策略会过度依赖“完美世界”。SONIC 在训练时随机改变摩擦、初始姿态、身体参数和目标动作,并随机给机器人施加扰动。

flowchart LRA["仿真中不断变化<br/>摩擦 / 初态 / 质量 / 扰动 / 观测噪声"]B["策略不能记住唯一动力学"]C["学会依赖反馈实时纠正"]D["更容易迁移到真实机器人"]A --> B --> C --> D

训练还会提高失败动作的采样概率,让模型反复练习难例,而不是被海量简单走路片段淹没。


6. 推理:每 \(20\,\mathrm{ms}\) 重做一次决定

真实机器人上,SONIC 以约 \(50\,\mathrm{Hz}\) 运行闭环控制。

sequenceDiagramparticipant U as 上层动作来源participant E as 动作编码器participant C as 控制解码器participant P as PD 控制器participant R as 真实机器人loop 每个控制周期U->>E: 当前与短期未来参考动作E->>C: 统一运动 tokenR->>C: IMU 与关节状态历史C->>P: 下一组目标关节角P->>R: 执行关节控制end

推理伪代码:

# 伪代码:真实机器人上的闭环推理
while 机器人正在运行:当前状态 = 读取IMU与关节传感器()本体历史缓冲区.加入(当前状态)参考动作 = 读取上层给出的最新动作()输入类型 = 判断参考动作格式()动作窗口 = 取得当前与短期未来参考帧(参考动作)潜变量 = 对应编码器(动作窗口)运动token = FSQ量化(潜变量)关节动作 = 控制解码器(运动token, 本体历史缓冲区)目标关节角 = 默认姿态 + 关节动作发送给PD控制器(目标关节角)等待下一个控制周期()

训练时 Actor 会从动作分布中采样以便探索;推理时直接使用分布均值,使控制保持确定和稳定。

一个最直观的例子

假设参考动作要求机器人“向前迈步并抬起右手”。

  • 没有受到扰动时,控制器正常迈步并抬手;
  • 身体向左倾时,token 没变,但本体状态变了,控制器会加入向右的平衡补偿;
  • 脚碰到障碍时,本体状态再次变化,下一拍关节目标也随之改变。

所以 SONIC 不是播放录好的动作,而是每一拍都在重新解决“怎样从当前真实状态继续完成目标动作”


7. 为什么 VR、文本、视频和 VLA 都能接入

SONIC 把“产生动作”和“执行动作”解耦了。

flowchart LRK["键盘或手柄"] --> P["运动规划器"]T["文本、音乐或视频"] --> G["人体动作生成或估计模型"]V["VR 头手设备"] --> M["混合动作命令"]L["VLA"] --> Z["运动 token 或人体动作"]P --> S["SONIC"]G --> SM --> SZ --> SS --> R["真实机器人闭环执行"]

各模块的职责是:

模块 负责什么
规划器、生成模型或 VLA 决定接下来想做什么动作
SONIC 把参考动作变成动力学上可执行的全身控制
PD 与电机 快速执行关节目标

统一 token 的意义就在这里:新的上层模型不必学习全部接触和平衡细节,只要输出 SONIC 能理解的动作表示即可。

需要特别注意:

SONIC 本身不是文本生成动作模型,也不是导航规划器。它是连接各种动作来源与真实机器人身体的通用低层控制基础。


8. 为什么这个方法有效

可以从一条完整因果链理解:

flowchart TDA["大量且多样的动作数据"] --> B["逐帧运动跟踪提供密集监督"]B --> C["PPO 学到平衡、接触和动力学执行"]C --> D["策略获得通用的身体控制能力"]D --> E["多编码器把不同输入对齐到统一语义"]E --> F["各种上层系统可以复用同一控制器"]D --> G["实时本体反馈形成闭环"]G --> H["受到扰动时仍能重新调整动作"]

如果删除其中某个关键设计,会发生什么:

缺少的设计 典型后果
大规模逐帧跟踪 控制器只会少量人工定义的动作
PPO 物理训练 动作看起来像,但可能无法保持平衡或正确接触
多编码器对齐 每种输入格式都需要单独训练控制器
token 重建与循环约束 共享表示可能丢失动作语义或跨模态漂移
本体反馈闭环 控制退化成动作回放,受到扰动就容易失败
Domain Randomization 仿真中表现好,真机上对误差非常敏感

9. 方法边界

SONIC 很强,但它不是万能模块。

  • 它擅长执行参考动作,不负责理解复杂场景和长期任务;
  • 上层给出的动作必须基本合理,明显不可达的目标仍可能失败;
  • 极端外力、陌生地形和超出训练分布的动作仍可能破坏平衡;
  • 仿真到真实的差距只能被缓解,不能被完全消除;
  • token 是通用动作接口,但不是自然语言 token,也不是完整世界模型。

理解这些边界,有助于把 SONIC 放在正确的位置:它是身体控制基础模型,不是包办感知、推理、规划和控制的一体化系统。


10. 最容易混淆的五个问题

Q1:SONIC 是动作生成模型吗?

不是。它主要是动作跟踪与执行模型。动作可以由规划器、人体动作模型、VR 或 VLA 生成。

Q2:token 就是关节角吗?

不是。token 表达紧凑的运动意图;控制解码器还要结合机器人当前状态,才能输出关节目标。

Q3:人体动作输入是否还需要运行时 IK 或重定向?

通常不需要显式在线重定向。训练时利用同步的人体与 G1 动作,让人体编码器和辅助解码器学会这种映射。

Q4:为什么不能直接播放参考关节轨迹?

因为真实机器人会受到重力、接触误差、传感器噪声和外部扰动。SONIC 使用实时反馈不断修正,而轨迹播放通常是开环的。

Q5:为什么既要 PPO,又要重建和对齐损失?

PPO 保证动作在物理世界里可执行;重建、对齐和循环一致性保证不同输入真的共享同一种动作语义。两类目标解决的是不同问题。


11. 最终心智模型

flowchart TBA["上层系统决定想做什么"] --> B["产生一小段参考动作"]B --> C["对应编码器翻译成统一运动 token"]C --> D["控制解码器同时读取真实本体状态"]D --> E["输出下一拍目标关节角"]E --> F["PD 控制器驱动机器人"]F -- "传感器反馈" --> DG["大规模动作跟踪与 PPO"] -- "教会物理执行" --> DH["重建、对齐与循环损失"] -- "教会共同动作语义" --> C

最后只需要记住三句话:

  1. 运动跟踪是基础任务:用海量逐帧动作监督,训练一个能执行大量全身动作的通用策略。
  2. 统一 token 是动作接口:机器人动作、人体动作和稀疏遥操作都被翻译成同一种运动语义。
  3. 控制始终是反馈闭环:token 说明“想做什么”,本体状态说明“现在怎么样”,控制器每一拍重新决定“此刻该怎么做”。

这三点,就是 SONIC 方法最核心的原理。

← 返回列表