PI0、PI0.5、PI0-FAST 原理讲解

这份文档用一条主线讲清楚三个模型:

数据怎么进来

-> state / action 怎么表示

-> 网络 forward 怎么走

-> mask 和位置编码怎么起作用

-> 损失函数怎么训练

-> 推理时怎么生成动作

三个模型都是 VLA 策略模型,也就是:

Vision + Language + robot state -> future action chunk

它们不是只预测下一步动作,而是一次预测未来 H 步动作,通常叫 action chunk 或 action horizon。

  1. 共同前提:不同机器人 state 怎么统一
    不同平台的 state 物理含义不一样:

ALOHA:

双臂关节角 + 双夹爪,常见 14 维

DROID:

Franka 7 个关节位置 + gripper,常见 8 维

LIBERO:

末端位姿 + gripper,常见 7 或 8 维

openpi 统一的是模型接口,不是自动统一物理语义。平台适配层把各平台原始数据整理成统一格式:

images:

base_0_rgb

left_wrist_0_rgb

right_wrist_0_rgb

image_masks:

每路相机是否真实存在;没有的相机可以补零图,并把 mask 设为 False

state:

平台适配后的低维状态向量

prompt:

任务文本

actions:

训练时的未来 H 步动作

所以换机器人时,真正要保证的是:

state 每一维是什么意思

action 每一维是什么意思

单位和坐标系是什么

动作是 absolute 还是 delta

归一化统计量是否匹配训练

0.1 归一化和 action padding

平台适配之后,state 和 action 还会归一化。

普通归一化:

x_norm = (x - mean) / (std + eps)

PI0.5 / PI0-FAST 常用分位数归一化,让大部分值落到近似 -1, 1

x_norm = (x - q01) / (q99 - q01 + eps) * 2 - 1

这很重要,因为 PI0.5 / PI0-FAST 会把 state 离散成 256 个桶。归一化错了,离散后的数字文本也会错。

另外,模型内部常用统一 action_dim,例如 32,但真实机器人可能只有 7、8 或 14 维动作。所以:

训练:

真实动作 padding 到模型 action_dim

推理:

模型输出裁剪回真实机器人动作维度

  1. PI0
    PI0 是连续动作空间里的 flow matching 模型。

一句话:

PI0 从一段随机动作噪声开始,根据图像、语言和 state,把噪声逐步修正成未来 H 步动作。

1.1 数据处理

PI0 的训练样本包含:

输入:

多路图像

当前 state

prompt 文本

标签:

未来 H 步连续动作

处理流程:

原始平台数据

-> 平台适配成 images / image_masks / state / prompt / actions

-> state 和 actions 归一化

-> 图像 resize 到 224 x 224

-> prompt 用 PaliGemma tokenizer 编码

-> state/actions padding 到模型 action_dim

PI0 不把 state 放进文本里。prompt 只会变成语言 token:

prompt:

"pick up the fork"

tokenized prompt:

BOS pick up the fork newline PAD ...

state 仍然是连续向量,后面会变成一个单独的 state token。

1.2 网络结构

PI0 可以看成三部分:

  1. 视觉编码器

    images -> image tokens

  2. 第 0 个专家流

    image tokens + text embeddings

    使用 PaliGemma/Gemma 那一路参数

  3. 第 1 个专家流

    state token + action tokens

    使用 Action Expert 参数

    最容易混淆的是:不是先完整跑一个 PaliGemma,再把 PaliGemma 输出送进动作网络。

真正 forward 是:

prefix_tokens = image tokens + text embeddings

suffix_tokens = state token + action tokens

prefix_out, suffix_out = two_expert_transformer(

prefix_tokens, suffix_tokens,

mask,

positions,

)

也就是说,两路 token 一开始就一起进入双专家 Transformer。它的思想和 MMDiT 类似:不同类型 token 有自己的参数流,但每层 attention 在同一个空间里融合。

结构图:

images -> vision encoder -> image tokens --------

prompt ids -> embedding table -> text embeddings ---> 双专家 Transformer -> suffix_out -> action velocity

/

state -> Linear -> state token ------------------/

noisy action x_t -> Linear + time MLP -> action tokens

1.3 双专家 Transformer 长什么样

每层 block 可以理解成:

输入:

xs0 = prefix tokens

xs1 = suffix tokens

  1. 两路分别 RMSNorm
  2. 把两路 Q/K/V 沿 sequence 维拼起来做同一个 attention
  3. attention 输出再按长度拆回两路
  4. 两路各自残差更新
  5. 两路分别过自己的 FFN/MLP
  6. 两路各自残差更新
    更具体一点:

def two_expert_block(xs, attn_mask, positions, adarms_cond=None):

中文注释:xs0 是 prefix 专家流,xs1 是 action 专家流

复制代码
normed = []
gates = []
for i, x in enumerate(xs):
    # 中文注释:每个专家有自己的 RMSNorm。
    # PI0.5 的 action 专家会在这里吃 time condition。
    y, gate = rms_norm_i(x, cond=adarms_cond[i])
    normed.append(y)
    gates.append(gate)

# 中文注释:跨分支融合发生在 attention。
attn_out = shared_attention(
    normed,
    mask=attn_mask,
    positions=positions,
)

for i in range(2):
    xs[i] = residual(xs[i], attn_out[i], gates[i])

ffn_out = []
ffn_gates = []
for i, x in enumerate(xs):
    y, gate = rms_norm_i(x, cond=adarms_cond[i])
    y = ffn_i(y)  # 中文注释:FFN 不共享,每个专家自己的参数。
    ffn_out.append(y)
    ffn_gates.append(gate)

for i in range(2):
    xs[i] = residual(xs[i], ffn_out[i], ffn_gates[i])

return xs

PI0 里通常是普通残差:

x = x + update

PI0.5 的 Action Expert 里可能带 adaRMS gate:

x = x + gate * update

1.4 PI0 forward 怎么走

训练时,PI0 一次性把 prefix 和 suffix 都送进双专家 Transformer。

  1. 图像编码

    images -> image_tokens

  2. 文本 embedding

    tokenized_prompt -> text_embeddings

  3. 构造 prefix

    prefix_tokens = concat(image_tokens, text_embeddings)

  4. 构造 flow 中间动作

    noise ~ N(0, I)

    t ~ Beta(...)

    x_t = t * noise + (1 - t) * real_actions

  5. 构造 suffix

    state -> Linear -> state_token

    x_t -> Linear -> action_tokens

    t -> sin/cos time embedding

    action_tokens + time embedding -> MLP -> action_expert_tokens

    suffix_tokens = concat(state_token, action_expert_tokens)

  6. 构造 mask 和 positions

    input_mask = concat(prefix_mask, suffix_mask)

    ar_mask = concat(prefix_ar_mask, suffix_ar_mask)

    attn_mask = make_attn_mask(input_mask, ar_mask)

    positions = cumsum(input_mask) - 1

  7. 双专家 Transformer

    prefix_out, suffix_out = llm(prefix_tokens, suffix_tokens, attn_mask, positions)

  8. 只读 action token 输出

    pred_velocity = action_out_proj(suffix_out:, -H:)

    伪代码:

def pi0_train_forward(obs, real_actions):

noise = random_normal_like(real_actions)

t = sample_time()

x_t = t * noise + (1 - t) * real_actions

target_velocity = noise - real_actions

复制代码
image_tokens = vision_encoder(obs.images)
text_embeddings = embed_text(obs.tokenized_prompt)
prefix_tokens = concat(image_tokens, text_embeddings)

state_token = linear_state(obs.state)[:, None, :]
action_tokens = linear_action(x_t)
time_emb = sincos_time_embedding(t)
time_tokens = repeat(time_emb, H)
action_tokens = mlp(concat(action_tokens, time_tokens))
suffix_tokens = concat(state_token, action_tokens)

attn_mask = build_pi0_attn_mask(prefix_tokens, suffix_tokens)
positions = build_positions_from_input_mask()

prefix_out, suffix_out = two_expert_transformer(
    [prefix_tokens, suffix_tokens],
    mask=attn_mask,
    positions=positions,
    adarms_cond=[None, None],
)

pred_velocity = action_out_proj(suffix_out[:, -H:])
loss = mean_square(pred_velocity - target_velocity)
return loss

注意:suffix = state_token + H 个 action tokens,所以最后取 suffix_out:, -H:。state token 是条件,不是输出动作。

1.5 mask 怎么理解

PI0 / PI0.5 里有两个关键 mask:

input_mask:

哪些 token 真实存在,哪些是 padding

ar_mask:

哪些位置开启新的 attention block

最终 attention mask:

block_id = cumsum(ar_mask)

attn_maski, j = block_idj <= block_idi

attn_mask 还会结合 input_mask,屏蔽 padding

PI0 的简化 token 顺序:

I1, I2, T1, T2, S, A1, A2, A3

其中:

I: image token

T: text token

S: state token

A: action token

PI0 的 ar_mask 大致是:

token: I1 I2 T1 T2 S A1 A2 A3

ar_mask: 0 0 0 0 1 0 0 0

block_id: 0 0 0 0 1 1 1 1

因此:

prefix token:

只能看 prefix,不看 state/action

state/action token:

可以看 prefix,也可以看整个 suffix

矩阵直观如下,行是 query,列是 key,1 表示能看:

复制代码
    I1 I2 T1 T2 S  A1 A2 A3

I1 1 1 1 1 0 0 0 0

I2 1 1 1 1 0 0 0 0

T1 1 1 1 1 0 0 0 0

T2 1 1 1 1 0 0 0 0

S 1 1 1 1 1 1 1 1

A1 1 1 1 1 1 1 1 1

A2 1 1 1 1 1 1 1 1

A3 1 1 1 1 1 1 1 1

这不是语言模型式的严格因果 mask。PI0 是 flow 模型,一次预测整个 action chunk,所以同一个 suffix block 内的 action tokens 可以互看。

1.6 位置编码在哪里

这里有三种容易混淆的"位置/时间"信号:

信号 用在哪里 作用

图像 patch position embedding 视觉编码器 告诉模型 patch 在图像哪个空间位置

RoPE positions Gemma/PaliGemma attention 告诉模型 token 在序列中的位置

flow time sin/cos embedding Action Expert 告诉模型当前去噪时间 t

视觉位置编码:

image -> patch tokens

patch_token = patch_content_embedding + image_position_embedding

Transformer 序列位置用 RoPE,不是给文本/action token 直接加 learned absolute position embedding:

def apply_rope(q_or_k, positions):

中文注释:positions 是每个 token 的整数位置。

angle = positions / frequency_by_head_dim()

sin, cos = sin(angle), cos(angle)

x1, x2 = split_last_dim(q_or_k)

return concat(x1 \* cos - x2 \* sin, x2 \* cos + x1 \* sin)

PI0 / PI0.5 的 positions 根据有效 token 生成:

positions = cumsum(input_mask) - 1

这样 padding 不会占真实位置。

例子:

token: I1 I2 T1 PAD T2 S A1 A2

input_mask: 1 1 1 0 1 1 1 1

cumsum: 1 2 3 3 4 5 6 7

positions: 0 1 2 2 3 4 5 6

padding 位置虽然也有数字,但会被 input_mask/attn_mask 屏蔽。

1.7 损失函数

PI0 用 flow matching MSE。

给真实动作 a 和噪声 z:

x_t = t * z + (1 - t) * a

target_velocity = z - a

pred_velocity = model(obs, x_t, t)

loss = mean((pred_velocity - target_velocity)^2)

直观理解:

t = 1:

x_t 接近纯噪声

t = 0:

x_t 接近真实动作

模型学的是:

在任意 t,把 x_t 往正确方向推的速度

1.8 推理过程

推理时没有真实动作,从随机噪声开始反向积分:

  1. 处理观测,得到 image / state / prompt
  2. 编码 prefix,并缓存 prefix 的 KV cache
  3. 初始化 x = 随机动作噪声
  4. 从 t=1 开始循环 num_steps 次
  5. 每步构造 suffix_tokens
  6. suffix 通过 KV cache 读取 prefix
  7. 预测 velocity
  8. Euler 更新 x = x + dt * velocity,其中 dt < 0
  9. 得到动作 chunk
  10. 反归一化、裁剪动作维度、必要时 delta 转 absolute
    训练和推理的关键区别:

训练:

prefix 和 suffix 一起 forward

推理:

prefix 先 forward 一次并缓存

每个去噪 step 只 forward suffix

伪代码:

def infer_pi0(obs, num_steps=10):

prefix_tokens, prefix_mask = encode_prefix(obs)

kv_cache = prefill_prefix(prefix_tokens, prefix_mask)

复制代码
x = random_normal([batch, H, action_dim])
t = 1.0
dt = -1.0 / num_steps

for _ in range(num_steps):
    suffix_tokens = build_pi0_suffix(obs.state, x, t)
    velocity = forward_suffix_with_cache(suffix_tokens, kv_cache)
    x = x + dt * velocity
    t = t + dt

return postprocess_actions(x)
  1. PI0.5

    PI0.5 仍然是 flow matching 连续动作模型。它和 PI0 的核心区别是:

  2. state 离散成数字文本,放进 prefix

  3. time 条件通过 adaRMS 调制 Action Expert

    一句话:

PI0.5 还是从动作噪声生成连续动作,但 state 更早进入语言上下文,time 更深地调制动作专家。

2.1 数据处理

PI0.5 的图像、action padding、归一化和 PI0 基本一样。区别在 prompt token 化。

PI0:

prompt -> text tokens

state -> 连续 state token,放进 suffix

PI0.5:

prompt + 离散 state -> text/state tokens

suffix 里不再有单独 state token

2.2 state 怎么离散化并放进 token 序列

先归一化 state,然后用 256 个桶离散化:

bins = linspace(-1, 1, 257):-1

bin = digitize(x, bins=bins) - 1

这相当于把 [-1, 1) 切成 256 段。

边界行为:

x >= 1:

通常落到 255

x < -1:

可能得到 -1

所以归一化统计量必须匹配训练,否则离散 state 文本会变味。

例子:

normalized_state = 2.20, -0.25, 0.40, 0.30

discrete_state = 255, 96, 179, 166

state_str = "255 96 179 166"

拼进文本模板:

Task: pick up the fork, State: 255 96 179 166;

Action:

再用 tokenizer 得到 token ids:

BOS Task : pick up the fork , State : 255 96 179 166 ; \n Action : PAD ...

注意:"255" 不一定刚好是一个 token。真正流程是:

连续 state

-> 归一化

-> 256 桶整数

-> 数字字符串

-> prompt 文本

-> tokenizer ids

伪代码:

def build_pi05_prompt(prompt, normalized_state):

boundaries = linspace(-1.0, 1.0, 257):-1

复制代码
bins = []
for x in normalized_state:
    b = digitize(x, boundaries) - 1
    bins.append(int(b))

state_str = " ".join(str(b) for b in bins)
text = f"Task: {prompt}, State: {state_str};\nAction: "
tokens = tokenizer.encode(text, add_bos=True)
return pad_or_truncate(tokens)

2.3 网络结构和 forward

PI0.5 的输入组织:

prefix_tokens:

image tokens

  • prompt/state text embeddings

suffix_tokens:

noisy action tokens

time_cond:

t -> sin/cos embedding -> MLP -> adaRMS condition

forward 仍然是一次双专家 Transformer:

prefix_out, suffix_out = llm(

prefix_tokens, suffix_tokens,

adarms_cond=None, time_cond,

mask=attn_mask,

positions=positions,

)

和 PI0 的结构差异:

项目 PI0 PI0.5

state 位置 suffix 里的连续 state token prefix 里的离散 state 文本

suffix 内容 state token + action tokens action tokens

time 进入方式 和 action token 拼接后过 MLP 作为 adaRMS 条件调制 Action Expert

PI0.5 的训练 forward:

  1. 数据阶段构造 tokenized_prompt

    prompt + discrete state -> text/state tokens

  2. 构造 flow 中间动作

    x_t = t * noise + (1 - t) * actions

    target_velocity = noise - actions

  3. prefix

    images -> image tokens

    tokenized_prompt -> text/state embeddings

    prefix_tokens = concat(image tokens, text/state embeddings)

  4. suffix

    x_t -> action_in_proj -> action tokens

    suffix_tokens = action tokens

  5. time condition

    t -> sin/cos -> MLP -> time_cond

  6. mask / positions

    和 PI0 一样,prefix 不看 suffix,suffix 可以看 prefix

  7. 双专家 Transformer

    llm(prefix_tokens, suffix_tokens, adarms_cond=None, time_cond)

  8. 输出

    action_out_proj(suffix_out:, -H:) -> pred_velocity

    伪代码:

def pi05_train_forward(obs, actions):

noise = random_normal_like(actions)

t = sample_time()

x_t = t * noise + (1 - t) * actions

target_velocity = noise - actions

复制代码
image_tokens = vision_encoder(obs.images)
text_state_embeddings = embed_text(obs.tokenized_prompt)
prefix_tokens = concat(image_tokens, text_state_embeddings)

suffix_tokens = action_in_proj(x_t)

time_emb = sincos_time_embedding(t)
time_cond = swish(time_mlp_in(time_emb))
time_cond = swish(time_mlp_out(time_cond))

attn_mask = build_pi05_attn_mask()
positions = build_positions_from_input_mask()

prefix_out, suffix_out = two_expert_transformer(
    [prefix_tokens, suffix_tokens],
    mask=attn_mask,
    positions=positions,
    adarms_cond=[None, time_cond],
)

pred_velocity = action_out_proj(suffix_out[:, -H:])
return mean_square(pred_velocity - target_velocity)

2.4 adaRMS 怎么理解

普通 RMSNorm:

normed = rms_norm(x)

PI0.5 的 Action Expert 会把 time condition 变成 scale、shift、gate:

scale, shift, gate = f(time_cond)

normed = rms_norm(x) * (1 + scale) + shift

residual = residual + gate * update

直觉:

t 接近 1:

当前动作很像噪声,需要大幅修正

t 接近 0:

当前动作接近最终动作,需要细调

PI0 是把 time embedding 融到 action token 里;PI0.5 是让 time 直接调制 Action Expert 每层。

2.5 mask、loss、推理

PI0.5 的 mask 思想和 PI0 一样:

prefix:

image + prompt + state text

不看 action suffix

suffix:

action tokens

可以看 prefix,也可以看整个 suffix

简化序列:

PI0:

I, T, S, A1, A2, A3

PI0.5:

I, T+StateText, A1, A2, A3

损失和 PI0 一样,仍然是 flow matching MSE:

x_t = t * noise + (1 - t) * actions

target_velocity = noise - actions

pred_velocity = model(obs, x_t, t)

loss = mean((pred_velocity - target_velocity)^2)

推理也和 PI0 一样从噪声反向积分,只是 prefix 里多了离散 state 文本:

def infer_pi05(obs, num_steps=10):

obs.tokenized_prompt = build_pi05_prompt(obs.prompt, obs.normalized_state)

kv_cache = prefill_prefix(obs.images, obs.tokenized_prompt)

复制代码
x = random_normal([batch, H, action_dim])
t = 1.0
dt = -1.0 / num_steps

for _ in range(num_steps):
    suffix_tokens = action_in_proj(x)
    time_cond = build_time_cond(t)
    velocity = forward_suffix_with_cache(suffix_tokens, kv_cache, time_cond)
    x = x + dt * velocity
    t = t + dt

return postprocess_actions(x)
  1. PI0-FAST
    PI0-FAST 和前两个模型差别最大。它不是 flow matching,也不在连续动作空间里多步去噪。

一句话:

PI0-FAST 先把连续动作变成 action tokens,然后像语言模型写句子一样,从左到右生成动作 token。

3.1 数据处理

PI0-FAST 的输入仍然来自平台适配:

images / image_masks / state / prompt / actions

处理方式:

images:

resize 到 224 x 224

视觉编码器变成 image embeddings

state:

归一化

256 桶离散化

变成数字文本

prompt:

小写、清洗

和 state 拼成 prefix

actions:

训练时用 FAST tokenizer 编成 action tokens

文本 prefix:

Task: pick up the fork, State: 255 96 179 166;

训练时再接动作 postfix:

Action: <action_token_1> <action_token_2> ... |

完整序列可以理解成:

image embeddings

BOS Task: ..., State: ...;

Action: A1 A2 A3 ... | EOS

3.2 FAST action tokenizer

PI0-FAST 需要把连续动作 H, action_dim 编成离散 token:

continuous actions -> FAST tokenizer -> raw action tokens

然后 raw action tokens 会映射到 PaliGemma/Gemma 词表末尾附近的一段 token id:

lm_action_token = vocab_size - 1 - skip_tokens - raw_action_token

直观理解:

语言模型原本预测文字 token

PI0-FAST 让动作也变成词表 token

于是动作生成变成 next-token prediction

3.3 网络结构和 forward

PI0-FAST 是单路自回归 Gemma Transformer,没有 PI0/PI0.5 的 Action Expert 双分支。

结构:

images -> vision encoder -> image embeddings ----

-> Gemma autoregressive Transformer -> vocab logits

text/state/action token ids -> token embeddings --/

forward:

  1. image embeddings
  2. tokenized prompt/action ids -> token embeddings
  3. concat 成一个长序列
  4. make_attn_mask(token_mask, ar_mask)
  5. Gemma Transformer
  6. 输出每个位置的 vocab logits
    伪代码:

def pi0_fast_forward(obs):

image_embeddings = vision_encoder(obs.images)

token_embeddings = gemma_embed(obs.tokenized_prompt)

复制代码
sequence = concat(image_embeddings, token_embeddings)
attn_mask = make_attn_mask(obs.token_mask, obs.ar_mask)

# 中文注释:训练时可以用默认 positions;
# decode 使用 KV cache 时必须显式传 positions。
hidden = gemma_transformer(sequence, mask=attn_mask)
logits = vocab_projection(hidden)
return logits

位置编码:

图像部分:

视觉编码器内部有 patch position embedding

Gemma token 序列:

attention 的 Q/K 使用 RoPE

decode 阶段:

新 token 的 position 要接在 prefix + 已生成 token 后面

3.4 mask 和损失

PI0-FAST 有三种 mask:

token_mask:

哪些位置有效,哪些是 padding

ar_mask:

哪些位置进入自回归区域

loss_mask:

哪些位置计算交叉熵损失

假设序列是:

I1, I2, T1, T2, ST1, ST2, "Action:", A1, A2, A3, "\|", EOS, PAD

mask 大致是:

token: I1 I2 T1 T2 ST1 ST2 Act A1 A2 A3 | EOS PAD

token_mask: 1 1 1 1 1 1 1 1 1 1 1 1 0

ar_mask: 0 0 0 0 0 0 1 1 1 1 1 1 0

loss_mask: 0 0 0 0 0 0 1 1 1 1 1 1 0

ar_mask 累加后:

token: I1 I2 T1 T2 ST1 ST2 Act A1 A2 A3 | EOS

ar_mask: 0 0 0 0 0 0 1 1 1 1 1 1

block_id: 0 0 0 0 0 0 1 2 3 4 5 6

因此:

prefix:

图像 + Task + State

作为条件,不算 loss

Action: / action tokens / |:

自回归区域

当前 token 只能看 prefix 和过去 token

这些位置计算 loss

PI0/PI0.5 和 PI0-FAST 的关键区别:

PI0 / PI0.5:

一次整体生成连续 action chunk

action tokens 在 suffix 内部可以互看

loss 是 velocity MSE

PI0-FAST:

从左到右生成 action tokens

当前 action token 不能看未来 action token

loss 是 next-token cross entropy

交叉熵损失:

loss = - sum(loss_maski * log P(tokeni | previous tokens)) / sum(loss_mask)

伪代码:

def train_pi0_fast(obs):

logits = pi0_fast_forward(obs)

复制代码
targets = obs.tokenized_prompt[:, 1:]
pred = logits[:, :-1]
mask = obs.token_loss_mask[:, 1:]

return cross_entropy(pred, targets, mask)

3.5 推理过程

推理时没有 action tokens,模型自己从 "Action:" 后面生成:

  1. 平台适配,得到 images / state / prompt
  2. state 归一化并离散成数字文本
  3. 构造 prefix: Task + State
  4. 编码图像和 prefix,建立 KV cache
  5. 每次生成一个 token
  6. 遇到 EOS 或达到最大步数停止
  7. 解析 Action: 后、| 前的 action tokens
  8. FAST tokenizer 解码成连续动作
  9. 反归一化并裁剪动作维度
    伪代码:

def infer_pi0_fast(obs, max_steps=256):

prefix_tokens = build_fast_prefix(obs.prompt, obs.normalized_state)

image_embeddings = vision_encoder(obs.images)

复制代码
cache, last_logits = gemma_prefill(image_embeddings, prefix_tokens)

generated = []
for step in range(max_steps):
    token = sample_or_argmax(last_logits)
    generated.append(token)

    if token == EOS:
        break

    # 中文注释:decode 时新 token 的 position 接在 cache 后面。
    cache, last_logits = gemma_decode_one_step(token, cache)

action_tokens = parse_action_tokens(generated)
actions = fast_tokenizer_decode(action_tokens)
return postprocess_actions(actions)
  1. 三个模型对比
    项目 PI0 PI0.5 PI0-FAST
    动作生成 flow matching flow matching 自回归 token 生成
    动作表示 连续动作 连续动作 FAST action tokens
    state 位置 连续 state token,放 suffix 离散数字文本,放 prefix 离散数字文本,放 prefix
    网络 双专家 Transformer 双专家 Transformer 单路 Gemma
    time 条件 拼到 action token 后过 MLP adaRMS 调制 Action Expert 无 flow time
    mask 重点 suffix 内 action 可互看 suffix 内 action 可互看 action tokens 严格自回归
    loss velocity MSE velocity MSE token cross entropy
    推理 噪声多步积分 噪声多步积分 逐 token 生成后解码
    一句话总结:

PI0:

连续 state + 连续动作 flow。

PI0.5:

离散 state 文本 + 连续动作 flow + time adaRMS。

PI0-FAST:

离散 state 文本 + 离散 action tokens + 自回归生成。

  1. 一条真实推理请求怎么走

以 LIBERO 风格输入为例:

raw_obs:

observation/image

observation/wrist_image

observation/state

prompt = "pick up the fork"

平台适配:

base_0_rgb <- observation/image

left_wrist_0_rgb <- observation/wrist_image

right_wrist_0_rgb <- zeros

image_mask <- True, True, False

state <- observation/state

prompt <- "pick up the fork"

PI0:

prompt -> text embeddings

state -> continuous state token

random action noise -> flow denoising -> continuous actions

PI0.5:

state -> normalize -> 256-bin numbers -> state text

prompt + state text -> prefix tokens

random action noise -> flow denoising -> continuous actions

PI0-FAST:

state -> normalize -> 256-bin numbers -> state text

prompt + state text -> prefix tokens

model generates action tokens after "Action:"

FAST tokenizer decodes tokens -> continuous actions

最后都会做:

动作反归一化

裁剪回真实机器人动作维度

如果训练用 delta action,则还原成 absolute action

返回 action chunk

实际执行时,机器人通常只执行 action chunk 的前几步,然后重新观察、重新规划。

  1. 最容易混淆的点
    6.1 PaliGemma 不是先完整跑完再接动作头
    PI0 / PI0.5 的关键 forward 是:

llm(prefix_tokens, suffix_tokens, ...)

prefix_tokens 和 suffix_tokens 同时进入双专家 Transformer,每层 attention 融合。PaliGemma/Gemma 参数流是第 0 个专家流,不是一个前置完整模型阶段。

6.2 state 统一不等于语义自动统一

模型接口都叫 state,但每个平台的 state 维度、单位、坐标系可能不同。适配器和训练配置必须定义清楚每一维含义。

6.3 PI0.5 的 state 不是专用 state token

PI0.5 的 state 是:

连续值 -> 归一化 -> 256 桶整数 -> 数字字符串 -> prompt 文本 -> tokenizer ids

它属于 prefix 文本条件。

6.4 PI0 和 PI0.5 损失一样

二者都是 flow matching MSE。区别不在损失,而在:

state 放在哪里

time 怎么进入 Action Expert

6.5 PI0-FAST 的 action tokens 不是普通文本

它们来自 FAST tokenizer,再映射到语言模型词表的一段 token id。模型像语言模型一样训练,但最后会把 token 解码回连续动作。

6.6 位置编码不是只有一种

图像位置:

视觉编码器的 patch position embedding

token 顺序:

Gemma/PaliGemma attention 里的 RoPE

去噪时间:

flow matching 的 time embedding

相关推荐
deepdata_cn1 小时前
AI双向赋能:一边吃掉巨量电力,一边重构新型能源体系
人工智能·能源
aiqianji1 小时前
垂直专业的AI短篇小说写作软件有哪些特点?
人工智能·python
在书中成长1 小时前
HarmonyOS 小游戏《对战五子棋》开发第33篇 - AI异步落子与setTimeout
人工智能·harmonyos
2601_955759621 小时前
code0 gpt-image-2 场景相关:广告投放素材快速生成流程
人工智能·gpt
方华世界1 小时前
企业级Java AI Agent应用平台
java·开发语言·人工智能
aqi001 小时前
15天学会AI应用开发(十三)上下文与RAG的阶段性总结
人工智能·python·大模型·ai编程·ai应用
m沐沐2 小时前
【深度学习】dlib 人脸关键点实时疲劳检测
人工智能·深度学习·计算机视觉·疲劳检测·关键点检测·人脸检测·dlib
一个处女座的程序猿2 小时前
AI之Tool:Flint(酷炫可编辑图表)的简介、安装和使用方法、案例应用之详细攻略
人工智能·chart·flint