前言

最近认真阅读了Physical Intelligence发布的π0.5论文以及OpenPI开源代码,最大的感受是π0.5的突破并不在模型结构,而在训练范式。

过去大家做VLA时,普遍认为更多机器人数据 = 更强泛化能力

但π0.5给出了一个不同答案:机器人泛化能力 = 机器人数据 + 互联网知识 + 语义理解能力

换句话说:

机器人不只是要学会动作,更要学会理解世界。

本文将结合论文和OpenPI源码,分析π0.5是如何实现Open-World Generalization的。

1. 为什么需要π0.5

VLA的发展路线

过去几年VLA发展非常快:

RT-2

Google提出:Vision + Language + Action,首次证明VLM可以直接驱动机器人。

OpenVLA

解决了训练成本高的问题。

π0

Physical Intelligence提出:VLM + Flow Matching,实现端到端机器人控制。架构如下:

Image + Language -> PaliGemma -> Action Expert -> Action

π0在多个机器人平台上表现优异。但仍存在一个问题:环境泛化能力不足。例如:

训练集:红色枕头、白色床、A房间

测试集:蓝色枕头、木质床、B房间

成功率就会明显下降。原因很简单:机器人数据规模太小,即使收集数十万条示教数据,仍远远少于互联网视觉数据,这就是π0.5诞生的背景。

2. π0.5改了什么

先看对比:

模块 π0 π0.5
VLM Backbone PaliGemma PaliGemma
Action Expert Flow Matching Flow Matching
Robot state输入 Continuous State(

Suffix

Tokenized State(

Prefix

Timestep注入 MLP Fusion AdaRMSNorm
训练方式 Robot Data Multi-source Co-Training

从整体框架来看,π0.5仍然沿用了π0的核心设计,包括PaliGemma Backbone以及基于Flow Matching的Action Expert,因此两者在宏观架构上保持了较高一致性。但如果进一步分析论文和源码实现,可以发现π0.5在状态表示、条件注入方式以及训练流程上都进行了调整,这些改动共同支撑了其更强的开放环境泛化能力。

3. π0.5核心思想:Co-Training

第一次阅读π0.5论文时,原以为作者会像很多VLA工作一样,把重点放在模型结构设计上,例如引入新的Action Head、增加更复杂的规划模块,或者进一步扩大模型规模。但深入阅读后发现,π0.5最大的创新其实并不在模型本身,而在于作者对机器人学习数据的重新思考。

传统机器人学习领域有一个默认假设:机器人想要学会完成任务,就必须依赖大量机器人示教数据。然而机器人数据有一个天然缺陷——获取成本极高。采集一条机器人轨迹往往需要真实机器人执行、人工标注以及后处理流程,相比互联网每天产生的海量视觉和语言数据,机器人数据规模始终处于劣势。

作者进一步分析发现,机器人示教数据实际上包含两类完全不同的信息。

第一类是动作知识(Action Knowledge)。例如如何抓取杯子、如何打开抽屉、如何控制机械臂完成轨迹规划等。这部分知识具有明显的机器人领域特征,只能通过机器人交互数据获得。

第二类则是世界知识(World Knowledge)。例如什么是枕头、什么是垃圾桶、什么是毛巾,或者在整理房间时哪些物体通常应该被放回原位。这类知识本质上属于视觉语义理解能力,而大型视觉语言模型已经在互联网数据上学习了大量相关内容。

这意味着一个有趣的问题:如果互联网数据已经帮助模型掌握了丰富的世界知识,那么机器人是否真的还需要从机器人数据中重新学习这些内容?π0.5的答案是否定的。因此作者提出了Co-Training(协同训练)框架,希望让机器人数据负责学习动作能力,而让互联网数据负责补充世界知识。与以往仅依赖机器人轨迹训练不同,π0.5将多种来源的数据统一纳入训练过程,包括机器人操作数据(Robot Data)、目标检测数据(Object Detection)、视觉问答数据(Visual QA)、语义子任务预测数据(Semantic Prediction)、互联网视觉语言数据(Web Data)以及来自不同机器人平台的数据(Cross-Robot Data)。

从直观上看,这有点类似于给机器人配备了一位经验丰富的老师。过去机器人只能通过“亲自做”来学习世界,而现在它既能通过实际操作积累经验,也能通过互联网知识理解周围环境。例如在执行“整理卧室”任务时,机器人可能从未见过某种新款抱枕,但由于视觉语言模型已经学习过大量家居场景,它依然能够推断出抱枕属于床上用品,并判断其应该被放置在床上而不是垃圾桶里。

更重要的是,论文中的实验表明,仅仅增加机器人数据规模并不能显著提升开放环境下的泛化能力,而引入互联网知识后,模型在新场景、新物体以及新任务上的成功率都有明显提升。这说明机器人泛化能力的瓶颈并不完全在于动作生成,而在于对环境的理解能力。

从这个角度来看,π0.5的Co-Training实际上体现了一种新的研究思路:机器人模型不应该只是一套动作控制系统,而应该成为一个能够利用互联网知识理解真实世界的具身智能系统。相比单纯堆积机器人数据,这种思路或许才是未来Foundation Robot Model的正确发展方向。

上图展示了π0.5在预训练和后训练阶段使用的典型任务形式。可以看到,除了机器人动作预测之外,作者还引入了图像描述、视觉问答、目标定位以及高层任务预测等任务。虽然这些任务看起来差异很大,但本质上都在帮助模型学习环境语义信息。相比于传统VLA仅依赖动作监督的训练方式,这种多任务联合训练更有利于模型建立对真实世界的理解能力。

4. 统一任务建模:π0.5如何利用不同来源的数据

在上一节介绍Co-Training时,讨论了π0.5为什么要引入机器人数据之外的知识来源。例如互联网视觉数据、目标检测数据以及高层任务数据等。这些数据能够帮助模型学习更加丰富的世界知识,从而提升开放环境下的泛化能力。不过当时还有一个问题没有解决。这些数据虽然都与机器人任务有关,但它们的监督形式其实完全不同。

例如目标检测任务需要预测目标框位置,视觉问答任务需要生成文本答案,高层任务预测需要生成下一步子任务,而机器人控制任务则需要预测连续动作轨迹。传统多任务学习通常会为每一种任务设计专门的输出头,不同任务之间虽然共享部分特征提取网络,但训练目标和输出形式往往彼此独立。

如果继续沿用这种思路,那么随着任务数量增加,模型结构会越来越复杂。同时,不同任务之间能够共享的信息也会受到限制。例如目标检测学到的物体识别能力,并不一定能够直接帮助机器人完成抓取动作;视觉问答学到的语义知识,也未必能够有效迁移到动作生成任务上。

而π0.5采用了另外一种思路。作者并没有针对不同任务设计大量专用模块,而是尽可能将不同来源的数据组织成统一的训练形式。无论是目标检测、视觉问答、高层任务预测还是机器人动作学习,本质上都可以被理解为一种条件预测问题:模型根据当前观察到的图像以及文本提示,预测对应的目标结果。

这种统一建模方式最大的价值在于,它让来自不同领域的数据第一次真正进入了同一个训练框架。模型在学习物体识别能力的同时,也在学习任务规划能力;在学习视觉语义理解的同时,也在学习机器人动作控制能力。不同任务不再是相互独立的训练目标,而是在共享参数的过程中逐渐形成统一表示。

事实上,这也是当前大模型发展的一个重要趋势。过去很多系统习惯于针对不同任务设计专门模块,而近年来越来越多工作开始尝试用统一的建模方式解决不同问题。对于机器人领域而言,这种变化尤为重要。因为机器人数据天然稀缺,仅依赖示教数据很难覆盖真实世界中的复杂场景,而统一任务建模则为利用互联网知识提供了可能。

个人认为,这一点其实是π0.5容易被忽略但非常关键的设计。Co-Training解决的是训练什么的问题,而统一任务建模解决的是“如何训练”的问题。只有当不同来源的数据能够被组织到同一个训练框架中时,互联网知识、语义知识以及机器人控制能力之间才有机会实现真正的知识迁移。

从后面的实验结果来看,这种设计确实带来了更好的开放环境泛化能力。模型不再只是单纯记忆动作轨迹,而是在一定程度上学会了利用已有知识理解场景,并将这种理解能力迁移到机器人控制任务中。

5. OpenPI源码解析

前面几节主要从论文角度分析了π0.5的核心思想,包括Co-Training和Hybrid Example。接下来从OpenPI源码(GitHub - Physical-Intelligence/openpi · GitHub)角度看一下,这套思想在工程实现中是如何落地的。

需要先说明一点:OpenPI仓库目前开源的是π0、π0-FAST和π0.5的训练/推理框架,其中π0.5在仓库中主要支持Flow Matching Action Head的训练和推理,并不是完整复现论文中所有大规模Co-Training预训练细节。

从整体上看,OpenPI的推理和训练Pipeline可以拆成四个阶段:

Raw Data -> Data Transform / Repack -> Model Input / Tokenize -> Pi0 / Pi0.5 Model -> Action Postprocess

也就是说,OpenPI并不是直接把原始数据送进模型,而是先通过一系列transform将不同机器人平台、不同数据格式统一成模型能够理解的标准格式。这个设计非常关键,因为它本质上就是Hybrid Example在工程侧的基础:不同来源的数据虽然原始字段不同,但最终都会被整理成统一的 image、state、prompt、actions等结构。

5.1 配置入口:pi05是如何被启用的

在OpenPI中,不同模型主要通过training/config.py中的TrainConfig进行配置。例如π0.5 DROID微调配置中,模型部分会显式设置:

model=pi0_config.Pi0Config(
    pi05=True,
    action_dim=32,
    action_horizon=16,
)

这里有几个参数比较重要。首先是pi05=True,这个字段决定当前模型走π0.5的逻辑,其次是action_dim=32,表示π0.5-DROID使用32维动作空间。最后是action_horizon=16,表示模型一次不是只预测单步动作,而是预测一个action chunk,也就是未来一段时间内的动作序列。

从代码角度看,π0.5与π0的差异并不是重新写了一套完全不同的模型,而是在同一个Pi0Config / Pi0 框架下通过pi05=True开启不同的条件建模方式。这也和论文中的描述一致:π0.5并不是彻底推翻π0,而是在π0的基础上增强泛化能力。

5.2 数据阶段:不同数据先被整理成统一结构

机器人数据通常来自不同平台,例如ALOHA、DROID、LIBERO等。不同数据集的字段命名并不统一,有的数据叫observation.image,有的数据叫observation.images.cam_high,还有的数据可能把action存在action字段里。因此,OpenPI做的第一件事就是通过RepackTransform将不同数据源重新打包。

源码中RepackTransform的注释已经说明了它的作用:

@dataclasses.dataclass(frozen=True)
class RepackTransform(DataTransformFn):
    """Repacks an input dictionary into a new dictionary.

    Example:
    {
        "images": {
            "cam_high": "observation.images.top",
            "cam_low": "observation.images.bottom",
        },
        "state": "observation.state",
        "actions": "action",
    }
    """
    structure: at.PyTree[str]

    def __call__(self, data: DataDict) -> DataDict:
        flat_item = flatten_dict(data)
        return jax.tree.map(lambda k: flat_item[k], self.structure)

这段代码的作用可以理解为字段重映射。原始数据可能长这样:

{
    "observation": {
        "images": {
            "cam_high": ...,
            "cam_left_wrist": ...,
            "cam_right_wrist": ...
        },
        "state": ...
    },
    "action": ...
}

经过RepackTransform后,会变成模型更容易处理的结构:

{
    "images": {
        "cam_high": ...,
        "cam_left_wrist": ...,
        "cam_right_wrist": ...
    },
    "state": ...,
    "actions": ...
}

这一步虽然看起来只是工程处理,但实际上非常重要。因为只有把不同机器人平台的数据格式统一起来,后面才有可能做Cross-Robot Training。否则每个机器人都有自己的字段结构,模型训练代码就会变得非常混乱。

5.3 Hybrid Example在OpenPI中的落地理解

论文中的Hybrid Example强调的是:不同任务不一定要设计不同Head,而是可以统一成多模态序列进行建模。例如目标检测、视觉问答、语义子任务预测和动作生成,本质上都可以被组织成:

Image + Prompt → Target

在OpenPI的开源实现中,可以看到这种思想的工程基础。无论数据来自哪种机器人平台,最后都会被整理成类似下面的结构:

{
    "image": {
        "base_0_rgb": ...,
        "left_wrist_0_rgb": ...,
        "right_wrist_0_rgb": ...
    },
    "image_mask": {
        "base_0_rgb": True,
        "left_wrist_0_rgb": True,
        "right_wrist_0_rgb": False
    },
    "state": ...,
    "prompt": "pick up the cup",
    "actions": ...
}

这里最关键的是prompt和actions。对于机器人控制任务来说,prompt是语言指令,actions是监督信号;而如果扩展到论文中的Detection、VQA或Semantic Prediction,本质上也可以沿用类似范式,只是target不再是连续动作,而是bbox、文本答案或高层子任务。

也就是说,OpenPI中的数 transform pipeline提供了Hybrid Example所需的基本接口:

不同原始数据 → 统一字段结构 → 统一图像输入 → 统一语言prompt → 统一target/action监督

由此可见,Hybrid Example是一种非常工程化的数据组织方式。它的关键并不在于某个特殊网络层,而在于将不同任务都转化成模型可以统一消费的输入输出格式。

5.4 输入预处理:从原始观测到模型输入

以LIBERO policy中的输入转换为例,OpenPI会把原始图像、状态和语言指令整理成模型需要的字段。相关代码大致如下:

@dataclasses.dataclass(frozen=True)
class LiberoInputs(transforms.DataTransformFn):
    model_type: _model.ModelType

    def __call__(self, data: dict) -> dict:
        base_image = _parse_image(data["observation/image"])
        wrist_image = _parse_image(data["observation/wrist_image"])

        inputs = {
            "state": data["observation/state"],
            "image": {
                "base_0_rgb": base_image,
                "left_wrist_0_rgb": wrist_image,
                "right_wrist_0_rgb": np.zeros_like(base_image),
            },
            "image_mask": {
                "base_0_rgb": np.True_,
                "left_wrist_0_rgb": np.True_,
                "right_wrist_0_rgb": np.False_,
            },
        }

        if "actions" in data:
            inputs["actions"] = data["actions"]

        if "prompt" in data:
            inputs["prompt"] = data["prompt"]

        return inputs

这段代码可以分成四步理解。

第一步是解析图像。_parse_image会把图像统一转换成uint8的HWC格式,因为不同数据集里图像可能是float32,也可能是CHW格式。

第二步是构造多视角图像输入。π0系列模型通常支持一个第三人称视角和两个腕部相机视角,因此代码中会构造base_0_rgb、left_wrist_0_rgb、right_wrist_0_rgb三路图像。

第三步是构造image_mask。如果某个相机不存在,就用零图像填充,并通过mask告诉模型这个图像是否有效。这个设计在机器人数据处理中非常常见,因为不同平台的相机数量并不一致。

第四步是传入prompt和actions。其中prompt是语言指令,推理和训练都需要;actions只在训练阶段存在,用来计算loss。

从这段代码可以看出,OpenPI的输入并不是简单的图像 + 文本,而是一个结构化观测:

多视角图像 + 图像有效性mask + 机器人状态state + 语言指令prompt + 动作监督actions

5.5 Prompt Tokenize:语言如何进入模型

在完成字段整理后,语言指令还需要被tokenize。OpenPI中对应的是TokenizePrompt:

@dataclasses.dataclass(frozen=True)
class TokenizePrompt(DataTransformFn):
    tokenizer: _tokenizer.PaligemmaTokenizer
    discrete_state_input: bool = False

    def __call__(self, data: DataDict) -> DataDict:
        if (prompt := data.pop("prompt", None)) is None:
            raise ValueError("Prompt is required")

        if not isinstance(prompt, str):
            prompt = prompt.item()

        tokens, token_masks = self.tokenizer.tokenize(prompt, state)

        return {
            **data,
            "tokenized_prompt": tokens,
            "tokenized_prompt_mask": token_masks
        }

首先,prompt在进入模型前会从原始字符串变成tokenized_prompt。也就是说,后续模型看到的不再是字符串,而是一串token id。随后,tokenize后还会生成tokenized_prompt_mask,用于标记哪些token是有效的,哪些是padding。这个mask后面会参与attention mask的构造。

5.6 Policy 创建:输入Transform和输出Transform如何串起来

OpenPI的推理入口通常是:

policy = policy_config.create_trained_policy(
    config,
    checkpoint_dir
)

action_chunk = policy.infer(example)

create_trained_policy会加载模型、加载归一化统计量,并把输入输出transform串起来:

return _policy.Policy(
    model,
    transforms=[
        *repack_transforms.inputs,
        transforms.InjectDefaultPrompt(default_prompt),
        *data_config.data_transforms.inputs,
        transforms.Normalize(norm_stats, use_quantiles=data_config.use_quantile_norm),
        *data_config.model_transforms.inputs,
    ],
    output_transforms=[
        *data_config.model_transforms.outputs,
        transforms.Unnormalize(norm_stats, use_quantiles=data_config.use_quantile_norm),
        *data_config.data_transforms.outputs,
        *repack_transforms.outputs,
    ],
    sample_kwargs=sample_kwargs,
)

从工程角度看,这种设计非常适合做多机器人训练。因为如果新增一个机器人平台,理论上只需要新增对应的input/output transform,而不需要改模型主体。

5.7 模型结构:Prefix和Suffix的设计

进入模型后,π0.5会把输入拆成两部分:Prefix和Suffix。

Prefix主要包含图像和语言信息:

def embed_prefix(self, obs):
    input_mask = []
    ar_mask = []
    tokens = []

    for name in obs.images:
        image_tokens, _ = self.PaliGemma.img(obs.images[name], train=False)
        tokens.append(image_tokens)
        input_mask.append(...)

    if obs.tokenized_prompt is not None:
        tokenized_inputs = self.PaliGemma.llm(
            obs.tokenized_prompt,
            method="embed"
        )
        tokens.append(tokenized_inputs)
        input_mask.append(obs.tokenized_prompt_mask)

    tokens = jnp.concatenate(tokens, axis=1)
    input_mask = jnp.concatenate(input_mask, axis=1)

    return tokens, input_mask, ar_mask

先用SigLIP图像编码器把多视角图像变成image tokens,再用Gemma的embedding层把语言prompt变成language tokens,最后把二者拼接起来。因此Prefix可以理解为:

Prefix = Image Tokens + Language Tokens

它表示当前任务的上下文,也就是模型需要根据什么信息来生成动作。

Suffix则主要和动作生成有关:

def embed_suffix(self, obs, noisy_actions, timestep):
    action_tokens = self.action_in_proj(noisy_actions)

    time_emb = posemb_sincos(
        timestep,
        self.action_in_proj.out_features,
        min_period=4e-3,
        max_period=4.0
    )

    if self.pi05:
        time_emb = self.time_mlp_in(time_emb)
        time_emb = nnx.swish(time_emb)
        time_emb = self.time_mlp_out(time_emb)
        time_emb = nnx.swish(time_emb)

        action_expert_tokens = action_tokens
        adarms_cond = time_emb
    else:
        ...

Suffix可以理解为:

Suffix = Noisy Action Tokens + Time Embedding

模型并不是直接输入observation后输出action,而是输入一个带噪声的动作x_t,再结合时间步t,预测从噪声到真实动作的速度场。

π0.5与π0在这里的一个重要区别是:当self.pi05=True时,时间信息通过adaRMS条件注入到action expert中,而不是像π0那样直接拼接state/time/action后走MLP。这也是源码中pi05=True真正影响模型结构的地方之一。

5.8 训练阶段:Flow Matching Loss是如何计算的

训练时的核心函数是compute_loss:

def compute_loss(self, rng, observation, actions, train=False):
    preprocess_rng, noise_rng, time_rng = jax.random.split(rng, 3)

    observation = _model.preprocess_observation(
        preprocess_rng,
        observation,
        train=train
    )

    noise = jax.random.normal(noise_rng, actions.shape)
    time = jax.random.beta(time_rng, 1.5, 1, batch_shape) * 0.999 + 0.001
    time_expanded = time[..., None, None]

    x_t = time_expanded * noise + (1 - time_expanded) * actions
    u_t = noise - actions

    prefix_tokens, prefix_mask, prefix_ar_mask = self.embed_prefix(observation)
    suffix_tokens, suffix_mask, suffix_ar_mask, adarms_cond = self.embed_suffix(
        observation,
        x_t,
        time
    )

    ...
    v_t = self.action_out_proj(suffix_out[:, -self.action_horizon:])
    return jnp.mean(jnp.square(v_t - u_t), axis=-1)

首先,模型会从高斯分布中采样一个随机噪声noise,然后随机采样一个时间步time。接着通过下面的插值公式构造带噪动作:

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

当time接近1时,x_t更接近随机噪声;当time接近 0 时,x_t更接近真实动作。模型要学习的是从真实动作到噪声之间的速度方向:

u_t = noise - actions

然后模型预测速度场v_t,并用MSE约束:

loss = MSE(v_t, u_t)

Flow Matching Loss和Diffusion Policy很像,都是从噪声恢复动作,但Flow Matching学的是连续流的方向,因此通常可以用更少的采样步数完成推理。

需要注意的是,OpenPI当前开源代码中的compute_loss主要体现的是action loss。论文中提到的更完整的Co-Training / VLM相关loss,在开源代码中并没有完整展开。

5.9 推理阶段:sample_actions如何生成动作

推理时模型调用的是sample_actions:

def sample_actions(self, rng, observation, num_steps=10, noise=None):
    observation = _model.preprocess_observation(
        None,
        observation,
        train=False
    )

    dt = -1.0 / num_steps
    batch_size = observation.state.shape[0]

    if noise is None:
        noise = jax.random.normal(
            rng,
            (batch_size, self.action_horizon, self.action_dim)
        )

    prefix_tokens, prefix_mask, prefix_ar_mask = self.embed_prefix(observation)
    _, kv_cache = self.PaliGemma.llm(
        [prefix_tokens, None],
        mask=prefix_attn_mask,
        positions=positions
    )

    def step(carry):
        x_t, time = carry

        suffix_tokens, suffix_mask, suffix_ar_mask, adarms_cond = self.embed_suffix(
            observation,
            x_t,
            jnp.broadcast_to(time, batch_size)
        )

        ...
        v_t = self.action_out_proj(suffix_out[:, -self.action_horizon:])
        return x_t + dt * v_t, time + dt

    x_0, _ = jax.lax.while_loop(cond, step, (noise, 1.0))
    return x_0

这段代码可以拆成三个阶段。

第一阶段,从随机噪声初始化动作:

noise = jax.random.normal(...)

第二阶段,先计算Prefix,并缓存KV Cache:

prefix_tokens = Image Tokens + Language Tokens
kv_cache = PaliGemma(prefix_tokens)

这么做是为了提高推理效率。因为图像和语言上下文在整个采样过程中是不变的,没必要每一步都重复计算。

第三阶段,通过多步ODE更新,把噪声逐渐变成动作:

x_t = x_t + dt * v_t

由于dt是负数,时间会从1逐步走到0。换句话说,模型从纯噪声动作开始,经过若干步Flow Matching迭代,最终得到真实可执行的动作轨迹。最终输出的不是单步动作,而是:

action_chunk = [a1, a2, ..., a16]

5.10 后处理:动作如何还原到机器人平台

模型输出的动作通常是归一化后的动作,并且为了适配统一action_dim,可能还包含padding维度。因此推理结束后,还需要经过output transform还原到真实机器人动作空间。

以LIBERO的输出转换为例:

@dataclasses.dataclass(frozen=True)
class LiberoOutputs(transforms.DataTransformFn):
    def __call__(self, data: dict) -> dict:
        return {
            "actions": np.asarray(data["actions"][:, :7])
        }

只取前7维动作。因为模型内部可能使用更大的统一动作维度,而LIBERO环境只需要7维控制量。此外,在create_trained_policy中还会调用:

transforms.Unnormalize(norm_stats)

也就是把模型输出从归一化空间还原回真实动作空间。如果训练时对action做了z-score或quantile normalization,那么推理时必须反归一化,否则机器人执行的动作尺度就是错误的。

5.11 小结:从源码看π0.5的工程逻辑

总结一下,π0.5的源码逻辑可以概括为:

1. 用config开启pi05模式

2. 用RepackTransform统一不同数据集字段

3. 用DataTransform将图像、状态、语言和动作整理成标准输入

4. 用TokenizePrompt将语言指令转成token

5. 用PaliGemma编码图像和语言,形成Prefix

6. 用Action Expert接收noisy action和timestep,形成Suffix

7. 训练时通过Flow Matching Loss学习速度场

8. 推理时从随机噪声出发,多步迭代生成action chunk

9. 最后通过Unnormalize和OutputTransform还原到机器人动作空间

6. 对PI系列模型的启发

6.1 subtask

目前大多数VLA模型仍然采用典型的End-to-End训练方式,即直接学习从图像和语言指令到机器人动作的映射关系。这种方式最大的优点是简单,模型结构清晰,训练流程也比较直接。但实际做项目时会发现,当任务逐渐变复杂之后,这种方式会面临一个比较明显的问题。例如用户给出“整理卧室”这样的指令时,机器人实际上需要先识别场景中的多个目标物体,再规划任务执行顺序,最后生成具体动作轨迹。如果直接让模型从高层指令映射到低层动作,那么任务理解、目标识别、子任务规划以及动作控制都会被压缩到同一个模型内部完成,训练难度会非常高。

而π0.5中引入的High-Level Subtask提供了另一种思路。模型不再直接从任务生成动作,而是先预测当前应该执行的子任务,再根据子任务生成动作轨迹。例如面对“整理卧室”这样的任务时,模型可能首先得到“拿起枕头”“整理被子”等高层动作,然后再调用Action Expert完成具体控制。从工程角度看,这种分层结构最大的优势其实不是性能,而是可解释性。过去机器人执行失败时,我们很难判断问题究竟出在视觉感知、任务理解还是动作控制上;而在分层架构中,可以通过观察Subtask的预测结果快速定位问题来源。因此如果后续继续探索PI系列模型,增加一个专门负责子任务预测的模块会是一个值得尝试的方向,让高层规划和低层控制适当解耦,而不是全部依赖单一模型完成。

6.2 引入GuidedVLA

另外一个方向是GuidedVLA中使用的辅助监督机制。做法是引入目标检测、语义分割以及深度信息等辅助监督,让模型在训练阶段获得更多关于场景结构的信息。这种思路和π0.5利用互联网知识提升泛化能力其实有一定相似之处,本质上都是在增强模型对环境的理解能力。对于PI系列模型来说,也许并不一定需要把深度图或者分割结果作为推理输入,更合理的做法可能是在训练阶段增加这些辅助任务,让模型学习更好的视觉表示,而在推理阶段仍然保持RGB和语言输入不变。这样既能够获得额外监督带来的收益,也不会增加部署复杂度。

6.3 Cross-Robot Co-Training

过去很多机器人模型往往只针对单一平台训练,例如ALOHA、Franka或WidowX,每个数据集都有自己独立的生态。但如果从Foundation Model的角度来看,未来机器人模型显然不应该只服务于某一种机械臂,而应该能够适配不同形态的机器人平台。OpenPI在工程实现上其实已经体现出了这种思想。通过transform和config的设计,不同机器人平台虽然拥有不同的相机配置、状态表示和动作空间,但最终都被映射到统一的数据格式中,再送入同一个模型进行训练。这种设计意味着未来可以更加方便地融合来自多个机器人平台的数据。相比于继续增加模型参数规模,我认为这种跨平台数据融合对于提升泛化能力可能更加重要,因为机器人领域真正稀缺的从来不是模型容量,而是高质量、多样化的数据。

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐