一条推理请求的一生:从观测到动作
openpi_Codebase/openpi_Codebase/openpi_Inference_Path.md观测进来 → 先「整理+缩放」成模型认识的张量 → 模型「采样」出动作 → 再「还原」成机器人能执行的格式。
渲染流程图…
下面分两部分:先看策略对象怎么一次性组装好,再看每次请求怎么走。
先弄清两个词
- 变换(transform):一个"把数据字典改一改"的小函数(重命名键、缩放数值、把文字切成 token……)。多个变换按顺序串起来就是一条管线。
- 归一化统计量(norm stats):训练数据里每个量(姿态、动作的每一维)的均值和取值范围。用它把数值缩放到统一区间(如 [-1,1]),模型才好学;推理时还要用同一套统计量还原回真实数值。
第一部分:策略对象怎么组装(一次性)
create_trained_policy(config, ckpt_dir)(policy_config.py:16)做三件事:
- 载模型——靠检查点里有没有
model.safetensors判定是 PyTorch 还是 JAX 模型(policy_config.py:49-50)。 - 载归一化统计量——默认从检查点自带的
assets目录载,而不是从配置目录,确保用的和训练时一模一样(policy_config.py:59-64)。 - 拼好输入、输出两条变换管线(
policy_config.py:75-94):- 输入管线(顺序):整理键名 → 补默认指令 → 机器人专属映射 → 归一化 → 切 token / 缩放图像。
- 输出管线是它的逆序:反归一化 → 机器人专属还原。
第二部分:一次请求怎么走(Policy.infer,policy.py:68)
- 先把输入拷贝一份(变换会原地改,
policy.py:70)。 - 过输入管线(
policy.py:71)→ 得到模型张量;再加一个 batch 维、转成 JAX/PyTorch 张量(policy.py:72-79)。 - 打包成
Observation→ 调model.sample_actions(采样动作;π0 是从噪声一步步去噪,原理见 模型架构)(policy.py:90-95)。 - 去掉 batch 维 → 过输出管线(
policy.py:102):把动作从归一化空间还原,并切回机器人真实动作维。 - 返回
actions(未来步数 × 机器人动作维)+ 推理耗时。
关键约定:每个机器人一对 Inputs/Outputs
输入管线里"机器人专属映射"这步,是适配不同机器人唯一要改的地方。每个平台写一对 dataclass:
LiberoInputs(libero_policy.py:29):把机器人的observation/image、observation/state搬到模型要的键(image.base_0_rgb、state),没有的相机用零填充。LiberoOutputs(libero_policy.py:87):模型输出的动作维被 pad 到 32,这里切回机器人真实的前 7 维。
换个机器人,照着改这一对就行,模型和其余管线都不用动。
归一化为什么是"硬依赖"
训练时所有姿态/动作都被缩放过;推理时模型也只认缩放后的数。必须用训练时那套统计量来缩放输入、还原输出(Normalize / Unnormalize,transforms.py:114 / 148)。统计量缺失或对不上,模型看到的数值尺度就错了,直接产出垃圾动作——所以它默认从检查点自带 assets 载。详见 设计决策。
远程推理变体(同一个 Policy)
机器人端算力弱时,可以把模型跑在远端 GPU:serve_policy.py 用同样的 create_trained_policy 造出 Policy,包进一个 WebSocket 服务(默认端口 8000)。客户端(packages/openpi-client)把观测用 msgpack 打包发过来,服务端收到就 policy.infer(obs)、把动作打包发回(websocket_policy_server.py:55-72)。机器人端只管"发观测、收动作"。
这条路径告诉你的事
- 归一化统计量是推理正确性的硬依赖(从检查点 assets 载,和训练对齐)。
- 同一套变换训练和推理共用:输出管线是输入管线的逆序,机器人专属逻辑全收在一对
Inputs/Outputs里。 - 模型对上层透明:
Policy不区分 π0 还是 π0-FAST,差异藏在sample_actions和 token 相关变换里。
出链
- [[openpi_Model_Architecture]]
- [[openpi_Decisions_and_Danger]]