现在开始整个系列的 Phase 0 + Phase 1。这一部分会刻意放慢一点,因为后面 Universal Token、PPO 和 deployment 都建立在这里的数据定义之上。
这次直接以当前 main 分支代码为准,而不是只根据 SONIC report 复述。当前 repo 已经发展到包含 Default SONIC、Low-latency SONIC 和 v1.1,因此个别参数会明确区分“原始 SONIC”和“当前代码”。(GitHub)


Phase 0: VLA → SONIC 的边界

0.1 最终部署系统

当前 repo 给出的 VLA inference 是一个很好的入口:

                 Camera / Prompt
                       │
                       ▼
                ┌─────────────┐
                │    VLA      │
                │ Isaac-GR00T │
                └──────┬──────┘
                       │
                 action / motion
                       │
                       ▼
              ┌─────────────────┐
              │      SONIC      │
              │  C++ WBC        │
              └────────┬────────┘
                       │
                  joint control
                       │
                       ▼
                      G1

launch_inference.py 实际上就是把这个系统串起来:它启动 C++ SONIC deployment,同时启动 Python VLA inference client、keyboard publisher,以及可选的数据 exporter。(GitHub)

所以这里首先要纠正一个非常容易产生的误解:SONIC 并不负责“理解 pick up the cup”。

例如:

"pick up the cup"
        │
        ▼
  VLA [task → action / motion intent]
        │
        ▼
   motion/action
        │
        ▼
  SONIC [motion intent / reference → physically executable whole-body behavior]
        │
        ▼
    G1 motion

这也是为什么 NVIDIA 可以把两者独立训练、独立部署。当前官方 VLA workflow 就是:

teleop data
    ↓
fine-tune Isaac-GR00T
    ↓
PolicyServer
    ↓
launch_inference.py
    ↓
  SONIC
    ↓
   G1

而不是重新训练一个端到端:image + language → motor torque 的巨大模型。(GitHub)


0.2 重要发现:SONIC Action Space

这里开始出现一个非常有意思的接口。当前 VLA Inference 文档明确写:

unitree_g1_sonic 的 action space 是 78 dimensions:(GitHub)

64-dim motion token
+
7-dim left hand joints
+
7-dim right hand joints
=
78

这意味着:

                 VLA
                  │
                  ▼
        ┌──────────────────┐
        │   78-D action    │
        ├──────────────────┤
        │ 64-D motion token│
        │ 7-D left hand    │
        │ 7-D right hand   │
        └────────┬─────────┘
                 │
                 ▼
               SONIC

这说明一个非常重要的架构事实:SONIC 的 64-D token 并不只是内部 latent。至少在当前 VLA 接口中,它已经成为一个可以由上层 policy 操作的 action-level interface

问题:

Universal Token 到底是 latent representation,还是 robot control interface?

初步判断是:

两者兼具。

但我们后面必须通过代码把它证明出来,而不是先下结论。


0.3 SONIC 并不是“VLA 后面的 PD Controller”

这一点也非常重要。传统机器人 pipeline:

VLA
 ↓
target pose
 ↓
IK
 ↓
joint target
 ↓
PD
 ↓
motor

SONIC 更像:

VLA / Motion Source
        ↓
 motion representation
        ↓
 ┌──────────────────────┐
 │       SONIC          │
 │                      │
 │ representation       │
 │       ↓              │
 │ whole-body policy    │
 │       ↓              │
 │ robot action         │
 └──────────┬───────────┘
            ↓
        low-level

所以 SONIC 的贡献不是做了一个更好的 IK,而是把 whole-body coordination 本身学习成一个 generalist policy。这和 SONIC report 的定位一致:官方现在把 SONIC 定义为 humanoid behavior foundation model,通过 scalable motion tracking 从大规模 human motion 中学习统一的 whole-body behavior。(GitHub)


Phase 1:Motion Reference → Observation

现在正式进入源码。整个 SONIC 从这里开始,因为:

policy 学什么,首先由 observation 决定。


1.1 Training 的真正入口

当前训练命令:

python gear_sonic/train_agent_trl.py \
    +exp=manager/universal_token/all_modes/sonic_release

(GitHub)

入口:gear_sonic/train_agent_trl.py

train_agent_trl.py
       │
       ├── Hydra config
       │
       ├── environment
       │
       ├── actor
       │
       ├── critic
       │
       └── trainer

然后 Hydra 加载 architecture:

config/base.yaml
        │
        ▼
exp/manager/universal_token/all_modes/sonic_release.yaml
        │
        ├── algo
        ├── actor_critic
        ├── manager_env
        ├── observations
        ├── rewards
        ├── terminations
        ├── events
        └── aux_losses

官方 training-code 文档已经把这条 composition chain 明确列出来。(GitHub)


1.2 Configuration hierarchy

当前 release 的核心配置是:

sonic_release
│
├── algo
│   └── ppo_im_phc
│
├── actor_critic
│   └── universal_token/all_mlp_v1
│
├── manager_env
│   │
│   ├── observations
│   │   ├── tokenizer
│   │   ├── policy
│   │   └── critic
│   │
│   ├── rewards
│   │
│   ├── terminations
│   │
│   └── events
│
├── aux_losses
│
└── trainer
    └── PPO + auxiliary loss

(GitHub)

这已经开始暴露出 SONIC 的核心结构:

                Environment
                     │
        ┌────────────┼────────────┐
        │            │            │
        ▼            ▼            ▼
    tokenizer      policy       critic
        │            │            │
        ▼            ▼            ▼
     Encoder       Actor        Value
        │
        ▼
 Universal Token
        │
        ▼
    Decoder

注意:tokenizer 和 policy 是两个不同的 observation group。


1.3 SONIC 的 Observation 并不是一个 vector

看当前:gear_sonic/envs/manager_env/mdp/observations.py。它定义了大量 observation terms。比如 policy group 里包括:

joint_pos
joint_vel
base_ang_vel
gravity_dir
actions

以及大量 motion-reference terms:

command_multi_future
command_multi_future_joint_pos
command_multi_future_joint_body_pos
command_multi_future_joint_body_diff_pos
...
smpl_joints_multi_future
...
motion_anchor_ori_b_mf
...

同时还有 teleoperation:

vr_3point_target_multi_future
vr_3point_orn_target_multi_future
vr_wrists_local_pos_target
vr_wrists_local_orn_target
vr_head_local_orn_target

等等。(GitHub)

因此更准确的抽象应该是:

                         Observation
                              │
        ┌─────────────────────┼─────────────────────┐
        │                     │                     │
        ▼                     ▼                     ▼
   Motion Reference       Robot State          Teleop / SMPL
        │                     │                     │
        └─────────────────────┬─────────────────────┘
                              ▼
                      observation dict

然后再根据用途拆成:

obs_dict
│
├── tokenizer
├── policy
├── critic
├── ...

1.4 为什么要拆成 tokenizer / policy / critic?

这是第一个值得研究的设计。官方文档明确:

Policy

给 actor 的:

joint_pos
joint_vel
base_ang_vel
gravity_dir
last_actions
...

Critic

可以拿到 privileged information:

base_lin_vel
body_pos
body_ori
...

Tokenizer

给 UniversalTokenModule:

multi-future joint commands
SMPL joints
VR targets
anchor orientations
...

(GitHub)

因此:

                 Environment
                      │
              ┌───────┴───────┐
              │               │
         motion ref       robot state
              │               │
              ▼               ▼
        ┌──────────┐    ┌──────────┐
        │ Tokenizer│    │  Policy  │
        │   Obs    │    │   Obs    │
        └────┬─────┘    └────┬─────┘
             │               │
             ▼               │
        Universal            │
          Token              │
             │               │
             └──────┬────────┘
                    ▼
                 Actor

这个结构说明:

SONIC 的 motion representation learning 和 control policy 并不是完全相同的东西。

它们之间存在一个明确的 bottleneck:

motion observations
        ↓
   tokenizer
        ↓
 universal token
        ↓
      actor

这就是下一阶段要重点扒开 Universal Token 的原因。


1.5 Motion Reference 来自哪里?

进入:

gear_sonic/envs/manager_env/mdp/commands.py

核心类:

TrackingCommand

当前代码对它的定义非常明确:

TrackingCommand 是 SONIC-style motion tracking RL environment 的 primary command term。

它负责:

  • 加载 motion library
  • sample motion ID
  • sample episode start time
  • 每个 simulation step 推进 motion cursor
  • 提供 current reference
  • 提供 multi-future reference
  • 提供 joint/body/root/SMPL 等数据
  • 做 robot-local / egocentric / heading-canonicalized transformation。(GitHub)

所以整个 motion pipeline 实际上是:

                 Motion Dataset
                       │
                       ▼
                 Motion Library
                       │
                       ▼
                 TrackingCommand
                       │
              ┌────────┴────────┐
              │                 │
         current frame      future frames
              │                 │
              └────────┬────────┘
                       ▼
                  observations

1.6 Future Reference 是怎么生成的?

这里终于可以把 report 里的“lookahead”落实到代码。

当前配置:

target_fps = 50 Hz
num_future_frames = 10
dt_future_ref_frames = 0.1 s

来自当前 configuration.md。(GitHub)

但是这里有一个非常值得注意的细节

当前 training config 的 dt_future_ref_frames0.1 s

也就是说训练配置中的未来 reference:

t
t + 100ms
t + 200ms
...

而模型 card 对 released deployment model 描述的是:

10 future frames
20 ms spacing
≈ 200 ms lookahead

(GitHub) 因此:不要简单把 README 的 release checkpoint observation protocol 和当前 training config 当成完全相同的东西。这是我们后面必须仔细追的一个版本差异。

TrackingCommand 内部实际上按照:

frame_skips =
    dt_future_ref_frames * target_fps

计算未来帧间隔,然后:

future_time_steps_init =
    arange(num_future_frames) * frame_skips

也就是:

future_time_steps
=
[0, frame_skip, 2*frame_skip, ...]

(GitHub) 这才是实际 motion sampling mechanism。


1.7 所以一个 Motion Clip 到底发生了什么?

假设某条 motion:

M = {x0, x1, x2, ..., xN}

每个 motion frame 是:

x_t =
{
  root pose
  body pose
  joint pose
  velocity
  orientation
  ...
}

episode reset 时:

motion_id = k
start_time = t0

然后第 n 个 RL step:

current_time = t0 + n * Δt

TrackingCommand 给出:

x(t)
x(t + Δt_ref)
x(t + 2Δt_ref)
...
x(t + HΔt_ref)

因此 policy 看到的不是:

“机器人现在应该在哪里”

而更接近:

“机器人现在在哪里
 +
 未来一段时间 reference 会怎么变化”

这其实是 SONIC 的一个非常重要的 inductive bias:

control policy 是 trajectory-conditioned,而不是 single-pose-conditioned。


1.8 更重要:Reference 会被变换到机器人坐标系

observations.py 里面大量出现:

*_b
*_l
*_w
*_mf

这几个后缀非常值得我们在以后形成条件反射:

w = world frame
b = body / local frame
l = local / heading-related frame
mf = multi-future

例如:

motion_anchor_ori_b_mf

可以理解成:

motion anchor orientation
    ↓
body/local frame
    ↓
multiple future frames

而:

smpl_joints_multi_future

就是:

SMPL joints
    ↓
multiple future frames

1.9 为什么不直接把 World-frame Pose 给 Policy?

这是 SONIC 非常典型的 robot learning design。假设:

reference:
    root = (10m, 5m)

policy 其实不应该关心:“机器人在世界坐标的 x=10, y=5。”

它应该关心: “reference 相对于我现在的位置在哪里?”

所以代码大量做:

world frame
   ↓
anchor frame
   ↓
robot local frame

例如 motion_anchor_gravity_dir()

world gravity
      ↓
inverse(anchor quaternion)
      ↓
anchor-local gravity

代码直接实现了这个变换。(GitHub)

这说明 SONIC 的 observation design 在努力做到:

把 global trajectory 转换成 robot-centric control problem。


1.10 一个特别值得关注的变化:v1.1

这里和当前 repo 的 evolution 有关系。

最新 SONIC v1.1 使用:

robot-heading-normalized target orientation

并加入:

wrist-pose augmentation

官方说这是为了提高 whole-body teleoperation 和 VLA execution 的 heading stability。(GitHub)

这说明一个很有研究价值的事实:SONIC 的 performance 很大程度上取决于 reference coordinate representation。

不是:

network bigger → performance better

而可能是:

better reference parameterization
        ↓
easier learning problem
        ↓
better whole-body control

1.11 Phase-1 数据流

现在不要急着进入 network。先把我们已经追到的部分固定下来:

                    Motion Dataset
                         │
                         ▼
                  Motion Library
                         │
                         ▼
                  TrackingCommand
                         │
             ┌───────────┼────────────┐
             │           │            │
          current      future       SMPL
           frame       frames       frames
             │           │            │
             └───────────┼────────────┘
                         │
                         ▼
              coordinate transforms
                         │
          ┌──────────────┼──────────────┐
          │              │              │
          ▼              ▼              ▼
       Tokenizer       Policy         Critic
       observations   observations   observations
          │              │              │
          │              │              │
          ▼              │              ▼
       Encoder           │          Value network
          │              │
          ▼              │
      Universal Token    │
          │              │
          └──────┬───────┘
                 ▼
               Actor [Decoder]
                 │
                 ▼
               Action

1.12 Decoder的输入

之前说:

Motion → Encoder → 64-D token → Policy [Decoder] → Action

这个说法作为高层理解没错,但现在看源码后需要精确化

当前 UniversalTokenModule 的代码 docstring 实际写的是:

tokenizer_obs
    ↓
encoder(s)
    ↓
latent
    ↓
FSQ
    ↓
token
    ↓
decoder(s)
    ↓
action_mean

而且 decoder 还可以同时接收:

token
+
proprioception

也就是说更加准确的是:

                  motion/reference
                        │
                        ▼
                 tokenizer obs
                        │
                        ▼
              ┌──────────────────┐
              │ Encoder(s)        │
              │                  │
              │ G1               │
              │ SMPL             │
              │ Teleop           │
              └────────┬─────────┘
                       │
                pre-quant latent
                       │
                       ▼
                     FSQ
                       │
                 universal token
                       │
                       ▼
              ┌──────────────────┐
              │ Decoder(s)       │
              │                  │
              │ + proprioception │
              └────────┬─────────┘
                       │
                       ▼
                  action_mean

当前源码明确把它实现成:

UniversalTokenModule

并且其 forward() 明确返回:

  • action_mean
  • aux_losses
  • decoded_outputs
  • tokenizer_obs
  • encoder_masks
  • encoded_tokens
  • encoded_latents

(GitHub) 这其实比我们最开始画的架构更加有意思。


1.13 不止“一个 Encoder”

当前 UniversalTokenModule 明确是:

encoders = ModuleDict

可以有:

g1
smpl
teleop
soma
...

当前 release configuration 使用:

G1 Encoder
SMPL Encoder
Teleop Encoder

而 extended Bones-SEED configuration 还可以加入 SOMA encoder。(GitHub)

所以真正的 architecture 是:

          G1 reference
              │
              ▼
          G1 Encoder ──┐
                       │
          SMPL ────────┤
              │        │
              ▼        │
        SMPL Encoder ──┼──→ FSQ → shared token
                       │
          Teleop ──────┤
              │        │
              ▼        │
       Teleop Encoder ─┘

这才是 Universal Token 真正的含义。


1.14 那么“Universal”究竟是什么意思?

现在我们可以给出一个暂时性的、源码支持的定义

Universal 并不是说一个 encoder 能理解所有 motion。

恰恰相反:

G1 → G1 encoder
SMPL → SMPL encoder
Teleop → Teleop encoder

每一种输入都有自己的 encoder。

真正 universal 的地方是:

different modality
      ↓
different encoder
      ↓
     same
shared latent/token space
      ↓
common decoder/control interface

即:

               modality-specific
                    encoders
                       │
          ┌────────────┼────────────┐
          │            │            │
         G1           SMPL        Teleop
          │            │            │
          ▼            ▼            ▼
        Enc-G1      Enc-SMPL     Enc-Teleop
          │            │            │
          └────────────┼────────────┘
                       ▼
                     FSQ
                       │
                       ▼
             Universal Motion Token
                       │
                       ▼
                 G1 Decoder
                       │
                       ▼
                 Robot Action

这其实是笔者认为整个 SONIC architecture 最漂亮的地方之一。


1.15 跨模态对齐机制

当前代码中明确出现了 encoder mask combinations:

g1 ↔ smpl
teleop ↔ smpl
g1 ↔ teleop
g1 ↔ soma

以及 auxiliary losses:

G1-SMPL alignment
cycle consistency
...

UniversalTokenModule 的 forward 还专门保留:

encoded_latents
encoded_tokens
aux_losses
encoder_masks

(GitHub)

这意味着 Universal Token 并不是:

三个 encoder
      ↓
简单 concat
      ↓
decoder

而是在训练过程中存在一种:

让不同 encoder 产生相互兼容 latent 的机制。

这就是我们下一章必须重点拆开的地方。


1.16 最初的第一个研究问题

Q:SONIC 到底看到了什么?

答案现在已经相当清楚:

它同时拥有三类信息:

① Reference motion

current + future

可能来自:

G1
SMPL
Teleop

② Robot proprioception

例如:

joint_pos
joint_vel
base_ang_vel
gravity_dir
last_actions

③ Privileged state(训练 critic)

例如:

base_lin_vel
body_pos
body_ori
height map
...

(GitHub)

所以它是:

                 Motion Reference
                       │
                       ▼
                    Token
                       │
                       │
Robot State ───────────┤
                       ▼
                  Controller
                       │
                       ▼
                    Action

也就是:

motion-conditioned + state-feedback control


1.17 第二个研究问题:为什么要 future reference?

现在也可以更准确地回答。如果只给:

x_ref(t)

policy 需要自己从:

x_ref(t)

推断:

dx_ref/dt
d²x_ref/dt²
future contact
future balance

而现在直接给:

x_ref(t)
x_ref(t+Δ)
x_ref(t+2Δ)
...

于是 policy 实际上获得:

trajectory context

因此它学习的是:

π(a_t | s_t, R_{t:t+H})

这对 humanoid 尤其重要,因为当前 pose 通常不足以决定:

下一步脚该落在哪里
身体 momentum 怎么变化
手臂应该如何配合

所以 future reference 是一个非常合理的 control inductive bias


1.18 一个小问题

既然 SONIC 给了 future trajectory:

为什么还需要一个 learned latent token?

理论上完全可以:

future reference
+
robot state
        ↓
MLP
        ↓
action

为什么还要:

reference
   ↓
encoder
   ↓
FSQ
   ↓
64D token
   ↓
decoder
   ↓
action

判断是:Universal Token 并不主要是为了“压缩”。真正的意义更接近:

把不同 motion modalities 映射到一个统一的 control representation。

也就是:

SMPL
G1
Teleop
SOMA
...
  ↓
shared latent
  ↓
same controller

这比单纯 compression 更重要。下一阶段我们验证这个判断。


Phase 1 小结

                       ┌──────────────┐
                       │ VLA / Teleop │
                       └──────┬───────┘
                              │
                              ▼
                     Motion Reference
                              │
                 ┌────────────┼────────────┐
                 │            │            │
                G1           SMPL        Teleop
                 │            │            │
                 └────────────┼────────────┘
                              ▼
                      TrackingCommand
                              │
                              ▼
                  Current + Future Motion
                              │
                              ▼
                    Coordinate Transform
                              │
                    ┌─────────┴─────────┐
                    │                   │
                    ▼                   ▼
               Tokenizer             Policy
                    │                   │
                    ▼                   │
              Encoder(s)               │
                    │                   │
                    ▼                   │
                   FSQ                  │
                    │                   │
                    ▼                   │
              Universal Token           │
                    │                   │
                    └────────┬──────────┘
                             ▼
                         Decoder
                             │
                      proprioception
                             │
                             ▼
                           Action
                             │
                             ▼
                         G1 / WBC

下一阶段:进入核心

Phase 2 — Universal Token

不再做高层概括,直接沿着当前代码:

gear_sonic/trl/modules/universal_token_modules.py

往下追。这个文件目前有 1256 行,里面已经把 SONIC 的核心机制基本暴露出来了。(GitHub) 我们重点逐行搞清楚:

tokenizer_obs
      ↓
encoder_index
      ↓
encoder_masks
      ↓
G1 / SMPL / Teleop Encoder
      ↓
pre-quantization latent
      ↓
FSQ
      ↓
64-D token
      ↓
latent residual
      ↓
G1 Dynamic Decoder
      ↓
action_mean

尤其重点帮解决三个问题:

64-D 到底是怎么来的?

不是只说“FSQ 输出 64-D”,而是把:

num_fsq_levels
fsq_level_list
max_num_tokens
down_t

全部对应到数学上的 latent shape。

为什么 G1 / SMPL / Teleop 可以共用一个 token space?

我们会追:

encoder masks
+
encoder sampling
+
auxiliary losses
+
latent alignment
+
reconstruction

最后回答:

这个 shared latent space 到底是怎么被训练出来的。

为什么 Decoder 还要 proprioception?

这是一个非常关键的问题:

    token
      +
proprioception
      ↓
  decoder
      ↓
    action

为什么 token 本身不能决定 action?

这个问题实际上会把我们带进 SONIC 最核心的 representation vs control 分工。

Logo

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

更多推荐