SmolVLA 模型架构
1. 整体架构概览
SmolVLA (Small Vision-Language-Action) 是由 Hugging Face 设计的轻量级视觉-语言-动作 (VLA) 模型,采用 SmolVLM2-500M-Video-Instruct 作为视觉语言主干网络,并引入独立的 Action Expert 网络通过交叉注意力机制从 VLM 中提取特征来生成机器人动作。SmolVLA 使用 Flow Matching(与 Pi0 相同的范式)进行动作生成,通过迭代去噪将高斯噪声转化为动作轨迹。其核心设计理念是:VLM 处理前缀序列(图像 + 语言 + 状态),Action Expert 处理后缀序列(含噪动作 + 时间步),两者通过交叉注意力层交互,最终输出预测动作。
(1~N 个视角)"] TXT["语言指令
(token 化)"] STATE["机器人状态
[B, max_state_dim]"] end subgraph VLMBackbone["SmolVLM2-500M 主干网络 (可冻结)"] VE["SigLIP 视觉编码器"] CONN["Connector 投影层"] LLM["SmolVLM2 语言模型
(前 16 层)"] end subgraph Projection["投影层"] SP["state_proj
Linear: state_dim -> hidden_size"] end subgraph ActionExpert["Action Expert 网络"] AIP["action_in_proj
Linear: action_dim -> expert_hidden"] TML["action_time_mlp
时间步融合 MLP"] EXP["lm_expert
(交叉注意力 + 自注意力
交替的 Transformer 层)"] AOP["action_out_proj
Linear: expert_hidden -> action_dim"] end subgraph FlowMatch["Flow Matching"] FM["高斯噪声采样
+ Beta(1.5, 1.0) 时间步"] end subgraph Output["输出"] ACT["预测动作轨迹
[B, chunk_size, action_dim]"] end IMG --> VE --> CONN --> LLM TXT --> LLM STATE --> SP SP -->|"状态嵌入"| LLM LLM -->|"KV Cache
前缀特征"| EXP FM -->|"含噪动作 x_t"| AIP AIP --> TML TML -->|"动作+时间步嵌入"| EXP EXP --> AOP --> ACT style Input fill:#e8f4fd,stroke:#2196F3 style VLMBackbone fill:#fff3e0,stroke:#FF9800 style Projection fill:#f3e5f5,stroke:#9C27B0 style ActionExpert fill:#e8f5e9,stroke:#4CAF50 style FlowMatch fill:#e0f7fa,stroke:#00BCD4 style Output fill:#fce4ec,stroke:#E91E63
SmolVLA 的核心设计特点:
| 特性 | SmolVLA | GR00T N1.5 (对比) |
|---|---|---|
| VLM 主干 | SmolVLM2-500M (SigLIP + SmolLM2) | Eagle (SigLIP2 + Qwen3-1.7B) |
| 动作生成 | Action Expert (交叉注意力) | DiT (交替注意力) |
| 动作范式 | Flow Matching (连续) | Flow Matching (连续) |
| 注意力模式 | 前缀双向 + 后缀交叉注意力 | 交替交叉/自注意力 |
| 模型规模 | ~500M 参数 | ~2B+ 参数 |
| 推理步数 | 10 步 | 4 步 |
2. 核心组件详解
2.1 SmolVLM2-500M 视觉语言模型
SmolVLA 使用 HuggingFaceTB/SmolVLM2-500M-Video-Instruct 作为 VLM 主干,内部包含 SigLIP 视觉编码器、Connector 投影层和 SmolLM2 语言模型。VLM 的作用是处理前缀序列(图像 + 语言 + 状态),生成供 Action Expert 使用的 KV Cache。
[B, 3, 512, 512]
像素范围 [-1, 1]"] --> SIG["SigLIP
视觉编码器"] SIG --> FEAT["图像特征
[B, N_patches, vis_dim]"] end subgraph Connector["Connector 投影层"] FEAT --> CPROJ["Connector
(Modality Projection
+ Resampling)"] CPROJ --> VFEAT["投影后特征
[B, N_tokens, hidden_size]"] end subgraph TextModel["SmolLM2 语言模型 (text_model)"] LANG["语言 token
[B, seq_len]"] --> LEMB["词元嵌入层"] LEMB --> NORM["嵌入 * sqrt(dim)
归一化"] VFEAT -->|"图像特殊 token
包裹后拼接"| MERGE["融合至序列"] NORM --> MERGE MERGE --> LAYERS["SmolLM2 Transformer
前 num_vlm_layers 层
(默认 16 层)"] end LAYERS -->|"VLM 隐藏状态
[B, prefix_len, hidden_size]"| OUT["前缀特征"] style VisionEncoder fill:#e3f2fd,stroke:#2196F3 style Connector fill:#f3e5f5,stroke:#9C27B0 style TextModel fill:#e8f5e9,stroke:#4CAF50
图像预处理流程:
- 图像缩放至
512x512,保持宽高比并用 0 填充(resize_with_pad) - 像素值从
[0, 1]映射至[-1, 1](SigLIP 要求) - SigLIP 编码后经过 Connector 投影至语言模型维度
- 图像嵌入乘以
sqrt(dim)进行归一化 - 可选:添加特殊图像 token(
fake_image_token+global_image_token)包裹
前缀序列拼接顺序:
[图像1特殊token] [图像1 patch嵌入] [图像1结束token] ... [语言嵌入] [状态嵌入]
VLM 层截断: 与 GR00T 类似,SmolVLA 通过 num_vlm_layers 参数(默认 16)截断 VLM 的 Transformer 层数,仅使用前 16 层。
冻结策略:
- freeze_vision_encoder=True(默认):SigLIP 视觉编码器完全冻结
- train_expert_only=True(默认):整个 VLM 冻结,仅训练 Action Expert
2.2 Action Expert 网络(交叉注意力)
Action Expert 是 SmolVLA 的核心创新:一个独立的、更小的 Transformer 网络,通过交叉注意力从 VLM 的 KV Cache 中读取信息。其宽度通过 expert_width_multiplier(默认 0.75)控制,隐藏维度为 VLM 隐藏维度的 75%。
Q, K, V 均来自后缀"] SA0 --> RES0["+ 残差"] RES0 --> PLN0["Post-Attention RMSNorm"] PLN0 --> MLP0["前馈 MLP"] MLP0 --> PRES0["+ 残差"] end subgraph Layer1["Layer 1 — 交叉注意力层 (layer_idx % 2 != 0)"] IN1["后缀嵌入"] --> LN1["RMSNorm"] LN1 --> CA1["交叉注意力
Q 来自后缀
K, V 来自 VLM KV Cache"] CA1 --> RES1["+ 残差"] RES1 --> PLN1["Post-Attention RMSNorm"] PLN1 --> MLP1["前馈 MLP"] MLP1 --> PRES1["+ 残差"] end PRES0 --> IN1 subgraph Layer2["Layer 2 — 自注意力层"] SA2["自注意力..."] end PRES1 --> SA2 SA2 --> DOTS["... 交替至第 N 层"] end subgraph KVSource["KV 来源"] VLM_KV["VLM KV Cache
(前缀特征的 K, V)"] end VLM_KV -->|"K, V 投影
(k_proj, v_proj 重新定义
输入维度 = vlm_kv_dim)"| CA1 style ExpertConfig fill:#fff8e1,stroke:#FFC107 style ExpertArch fill:#e8f5e9,stroke:#4CAF50 style Layer0 fill:#e3f2fd,stroke:#2196F3 style Layer1 fill:#fce4ec,stroke:#E91E63 style Layer2 fill:#e3f2fd,stroke:#2196F3 style KVSource fill:#fff3e0,stroke:#FF9800
交叉注意力层的 K, V 投影重塑:
在交叉注意力层中,K 和 V 来自 VLM 的 KV Cache,其维度为 num_kv_heads * head_dim。由于 Expert 的隐藏维度不同于 VLM,SmolVLA 在初始化时重新创建了这些层的 k_proj 和 v_proj:
# 交叉注意力层的 K, V 投影
k_proj = Linear(vlm_kv_heads * vlm_head_dim, expert_kv_heads * expert_head_dim)
v_proj = Linear(vlm_kv_heads * vlm_head_dim, expert_kv_heads * expert_head_dim)
层对应关系: 当 num_expert_layers < num_vlm_layers 时,Expert 层与 VLM 层按比例对应。例如 VLM 有 16 层、Expert 有 8 层时,每隔一层 VLM 层对应一个 Expert 层。未对应的层跳过 Expert 处理。
2.3 Flow Matching 扩散机制
SmolVLA 使用 Flow Matching(条件流匹配)范式生成动作,与 Pi0 相同。核心思想是学习从噪声分布到动作分布的"速度场",训练时通过线性插值构造含噪样本,推理时通过欧拉积分迭代去噪。
[B, 50, action_dim]"] NOISE["高斯噪声 epsilon
~ N(0, 1)"] BETA["时间步 t
~ Beta(1.5, 1.0) * 0.999 + 0.001"] A_GT --> INTERP["线性插值
x_t = t * epsilon + (1 - t) * a"] NOISE --> INTERP BETA --> INTERP A_GT --> TARGET["速度目标
u_t = epsilon - a"] NOISE --> TARGET INTERP --> XT["含噪动作 x_t"] TARGET --> UT["目标速度 u_t"] end subgraph Model["模型预测"] XT --> VLA["SmolVLA
(VLM + Expert)"] BETA -->|"时间步条件"| VLA VLA --> VT["预测速度 v_t"] end subgraph Loss["损失"] UT --> MSE["MSE Loss
||u_t - v_t||^2"] VT --> MSE end style Training fill:#e0f7fa,stroke:#00BCD4 style Model fill:#e8f5e9,stroke:#4CAF50 style Loss fill:#fce4ec,stroke:#E91E63
Flow Matching 关键公式:
| 公式 | 说明 |
|---|---|
t ~ Beta(1.5, 1.0) * 0.999 + 0.001 |
时间步采样,偏向 t 接近 1(更接近噪声) |
x_t = t * noise + (1 - t) * action |
线性插值构造含噪样本(t=0 为干净动作,t=1 为纯噪声) |
u_t = noise - action |
速度目标(从动作到噪声的方向) |
loss = MSE(u_t, v_t) |
训练损失 |
x_{t+dt} = x_t + dt * v_t |
推理时欧拉积分(dt = -1/num_steps) |
注意: SmolVLA 的插值方向与某些文献相反:
t=0对应干净动作,t=1对应纯噪声。推理时从t=1开始,逐步向t=0去噪。
2.4 状态投影与动作投影
SmolVLA 通过一系列线性投影层实现不同维度空间之间的映射。
[B, max_state_dim=32]"] S_IN --> SP["Linear
(32 -> vlm_hidden_size)"] SP --> S_OUT["状态嵌入
[B, 1, vlm_hidden_size]"] end subgraph ActionInProj["动作输入投影 (action_in_proj)"] A_IN["含噪动作 x_t
[B, 50, max_action_dim=32]"] A_IN --> AIP["Linear
(32 -> expert_hidden_size)"] AIP --> A_EMB["动作嵌入
[B, 50, expert_hidden]"] end subgraph TimeEmb["时间步嵌入"] T_IN["时间步 t
[B]"] T_IN --> SIN["正弦余弦位置编码
dim=expert_hidden_size
period: [4e-3, 4.0]"] SIN --> T_EMB["时间嵌入
[B, 1, expert_hidden]"] T_EMB --> EXPAND["广播至动作长度
[B, 50, expert_hidden]"] end subgraph TimeFusion["时间步融合 MLP (action_time_mlp)"] A_EMB --> CAT["Concat
[B, 50, expert_hidden*2]"] EXPAND --> CAT CAT --> MLP_IN["Linear
(expert_hidden*2 -> expert_hidden)"] MLP_IN --> SILU["SiLU 激活函数"] SILU --> MLP_OUT["Linear
(expert_hidden -> expert_hidden)"] MLP_OUT --> FUSED["融合后的动作+时间嵌入
[B, 50, expert_hidden]"] end subgraph ActionOutProj["动作输出投影 (action_out_proj)"] EXP_OUT["Expert 输出
[B, 50, expert_hidden]"] EXP_OUT --> CAST["上转型至 float32"] CAST --> AOP["Linear
(expert_hidden -> 32)"] AOP --> V_OUT["预测速度 v_t
[B, 50, max_action_dim=32]"] end style StateProj fill:#e3f2fd,stroke:#2196F3 style ActionInProj fill:#f3e5f5,stroke:#9C27B0 style TimeEmb fill:#fff8e1,stroke:#FFC107 style TimeFusion fill:#e0f7fa,stroke:#00BCD4 style ActionOutProj fill:#fce4ec,stroke:#E91E63
维度填充机制: SmolVLA 将所有状态和动作向量填充至固定维度(max_state_dim=32,max_action_dim=32),不足部分补零。推理后再截断回原始维度。这使得模型能够兼容不同自由度的机器人。
3. 训练流水线
训练时,模型接收完整的前缀和后缀序列,一次前向传播同时处理 VLM 和 Expert。注意力掩码确保前缀序列(图像、语言)采用双向注意力,而后缀序列(含噪动作)只能通过交叉注意力关注前缀。
-> [512, 512]
-> [-1, 1]"] STATE_RAW["原始状态"] --> PAD_S["pad_vector
-> [max_state_dim=32]"] ACT_RAW["原始动作"] --> PAD_A["pad_vector
-> [B, 50, 32]"] LANG_RAW["语言指令"] --> TOKENIZE["分词器
max_length=48"] end subgraph NoiseSample["噪声采样"] direction LR EPSILON["epsilon ~ N(0, 1)
[B, 50, 32]"] T_SAMPLE["t ~ Beta(1.5, 1.0)
* 0.999 + 0.001"] T_SAMPLE --> INTERP EPSILON --> INTERP["x_t = t*epsilon + (1-t)*action"] PAD_A --> INTERP EPSILON --> VEL_TARGET["u_t = epsilon - action"] PAD_A --> VEL_TARGET end subgraph PrefixEmbed["前缀嵌入 (embed_prefix)"] RESIZE --> VE["SigLIP 编码"] VE --> IMG_EMB["图像嵌入 * sqrt(dim)"] TOKENIZE --> LANG_EMB["语言嵌入 * sqrt(dim)"] PAD_S --> STATE_EMB["state_proj"] IMG_EMB --> PREFIX_CAT["拼接: [img | lang | state]"] LANG_EMB --> PREFIX_CAT STATE_EMB --> PREFIX_CAT PREFIX_CAT --> PREFIX["prefix_embs
[B, prefix_len, vlm_hidden]"] end subgraph SuffixEmbed["后缀嵌入 (embed_suffix)"] INTERP --> AIN["action_in_proj"] T_SAMPLE --> TIME_EMB["正弦余弦编码"] AIN --> FUSE["concat + action_time_mlp
+ SiLU"] TIME_EMB --> FUSE FUSE --> SUFFIX["suffix_embs
[B, 50, expert_hidden]"] end subgraph AttMask["注意力掩码构造"] direction LR AM1["前缀 att_mask: 0
(双向注意力)"] AM2["状态 att_mask: 1
(因果边界)"] AM3["后缀 att_mask: 1
(因果边界)"] AM1 --> MASK_2D["make_att_2d_masks
构造 2D 注意力掩码"] AM2 --> MASK_2D AM3 --> MASK_2D end subgraph Forward["联合前向传播"] PREFIX --> VLM_EXP["VLM + Expert
逐层交替处理"] SUFFIX --> VLM_EXP MASK_2D --> VLM_EXP VLM_EXP --> EXPERT_OUT["Expert 输出
取最后 50 个 token"] EXPERT_OUT --> AOUT["action_out_proj
(float32)"] AOUT --> V_PRED["预测速度 v_t"] end subgraph LossCalc["损失计算"] V_PRED --> MSE["MSE Loss
||u_t - v_t||^2"] VEL_TARGET --> MSE MSE --> MASK_LOSS["应用 in_episode_bound 掩码
+ 截断至原始 action_dim"] MASK_LOSS --> FINAL_LOSS["最终损失"] end style DataPrep fill:#fff3e0,stroke:#FF9800 style NoiseSample fill:#e0f7fa,stroke:#00BCD4 style PrefixEmbed fill:#e3f2fd,stroke:#2196F3 style SuffixEmbed fill:#f3e5f5,stroke:#9C27B0 style AttMask fill:#fff8e1,stroke:#FFC107 style Forward fill:#e8f5e9,stroke:#4CAF50 style LossCalc fill:#fce4ec,stroke:#E91E63
训练时注意力掩码详解:
前缀 (图像+语言): att_mask = [0, 0, 0, ..., 0] -> 双向注意力(互相可见)
前缀 (状态): att_mask = [1] -> 因果边界(图像/语言不关注状态)
后缀 (动作+时间): att_mask = [1, 1, ..., 1] -> 因果边界(前缀不关注后缀)
通过 make_att_2d_masks 函数,基于 att_mask 的累积和构造完整的 2D 注意力掩码矩阵。结果是:
- 图像和语言 token 之间双向可见
- 状态和动作 token 可以看到前面的所有 token
- 图像和语言 token 无法看到状态和动作 token
4. 推理流水线
推理时采用两阶段:先计算前缀的 KV Cache(一次),然后通过 10 步欧拉积分迭代去噪后缀。KV Cache 使得前缀只需计算一次,大幅节省推理时间。
SigLIP + 语言嵌入 + 状态投影"] LANG_IN["语言指令"] --> EMB_P STATE_IN["机器人状态"] --> EMB_P EMB_P --> VLM_FWD["VLM 前向
fill_kv_cache=True"] VLM_FWD --> KV["KV Cache
(每层的 key_states, value_states)"] end subgraph Phase2["阶段 2: 迭代去噪 (10 步欧拉积分)"] INIT["初始化
x_1 ~ N(0, 1)
[B, 50, 32]"] INIT --> STEP1 subgraph STEP1["Step 1: t=1.0"] E1["embed_suffix(x_t, t)"] --> D1["denoise_step
Expert 前向 + KV Cache"] D1 --> V1["v_1"] V1 --> U1["x_t += dt * v_1
dt = -0.1"] end subgraph STEP2["Step 2: t=0.9"] E2["embed_suffix(x_t, t)"] --> D2["denoise_step"] D2 --> V2["v_2"] V2 --> U2["x_t += dt * v_2"] end subgraph STEP3["Step 3~9: t=0.8 ... 0.2"] DOTS2["...重复去噪..."] end subgraph STEP10["Step 10: t=0.1"] E10["embed_suffix(x_t, t)"] --> D10["denoise_step"] D10 --> V10["v_10"] V10 --> U10["x_t += dt * v_10"] end U1 --> STEP2 U2 --> STEP3 STEP3 --> STEP10 end KV -->|"传入每步"| D1 KV -->|"传入每步"| D2 KV -->|"传入每步"| D10 subgraph Output["输出"] U10 --> UNPAD["截断至原始 action_dim"] UNPAD --> ACTIONS["最终动作轨迹
[B, 50, action_dim]"] end style Phase1 fill:#e3f2fd,stroke:#2196F3 style Phase2 fill:#e8f5e9,stroke:#4CAF50 style STEP1 fill:#f1f8e9,stroke:#8BC34A style STEP2 fill:#f1f8e9,stroke:#8BC34A style STEP3 fill:#f1f8e9,stroke:#8BC34A style STEP10 fill:#f1f8e9,stroke:#8BC34A style Output fill:#fce4ec,stroke:#E91E63
单步去噪详解 (denoise_step)
[B, 50, 32]"] --> AIP["action_in_proj"] TS["timestep t"] --> SIN_COS["正弦余弦编码
[B, expert_hidden]"] AIP --> ACT_EMB["动作嵌入
[B, 50, expert_hidden]"] SIN_COS --> EXPAND["广播
[B, 50, expert_hidden]"] ACT_EMB --> CAT["Concat
[B, 50, expert_hidden*2]"] EXPAND --> CAT CAT --> TML_IN["action_time_mlp_in"] TML_IN --> SILU["SiLU"] SILU --> TML_OUT["action_time_mlp_out"] TML_OUT --> SUFFIX["suffix_embs"] SUFFIX --> ATTN["构造注意力掩码
(prefix KV + suffix 2D mask)"] KV["KV Cache"] --> ATTN ATTN --> EXPERT["Expert 前向
(交叉注意力读取 KV Cache)"] EXPERT --> LAST50["取最后 50 token"] LAST50 --> FP32["转为 float32"] FP32 --> AOP["action_out_proj"] AOP --> VT["预测速度 v_t
[B, 50, 32]"] style XT fill:#e0f7fa,stroke:#00BCD4 style EXPERT fill:#e8f5e9,stroke:#4CAF50 style VT fill:#fce4ec,stroke:#E91E63
推理时的注意力掩码: 后缀的每个 token 可以通过交叉注意力看到前缀 KV Cache 中的所有有效 token(基于 prefix_pad_masks),同时后缀 token 之间通过 suffix_att_masks 控制可见性。
5. 关键超参数表
| 参数 | 默认值 | 说明 |
|---|---|---|
| 模型结构 | ||
vlm_model_name |
HuggingFaceTB/SmolVLM2-500M-Video-Instruct |
VLM 主干模型 |
num_vlm_layers |
16 | VLM 使用的 Transformer 层数 |
num_expert_layers |
-1 (与 VLM 相同) | Action Expert 层数 |
expert_width_multiplier |
0.75 | Expert 隐藏维度 = VLM 隐藏维度 * 0.75 |
self_attn_every_n_layers |
2 | 每 2 层插入一个自注意力层 |
attention_mode |
cross_attn |
交叉注意力模式 |
| 输入/输出 | ||
chunk_size |
50 | 动作块大小(预测未来 50 步) |
n_action_steps |
50 | 每次执行的动作步数 |
n_obs_steps |
1 | 观测步数 |
max_state_dim |
32 | 状态向量最大维度(不足补零) |
max_action_dim |
32 | 动作向量最大维度(不足补零) |
| 图像处理 | ||
resize_imgs_with_padding |
(512, 512) | 图像缩放至 512x512 |
empty_cameras |
0 | 空相机数量(用零填充) |
add_image_special_tokens |
False | 是否添加图像特殊 token |
| Flow Matching | ||
num_steps |
10 | 推理去噪步数 |
| 噪声分布 | N(0, 1) | 高斯噪声 |
| 时间步分布 | Beta(1.5, 1.0) * 0.999 + 0.001 | 偏向 t 接近 1 |
min_period |
4e-3 | 时间步正弦编码最小周期 |
max_period |
4.0 | 时间步正弦编码最大周期 |
| 训练设置 | ||
freeze_vision_encoder |
True | 冻结 SigLIP 视觉编码器 |
train_expert_only |
True | 仅训练 Action Expert |
train_state_proj |
True | 训练状态投影层 |
optimizer_lr |
1e-4 | 学习率 |
optimizer_betas |
(0.9, 0.95) | Adam beta 参数 |
optimizer_weight_decay |
1e-10 | 权重衰减 |
optimizer_grad_clip_norm |
10 | 梯度裁剪 |
scheduler_warmup_steps |
1,000 | 学习率预热步数 |
scheduler_decay_steps |
30,000 | 余弦衰减步数 |
scheduler_decay_lr |
2.5e-6 | 衰减后最低学习率 |
| 推理优化 | ||
use_cache |
True | 使用 KV Cache 加速推理 |
tokenizer_max_length |
48 | 分词器最大长度 |
prefix_length |
-1 (不填充) | 前缀固定长度(-1 表示不填充) |
6. 关键源文件表
| 组件 | 类名 | 文件路径 |
|---|---|---|
| 策略封装 | SmolVLAPolicy |
lerobot/policies/smolvla/modeling_smolvla.py:225 |
| 核心模型 | VLAFlowMatching |
lerobot/policies/smolvla/modeling_smolvla.py:530 |
| VLM + Expert 封装 | SmolVLMWithExpertModel |
lerobot/policies/smolvla/smolvlm_with_expert.py:61 |
| 模型配置 | SmolVLAConfig |
lerobot/policies/smolvla/configuration_smolvla.py:29 |
| 注意力掩码工具 | make_att_2d_masks |
lerobot/policies/smolvla/modeling_smolvla.py:102 |
| 正弦位置编码 | create_sinusoidal_pos_embedding |
lerobot/policies/smolvla/modeling_smolvla.py:81 |
| RoPE 实现 | apply_rope |
lerobot/policies/smolvla/smolvlm_with_expert.py:28 |
| 图像缩放 | resize_with_pad |
lerobot/policies/smolvla/modeling_smolvla.py:135 |
| 向量填充 | pad_vector |
lerobot/policies/smolvla/modeling_smolvla.py:157 |
| RTC 处理器 | RTCProcessor |
lerobot/policies/rtc/modeling_rtc.py |