阶段三:Token → Policy → Action → PPO

这一阶段我们把上一阶段得到的 Universal Token 接到完整 RL 闭环里。

上一阶段:

motion reference → E → z → F S Q → token \text{motion reference} \rightarrow E \rightarrow z \rightarrow \mathrm{FSQ} \rightarrow \text{token} motion reference→E→z→FSQ→token

现在继续往后:

token + s t → D dyn → μ t → π ( a t ∣ s t ) → a t → Isaac Lab → r t , s t + 1 → PPO \text{token}+s_t \rightarrow D_{\text{dyn}} \rightarrow \mu_t \rightarrow \pi(a_t|s_t) \rightarrow a_t \rightarrow \text{Isaac Lab} \rightarrow r_t,s_{t+1} \rightarrow \text{PPO} token+st​→Ddyn​→μt​→π(at​∣st​)→at​→Isaac Lab→rt​,st+1​→PPO

这里最重要的一点是:UniversalTokenModule 是 Actor 内部的 backbone,Actor 再在它外面包了一层 Gaussian policy。

也就是说,从软件结构看:

Actor
 ├── UniversalTokenModule
 │    ├── Encoder
 │    ├── FSQ
 │    └── G1 Dynamic Decoder
 │
 └── Gaussian action distribution

这一点在 actor_critic_modules.py 中非常明确:Actor.__init__() 通过 custom_instantiate() 创建 self.actor_module,而 SONIC 配置把这个 actor_module 指向 UniversalTokenModule。Actor 自己负责 action distribution。(GitHub)


1. 先看清楚整个 Actor

如果把我们前面分析的内容压缩成一个数学函数,SONIC Actor 可以写成:

μ t = D d y n ( Q ( E ( x t ) ) , s t ) \mu_t = D_{\mathrm{dyn}}\left( Q(E(x_t)), s_t \right) μt​=Ddyn​(Q(E(xt​)),st​)

其中:

  • x t x_t xt​:motion/tokenizer observation,例如未来 10 帧运动参考;
  • E E E:G1 / SMPL / Teleop encoder;
  • Q Q Q:FSQ;
  • z t = Q ( E ( x t ) ) z_t=Q(E(x_t)) zt​=Q(E(xt​)):64 维 flattened latent token;
  • s t s_t st​:robot proprioception,也就是 actor_obs;
  • D d y n D_{\mathrm{dyn}} Ddyn​:G1 dynamic decoder;
  • μ t \mu_t μt​:动作分布的 mean。

然后 Actor 在外面定义:

a t ∼ N ( μ t , σ 2 ) a_t\sim\mathcal N(\mu_t,\sigma^2) at​∼N(μt​,σ2)

所以完整的 policy 是:

π θ ( a t ∣ o t ) = N ( D θ ( Q ( E θ ( x t ) ) , s t ) , σ θ 2 ) \boxed{ \pi_\theta(a_t|o_t)= \mathcal N \left( D_\theta(Q(E_\theta(x_t)),s_t), \sigma_\theta^2 \right) } πθ​(at​∣ot​)=N(Dθ​(Q(Eθ​(xt​)),st​),σθ2​)​

这里已经出现了 SONIC 非常关键的一层结构:

motion latent 决定“想怎么动”,proprioception 决定“当前机器人状态下应该怎么执行”。

这也是为什么上一阶段强调 g1_dyn 的输入是 token_flattened + proprioception。它使 decoder 具备状态反馈性质。


2. Actor.forward() 到底做了什么?

源码中 Actor.forward() 的核心流程非常干净。

它首先拿到 obs_dict。SONIC 当前配置设置了 input_obs_dict: true,因此整个 observation dictionary 会传给 UniversalTokenModule。源码里对应的是:

net_input = obs_dict

然后执行:

output = self.actor_module(net_input, ...)

如果训练阶段开启 auxiliary loss,Actor 要求 backbone 同时计算这些 loss;最后从 output 中取出 output["action_mean"]。(GitHub)

所以可以把这里理解成:

o t ⟶ Actor { μ t L a u x o_t \overset{\text{Actor}} {\longrightarrow} \begin{cases} \mu_t\\ L_{\mathrm{aux}} \end{cases} ot​⟶Actor​{μt​Laux​​

这里的 action_mean 已经来自上一阶段我们分析的 G1 dynamic decoder。


3. 为什么还需要 Gaussian Distribution?

拿到 action_mean 后,Actor 会构造:Normal(mean, std)

源码中 update_distribution() 明确创建了 PyTorch 的 Normal distribution;std 可以采用直接参数化或 log_std 参数化,并且支持 clamp。(GitHub)

因此:

μ t = f θ ( o t ) \mu_t = f_\theta(o_t) μt​=fθ​(ot​)

之后:

a t = μ t + σ θ ϵ , ϵ ∼ N ( 0 , I ) a_t=\mu_t+\sigma_\theta\epsilon,\qquad \epsilon\sim\mathcal N(0,I) at​=μt​+σθ​ϵ,ϵ∼N(0,I)

这个 stochastic action 是 PPO 探索所需要的。

源码里的 act() 会先更新 distribution,然后执行 self.distribution.sample();同时返回 action_mean 和 action_sigma。(GitHub)

所以训练时实际上保存的是三样东西:

  • sampled action a t a_t at​
  • mean μ t \mu_t μt​
  • standard deviation σ t \sigma_t σt​

以及最关键的:

log ⁡ π θ ( a t ∣ o t ) \log\pi_\theta(a_t|o_t) logπθ​(at​∣ot​)


4. 一个很容易忽略的地方:Rollout 时到底取哪一个 timestep?

这个 repo 当前 Actor 支持 temporal observation buffer。

Actor.rollout() 会先把当前 observation 放进 obs_dict_buffer,然后把整个 history 输入 backbone,最后设置 last_step_only=True。也就是说,如果当前 buffer 是:

[ o t − k + 1 , … , o t − 1 , o t ] [o_{t-k+1},\ldots,o_{t-1},o_t] [ot−k+1​,…,ot−1​,ot​]

那么 backbone 可以处理整个序列,但最终只使用 (t) 时刻的 action mean。源码在 rollout() 中明确这么做。(GitHub)

对于当前 SONIC release,max_rollout_history 的实际配置需要结合具体 experiment config 判断;默认 Actor 类本身的参数是 1。这里不要把 motion reference 的 future horizon 和 policy 的 temporal history 混为一谈。

这两个时间方向完全不同:

时间结构含义
num_future_frames=10当前时刻向未来看 10 帧 motion reference
max_rollout_historypolicy 向过去看多少个 observation
FSQ token将未来运动参考压缩成 latent
proprioception当前机器人状态

这其实构成了一个很有意思的时序结构:

x t : t + 9 ⏟ future reference → z t \underbrace{x_{t:t+9}}_{\text{future reference}} \rightarrow z_t future reference xt:t+9​​​→zt​

同时:

s t − k : t ⏟ robot history → policy \underbrace{s_{t-k:t}}_{\text{robot history}} \rightarrow \text{policy} robot history st−k:t​​​→policy

所以 SONIC 的 controller 同时利用了 future intent 和 current physical state。


5. Action 到底是什么?

这里需要特别区分三个概念:

motion token ≠ policy action ≠ motor torque \boxed{ \text{motion token} \neq \text{policy action} \neq \text{motor torque} } motion token=policy action=motor torque​

对于当前 SONIC training path:

Motion token

大致是:

z t ∈ R 64 z_t\in\mathbb R^{64} zt​∈R64

对应两个 32-D FSQ token。

Policy action

经过 g1_dyn decoder 后得到 G1 body action。

当前 G1 body controller 使用 29 个 body joints。repo 的 action/joint ordering 定义了完整的 G1 29-DOF 顺序,包括腿、腰、肩、肘和腕。(GitHub)

Simulator / robot command

这个 action 随后进入 Isaac Lab 的 action manager。官方 training reference 将这里描述为 Joint Position Action Space。(GitHub)

因此可以把这一层写成:

z t , s t → D d y n → a t 29 → q t t a r g e t → robot dynamics z_t,s_t \rightarrow D_{\mathrm{dyn}} \rightarrow a_t^{29} \rightarrow q_t^{target} \rightarrow \text{robot dynamics} zt​,st​→Ddyn​→at29​→qttarget​→robot dynamics

具体的低层 PD / actuator dynamics 再由 Isaac Lab 和机器人配置处理。


7. 环境 step:Action 从网络真正进入机器人

训练循环中的核心 interaction 位于 TRLPPOTrainer._rollout_step()。

源码结构非常清晰:

  1. policy 根据 observation 生成 action;
  2. 将 observation、action、log probability 等保存进 RolloutStorage;
  3. 调用 self.env.step(policy_state_dict);
  4. 环境返回新的 observation、reward、done 和 info;
  5. 保存这些 transition;
  6. 重复 num_steps_per_env 次。(GitHub)

其中 policy step:

policy_model.rollout(...)

得到 action 后,又调用:

policy_model.get_actions_log_prob(actions)

计算:

log ⁡ π θ o l d ( a t ∣ o t ) \log\pi_{\theta_{\mathrm{old}}}(a_t|o_t) logπθold​​(at​∣ot​)

然后一起放进 rollout storage。(GitHub)

所以一个 transition 实际上包含:

( o t , a t , r t , d t , log ⁡ π θ o l d ( a t ∣ o t ) , V t , … ) (o_t,a_t,r_t,d_t,\log\pi_{\theta_{\rm old}}(a_t|o_t),V_t,\ldots) (ot​,at​,rt​,dt​,logπθold​​(at​∣ot​),Vt​,…)

这正是后面 PPO update 所需要的数据。


8. 这里有一个非常重要的时间尺度

当前 PPO 配置:

  • num_steps_per_env = 32
  • num_learning_epochs = 5
  • num_mini_batches = 4
  • gamma = 0.99
  • lambda = 0.95
  • clip_param = 0.2
  • actor learning rate = 2 × 10 − 5 2\times10^{-5} 2×10−5
  • critic learning rate = 10 − 3 10^{-3} 10−3
  • initial noise std = 0.05。(GitHub)

假设 SONIC policy 的控制频率是 50 Hz,那么:

32 × 20 ms = 640 ms 32\times20\text{ms}=640\text{ms} 32×20ms=640ms

也就是说,每个 environment 在一次 PPO iteration 中先收集约 0.64 秒的 trajectory,然后利用这批数据做多轮更新。同时,官方训练 reference 给出的环境结构是 IsaacLab vectorized environments,并且默认训练规模是 4096 个并行环境。(GitHub)

因此单次 rollout 大致是: 4096 × 32 = 131072 4096\times32=131072 4096×32=131072 个 environment transitions。

这解释了为什么 SONIC 可以用 PPO 做大规模运动 tracking:单次 iteration 本身就能产生大量 on-policy samples。


9. Critic 在这里干什么?

Actor 得到 action。

Critic 则负责估计:

V ϕ ( s t ) = E [ ∑ k = 0 ∞ γ k r t + k ∣ s t ] V_\phi(s_t)= \mathbb E \left[ \sum_{k=0}^{\infty}\gamma^k r_{t+k} \mid s_t \right] Vϕ​(st​)=E[k=0∑∞​γkrt+k​∣st​]

代码里的 Critic 是独立 backbone,并输出 value estimate。源码中 Critic.evaluate() 接收 critic_obs,经过 critic backbone 得到 value。(GitHub)

这里 SONIC 使用了一个很重要的 asymmetric actor-critic 思路:

Actor

使用 policy observation。

Critic

使用更丰富的 privileged observation。

训练代码文档明确区分了:

  • policy
  • critic
  • tokenizer

三个 observation group;critic observation 包含更多 privileged physical information,例如 base velocity、body position、body orientation 等。(GitHub)

因此可以写成:

π θ ( a t ∣ o t p o l i c y , x t m o t i o n ) \pi_\theta(a_t|o_t^{policy},x_t^{motion}) πθ​(at​∣otpolicy​,xtmotion​)

而:

V ϕ ( o t c r i t i c , x t m o t i o n ) V_\phi(o_t^{critic},x_t^{motion}) Vϕ​(otcritic​,xtmotion​)

critic 可以看到更完整的 simulation state,从而提供更稳定的 advantage estimate。

部署时只需要 actor。


10. Rollout 完以后,怎么得到 Advantage?

_rollout_step() 收集完 32 个 timestep 后,会把整条 trajectory 的 critic observations 拼起来,并额外加入最后一个 observation。

然后 critic 一次性计算:

V 0 , V 1 , … , V T V_0,V_1,\ldots,V_{T} V0​,V1​,…,VT​

源码中这一过程可以直接看到:收集完 trajectory 后,构造 all_obs_dict,调用 _chunked_value_evaluate(),得到整个 trajectory 的 value predictions。(GitHub)

随后处理 timeout:

r t ← r t + γ ⋅ timeout t ⋅ V ( s t + 1 ) r_t \leftarrow r_t+\gamma \cdot \text{timeout}_t\cdot V(s_{t+1}) rt​←rt​+γ⋅timeoutt​⋅V(st+1​)

源码明确对 timeout transition 做了这种 bootstrap。(GitHub)

之后进入:

self._compute_returns(...)

计算 returns 和 advantages。(GitHub)

从算法层面就是标准 GAE:

δ t = r t + γ V ( s t + 1 ) − V ( s t ) A t = δ t + γ λ δ t + 1 + γ 2 λ 2 δ t + 2 + ⋯ \delta_t= r_t+\gamma V(s_{t+1})-V(s_t)\\ A_t= \delta_t+\gamma\lambda\delta_{t+1} +\gamma^2\lambda^2\delta_{t+2}+\cdots δt​=rt​+γV(st+1​)−V(st​)At​=δt​+γλδt+1​+γ2λ2δt+2​+⋯

当前配置使用: γ = 0.99 , λ = 0.95 \gamma=0.99,\lambda=0.95 γ=0.99,λ=0.95 (GitHub).


11. PPO 更新在优化什么?

这里需要把 PPO 和 SONIC auxiliary loss 分开理解。

标准 PPO 部分可以写成:

r t ( θ ) = π θ ( a t ∣ o t ) π θ o l d ( a t ∣ o t ) r_t(\theta)= \frac{ \pi_\theta(a_t|o_t) }{ \pi_{\theta_{\mathrm{old}}}(a_t|o_t) } rt​(θ)=πθold​​(at​∣ot​)πθ​(at​∣ot​)​

然后:

L P P O = − E t [ min ⁡ ( r t A t , clip ⁡ ( r t , 1 − ϵ , 1 + ϵ ) A t ) ] L_{\mathrm{PPO}}= -\mathbb E_t \left[ \min \left( r_t A_t, \operatorname{clip}(r_t,1-\epsilon,1+\epsilon)A_t \right) \right] LPPO​=−Et​[min(rt​At​,clip(rt​,1−ϵ,1+ϵ)At​)]

当前: ϵ = 0.2 \epsilon=0.2 ϵ=0.2。此外还有 critic value loss 和 entropy regularization。配置中 value_loss_coef = 1.0 , entropy_coef = 0.01 \text{value\_loss\_coef}=1.0,\text{entropy\_coef}=0.01 value_loss_coef=1.0,entropy_coef=0.01 (GitHub)。因此传统 PPO 部分可以概括成:

L R L = L p o l i c y + L v a l u e − β H ( π ) L_{\mathrm{RL}}= L_{\mathrm{policy}} + L_{\mathrm{value}}- \beta H(\pi) LRL​=Lpolicy​+Lvalue​−βH(π)


12. PPO Loss 之外的Auxiliary Loss

现在来到整个阶段三最重要的一点。

TRLAuxLossPPOTrainer 继承 TRLPPOTrainer。

它在父类 PPO loss 的基础上,把 UniversalTokenModule 返回的 auxiliary losses 加进去。

源码明确写的是:

L = L P P O + aux_loss_scale ∑ i c i L i L= L_{\mathrm{PPO}} + \text{aux\_loss\_scale} \sum_i c_iL_i L=LPPO​+aux_loss_scalei∑​ci​Li​

(GitHub)

在 _compute_loss() 中,先执行:

super()._compute_loss(...)

得到标准 PPO loss,然后再执行 _compute_aux_loss(),最后:

loss_dict["loss"] += aux_loss_result["total_aux_loss"]

(GitHub)

所以现在整个 SONIC policy 的训练目标可以写成:

L S O N I C = L P P O + λ a u x ( ∑ i c i L i a u x ) \boxed{ L_{\mathrm{SONIC}}= L_{\mathrm{PPO}} + \lambda_{\mathrm{aux}} \left( \sum_i c_iL_i^{aux} \right) } LSONIC​=LPPO​+λaux​(i∑​ci​Liaux​)​

这就是我们上一阶段看到的 latent alignment、reconstruction、cycle consistency 真正进入 RL training 的位置。


13. Auxiliary Loss 的梯度到底流到哪里?

这点非常关键。训练时 Actor 的 forward() 会设置:compute_aux_loss=True

然后 UniversalTokenModule 输出:

{ action_mean , aux_losses , aux_loss_coef } \{\text{action\_mean},\text{aux\_losses},\text{aux\_loss\_coef}\} {action_mean,aux_losses,aux_loss_coef}

Actor 把这些 loss 保存下来,再由 TRLAuxLossPPOTrainer 加到总 loss 中。(GitHub)

于是计算图实际上长这样:

Motion / Tokenizer Obs
        │
        ▼
   G1 / SMPL / Teleop Encoder
        │
        ├──────────────┐
        │              │
        ▼              ▼
   pre-FSQ latent     Auxiliary Losses
        │
        ▼
       FSQ
        │
        ▼
      Token
        │
        ▼
  G1 Dynamic Decoder
        │
        ▼
    action_mean
        │
        ▼
    PPO objective

因此 Encoder 和 Decoder 同时收到两类梯度:

∇ θ L P P O \nabla_\theta L_{\mathrm{PPO}} ∇θ​LPPO​

以及:

∇ θ L a u x \nabla_\theta L_{\mathrm{aux}} ∇θ​Laux​

最终:

∇ θ L S O N I C = ∇ θ L P P O + λ a u x ∇ θ L a u x \nabla_\theta L_{\mathrm{SONIC}}= \nabla_\theta L_{\mathrm{PPO}} + \lambda_{\mathrm{aux}} \nabla_\theta L_{\mathrm{aux}} ∇θ​LSONIC​=∇θ​LPPO​+λaux​∇θ​Laux​

这就是 SONIC 与普通 imitation RL controller 的一个重要区别:latent representation 本身也受到显式监督,同时参与最终控制策略学习。


14. 优化关系

考虑 G1 latent:

z g 1 = E g 1 ( x g 1 ) z_{g1}=E_{g1}(x_{g1}) zg1​=Eg1​(xg1​)

SMPL latent:

z s m p l = E s m p l ( x s m p l ) z_{smpl}=E_{smpl}(x_{smpl}) zsmpl​=Esmpl​(xsmpl​)

上一阶段我们看到:

L a l i g n = ∥ z g 1 − z s m p l ∥ 2 L_{\mathrm{align}}= \|z_{g1}-z_{smpl}\|^2 Lalign​=∥zg1​−zsmpl​∥2

与此同时,RL 又希望:

D ( z , s t ) D(z,s_t) D(z,st​)

产生高 reward 的 action。

所以 encoder 实际受到三个方向的约束:

第一层:运动重建

让 latent 保留 motion information。

L r e c o n L_{\mathrm{recon}} Lrecon​

第二层:跨模态 alignment

让不同 motion source 映射到相近 latent。

L a l i g n L_{\mathrm{align}} Lalign​

第三层:control utility

让 decoder 使用 latent 能产生高 reward action。

L P P O L_{\mathrm{PPO}} LPPO​

于是 latent 的学习目标可以概念化为:

latent quality = information preservation + cross-modal consistency + control utility \boxed{ \text{latent quality}= \text{information preservation} + \text{cross-modal consistency} + \text{control utility} } latent quality=information preservation+cross-modal consistency+control utility​

这是理解 SONIC 最关键的地方之一。它的 latent 并非只由 reconstruction objective 定义,而是在 representation learning + multimodal alignment + reinforcement learning 三个方向共同塑造。


15. 4096 并行环境

训练代码采用 vectorized IsaacLab environment。

所以实际上不是:

robot 1
  ↓
policy
  ↓
step

而是:

env 1 ─┐
env 2 ─┤
env 3 ─┤
 ...   ├──→ Actor ──→ actions
env4096┘
              │
              ▼
         Isaac Lab
              │
        ┌─────┴─────┐
        ▼           ▼
     rewards       next obs

因此 policy 输入的 batch 是:

B = 4096 B=4096 B=4096

每次 policy inference 同时处理 4096 个 humanoid simulation。

一次 rollout:

B × T = 4096 × 32 = 131072 B\times T= 4096\times32= 131072 B×T=4096×32=131072

然后 PPO epoch 对这些 samples 做 mini-batch update。配置里:

N e p o c h = 5 , N m i n i = 4 N_{\mathrm{epoch}}=5,\qquad N_{\mathrm{mini}}=4 Nepoch​=5,Nmini​=4

所以每轮 rollout 数据会被重复利用多次。(GitHub)


16. 一次 PPO iteration 的完整链路

现在可以把整个过程串起来:

┌───────────────────────────────────────────────┐
│             IsaacLab 4096 environments        │
└──────────────────────┬────────────────────────┘
                       │
                       │ obs_dict
                       ▼
                ┌──────────────┐
                │    Actor     │
                └──────┬───────┘
                       │
                       ▼
             UniversalTokenModule
                       │
          ┌────────────┼────────────┐
          ▼            ▼            ▼
       G1 Encoder   SMPL Encoder  Teleop Encoder
          │
          ▼
       pre-FSQ latent
          │
          ▼
          FSQ
          │
          ▼
       2 × 32 token
          │
          ▼
     token_flattened
          │
          ├───────────────┐
          │               │
          ▼               ▼
   proprioception    Dynamic Decoder
                          │
                          ▼
                       μ(action)
                          │
                          ▼
                    Gaussian π(a|o)
                          │
                          ▼
                       sample a
                          │
                          ▼
                 ┌────────────────┐
                 │ IsaacLab.step  │
                 └───────┬────────┘
                         │
                ┌────────┴────────┐
                ▼                 ▼
              reward          next obs
                │
                ▼
          RolloutStorage
                │
                ▼
          Critic evaluation
                │
                ▼
             V(s_t)
                │
                ▼
              GAE
                │
                ▼
           advantages
                │
                ▼
         PPO mini-batch update
                │
                ├───────────────┐
                │               │
                ▼               ▼
          PPO objective    Auxiliary losses
                │               │
                └───────┬───────┘
                        ▼
                   total loss
                        │
                        ▼
                    gradient
                        │
                        ▼
                    optimizer

这基本就是 SONIC training loop 的骨架。官方 training reference 也把整个训练系统明确拆成 environment、Actor/Critic、UniversalTokenModule、PPO trainer 和 auxiliary loss trainer 几个组件。(GitHub)


17. 注意:Rollout 时和 Update 时的行为不同

Rollout

代码使用:

torch.no_grad()

因此 rollout 主要负责收集:

( o t , a t , r t , log ⁡ π θ o l d , V t ) (o_t,a_t,r_t,\log\pi_{\theta_{\rm old}},V_t) (ot​,at​,rt​,logπθold​​,Vt​)

不保留训练计算图。源码 _rollout_step() 明确把整个 environment interaction 放在 torch.no_grad() 下。(GitHub)

PPO Update

之后重新把 rollout data 喂给 Actor。

此时重新执行:

o t → E → F S Q → D → π θ o_t \rightarrow E \rightarrow FSQ \rightarrow D \rightarrow \pi_\theta ot​→E→FSQ→D→πθ​

并计算新的:

log ⁡ π θ ( a t ∣ o t ) \log\pi_\theta(a_t|o_t) logπθ​(at​∣ot​)

以及 auxiliary losses。

所以 rollout 中的 action mean / log probability 是 old policy 数据;update 阶段重新 forward 得到的是 current policy。

这正是 PPO ratio:

r t ( θ ) = exp ⁡ ( log ⁡ π θ ( a t ∣ o t ) − log ⁡ π θ o l d ( a t ∣ o t ) ) r_t(\theta)= \exp \left( \log\pi_\theta(a_t|o_t)- \log\pi_{\theta_{\rm old}}(a_t|o_t) \right) rt​(θ)=exp(logπθ​(at​∣ot​)−logπθold​​(at​∣ot​))

能够成立的原因。


18. 抽象总结SONIC Actor

从代码结构看,答案非常明确:

SONIC Actor = UniversalTokenModule + Gaussian Policy \boxed{ \text{SONIC Actor}= \text{UniversalTokenModule} + \text{Gaussian Policy} } SONIC Actor=UniversalTokenModule+Gaussian Policy​

而 UniversalTokenModule 自己又包含:

UniversalTokenModule = E multi-modal + Q FSQ + D motion/control \boxed{ \text{UniversalTokenModule}= E_{\text{multi-modal}} + Q_{\text{FSQ}} + D_{\text{motion/control}} } UniversalTokenModule=Emulti-modal​+QFSQ​+Dmotion/control​​

所以 SONIC 的完整 controller 可以抽象成:

π ( a t ∣ o t , x t ) = N ( D ( Q ( E ( x t ) ) , s t ) , σ 2 ) \boxed{ \pi(a_t|o_t,x_t)= \mathcal N \left( D( Q(E(x_t)), s_t ), \sigma^2 \right) } π(at​∣ot​,xt​)=N(D(Q(E(xt​)),st​),σ2)​


19. 4 个设计点

① Token 和 action 的职责被严格分开

x motion → z token → a joint x_{\text{motion}} \rightarrow z_{\text{token}} \rightarrow a_{\text{joint}} xmotion​→ztoken​→ajoint​

这使 VLA、SMPL、teleoperation 等不同上游输入都可以共享同一个控制接口。


② Proprioception 被放在 decoder 侧

a t = D ( z t , s t ) a_t=D(z_t,s_t) at​=D(zt​,st​)

这使 latent 更偏向描述 motion intent / reference,而具体动作由当前机器人状态决定。

这也是后面 VLA 接入 SONIC 时非常重要的接口基础。


③ Auxiliary loss 与 RL loss 共用同一套网络参数

L = L P P O + λ a u x L a u x L= L_{\mathrm{PPO}} + \lambda_{\mathrm{aux}}L_{\mathrm{aux}} L=LPPO​+λaux​Laux​

因此 latent representation 同时受到 motion reconstruction、跨模态 alignment 和 control reward 的约束。


④ Critic 使用 privileged observation

Actor 学习:

π ( a ∣ o p o l i c y ) \pi(a|o_{\mathrm{policy}}) π(a∣opolicy​)

Critic 学习:

V ( o p r i v i l e g e d ) V(o_{\mathrm{privileged}}) V(oprivileged​)

这降低了 value estimation 难度,同时保持最终部署 policy 的 observation 要求相对有限。官方文档也明确将 policy / critic observation groups 分开设计。(GitHub)


20. 核心训练闭环

现在从最开始的 motion 到 PPO 更新,已经可以完整写成:

x t : t + 9 → E G1/SMPL/Teleop → z p r e → F S Q → z t ∈ R 64 → [ z t , s t ] → D G 1 → μ t → π θ ( a t ∣ o t ) → a t → IsaacLab → r t , s t + 1 → V ϕ → A t → L P P O + L r e c o n + L a l i g n + L c y c l e → ∇ θ L \boxed{ \begin{aligned} x_{t:t+9} &\rightarrow E_{\text{G1/SMPL/Teleop}} \\ &\rightarrow z^{pre} \\ &\rightarrow FSQ \\ &\rightarrow z_t\in\mathbb R^{64} \\ &\rightarrow [z_t,s_t] \\ &\rightarrow D_{\mathrm{G1}} \\ &\rightarrow \mu_t \\ &\rightarrow \pi_\theta(a_t|o_t) \\ &\rightarrow a_t \\ &\rightarrow \text{IsaacLab} \\ &\rightarrow r_t,s_{t+1} \\ &\rightarrow V_\phi \\ &\rightarrow A_t \\ &\rightarrow L_{\mathrm{PPO}} \\ &\quad +L_{\mathrm{recon}} +L_{\mathrm{align}} +L_{\mathrm{cycle}} \\ &\rightarrow \nabla_\theta L \end{aligned} } xt:t+9​​→EG1/SMPL/Teleop​→zpre→FSQ→zt​∈R64→[zt​,st​]→DG1​→μt​→πθ​(at​∣ot​)→at​→IsaacLab→rt​,st+1​→Vϕ​→At​→LPPO​+Lrecon​+Lalign​+Lcycle​→∇θ​L​​

到这里,SONIC 的“训练时内部机制”已经基本闭环。

下一阶段:阶段四:从训练模型到真正的 VLA / 外部 Token 输入。

那里我们会把现在的训练链:

motion reference → encoder → token → decoder \text{motion reference} \rightarrow \text{encoder} \rightarrow \text{token} \rightarrow \text{decoder} motion reference→encoder→token→decoder

改成:

VLA → 64-D Universal Token → SONIC Decoder → G1 action \text{VLA} \rightarrow \boxed{\text{64-D Universal Token}} \rightarrow \text{SONIC Decoder} \rightarrow \text{G1 action} VLA→64-D Universal Token​→SONIC Decoder→G1 action

尤其值得追 gear_sonic_deploy 和 VLA tutorial 里的实际接口,因为源码里已经出现了一个非常有研究价值的能力:Actor.rollout_with_tokens() 可以绕过 encoder,直接接收外部 (B, 2, 32) FSQ tokens,再调用 decoder 生成 action。源码甚至明确把它描述为 external model 提供 pre-computed FSQ tokens 的路径。(GitHub)

这实际上已经把我们之前提出的 “Universal Token 作为 VLA → Humanoid Controller 标准接口” 从概念推进到了代码级接口。

Logo

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

更多推荐