开源!π0.6-MEM 机器人长时记忆架构完整实现——基于 Physical Intelligence 最新论文的工程落地

GitHub 地址:https://github.com/hzm8341/pi0.6

参考论文:MEM: Multi-Scale Embodied Memory for Vision Language Action Models,Physical Intelligence,2026年3月


一、为什么要做这个项目?

现有的机器人基础模型(如 π0、π0.5)能够执行"洗锅"、"叠衣服"、"做花生酱三明治"等单项任务,但面对连续多步骤的复杂任务------比如清洁整个厨房或从零开始做一顿饭------就会频繁"失忆":

  • 无历史记忆:每次推理只看当前帧,不知道自己做到哪一步了
  • 无遮挡补偿:手臂挡住目标物体就找不到了
  • 无错误恢复:同样的失败会无限重复,不会调整策略
  • 任务时长限制:实验通常只在几分钟的短任务上演示

Physical Intelligence 在 2026 年 3 月发布的 **MEM(Multi-Scale Embodied Memory)**论文提出了双模态记忆架构,将机器人可处理的任务时长从约 2 分钟扩展到 15 分钟以上 ,平均任务完成率从约 30% 提升到 70%

本项目基于开源的 openpi(π0.5 代码库),完整工程化实现了 π0.6-MEM 架构的所有核心组件,并将代码开源在 GitHub 上,希望帮助更多研究者和工程师快速上手。


二、MEM 核心思想:双模态分层记忆

MEM 的核心洞察是:机器人的记忆需求天然地分为两个时间尺度,需要用不同的模态来表达。

复制代码
短期记忆(秒级)                     长期记忆(分钟级)
─────────────────────────────────    ──────────────────────────────────
• 需要精细空间信息                    • 只需要语义级信息
• 处理遮挡(手臂挡住物体)             • 跟踪任务进度(做到第几步了)
• 调整抓取姿势                        • 记住已放好的食材
• 表示形式:图像序列(视频)           • 表示形式:自然语言摘要
─────────────────────────────────    ──────────────────────────────────

关键数学分解(来自论文 Section III):

把策略分解为高层策略 πHL 和低层策略 πLL:

复制代码
π(a_{t:t+H}, l_{t+1}, m_{t+1} | o_{t-T:t}, m_t, g)
  ≈ πLL(a_{t:t+H} | o_{t-K:t}, l_{t+1}, g)      ← 低层:生成动作
  × πHL(l_{t+1}, m_{t+1} | o_t, m_t, g)          ← 高层:子任务 + 更新记忆

其中:

  • a = 动作序列,l = 子任务指令,m = 语言记忆,g = 任务目标
  • K << T:低层策略只需要最近 K 帧,大幅降低计算量

三、项目整体架构

复制代码
openpi-mem/
├── src/openpi/
│   ├── models/
│   │   ├── model.py            ← ✅ 扩展 Observation(新增 MEM 字段)
│   │   ├── siglip.py           ← ✅ 新增 SpaceTimeSeparableBlock 视频编码器
│   │   ├── pi0_config.py       ← ✅ 新增 MEMConfig 配置类
│   │   ├── pi0.py              ← ✅ embed_prefix 多模态记忆注入
│   │   ├── memory_manager.py   ← ✅ 语言记忆标注生成与管理
│   │   └── high_level_policy.py← ✅ 高层策略(子任务 + 记忆更新)
│   ├── transforms.py           ← ✅ VideoFrameStack / TokenizeMemory
│   ├── training/
│   │   └── data_loader.py      ← ✅ MEMLeRobotDataset 多帧数据加载
│   └── policies/
│       └── policy.py           ← ✅ MEMPolicy 推理接口(帧缓存)
└── scripts/
    └── gen_memory_labels.py    ← ✅ 离线语言记忆标注生成脚本

改动统计:新增/修改 15 个文件,约 2668 行核心代码,以及 5 个测试文件(129 项测试全部通过)。


四、核心实现详解

4.1 短期视觉记忆:SpaceTimeSeparableBlock

这是整个架构最核心的创新------零参数量增加地让 SigLIP ViT 具备视频理解能力。

设计思路 :在 ViT 每隔 4 层的位置,将原来的纯空间注意力替换为时空分离注意力------先做空间注意力(patch 之间),再对每个 patch 的不同时间步做因果时间注意力(历史帧之间)。

复制代码
原始 Encoder1DBlock 的计算复杂度:O(n²)(n = patch 数)
时空联合注意力的复杂度:O(n²K²)(K = 帧数)⟶ 不可接受
SpaceTimeSeparableBlock:O(K·n² + n·K²)  ⟶ 可接受!

关键代码src/openpi/models/siglip.py):

复制代码
class SpaceTimeSeparableBlock(nn.Module):
    """
    时空分离注意力 Block(MEM 核心)
    
    关键特性:
    - 不引入新参数(复用原 QKV 权重)
    - K=1 时数值等价于原 Encoder1DBlock(零时间编码保证)
    - 计算复杂度 O(K·n² + n·K²) vs 联合注意力 O(n²·K²)
    """
    num_timesteps: int = 1  # K

    @nn.compact
    def __call__(self, x, deterministic=True):
        K = self.num_timesteps
        BK, N, D = x.shape
        B = BK // K

        # Step 1: 标准空间注意力(与原 Encoder1DBlock 完全相同)
        x = self._spatial_attention(x, deterministic)

        # Step 2: 因果时间注意力(MEM 核心)
        if K > 1:
            # 维度变换:(B*K, N, D) → (B*N, K, D)
            x_t = x.reshape(B, K, N, D).transpose(0, 2, 1, 3).reshape(B * N, K, D)

            # 时间位置编码(t=0 时值为 0,保证 K=1 等价性)
            x_t = x_t + self._sinusoidal_time_emb(K, D)[None, :, :]

            # 因果掩码:时刻 t 只能 attend 到 t' ≤ t
            causal_mask = jnp.tril(jnp.ones((K, K), dtype=jnp.bool_))[None, :, :]

            # 时间维度注意力
            x_t = x_t + self._temporal_attn(x_t, causal_mask, deterministic)

            # 维度还原:(B*N, K, D) → (B*K, N, D)
            x = x_t.reshape(B, N, K, D).transpose(0, 2, 1, 3).reshape(B * K, N, D)

        return x, {}

    def _sinusoidal_time_emb(self, K, D):
        """时间位置编码,t=0 时恒为零(保证 K=1 数值等价)"""
        positions = jnp.arange(K, dtype=jnp.float32)
        half_d = D // 2
        freqs = jnp.exp(-jnp.log(10000.0) * jnp.arange(half_d) / (half_d - 1))
        args = positions[:, None] * freqs[None, :]
        emb = jnp.concatenate([jnp.sin(args), jnp.cos(args)], axis=-1)
        return emb - emb[0:1, :]  # shift 使 t=0 → 零向量

历史 Token 丢弃机制 :在 ViT 上层(后 4 层),只保留当前帧的 token,输出形状从 (B*K, N, D) 还原为 (B, N, D),推理 token 数量与单帧完全一致:

复制代码
# Encoder.__call__ 中的 drop_history 逻辑
if use_video and lyr == drop_after:
    BK, N, D = x.shape
    B = BK // K
    x = x.reshape(B, K, N, D)[:, -1, :, :]  # 只取最后一帧(当前帧)
    use_video = False  # 后续层正常处理 B 帧

4.2 长期语言记忆:MemoryDataGenerator

语言记忆是一段持续压缩更新的自然语言摘要,论文中最关键的发现是:

失败的子任务绝对不能更新记忆------否则会产生严重的训练-推理分布偏移。

复制代码
# src/openpi/models/memory_manager.py

class MemoryDataGenerator:
    """离线生成语言记忆训练标注"""

    def generate_labels_for_episode(self, episode_id, subtasks):
        labels = []
        current_memory = ""

        for t, subtask in enumerate(subtasks):
            # 只用成功步骤构建历史
            successful = [
                f"Step {i+1}: {s['instruction']}"
                for i, s in enumerate(subtasks[:t])
                if s.get("success", True)   # ← 关键:只记录成功的步骤
            ]
            new_memory = self._call_llm(successful) if successful else ""

            labels.append(MemoryLabel(
                memory_before=current_memory,
                memory_after=new_memory,
                ...
            ))

            # ← 核心:失败时不更新记忆!防止分布偏移
            if subtask.get("success", True):
                current_memory = new_memory

        return labels

为什么 Naive 方法(直接拼接历史指令)不行?因为训练数据里每个子任务基本只出现一次(人类演示接近最优),但推理时失败的子任务会反复出现,导致高层策略"懵了"。MEM 的压缩记忆机制完美规避了这个问题。

4.3 高层策略:HighLevelPolicy

高层策略以低频(每个子任务结束时)运行,使用 VLM 的语言生成能力,通过 chain-of-thought JSON 格式同时输出子任务指令和记忆更新:

复制代码
# src/openpi/models/high_level_policy.py

_HL_PROMPT_TEMPLATE = """
Task goal: {task_goal}

Memory of completed steps:
{language_memory}

Based on current observation, output JSON:
{{
  "subtask": "<next concrete robot action>",
  "updated_memory": "<compressed past-tense summary>"
}}
"""

class HighLevelPolicy:
    def update(self, observation_image, subtask_success=True):
        raw = self._vlm(observation_image, prompt, max_tokens)
        parsed = json.loads(raw.strip())
        new_subtask = parsed["subtask"]
        new_memory  = parsed["updated_memory"]

        # ← 核心约束:失败时保持原记忆不变
        if subtask_success:
            self._language_memory = new_memory

        return new_subtask, self._language_memory

4.4 模型配置:MEMConfig

所有 MEM 参数通过独立的 MEMConfig 管理,默认值全部关闭,与原 π0.5 完全向后兼容

复制代码
# src/openpi/models/pi0_config.py

@dataclasses.dataclass(frozen=True)
class MEMConfig:
    # 短期视觉记忆
    use_video_memory: bool = False   # 默认关闭 → π0.5 行为
    video_memory_frames: int = 6     # K 帧(预训练用 6,微调可到 18)
    video_frame_stride_sec: float = 1.0
    temporal_attn_every_n_layers: int = 4
    drop_history_tokens_after_layer: int = -4

    # 长期语言记忆
    use_language_memory: bool = False
    max_memory_tokens: int = 256

    # 本体感觉历史嵌入
    use_state_history: bool = False
    state_history_frames: int = 6

# 启用 MEM 的配置示例:
config = Pi0Config(
    pi05=True,
    mem=MEMConfig(
        use_video_memory=True,
        video_memory_frames=6,
        use_language_memory=True,
        use_state_history=True,
    )
)

4.5 前向传播:embed_prefix 四段 Token

MEM 扩展了 embed_prefix,按以下顺序拼接 prefix token:

复制代码
[语言记忆 tokens] → [图像 tokens × N_cams] → [语言 prompt tokens] → [状态历史 tokens]
        ↑                    ↑                          ↑                    ↑
   长期记忆上下文         当前帧/视频融合           任务描述             连续嵌入(非文本)

# src/openpi/models/pi0.py  embed_prefix()

# (A) 长期语言记忆
if self.use_language_memory and obs.tokenized_memory is not None:
    memory_tokens = self.PaliGemma.llm(obs.tokenized_memory, method="embed")
    tokens.append(memory_tokens)

# (B) 图像 tokens(视频编码器融合历史帧)
for name in obs.images:
    if self.use_video_memory and obs.image_history is not None:
        image_tokens = self._encode_video_frames(
            obs.image_history[name],  # (B, K-1, H, W, C)
            obs.images[name]          # (B, H, W, C)
        )
    else:
        image_tokens, _ = self.PaliGemma.img(obs.images[name], train=False)
    tokens.append(image_tokens)

# (C) 语言 prompt(原有逻辑不变)
if obs.tokenized_prompt is not None:
    tokens.append(self.PaliGemma.llm(obs.tokenized_prompt, method="embed"))

# (D) 本体感觉历史(连续线性投影,不用文本 token)
if self.use_state_history and obs.state_history is not None:
    state_hist_tokens = self.state_history_proj(obs.state_history)  # (B, K-1, D)
    tokens.append(state_hist_tokens)

4.6 推理接口:MEMPolicy

MEMPolicy 在推理阶段自动维护滑动帧缓存,完全向下兼容原 Policy 接口:

复制代码
# src/openpi/policies/policy.py

class MEMPolicy(Policy):
    def __init__(self, model, *, num_video_frames=6, ...):
        # 滑动帧缓存(双端队列,maxlen = K-1)
        self._frame_buf = {
            cam: deque(maxlen=K-1) for cam in camera_keys
        }
        self._language_memory = ""

    def reset_episode(self, task_goal=""):
        """新 episode 开始:清空所有缓存"""
        for buf in self._frame_buf.values():
            buf.clear()
        self._language_memory = ""

    def infer(self, obs, *, noise=None):
        # 1. 从 deque 构建 image_history
        obs_aug = self._build_obs_with_history(obs)

        # 2. 注入语言记忆 token
        if self._language_memory:
            ids, mask = self._hl.tokenize_memory(self._language_memory)
            obs_aug["tokenized_memory"] = ids
            obs_aug["tokenized_memory_mask"] = mask

        # 3. 调用底层推理
        result = super().infer(obs_aug, noise=noise)

        # 4. 推理完成后更新帧缓存(重要:推理后更新)
        self._push_to_buffers(obs)

        # 5. 按需触发高层策略更新
        if self._hl and self._hl.should_update():
            _, new_mem = self._hl.update(obs["base_0_rgb"])
            self._language_memory = new_mem

        return result

五、快速上手

5.1 克隆项目

复制代码
git clone https://github.com/hzm8341/pi0.6.git
cd pi0.6
pip install -e .

5.2 启用 MEM 的最小配置

复制代码
from openpi.models.pi0_config import Pi0Config, MEMConfig

# 只启用短期视觉记忆(不需要语言记忆标注)
config = Pi0Config(
    pi05=True,
    mem=MEMConfig(
        use_video_memory=True,
        video_memory_frames=6,      # 6 帧历史(5秒@1Hz)
    )
)

# 完整 MEM(需要预先生成语言记忆标注)
config_full = Pi0Config(
    pi05=True,
    mem=MEMConfig(
        use_video_memory=True,
        video_memory_frames=6,
        use_language_memory=True,
        max_memory_tokens=256,
        use_state_history=True,
    )
)

5.3 推理(使用 MEMPolicy)

复制代码
from openpi.policies.policy import MEMPolicy
from openpi.models.high_level_policy import HighLevelPolicy, HighLevelPolicyConfig

# (可选)创建高层策略
hl = HighLevelPolicy(
    vlm_inference_fn=your_vlm_fn,   # 你的 VLM 推理函数
    tokenizer=your_tokenizer,
    config=HighLevelPolicyConfig(subtask_trigger_steps=50),
)

# 创建 MEM 策略
policy = MEMPolicy(
    model=model,
    high_level_policy=hl,
    num_video_frames=6,
)

# Episode 循环
policy.reset_episode(task_goal="Clean the kitchen")
for step in range(max_steps):
    obs = env.get_observation()
    result = policy.infer(obs)       # 自动维护帧缓存和记忆状态
    env.step(result["actions"])

5.4 生成语言记忆训练标注

复制代码
# 准备 subtask_annotations.jsonl(见文档格式说明)
python scripts/gen_memory_labels.py \
    --dataset_path /path/to/your/dataset \
    --output_path  /path/to/memory_labels.jsonl \
    --api_key      YOUR_ANTHROPIC_API_KEY

六、测试验证

项目包含 129 项测试,覆盖所有核心模块:

复制代码
# 向后兼容性测试(无需 GPU)
pytest src/openpi/models/test_mem_observation.py  -v

# 记忆管理器测试
pytest src/openpi/models/test_memory_manager.py   -v

# 数据变换测试
pytest src/openpi/transforms_test_mem.py          -v

# 帧缓存推理接口测试
pytest src/openpi/policies/test_policy_mem.py     -v

# 视频编码器核心逻辑测试(需要 JAX/GPU)
pytest src/openpi/models/test_video_encoder.py    -v

主要测试覆盖点:

  • K=1 时视频编码器数值等价于原 ViT(误差 < 1e-5)
  • 时间信息确实从历史帧流向当前帧 token
  • 因果掩码不泄露未来信息
  • 失败子任务不更新语言记忆
  • 帧缓存滑动窗口正确性(顺序、padding、maxlen)

七、关键实现细节与踩坑

7.1 K=1 数值等价性保证

最精妙的设计之一是时间位置编码的 shift 技巧:

复制代码
def _sinusoidal_time_emb(self, K, D):
    emb = ... # 标准正弦编码
    emb = emb - emb[0:1, :]  # ← 减去 t=0 的值
    # 效果:emb[0] 恒为零向量
    # K=1 时:时间注意力的 Q/K/V 都加了零偏移 → 退化为 self-attention → 等价于无时间注意力

这保证了可以直接从 π0.5 的 checkpoint 热启动,无需重新初始化。

7.2 scan=False 的选择

π0.5 的 SigLIP ViT 使用 scan=True(JAX scan 加速)。视频编码器需要按层条件判断是否插入时间注意力,这与 scan 的"所有步骤相同函数"约束冲突,所以视频编码器强制 scan=False

复制代码
img = _siglip.Module(
    scan=(K == 1),  # K=1 用原始 scan,K>1 切换到逐层 non-scan
    num_timesteps=K,
    ...
)

7.3 论文结论复现的关键数字

指标 无记忆基线 仅视频记忆 仅语言记忆 Naive 文本+视频 MEM(完整)
平均任务进度 ~30% ~40% ~35% ~33% ~70%
Recipe Setup
Clean Kitchen 极低 中高

Naive 文本+视频 (直接拼接历史指令,不做压缩)性能接近无记忆基线,这验证了记忆压缩机制(失败不更新)的关键性。

7.4 预训练必须包含视频记忆

论文实验:仅在微调阶段引入视频编码器 vs 预训练就加入视频编码器,前者比后者低约 20-30pp。因此训练数据混合中必须包含:

复制代码
机器人遥操作数据(40%)
视频-语言数据,如视频字幕(20%)  ← 关键:非机器人视频
带记忆标注的长任务数据(25%)
人工干预/纠错数据(10%)
单帧数据防遗忘(5%)

八、与原 π0.5 的兼容性

所有新字段均为 Optional,默认 None一行代码不改,行为与 π0.5 完全一致

复制代码
# 这段代码在新旧版本行为完全相同
config = Pi0Config(pi05=True)  # mem 默认 MEMConfig(),所有 MEM 开关默认 False
model = config.create(rng)
obs = Observation.from_dict(old_data_dict)  # 没有 image_history 字段 → 全 None
actions = model.sample_actions(rng, obs)    # 完全走 π0.5 路径

九、项目文件一览

文件 改动类型 核心内容
models/model.py 修改 新增 5 个 MEM Optional 字段
models/siglip.py 修改 SpaceTimeSeparableBlock + 视频 Encoder
models/pi0_config.py 修改 MEMConfig + Pi0Config 属性透传
models/pi0.py 修改 embed_prefix 4 段 token + _encode_video_frames
models/memory_manager.py 新增 语言记忆标注生成/管理
models/high_level_policy.py 新增 πHL:子任务 + 记忆更新
transforms.py 修改 VideoFrameStack + TokenizeMemory
training/data_loader.py 修改 MEMLeRobotDataset 多帧加载
policies/policy.py 修改 MEMPolicy 帧缓存推理接口
scripts/gen_memory_labels.py 新增 离线标注生成 CLI
models/test_*.py(3 个) 新增 核心模块测试
transforms_test_mem.py 新增 变换测试
policies/test_policy_mem.py 新增 推理接口测试

十、后续计划

  • \] **预训练权重发布**:基于开源机器人数据集训练 π0.6-MEM 基础权重

  • \] **记忆时间窗口扩展**:从 54 秒(18 帧)进一步扩展到小时级别


参考资料

  1. Physical Intelligence,MEM: Multi-Scale Embodied Memory for Vision Language Action Models ,2026,arxiv
  2. Physical Intelligence,π0: A Vision-Language-Action Flow Model for General Robot Control,2024
  3. Physical Intelligence,π0.5: A Vision-Language-Action Model with Open-World Generalization,2025
  4. Bertasius et al., Is Space-Time Attention All You Need for Video Understanding?,ICML 2021
  5. openpi 代码库:https://github.com/Physical-Intelligence/openpi

项目地址https://github.com/hzm8341/pi0.6

欢迎 Star ⭐ / Issue / PR!如果对机器人具身智能、VLA 架构设计感兴趣,欢迎交流讨论。

相关推荐
星始流年1 小时前
AI Agent 开发系列 之 01 🔎重新认识 LLM
人工智能·llm·agent
Henrybit933682 小时前
Claude与OpenAI的差异
人工智能
树上有只程序猿2 小时前
OpenClaw确实好用,但你得明白自己想要什么
人工智能
天天讯通2 小时前
智能语音机器人未来的发展方向
人工智能·机器人·语音识别
前端AI充电站2 小时前
Google 开始卷价格了:Gemini 3.1 Flash-Lite,会不会把 AI 应用成本真的打下来?
前端·人工智能
guts3502 小时前
使用python里的OpenCV包做简单的车道线检测
人工智能·python·opencv
金智维科技官方2 小时前
APA智能流程自动化是什么?
大数据·人工智能·ai·智能体·apa
智慧化智能化数字化方案2 小时前
可信数据空间——详解2025 华为AI可信数据空间白皮书【附全文阅读】
人工智能·可信数据空间·华为ai可信数据空间白皮书
两只羊2 小时前
折腾 OpenClaw:从零开始在 Ubuntu 上部署并搞定局域网访问
人工智能