【SONIC源码阅读系列2】运动数据到观测
现在开始整个系列的 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_frames是 0.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_meanaux_lossesdecoded_outputstokenizer_obsencoder_masksencoded_tokensencoded_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 分工。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)