Phase 5:SONIC Deploy——从 Universal Token 到 G1 真机

前面四个阶段已经得到:

VLA→Universal Token→SONIC Policy \boxed{ \text{VLA} \rightarrow \text{Universal Token} \rightarrow \text{SONIC Policy} } VLAUniversal TokenSONIC Policy

第五阶段只解决一个问题:

这个 64-D token 到底是怎么变成 G1 的真实关节运动的?

因此这一阶段我们沿着一条主线走:

Token→C++ Deployment→TensorRT→Joint Target→G1 \boxed{ \text{Token} \rightarrow \text{C++ Deployment} \rightarrow \text{TensorRT} \rightarrow \text{Joint Target} \rightarrow \text{G1} } TokenC++ DeploymentTensorRTJoint TargetG1


一、Deployment 层整体架构

这一层暂时不要看具体 C++ 函数。先把整个 deployment 看成三个层级:

┌──────────────────────────────────────────────┐
│ Layer 1:上游输入                            │
│                                              │
│ VLA / Motion Reference / Teleoperation       │
└──────────────────────┬───────────────────────┘
                       │
                       │ 64-D Universal Token
                       ▼
┌──────────────────────────────────────────────┐
│ Layer 2:SONIC Controller                    │
│                                              │
│ token + robot state                          │
│              ↓                               │
│        TensorRT Policy                       │
│              ↓                               │
│        29-D body action                      │
└──────────────────────┬───────────────────────┘
                       │
                       │ joint target
                       ▼
┌──────────────────────────────────────────────┐
│ Layer 3:Robot Execution                     │
│                                              │
│ position target + Kp/Kd                      │
│              ↓                               │
│       Unitree DDS / LowCmd                   │
│              ↓                               │
│             G1                               │
└──────────────────────────────────────────────┘

所以第五阶段实际上只涉及三个层:

Layer 1:Input

解决:

token 从哪里来?

Layer 2:SONIC

解决:

token + 当前机器人状态如何产生身体动作?

Layer 3:Robot

解决:

身体动作如何真正发送给电机?

这样我们就不会把 VLA、SONIC、DDS、PD 全部混在一起。


二、Layer 1:Token 如何进入 Deployment?

这一层承接第四阶段。第四阶段我们已经知道 VLA 输出:

zt∈R64 z_t\in\mathbb R^{64} ztR64

Python 端最终通过 ZMQ 把它包装成:

token_state
frame_index
hand actions

发送给 C++。

所以 C++ deployment 收到的核心东西就是:

zt=external token[64] \boxed{ z_t = \text{external token}[64] } zt=external token[64]


2.1 C++ 中保存在哪里?

G1Deploy 中有:

std::vector<double> token_state_data_;

它就是当前 token 的缓存。收到 external token 后,C++ 会把它复制进去,并设置:

is_using_encoder_ = false;

这两个变量实际上已经把整个接口设计表达得很清楚:

External Token
      │
      ▼
token_state_data_[64]
      │
      └── is_using_encoder_ = false

也就是说:

当前 controller 不需要自己再计算 token,而是直接使用外部给定的 token。


2.2 那 model_encoder.onnx 是干什么的?

这是第五阶段最容易混淆的地方。SONIC deployment 同时支持两种输入模式。

模式 A:给它 motion reference

例如:

SMPL
Motion Reference
Teleoperation
        │
        ▼
model_encoder.onnx
        │
        ▼
64-D token

也就是:

xmotion→ESONIC→z x_{\text{motion}} \rightarrow E_{\text{SONIC}} \rightarrow z xmotionESONICz


模式 B:直接给它 token

VLA 就属于这个情况:

VLA
 │
 ▼
64-D token
 │
 X
 │   encoder bypass
 ▼
SONIC decoder

即:

oVLA→FVLA→z o_{\text{VLA}} \rightarrow F_{\text{VLA}} \rightarrow z oVLAFVLAz

然后直接进入 SONIC。


2.3 所以 Encoder 并不是 Deployment 必经层

这一点现在可以明确下来:

SONIC Deployment={xmotion→E→z→Dzexternal→D \boxed{ \text{SONIC Deployment}= \begin{cases} x_{\text{motion}}\rightarrow E\rightarrow z\rightarrow D\\[3pt] z_{\text{external}}\rightarrow D \end{cases} } SONIC Deployment={xmotionEzDzexternalD

报告里的统一 latent architecture当前 VLA deployment 的接口形式是两个层次的问题。

SONIC 定义了:

Universal Token→Universal Controller \text{Universal Token}\rightarrow\text{Universal Controller} Universal TokenUniversal Controller

而 VLA 已经学会直接输出这个 Universal Token,所以运行时可以绕过 SONIC Encoder。


三、Layer 2:SONIC Controller

现在我们把注意力从“token 怎么进来”转移到真正的 controller。

这是第五阶段最核心的一层。

它的输入是:

zt+st \boxed{ z_t + s_t } zt+st

其中:

  • ztz_tzt:64-D Universal Token
  • sts_tst:当前 G1 proprioception / robot state

输出:

at∈R29 \boxed{ a_t\in\mathbb R^{29} } atR29

所以整个 neural controller 可以抽象成:

at=DSONIC(zt,st) \boxed{ a_t=D_{\text{SONIC}}(z_t,s_t) } at=DSONIC(zt,st)

这就是前面训练阶段 UniversalTokenModule 的 deployment 版本。


四、SONIC Controller 内部又可以分成两个步骤

不要把它和上一层混在一起。

Layer 2 内部实际上只有:

       Token
         │
         │
Robot State
         │
         ▼
┌─────────────────┐
│ Observation     │
│ Builder         │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ TensorRT Policy │
└────────┬────────┘
         │
         ▼
     29-D action

也就是说,C++ deployment 首先负责组 observation,然后 TensorRT 才负责神经网络推理


4.1 Observation Builder:把机器人状态和 token 拼起来

C++ 每个 control cycle 都需要构造固定长度的 observation:

ot=[zt, st, ⋯ ] o_t= [ z_t,\, s_t,\, \cdots ] ot=[zt,st,]

具体有哪些字段,由 observation_config.yaml决定。所以 YAML 的真正作用不是“模型参数”,它更像是一个:

PyTorch 模型 observation interface 和 C++ runtime observation interface 之间的协议。


为什么这一点重要?

因为神经网络只认识 RN\mathbb R^NRN,它并不知道:

第 0~63 维 = token
第 64~... = joint position
...

这些语义由 observation configuration 和 C++ observation functions 决定。

最终 C++ 得到一个固定 tensor:

ot∈RN \boxed{ o_t\in\mathbb R^N } otRN

然后交给 TensorRT。


4.2 TensorRT Policy:真正执行 SONIC Decoder

接下来才进入 model_decoder.onnx,C++ 中对应 PolicyEngine

可以把它理解成:

PolicyEngine:ot→at \boxed{ \text{PolicyEngine} : o_t \rightarrow a_t } PolicyEngine:otat

对于当前 VLA deployment:

[zt,st]→model_decoder.onnx→at29 \boxed{ [z_t,s_t] \rightarrow \text{model\_decoder.onnx} \rightarrow a_t^{29} } [zt,st]model_decoder.onnxat29

所以到这里:VLA 已经完全退出了,它只负责产生 ztz_tzt。从这里开始,全部是 SONIC controller。


五、和第二、三阶段重新接起来

前面训练阶段我们得到:

Encoder→FSQ→z→Decoder→a \text{Encoder} \rightarrow \text{FSQ} \rightarrow z \rightarrow \text{Decoder} \rightarrow a EncoderFSQzDecodera

现在部署阶段:

如果输入是 motion reference:

x→EONNX→z→DONNX→a x \rightarrow E_{\text{ONNX}} \rightarrow z \rightarrow D_{\text{ONNX}} \rightarrow a xEONNXzDONNXa

如果输入是 VLA:

VLA→z→DONNX→a \text{VLA} \rightarrow z \rightarrow D_{\text{ONNX}} \rightarrow a VLAzDONNXa

所以部署端真正固定的是:

z+s→D→a \boxed{ z+s\rightarrow D\rightarrow a } z+sDa

zzz 的产生方式可以变化。

这就是 Universal Token 在整个系统中的核心位置。


六、Layer 2 的输出:29-D action 到底意味着什么?

现在进入第三层之前,我们必须先搞清楚 at29a_t^{29}at29 究竟是什么。

不是 torque

C++ 中做了:

aiscaled=giai a_i^{scaled}=g_i a_i aiscaled=giai

然后:

qitarget=qidefault+aiscaled \boxed{ q_i^{target}= q_i^{default} + a_i^{scaled} } qitarget=qidefault+aiscaled

因此 neural network 输出的是:

joint position offset \boxed{ \text{joint position offset} } joint position offset

经过 scaling 后变成:

joint position target \boxed{ \text{joint position target} } joint position target


七、Layer 3:从 Joint Target 到 G1

现在才进入真正的 robot execution 层。

数据流非常简单:

29-D neural action
        │
        ▼
action scale
        │
        ▼
default joint angle
        │
        ▼
29-D q_target
        │
        ▼
MotorCommand
        │
        ▼
Unitree LowCmd
        │
        ▼
       DDS
        │
        ▼
        G1

所以这里发生了一个非常重要的转换:

Neural Action→Joint Target \boxed{ \text{Neural Action} \rightarrow \text{Joint Target} } Neural ActionJoint Target

然后才:

Joint Target→Low-Level Control \boxed{ \text{Joint Target} \rightarrow \text{Low-Level Control} } Joint TargetLow-Level Control


八、G1 的低层控制

C++ 给每个 motor 设置:

qtargetdqtarget=0τff=0 q_{target}\\ dq_{target}=0\\ \tau_{ff}=0 qtargetdqtarget=0τff=0

以及 Kp, KdK_p,\ K_dKp, Kd

因此可以近似理解成:

τ=Kp(qtarget−q)+Kd(0−q˙) \tau= K_p(q_{target}-q) + K_d(0-\dot q) τ=Kp(qtargetq)+Kd(0q˙)

也就是:

SONIC→qtarget→PD→Motor \boxed{ \text{SONIC} \rightarrow q_{target} \rightarrow \text{PD} \rightarrow \text{Motor} } SONICqtargetPDMotor

所以 SONIC 并没有直接学习 motor torque。它学习的是:

如何根据 latent motion intention 和当前身体状态,生成合适的 joint position target。


九、 50 Hz 和 500 Hz 两个频率

这是 Deployment architecture 中另一个重要设计。

不要把它理解成两个 policy。

实际上只有一个 neural policy:

50Hz \boxed{50Hz} 50Hz

即:

Tpolicy=20ms T_{\text{policy}}=20ms Tpolicy=20ms

每 20 ms 做一次:

[zt,st]→at [z_t,s_t]\rightarrow a_t [zt,st]at

但是 motor command writer:

500Hz \boxed{500Hz} 500Hz

即每:

2ms 2ms 2ms

向 robot command interface 发布一次当前 command。

因此:

                 50 Hz
        ┌────────────────────┐
        │ SONIC TensorRT     │
        │                    │
        │ token + state      │
        │       ↓            │
        │ joint target      │
        └─────────┬──────────┘
                  │
                  ▼
          command buffer
                  │
          ────────┼────────
             500 Hz
                  │
                  ▼
             Unitree DDS
                  │
                  ▼
                  G1

这里体现的是两个时间尺度的解耦

Neural Control: 50 Hz \boxed{ \text{Neural Control: 50 Hz} } Neural Control: 50 Hz

Command Transmission: 500 Hz \boxed{ \text{Command Transmission: 500 Hz} } Command Transmission: 500 Hz


十、那么 VLA 的频率又在哪里?

这时候把第四阶段也接回来:

VLA≈2.5Hz \boxed{ \text{VLA}\approx2.5Hz } VLA2.5Hz

SONIC=50Hz \boxed{ \text{SONIC}=50Hz } SONIC=50Hz

Motor Command=500Hz \boxed{ \text{Motor Command}=500Hz } Motor Command=500Hz

所以整个 system 实际上是三级时间尺度:

VLA
~2.5 Hz
 │
 │ latent action chunk
 ▼
SONIC
50 Hz
 │
 │ joint target
 ▼
Motor Interface
500 Hz
 │
 ▼
G1

为什么 VLA 这么慢?

因为 VLA 并不是每 20 ms 重新规划一次身体动作。

它给的是:

zt:t+H z_{t:t+H} zt:t+H

也就是一段未来 token sequence。

例如前面看到的:

H=40 H=40 H=40

对应:

40×20ms=800ms 40\times20ms=800ms 40×20ms=800ms

所以 VLA 可以每隔约 400 ms 重新产生一个 chunk,而 SONIC 在这段时间里持续以 50 Hz 执行。

于是:

VLA负责时序上的“意图/规划” \boxed{ \text{VLA负责时序上的“意图/规划”} } VLA负责时序上的意图/规划

SONIC负责高频的全身运动控制 \boxed{ \text{SONIC负责高频的全身运动控制} } SONIC负责高频的全身运动控制


十二、External Token 为什么还需要 timeout?

这是 deployment safety 层,不属于 neural network 本身。

C++ 对 external token 设置了大约:

200ms 200ms 200ms

的 freshness threshold。

因为:

50Hz=20ms 50Hz=20ms 50Hz=20ms

所以 200 ms 大约对应:

10 10 10

个 SONIC control cycles。

如果 VLA/ZMQ 暂时没有新 token:

new token
   ↓
token buffer
   ↓
SONIC keeps running

而不是每一次通信抖动就直接让 controller 崩掉。

这也是为什么第四阶段的:

action chunk + asynchronous inference

能够和第五阶段的:

50 Hz real-time controller

自然连接起来。


十三、第五阶段压缩成源码级数据流

最终链路:

                    VLA
                     │
                     │ 64-D motion_token
                     ▼
                    ZMQ
                     │
                     ▼
             token_state_data_[64]
                     │
                     │
                     ▼
            ┌───────────────────┐
            │ Observation       │
            │ Builder           │
            │                   │
            │ token + robot     │
            │ proprioception    │
            └─────────┬─────────┘
                      │
                      ▼
             model_decoder.onnx
                      │
                 TensorRT
                      │
                      ▼
                 action[29]
                      │
                 action scale
                      │
                      ▼
              q_default + offset
                      │
                      ▼
                q_target[29]
                      │
                      ▼
                MotorCommand
                      │
                    500Hz
                      │
                      ▼
                Unitree DDS
                      │
                      ▼
                     G1

如果是普通 motion reference,则只需要把最上面换成:

Motion Reference
      │
      ▼
model_encoder.onnx
      │
      ▼
64-D token
      │
      ▼
后面完全相同

因此整个 deployment 最核心的结构其实非常干净:

Input⏟产生 token→SONIC⏟token→body action→Robot⏟action→motor \boxed{ \underbrace{\text{Input}}_{\text{产生 token}} \rightarrow \underbrace{\text{SONIC}}_{\text{token→body action}} \rightarrow \underbrace{\text{Robot}}_{\text{action→motor}} } 产生 tokenInputtoken→body actionSONICaction→motorRobot


十四、回头看 gear_sonic_deploy,应该用什么视角?

按照下面的三层结构看。

第一层:Input Layer

主要关注:

ZMQ
External Token
Motion Reference
Encoder

核心问题只有一个:

token 从哪里来?


第二层:Controller Layer

主要关注:

observation_config.yaml
Observation Builder
PolicyEngine
model_decoder.onnx
TensorRT

核心问题只有一个:

token + 当前 robot state 如何变成 29-D action?


第三层:Robot Layer

主要关注:

action scale
default angle
MotorCommand
Kp / Kd
LowCmd
DDS
500 Hz

核心问题只有一个:

29-D action 如何变成 G1 的真实电机运动?


十五、核心认识

到这里其实不需要记住几十个类名。只需要建立下面这个 mental model:

VLA⟶2.5Hz64-D Universal Token⟶50Hz29-D Joint Action⟶500HzG1 \boxed{ \textbf{VLA} \quad \overset{\text{2.5Hz}}{\longrightarrow} \quad \textbf{64-D Universal Token} \quad \overset{\text{50Hz}}{\longrightarrow} \quad \textbf{29-D Joint Action} \quad \overset{\text{500Hz}}{\longrightarrow} \quad \textbf{G1} } VLA2.5Hz64-D Universal Token50Hz29-D Joint Action500HzG1

而 SONIC 自己位于中间:

DSONIC:(zt,st)→at \boxed{ D_{\text{SONIC}}: (z_t,s_t) \rightarrow a_t } DSONIC:(zt,st)at

其中:

  • VLA:产生 latent motion intention;
  • Universal Token:跨 VLA / motion / teleop 的接口;
  • SONIC Decoder:把 latent intention + 当前身体状态变成 whole-body joint command;
  • PD / Unitree:把 joint target 执行成真实机器人运动。

最后,把第五阶段和前四阶段合并

现在我们已经真正完成了一条从训练到真机的闭环:

Motion Data→E→FSQ→z→D→a→PPO \boxed{ \begin{aligned} \text{Motion Data} &\rightarrow E \rightarrow FSQ \rightarrow z \\ &\rightarrow D \rightarrow a \rightarrow \text{PPO} \end{aligned} } Motion DataEFSQzDaPPO

训练完成后:

VLA→z→DSONIC→a→qtarget→G1 \boxed{ \text{VLA} \rightarrow z \rightarrow D_{\text{SONIC}} \rightarrow a \rightarrow q_{target} \rightarrow G1 } VLAzDSONICaqtargetG1

所以从研究角度看,整个 SONIC 系统真正的“桥”就是:

64-D Universal Token \boxed{\textbf{64-D Universal Token}} 64-D Universal Token

它把上层的 VLA / motion generation下层的 whole-body control连接起来。这也是为什么第五阶段最值得理解是:

Universal Token如何成为跨模块、跨频率、跨软件栈的 control interface \boxed{ \text{Universal Token} \quad\text{如何成为跨模块、跨频率、跨软件栈的 control interface} } Universal Token如何成为跨模块、跨频率、跨软件栈的 control interface

Logo

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

更多推荐