π0 要解决的核心矛盾很直接:视觉语言模型擅长理解图像和指令,却只能输出离散文本;机器人控制需要连续、高频、精细的动作。Physical Intelligence 的做法不是把动作粗暴地离散成 token,而是在 PaliGemma 旁边增加一个约 3 亿参数的动作专家,用 Flow Matching 一次生成 50 步连续动作块,再用跨机器人数据完成预训练和任务后训练。
先把结论放在前面:
- 模型主体是双专家 Transformer。图像和文本走预训练 VLM 权重,机器人状态和带噪动作走独立 action expert,两组权重通过同一个注意力计算交互。
- 动作不是离散 token,也不是逐步自回归输出。π0 用条件 Flow Matching 生成长度为 50 的连续动作块,论文实验使用 10 次 Euler 积分。
- 训练 recipe 与模型结构同样重要。预训练用超过 1 万小时、多任务、多本体数据学习通用能力;后训练用高质量任务数据换取动作流畅度和复杂任务成功率。
- 论文结果说明了预训练的价值,但不能简单理解为开放世界泛化。论文的直接提示评测主要还是训练分布内任务;真正强调新环境开放世界泛化的是后续 π0.5。
论文:https://arxiv.org/abs/2410.24164
官方项目页:https://www.pi.website/blog/pi0
官方源码:https://github.com/Physical-Intelligence/openpi
模型与检查点说明:https://github.com/Physical-Intelligence/openpi#released-models

图 1 把 π0 的完整路线画得很清楚:左侧是多机器人、多任务数据,中间是预训练 VLM 与动作专家,右侧则是直接提示、复杂任务后训练和少量数据适配三种使用方式。π0 的重点并不是单独发明一个动作头,而是把互联网视觉语言预训练、跨本体机器人数据、连续动作生成和两阶段训练连成了一套完整方案。
1、π0 的定位:它是 VLA,不是 World Model
理解 π0 前,先把几个容易混淆的概念分开。
| 概念 | π0 中是否存在 | 具体含义 |
|---|---|---|
| VLM backbone | 是 | PaliGemma,负责图像与语言表征 |
| VLA | 是 | 以图像、语言和本体状态为条件直接生成机器人动作 |
| action expert | 是 | 专门处理状态、带噪动作和 Flow Matching 时间的约 300M 参数专家 |
| action chunk | 是 | 一次预测未来 50 个控制步,而不是只预测下一步 |
| latent action | 否 | 论文没有先学习无标注视频中的潜在动作变量 |
| future visual feature / future image | 否 | 推理链路不预测未来视觉子目标 |
| World Model / World-Action Model | 否 | 没有显式学习"执行动作后世界会怎样变化"的预测模型 |
| high-level policy | 可选、外置 | 部分长任务由另一个高层 VLM 提供中间语言指令,不属于 π0 动作模型本身 |
π0 的策略分布可以写成:
p ( A t ∣ o t ) p(A_t\mid o_t) p(At∣ot)
其中:
| 符号 | 含义 | 论文中的具体内容 |
|---|---|---|
| o t o_t ot | 当前时刻观测 | 2~3 路 RGB 图像、语言指令、机器人关节状态 |
| A t A_t At | 从当前时刻开始的动作块 | A t = a t , a t + 1 , ... , a t + H − 1 A_t=a_t,a_{t+1},\\ldots,a_{t+H-1} At=at,at+1,...,at+H−1 |
| H H H | 动作块长度 | 论文实验取 H = 50 H=50 H=50 |
这里没有未来图像、深度图或环境动态表征。模型需要的是当前 RGB 观测、指令和本体状态。多相机数量不足时使用 mask;不同机器人动作维度不同,则统一补零到最大维度。论文的数据处理中最大维度是 18,而当前开源 Pi0Config 默认预留 32 维,这一点后面会单独解释。
2、模型架构:PaliGemma 负责理解,动作专家负责控制

π0 以 3B PaliGemma 为基础。PaliGemma 由 SigLIP 图像编码器和 Gemma 语言模型组成,负责把多路图像与语言指令映射到 Transformer 表征空间。作者再增加约 300M 参数的 action expert,总参数量约 3.3B。
这不是"先跑完 VLM,再把特征交给另一个独立 Diffusion Policy"的普通串联结构。更准确地说,π0 是一个带两套 Transformer 权重的结构:
- 图像 patch 和文本 token 路由到预训练 VLM 专家;
- 本体状态 token 和动作 token 路由到动作专家;
- 两个专家在每层注意力中共享上下文,因此动作 token 可以读取视觉、语言和本体状态;
- 动作专家更窄,减少每次去噪迭代的计算量。
论文中的主要配置如下:
| 模块 | 规模或配置 | 作用 |
|---|---|---|
| PaliGemma VLM | 约 3B | 提供互联网预训练得到的视觉语义和语言能力 |
| Gemma 主干 | width 2048、18 层 | 处理图像与语言 token |
| action expert | width 1024、MLP dim 4096、约 300M | 处理状态、带噪动作并预测向量场 |
| 图像输入 | 每种机器人 2~3 路 RGB | 提供外部视角和腕部视角 |
| 状态与动作 | 论文最多 18 维 | 低维机器人使用补零和 mask 对齐 |
| 动作块 | 50 步 | 支持 20Hz 或 50Hz 控制任务 |
2.1、三块注意力掩码
论文把序列分成三块:
[图像, 语言]:块内双向注意力,不能看到后面的机器人状态和动作;[本体状态]:可以读取图像和语言,但不能看到动作;[带噪动作块]:50 个动作 token 块内双向注意力,并可读取前面全部条件。
这种设计有两个实际作用。第一,图像和语言部分保持接近 PaliGemma 的预训练分布;第二,推理时图像、语言和状态不随 Flow Matching 步数变化,可以缓存其 KV,只重复计算动作后缀。
2.2、一个"叠碗"任务的输入输出
假设机器人收到指令"把四个碗按大小叠起来"。一次推理的输入可能包括头部相机、腕部相机、指令 token 和当前关节角。模型不会先生成一张"碗已经叠好"的未来图像,而是从一个随机动作块开始,经过 10 次向量场更新,得到 50 步连续关节/夹爪动作。机器人执行其中一部分动作后重新观察,再滚动预测下一块,因此闭环来自反复观测,而不是内部世界模型。
3、Flow Matching 如何生成连续动作
3.1、训练目标
论文用线性高斯路径把真实动作块与高斯噪声连接起来。为避免符号方向造成误解,下面采用当前开源代码的时间约定: t = 0 t=0 t=0 对应真实动作, t = 1 t=1 t=1 对应噪声。
先采样高斯噪声:
ϵ ∼ N ( 0 , I ) \epsilon\sim\mathcal{N}(0,I) ϵ∼N(0,I)
再构造带噪动作:
x t = t ϵ + ( 1 − t ) A x_t=t\epsilon+(1-t)A xt=tϵ+(1−t)A
目标向量场为:
u t = ϵ − A u_t=\epsilon-A ut=ϵ−A
模型预测 v θ ( x t , o ) v_\theta(x_t,o) vθ(xt,o),使用均方误差训练:
L ( θ ) = E ∥ v θ ( x t , o ) − u t ∥ 2 2 \mathcal{L}(\theta)=\mathbb{E}\left\\left\\\|v_\\theta(x_t,o)-u_t\\right\\\|_2\^2\\right L(θ)=E∥vθ(xt,o)−ut∥22
| 符号 | 含义 | 在模型中的作用 |
|---|---|---|
| A A A | 真实连续动作块 | 监督目标,形状可理解为 H × d a H\times d_a H×da |
| ϵ \epsilon ϵ | 与动作块同形状的高斯噪声 | Flow Matching 起点 |
| t t t | 生成模型内部时间 | 只控制噪声程度,不是机器人真实物理时间 |
| x t x_t xt | 带噪动作 | 输入动作专家的连续 token |
| o o o | 条件观测 | RGB 图像、语言和本体状态 |
| v θ v_\theta vθ | 模型预测的向量场 | 指示动作块应该向哪个方向更新 |
论文会更强调低噪声一侧以外的训练区域:时间从 Beta 分布采样,并乘以 0.999 0.999 0.999,避免直接采到端点。当前 JAX 代码对应 jax.random.beta(..., 1.5, 1) * 0.999 + 0.001。
3.2、推理过程
推理从随机噪声动作块开始,按预测向量场反向积分。当前代码令 Δ t = − 1 / N \Delta t=-1/N Δt=−1/N,从 t = 1 t=1 t=1 走到 t = 0 t=0 t=0:
x t + Δ t = x t + Δ t v θ ( x t , o ) x_{t+\Delta t}=x_t+\Delta t\,v_\theta(x_t,o) xt+Δt=xt+Δtvθ(xt,o)
论文实验取 N = 10 N=10 N=10。每次推理的流程是:
- 编码多路 RGB 图像和语言指令;
- 编码机器人本体状态并缓存条件部分 KV;
- 随机采样一个 50 × d a 50\times d_a 50×da 的噪声动作块;
- 重复 10 次动作专家前向和 Euler 更新;
- 反归一化得到真实机器人控制量;
- 执行动作块的一部分,再获取新观测重新规划。
论文在 RTX 4090、3 路相机条件下报告:图像编码约 14ms,观测前向约 32ms,10 次动作前向合计约 27ms;本地总推理约 73ms,移动机器人经 Wi-Fi 的异机推理约 86ms。这里的 50Hz 是机器人动作控制频率,不等于模型每秒完整运行 50 次:50Hz 平台每执行 25 个动作、约 0.5 秒重新推理一次;20Hz 平台每执行 16 个动作、约 0.8 秒重新推理一次。
4、训练 recipe:通用预训练与高质量后训练分工

π0 的训练思路可以概括为"先学得广,再做得稳"。预训练数据追求任务、场景、动作策略和失败恢复的覆盖;后训练数据追求一致、流畅、有效的任务执行方式。
论文使用超过 1 万小时的机器人数据。自采部分包含约 9.03 亿个 timestep,其中约 1.06 亿来自单臂、7.97 亿来自双臂;同时混入 OXE、Bridge 和 DROID 等公开数据。论文按 timestep 统计时,公开数据占训练 mixture 的 9.1%。

跨本体数据覆盖 7 类机器人配置和 68 个宽口径任务,包括单臂、双臂和移动双臂平台。这里的"68 个任务"不是简单的动词-名词组合;例如 bussing 本身就包含垃圾/餐具识别、抓取、分类放置和遮挡恢复等多种行为。
不同机器人通过三种方式对齐:
- 状态和动作补零到统一最大维度;
- 缺失相机槽使用 mask;
- 每个"任务-机器人"组合按 n 0.43 n^{0.43} n0.43 加权,削弱超大数据组的支配效应。
预训练和后训练的差异可以这样理解:
| 阶段 | 数据特点 | 学到的能力 | 典型用法 |
|---|---|---|---|
| 预训练 | 大规模、多任务、多机器人、策略多样 | 基础操作、语义理解、纠错和恢复 | 直接提示或作为下游初始化 |
| 后训练 | 规模更小但质量高、任务策略一致 | 流畅度、效率和复杂任务完成度 | 洗衣、装箱、复杂收台等 |
只用高质量数据,模型很少见到失败状态,出错后容易"不会救";只用杂而广的数据,动作策略又可能不够稳定。π0 的经验是两者需要组合,而不是二选一。
5、训练流与推理流
5.1、训练流
- 从多机器人数据中取当前多路 RGB、本体状态、语言标注和未来 50 步动作;
- 按机器人统计量归一化状态和动作,并将低维动作补齐到统一维度;
- PaliGemma 编码图像和指令,动作专家编码状态;
- 对真实动作块采样噪声与 Flow Matching 时间,得到带噪动作;
- 动作 token 读取视觉、语言和状态条件,预测向量场;
- 以逐动作维的 MSE 训练整套模型;
- 先在大规模 mixture 上预训练,再用高质量任务数据后训练。
需要注意:论文借鉴 Transfusion 讨论了离散 token 的交叉熵目标,但 π0 机器人策略的核心训练目标是连续动作的 Flow Matching 损失。当前开源 Pi0.compute_loss() 也只返回动作向量场 MSE,没有额外语言生成损失。
5.2、推理流
- 输入当前 RGB 图像、语言指令和本体状态;
- 编码并缓存不随去噪过程变化的条件前缀;
- 从随机噪声初始化动作块;
- 用动作专家进行 10 步 Flow Matching 积分;
- 输出连续动作块并反归一化;
- 开环执行动作块的一部分;
- 获取新观测并滚动调用模型。
推理时不需要真实未来动作、未来图像、深度图或后训练标签。高层 VLM 也不是固定必需项:短任务可以直接用总指令;部分长任务中,作者额外使用高层 VLM 把"收拾桌面"分解成"拿起餐巾并扔进垃圾桶"等中间指令,再交给 π0 执行。不要把这个外置规划器误写成 π0 内部自带的分层推理。
6、源码解析:论文概念在 openpi 中如何落地
本文检查的官方仓库为 Physical-Intelligence/openpi,分支 main,commit 15a9616a00943ada6c20a0f158e3adb39df2ccac,提交日期 2026-06-16。仓库已经同时包含 π0、π0-FAST 和 π0.5,因此阅读代码时必须确认配置中的 pi05=False,不能把 π0.5 的离散状态输入或 AdaRMSNorm 写到 π0 上。
6.1、关键目录
text
openpi/
├── src/openpi/models/
│ ├── pi0.py # JAX版模型、损失和动作采样
│ ├── pi0_config.py # π0/π0.5模型配置
│ ├── gemma.py # VLM与action expert双专家Transformer
│ ├── siglip.py # 图像编码器
│ └── model.py # Observation与BaseModel接口
├── src/openpi/models_pytorch/
│ └── pi0_pytorch.py # PyTorch实现
├── src/openpi/training/
│ ├── config.py # 数据、训练和checkpoint配置
│ └── data_loader.py # LeRobot/RLDS数据加载与动作块采样
├── src/openpi/policies/
│ ├── policy.py # infer()推理封装
│ └── policy_config.py # checkpoint、归一化与策略构建
├── src/openpi/transforms.py # 图像、动作、prompt与归一化变换
├── scripts/train.py # JAX训练入口
└── scripts/serve_policy.py # 策略服务入口
6.2、论文模块到源码的映射
| 论文概念 | 源码文件 / 类 / 函数 | 实际作用 |
|---|---|---|
| PaliGemma + action expert | models/pi0.py::Pi0.__init__ |
建立 Gemma 2B 主干、Gemma 300M 动作专家和 SigLIP |
| 图像与语言前缀 | Pi0.embed_prefix() |
编码所有有效图像和 tokenized prompt |
| 状态与动作后缀 | Pi0.embed_suffix() |
投影状态、动作与时间嵌入,构造动作专家 token |
| 块式注意力 | make_attn_mask() |
根据 mask_ar 生成块间因果、块内双向的 mask |
| Flow Matching 损失 | Pi0.compute_loss() |
采样噪声和时间,预测 v_t,计算 MSE |
| 10 步动作生成 | Pi0.sample_actions() |
缓存前缀 KV,循环更新噪声动作块 |
| 数据归一化 | transforms.Normalize/Unnormalize |
训练前归一化,推理输出后恢复物理量纲 |
| 动作块读取 | training/data_loader.py |
根据数据集 FPS 构造未来 action_horizon 步 |
| 训练入口 | scripts/train.py::train_step() |
调用 compute_loss()、反传、优化并更新 EMA |
| 推理入口 | policies/policy.py::Policy.infer() |
数据变换、批处理、采样动作和输出变换 |
6.3、模型初始化
π0 与 π0.5 共用一个类,区别由配置开关控制。π0 默认使用连续状态 token 和普通动作时间 MLP:
python
paligemma_config = get_config(config.paligemma_variant)
action_expert_config = get_config(config.action_expert_variant)
llm = Module(configs=[paligemma_config, action_expert_config])
self.state_proj = nnx.Linear(config.action_dim, action_expert_config.width)
self.action_in_proj = nnx.Linear(config.action_dim, action_expert_config.width)
self.action_out_proj = nnx.Linear(action_expert_config.width, config.action_dim)
状态和动作不是经 PaliGemma 词表离散化,而是通过线性层进入动作专家宽度。configs=[主干配置, 动作专家配置] 对应论文的两套专家权重。
6.4、Flow Matching 损失
compute_loss() 与前面的公式一一对应:
python
noise = jax.random.normal(noise_rng, actions.shape)
time = jax.random.beta(time_rng, 1.5, 1, batch_shape) * 0.999 + 0.001
x_t = time[..., None, None] * noise + (1 - time[..., None, None]) * actions
u_t = noise - actions
v_t = self.action_out_proj(suffix_out[:, -self.action_horizon:])
return jnp.mean(jnp.square(v_t - u_t), axis=-1)
返回值先对动作维求均值,scripts/train.py 再对 batch 和动作时间维求均值。当前实现没有单独的 future-image loss、深度 loss、语言交叉熵或世界模型损失。
6.5、动作采样与 KV cache
推理先计算一次图像/语言前缀并保存 KV cache,随后只重复计算动作后缀:
python
dt = -1.0 / num_steps
_, kv_cache = self.PaliGemma.llm([prefix_tokens, None], ...)
def step(carry):
x_t, time = carry
suffix_tokens, ... = self.embed_suffix(observation, x_t, time)
(_, suffix_out), _ = self.PaliGemma.llm(
[None, suffix_tokens], kv_cache=kv_cache, ...
)
v_t = self.action_out_proj(suffix_out[:, -self.action_horizon:])
return x_t + dt * v_t, time + dt
这里可以看到,训练期的真实动作只用于构造监督;推理期从纯噪声开始。图像和语言没有在每个积分步重新编码,这也是论文能把 10 次动作专家前向控制在约 27ms 的关键。
6.6、数据和控制频率
开源数据加载器根据数据集 FPS 生成动作序列时间戳:第 k k k 个动作对应 k / f p s k/\mathrm{fps} k/fps 秒。模型配置决定动作步数,不同数据集通过各自的映射、归一化统计量和输出变换适配真实机器人。
官方文档特别提醒:如果目标机器人与预训练平台一致,可以尝试复用对应的 normalization stats;如果动作空间定义不同,则应重新计算统计量并同时验证复用/重算两种方案。维度相同并不代表动作语义相同,关节顺序、绝对/增量动作和夹爪范围必须逐项对齐。
7、实验结果:π0 的提升来自架构与预训练组合
7.1、直接提示评测

作者在 5 个预训练分布内任务上做直接提示评测,每个任务、每种方法平均 10 次试验;满分 1.0,部分完成按 rubric 给部分分。完整 π0 训练 70 万步,"compute parity"版本训练 16 万步;OpenVLA 为 16 万步,Octo 为 32 万步,因此论文同时给 parity 版本以减少训练更新数不一致的影响。
完整 π0 在衬衫折叠、简单收台、困难收台、杂货装袋和取吐司上均为最佳。官方项目页给出的完整 π0 分数分别为 1.000、0.971、0.875、0.786 和 0.750。π0-small 在这些任务上明显下降,OpenVLA 和 Octo 只在少数简单任务上获得非零分数。
这个结果支持两个判断:Flow Matching 动作块比逐动作离散自回归更适合高频灵巧控制;VLM 预训练又明显强于不使用 VLM 初始化的 470M π0-small。但它并不是完全严格的单变量消融,因为 π0-small 的参数量也更小,作者在论文中明确承认这一混杂因素。
7.2、语言指令与外置高层策略

语言实验包含收台、杂货装袋和摆桌三个任务,每个条件、每个任务平均 10 次。flat 只给总任务指令;human 由人类给约 2 秒粒度的中间指令;HL 由外置高层 VLM 自动给中间指令。
π0 的语言跟随率显著高于 π0-small,因此人类中间指令能明显提升任务进度,高层 VLM 也有一定帮助。π0-small 由于语言理解较弱,加入高层指令并没有稳定收益。这说明"有语言接口"不等于"能有效利用细粒度语言指导",互联网 VLM 预训练仍然是关键。
7.3、少量数据微调

作者在叠碗、毛巾折叠、微波炉放盒、替换纸巾卷和抽屉收纳上比较 1、5、10 小时微调数据。每个点平均 10 次真机试验。π0 总体优于从头训练的 π0、Diffusion Policy、ACT、OpenVLA 和 Octo;与预训练任务越相似,数据效率优势通常越明显。论文也没有声称预训练在所有任务和所有数据量下都单调占优,例如微波炉任务的部分设置中差距较小。
7.4、复杂长时程任务

复杂任务包括固定/移动双臂折衣、烘干机取衣、真实餐桌收台、纸箱组装、打包餐盒和装鸡蛋,每个任务平均 10 次。完整的"预训练 + 后训练"π0 在所有任务上都超过最大分数的 50%,并在多数任务上优于从头训练和仅预训练版本。
这张图还揭示了一个容易忽略的现象:预训练并非永远有利。装鸡蛋任务中,从头训练与预训练后微调接近,说明跨任务迁移依赖动作结构、物体属性和数据配比;"数据越多必然越好"并不是这篇论文已经证明的结论。
8、论文与当前开源实现的差异和复现注意事项
| 项目 | 论文设定 | 当前 openpi | 复现影响 |
|---|---|---|---|
| Flow 时间方向 | 正文按 τ = 0 \tau=0 τ=0 噪声、 τ = 1 \tau=1 τ=1 动作描述 | 代码按 t = 1 t=1 t=1 噪声、 t = 0 t=0 t=0 动作,并用负步长积分 | 两种约定等价,但公式和代码不能混用符号 |
| 动作维度 | 数据 mixture 最大 18 维 | Pi0Config.action_dim=32 默认预留 32 维 |
checkpoint、数据变换和机器人输出必须一致 |
| 模型范围 | 论文只讨论 π0 | 仓库同时支持 π0、π0-FAST、π0.5,另有 PyTorch 版 | 不要把 pi05=True 的 AdaRMS/离散状态写成 π0 |
| 预训练数据 | 大量 PI 私有数据 + 公开数据 | 仓库公开模型代码、部分数据接口和检查点,但完整论文 mixture 不可直接等量复现 | 可做下游微调,不能据此声称已复现论文预训练 |
| 训练目标 | 机器人策略核心为 Flow Matching | Pi0.compute_loss() 只计算动作向量场 MSE |
当前代码不是联合训练语言生成头的完整研究平台 |
| 推理实现 | 10 步 Flow Matching、KV cache | JAX 与 PyTorch 都已提供;PyTorch 功能仍与 JAX 有差异 | 以目标 checkpoint 对应框架和 README 为准 |
官方 README 给出的单卡显存参考是:推理大于 8GB;LoRA 微调大于 22.5GB;全量微调大于 70GB。仓库测试环境为 Ubuntu 22.04。PyTorch 版已经支持 π0/π0.5 训练和推理,但截至所检查提交,混合精度训练、FSDP、LoRA 和 EMA 等功能仍与 JAX 版不完全对齐,因此工程上优先把 JAX 版当作功能基准更稳妥。
最小环境和官方入口可以概括为:
bash
git clone https://github.com/Physical-Intelligence/openpi.git
cd openpi
GIT_LFS_SKIP_SMUDGE=1 uv sync
uv run scripts/compute_norm_stats.py --config-name <your_config>
uv run scripts/train.py <your_config> --exp-name=<experiment_name> --overwrite
uv run scripts/serve_policy.py policy:checkpoint \
--policy.config=<your_config> \
--policy.dir=<checkpoint_dir>
这些命令只是官方流程入口。真正接入新机器人还需要完成相机键映射、状态/动作语义对齐、控制频率、归一化统计、动作反变换和安全限幅,不能只把动作维数改成一致就直接上机。
9、方法价值、局限与工程判断
π0 最有价值的地方,是证明了 VLM 的语义能力与连续生成策略并不冲突。动作专家把高频控制从离散语言建模中拆出来,又通过共享注意力读取 VLM 表征;动作块和 KV cache 则把 10 步生成的计算成本控制在可用范围内。
它也有几项明确局限:
- 直接提示评测主要是预训练分布内任务,不能当作开放世界泛化证明;
- 最强能力依赖超过 1 万小时、包含大量非公开数据的预训练 mixture;
- 高层长时程规划在部分实验中依赖外置 VLM,而不是 π0 自身自动分解任务;
- 动作块执行期间主要是开环控制,论文早期尝试的 temporal ensembling 反而降低性能;
- 不同本体用补零统一维度解决了接口问题,但并没有从理论上解决所有跨本体负迁移。