跳到主要内容

Pi0

流程概览​

训练
pi0_base (HF 预训练)
│ finetune (v3.0 数据集, 30 fps)
▼
checkpoints/030000/pretrained_model (bf16, chunk=50, 绝对动作)

│
▼
量化 + 编译(oe_llm_s600/pi0_conver/)
浮点参考 → 浮点基线 / 参考 dump
校准 → fake-quant 权重 (pi0 无 time-mod LUT)
伪量化评估 → 伪量化精度 (校准集)
编译 → 3× HBM (w8 nash-p)

│
▼
板端部署与运行
3× HBM + norm_stats_runtime.json + tokenizer/
服务端 (vla_sdk_demo) + 客户端 (case_client)
  • 模型:PaliGemma(gemma_2b) + Gemma 动作专家(gemma_300m),3 路相机 + 14 维 state/action(pad 32),chunk=50,10 步去噪。
  • 量化:int8 权重(w8)/ 动作专家 W8A16,编译为 nash-p HBM。

运行效果​

硬件清单​

说明

Pi0 & Pi05 不要求使用指定的机械臂或结构,下表仅为参考硬件清单。更换其他机械臂、其他结构同样可以,但需要重新采集数据、重新训练并重新量化编译。

设备名称数量图片
RDK S600 开发套件1S600
松灵 Piper 机械臂2松灵 Piper 机�械臂
RealSense D435i3RealSense D435i
机械臂支架1机械臂支架
手部相机 3D 打印件2手部相机 3D 打印件
头部相机 3D 打印件1头部相机 3D 打印件

获取工具包​

wget https://archive.d-robotics.cc/downloads/rdk_demo/rdk_s600_demo/pi0_s600.tar.gz

训练​

环境准备​

用途conda 环境说明
训练lerobot
  • RTX 5090
  • CUDA 13.3
  • Python 3.12
  • torch 2.11
  • lerobot 0.6.2(editable)
  • 基础环境搭建完成之后,先激活训练环境 lerobot,再进入仓库安装依赖:

    conda activate lerobot
    git clone https://github.com/huggingface/lerobot.git
    cd lerobot

    pip install -e ".[core_scripts]" # For robot workflows (recording, replaying, calibrate)
    pip install -e ".[training]" # For training policies
    pip install -e ".[all]" # Everything (all policies, envs, hardware, dev tools)
  • 数据集格式:训练用 v3.0(CODEBASE_VERSION=v3.0),v2.1 需先转 v3.0(convert_dataset_v21_to_v30.py)。v2.1/v3.0 的目录结构对照见 数据集格式说明。本地数据转换在 clone 所在目录(lerobot 的上一级)执行,--root 换成自己的数据路径:

    python lerobot/src/lerobot/scripts/convert_dataset_v21_to_v30.py  \
    --repo-id=xxx \
    --root=xxx \
    --push-to-hub=false
    注意
    • 训练依赖两份 Hugging Face 资源:

      • 预训练权重 lerobot/pi0_base,若可以正常访问 Hugging Face,则不必提前下载,训练时 lerobot-train 会按仓库名自动拉取。若网络较差,可参考下方命令,先下载权重到本地,训练时设置 HF_HUB_OFFLINE=1,并更换路径即可。

      • google/paligemma-3b-pt-224 是 gated 模型,下载前需要先授权:登录 Hugging Face,打开 google/paligemma-3b-pt-224,点击 Acknowledge license 同意 Gemma 使用许可,填写信息获取授权后,即可下载模型。

    • 网络较差,或训练命令带有 HF_HUB_OFFLINE=1(只读本地文件,不再访问 Hugging Face)时,在 lerobot 目录下提前下载。./pi0_base 作为 --policy.pretrained_path。下载完成后,把 pi0_base/policy_preprocessor.json 里的 tokenizer_name 改为 ./paligemma-3b-pt-224。

      cd lerobot
      hf download lerobot/pi0_base --local-dir ./pi0_base
      hf download google/paligemma-3b-pt-224 --local-dir ./paligemma-3b-pt-224
  • 若训练时欲通过 wandb 查看模型训练情况,可先登录:

    wandb login YOUR_API_KEY

训练命令​

示例数据不对外开放,开发者根据实机环境和应用场景自行采集。--dataset.repo_id 与 --dataset.root 中的 xxx 换成自己的数据集标识和路径。

#启动lerobot环境
cd lerobot
conda activate lerobot

#从 pi0_base 微调(数据集若需要重映射则使用rename_map)
HF_HUB_OFFLINE=1 lerobot-train \
--dataset.repo_id=xxx \
--dataset.root=xxx \
--policy.type=pi0 \
--output_dir=./outputs/pi0_training \
--job_name=pi0_training \
--policy.pretrained_path=./pi0_base \
--policy.compile_model=true \
--policy.gradient_checkpointing=true \
--policy.dtype=bfloat16 \
--policy.freeze_vision_encoder=true \
--policy.train_expert_only=true \
--policy.push_to_hub=false \
--steps=30000 \
--policy.device=cuda \
--batch_size=32 \
--wandb.enable=true \
--wandb.project=pi0_training \
--rename_map '{"state":"observation.state","head_cam":"observation.images.head_cam",
"left_cam":"observation.images.left_cam","right_cam":"observation.images.right_cam"}'

# 若需断点重训,可参考一下指令:
HF_HUB_OFFLINE=1 lerobot-train \
--config_path=outputs/pi0_training/checkpoints/030000/pretrained_model \
--resume=true --steps=60000 --dataset.eval_split=0.05 --eval_steps=1000

关键配置

train_config.json / config.json

项值
policypi0(gemma_2b + gemma_300m)
batch32
steps30000
dtypebfloat16
compile_model=true
compile_mode=max-autotune
freezetrue
train_expert_onlytrue
chunk50
action / state输出 14 维(pad 32);state 14 维(pad 32)
归一化ACTION/STATE=MEAN_STD
VISUAL=IDENTITY
tokenizertokenizer_max_length=48

训练资源与耗时

项值
机器1× NVIDIA RTX 5090 32GB(32607 MiB,驱动 610.57.04)+ Intel i9-14900KF(32 线程)
GPU 显存占用~20.5 GB
batch32
精度bfloat16
显存优化gradient_checkpointing=true
freeze_vision_encoder=true
train_expert_only=true
compile_model=true
训练步数30000
数据时长35s
数据数量300 条
该段耗时~16.3 h

数据集格式说明​

说明

lerobot 存在 v2.1 与 v3.0 两种数据集格式。本次使用的训练环境 lerobot(0.6.2,CODEBASE_VERSION=v3.0)只能读 v3.0。示例数据不对外开放,开发者根据实机环境和应用场景自行采集。

v2.1

.
├── meta/
│ ├── info.json codebase_version = "v2.1"
│ ├── tasks.jsonl {"task_index":0,"task":"Fold the towel from bottom to top twice, then from right to left."}
│ ├── episodes.jsonl {"episode_index","tasks","length"}
│ ├── episodes_stats.jsonl {"episode_index","stats":{feature:{min,max,mean,std,count}}}
│ └── modality.json state/action/endpose/... 的分段索引
├── data/
│ └── chunk-000/
│ ├── episode_000000.parquet ← 一 episode 一文件(共 320 个)
│ └── ...
└── videos/
└── chunk-000/
├── head_cam/episode_000000.mp4
├── left_cam/episode_000000.mp4
└── right_cam/episode_000000.mp4

v3.0

.
├── meta/
│ ├── info.json codebase_version = "v3.0"
│ ├── tasks.parquet task_index | task
│ ├── stats.json 聚合 stats
│ └── episodes/
│ └── chunk-000/
│ └── file-000.parquet ← 一行一 episode(data/video 索引 + from/to_ts + length + 各 feature 的 stats 列)
├── data/
│ └── chunk-000/
│ ├── file-000.parquet ← 按 data_files_size_in_mb 聚合
│ └── ...
└── videos/
├── head_cam/chunk-000/file-000.mp4 ← video_key 提到 chunk 之前,按 video_files_size_in_mb 聚合
├── left_cam/chunk-000/file-000.mp4
└── right_cam/chunk-000/file-000.mp4

关键差异

项v2.1v3.0
data一个 episode 对应一个文件多个 episode 聚合成一个文件
video一个 episode 对应一个 video 文件多个 episode 聚合成一个 video 文件
逐 episode 元数据episodes.jsonl + episodes_stats.jsonlmeta/episodes/chunk-XXX/file-XXX.parquet
tasktasks.jsonltasks.parquet
info.json无 per-feature fps、有 total_videos/total_chunks每个 feature 带 fps、有 data_files_size_in_mb
modality.json有无
注意
  • 用 v2.1 数据集直接训练时,lerobot 0.6.2 会抛出 BackwardCompatibilityError。按 环境准备 中的命令先转成 v3.0。
  • 本次使用的数据集特征名都是扁平名(如 head_cam、state),策略需要的是 observation.images.* 和 observation.state。训练时加上 --rename_map,映射示例见 训练命令。
  • info.json.features.*.info.video.codec 为 av1 时,解码依赖 ffmpeg。需在训练机安装 ffmpeg,并确认它在 PATH 中。

量化编译​

获取 SDK​

量化编译需要获取官网最新的 LLM SDK 并解压。

mkdir -p ~/VLA/oe_llm_s600/pi0_sdk
cd ~/VLA/oe_llm_s600/pi0_sdk
wget https://archive.d-robotics.cc/toolchain/llm/s100_s600_llm/llm_2.0.0/D-Robotics_LLM_2.0.0_S600_S100_SDK.tar.gz
tar -xzf D-Robotics_LLM_2.0.0_S600_S100_SDK.tar.gz
export SDK=$HOME/VLA/oe_llm_s600/pi0_sdk/D-Robotics_LLM_2.0.0_S600_S100_SDK
说明

~/VLA 只是示例工作目录,可替换为任意路径,只要 export SDK 指向实际的 SDK 根目录即可。

环境准备​

conda create -n oellm python=3.10 -y
conda activate oellm
pip install torch==2.8.0+cu128 torchvision==0.23.0+cu128 torchaudio==2.8.0+cu128 --index-url https://download.pytorch.org/whl/cu128
pip install $SDK/package/host/*.whl # horizon / hbdk / hbm
pip install -r $SDK/llm_compression/requirements.txt
pip install numpy==1.26.4
pip install --force-reinstall setuptools==80.10.2

# 创建 pi0_conver文件夹,量化所需文件以及生成产物都放在 pi0_conver
mkdir oe_llm_s600/pi0_conver

数据与模型准备​

用 pi0_s600/host/prep_pi0.py 生成模型目录、校准数据、norm_stats_runtime.json 与量化所需配置,再用 prepare_sdk20.py 生成官方 SDK 的 samples.json 与 sdk20/ 配置。

cd ~/VLA
conda activate lerobot
python3 pi0_s600/host/prep_pi0.py \
--ckpt-dir lerobot/outputs/pi0_training/checkpoints/030000 \
--dataset-root xxx \
--output-dir oe_llm_s600/pi0_conver \
--num-samples 30 \
--tokenizer lerobot/paligemma-3b-pt-224/tokenizer.model --copy-tokenizer

conda activate oellm
python3 pi0_s600/tools/apply_sdk_patches.py --sdk "$SDK" --scope host
python3 pi0_s600/host/prepare_sdk20.py \
--assets oe_llm_s600/pi0_conver \
--checkpoint lerobot/outputs/pi0_training/checkpoints/030000
说明
  • ~/VLA 只是示例工作目录,可换为现有目录;数据路径按实际修改。
  • Pi0 的 state 作为连续张量输入:按 checkpoint 的 MEAN_STD 处理后补零到 32 维,不注入文本。
  • host 位置编号/attention 补丁与板端位置编号补丁配套使用。
产物说明
模型目录<output-dir>/model_<step>/config.jsonPi0 配置:max_token_len=48
<output-dir>/model_<step>/model.safetensors由原生 ckpt 自动去 model. 前缀生成
<output-dir>/model_<step>/paligemma_tokenizer.modeltokenizer 软链(默认从 --ckpt-dir 自动定位)
校准数据<output-dir>/pi0_calib_image/{sid}/image_{0,1,2}.jpghead / left / right(640×480,quality 95)
<output-dir>/pi0_action_calib_data/{sid}/state.npy
action.npy
x_t.npy
[14] raw + [50,32] flow 噪声
<output-dir>/pi0_prompt.json[{"text": ...} x N](默认读数据集 tasks)
<output-dir>/norm_stats.jsonactions 的 mean/std/q01/q99(全量统计)
板端 norm<output-dir>/norm_stats_runtime.jsonstate + actions 的 mean/std/q01/q99(板端运行时用)
官网 SDK 输入<output-dir>/sdk20/samples.jsoncalibration 与 evaluation 的 data_path
<output-dir>/sdk20/float.yml浮点参考。已去掉 evaluation.calib_ckpt_load_path
<output-dir>/sdk20/case.yml校准、伪量化评估和编译。后续命令使用这些生成文件
  • 采样约定:对数据集全局帧等距采集,相机序固定 head→left→right。action/state 存 raw 物理量。
  • --ckpt-dir 是输入(原生 lerobot 模型目录)。
  • --model-dir 是输出(转换后的 llm_compression 目录,默认 <output-dir>/model_<step>)。
  • model. 前缀去除在脚本内完成。

量化编译​

conda activate oellm
cd ~/VLA/oe_llm_s600/pi0_conver
export PYTHONPATH="$SDK:$SDK/llm_compression/lightcompress:${PYTHONPATH:-}"

# 1. 浮点参考
python3 ~/VLA/pi0_s600/host/native_float_reference.py --config_path "$PWD/sdk20/float.yml"
# 2. 校准,调用官网 calib.py,关闭梯度记录
python3 ~/VLA/pi0_s600/host/calib_no_grad.py --config_path "$PWD/sdk20/case.yml"
# 3. 伪量化评估
TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 bash "$SDK/llm_compression/scripts/torch_eval.sh" --config_path "$PWD/sdk20/case.yml"
# 4. 编译 HBM
TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD=1 bash "$SDK/llm_compression/scripts/compile.sh" --config_path "$PWD/sdk20/case.yml"

sdk20/case.yml 关键项:

model:
type: Pi0
# path、norm_stats_path 由准备脚本填写
model_list: [visual, lm, action]
model_dtype: bfloat16
q_config: performance
vision_token_num: 256
num_views: 3
action_horizon: 50
max_token_len: 48
discrete_state_input: false
valid_state_dim: null
enable_time_mod_lut: false
calibration:
dataset_type: vla_dataset
calibration_step: 30
# data_path 指向 sdk20/samples.json
evaluation:
dataset_type: vla_dataset
eval_step: 30
use_delta_joint_actions: false
use_quantiles_norm: false
vla_eval:
eval_stages: [calib]
valid_action_dim: 14
skip_dump_if_exists: false
compile:
opt_level: 2
jobs: 32
lm: {enable_hpc: true, core_num: 4, max_l2m_size: 25165824}
action: {enable_hpc: false, core_num: 4, max_l2m_size: 25165824}

量化方案(pi0_model.py::get_qconfig_setting("action")):

  • 默认:Linear qint16 in / qint8 w / fp16 out。qk/sv matmul qint16×qint16。norm/add/cat/gate fp16。
  • pi0 独有且默认被量化的模块:state_proj、action_time_mlp_in、action_time_mlp_out。
  • model.action_w16=true → action 专家权重用 qint16(W16A16)而非 qint8(W8A16)。

产物​

文件部件大小
xxxx_vision_224x224_w8_nash-p_corenum_1.hbmSigLIP~487 MB
xxxx_llm_action_horizon_50_w8_nash-p_corenum_4.hbmGemma-2B LM~3.45 GB
xxxx_action_horizon_50_w8_nash-p_corenum_4.hbmGemma-300M 动作专家~463 MB
  • 量化精度:模型精度指标参见 量化精度,量化模型精度查验与调优见 OELLM 2.0 手册「精度评测」章节,此处不展开。
  • 编译耗时(Intel Core i9-14900KF):visual ~23min + lm ~1h58min + action ~24min ≈ 2h46min。

板端部署与运行​

将三个编译产物、SDK 和新源码包导入板端。

  • 源码包解压到 /root/VLA/pi0_s600,SDK 解压到 /root/VLA/oe_llm_s600/pi0_sdk。
  • 三个 HBM 放到 /root/VLA/pi0_models,文件名沿用 sdk20/compile/ 的输出。
  • 同时放入 norm_stats_runtime.json(改名为 norm_stats.json),以及 PaliGemma 的 tokenizer.json、tokenizer_config.json、tokenizer.model(改名为 paligemma_tokenizer.model)。统计量必须包含 state 和 actions。

运行所需文件​

模型资源(/root/VLA/pi0_models):

/root/VLA/pi0_models/
├── model_030000_vision_224x224_w8_nash-p_corenum_1.hbm
├── model_030000_language_w8_nash-p_corenum_4.hbm
├── model_030000_action_ah_50_w8_nash-p_corenum_4.hbm
├── norm_stats.json # norm_stats_runtime.json 改名,必须含 state + actions
├── paligemma_tokenizer.model # tokenizer.model 改名
└── tokenizer.json / tokenizer_config.json

案例配置(/root/VLA/pi0_s600):

/root/VLA/pi0_s600/
├── configs/runtime.json # configure.py 生成的模型配置
├── configs/network.json # 网络配置(127.0.0.1:30005)
└── configs/client.yaml # 客户端与机械臂配置

构建运行时​

安装板端构建依赖,然后应用补丁、构建 full runtime 并生成案例配置:

apt-get update
apt-get install -y build-essential cmake pkg-config libopencv-dev libprotobuf-dev \
protobuf-compiler python3-venv ffmpeg patch

export SDK=/root/VLA/oe_llm_s600/pi0_sdk/D-Robotics_LLM_2.0.0_S600_S100_SDK
python3 /root/VLA/pi0_s600/tools/apply_sdk_patches.py --sdk "$SDK" --scope board
source "$SDK/oellm_runtime_s600/env.sh" 4
cd "$SDK/oellm_runtime_s600/examples/vla_demo/pi0"
BUILD_TARGET=full MODE=full bash build_demo.sh
cd /root/VLA/pi0_s600
bash case_client/setup.sh
./case_client/.venv/bin/python -m pip install pillow pyarrow
./case_client/.venv/bin/python board/configure.py --model-dir /root/VLA/pi0_models

运行时配置参考​

配置示例使用包内 configs/runtime.template.json。运行时加载 board/configure.py 生成的 configs/runtime.json。模板已填写三个 HBM 文件名,生成脚本把模型目录绑定到 /root/VLA/pi0_models。

configs/runtime.template.json

{
"runtime_type": "Pi0Sdk",
"model": {
"version": "0",
"denoise_num": 10,
"work_path": ".",
"model_runner": {
"visual": "model_030000_vision_224x224_w8_nash-p_corenum_1.hbm",
"lm": "model_030000_language_w8_nash-p_corenum_4.hbm",
"action": "model_030000_action_ah_50_w8_nash-p_corenum_4.hbm"
},
"backends": {
"visual": [1, 2, 3],
"lm": [1, 2, 3, 4],
"action": [1, 2, 3, 4]
},
"state_size": 14,
"num_flow_steps": 10,
"use_rtc": true
},
"interface": {
"action_horizon": 50,
"raw_state_size": 14,
"raw_action_size": 14,
"image_mask": [true, true, true]
},
"processing": {
"preproc": true,
"postproc": true,
"filter": {"type": "none", "fs": 0},
"use_absolute_action": true,
"use_quantiles_norm": false,
"delta_action_mask": [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0],
"inject_state": false
},
"dfx": {
"dump": false,
"dump_per_chunk": false,
"dump_dir": "./output/",
"stub_dir": "./stub/"
},
"warm_start": {"enabled": false},
"rtc": {
"execution_horizon": 15,
"leftover_count": 10,
"inference_delay": 9,
"max_guidance_weight": 5.0,
"prefix_attention_schedule": "linear",
"control_frequency": 30.0,
"use_model_prev": true
}
}

configs/network.json

{
"default_mode": "network",
"modes": ["local", "network"],
"io": {
"input_dir": "./inputs",
"output_dir": "./outputs/openloop",
"multi_chunk": true,
"reset_filter_first": true
},
"demo": {"infer_nums": 1, "image_count": 3, "language_count": 1, "state_count": 1},
"network": {"server_ip": "127.0.0.1", "server_port": 30005, "timeout": 120}
}
说明
  • 保留三视角、action_horizon=50、有效 state/action 为 14 维、绝对动作、MEAN_STD,以及 processing.inject_state=false。
  • model.num_flow_steps=10,同时保留 denoise_num=10。滤波为 processing.filter.type=none、fs=0,warm_start.enabled=false。RTC 原参数保留,并设置 rtc.use_model_prev=true。
  • 客户端发送原始 state,runtime 按 checkpoint 统计量归一化,再补零到 32 维作为连续输入,不注入文本。
  • 输入图:可发 640×480 交给板端 resize(板端 ResizeWithPadToBuffer 非抗锯齿,与训练 torch 一致)。
  • 运行时端口:configs/network.json(server_port=30005)。

开环测试​

不开硬件或遥控,用 GT 观测(图像 + state)逐步喂板端 HBM,把预测动作与 GT 对比。板端脚本为 pi0_s600/board/openloop_infer_piper.py。

数据获取(训练机侧)

用 pi0_s600/host/extract_episodes_v3.py 从 v3.0 数据集抽取指定 episode。--dataset-root 的 xxx 换成实际 v3.0 数据目录,输出目录以 eval_data 为例:

conda activate lerobot
# 抽 episode 0/3/5,每个 episode 每 30 帧一条(≈ 每个 chunk 起点)
python3 pi0_s600/host/extract_episodes_v3.py \
--dataset-root xxx \
--episodes 0 3 5 --stride 30 -o eval_data
# 每个 episode 均匀抽 20 条:加 --num-per-episode 20

将 eval_data 导入板端 /root/VLA/eval_data。产出布局:

eval_data/calib_image/<sid>/image_{0,1,2}.jpg        # head_cam, left_cam, right_cam
eval_data/action_calib_data/<sid>/state.npy # [14] raw
eval_data/action_calib_data/<sid>/action.npy # [14] GT (chunk step0, raw)
eval_data/action_calib_data/<sid>/x_t.npy # [50,32](板端不用)
eval_data/prompt.json · norm_stats.json · manifest.json

板端开环

使用「板端部署与运行」中创建的客户端环境,并加载同一 SDK 的 env.sh 4:

cd /root/VLA
export SDK=/root/VLA/oe_llm_s600/pi0_sdk/D-Robotics_LLM_2.0.0_S600_S100_SDK
source "$SDK/oellm_runtime_s600/env.sh" 4
export LD_LIBRARY_PATH="$SDK/oellm_runtime_s600/examples/vla_demo/pi0/dist:${LD_LIBRARY_PATH:-}"
./pi0_s600/case_client/.venv/bin/python pi0_s600/board/openloop_infer_piper.py \
--model pi0 --model-dir /root/VLA/pi0_models \
--model-config /root/VLA/pi0_s600/configs/runtime.json \
--sdk-root "$SDK/oellm_runtime_s600" \
--demo-dir "$SDK/oellm_runtime_s600/examples/vla_demo/pi0" \
--eval-data /root/VLA/eval_data --output-dir /root/VLA/pi0_s600/outputs/openloop --no-plots \
--model-config-patch '{"model":{"use_rtc":false}}'
注意
  • --model pi0:--model 默认 pi05。用 pi0 HBM 加 pi05 预设会报 [layout] suffix_pad=1 invalid ... suffix layout mismatch with HBM(version/suffix 不匹配)。
  • --model-dir 决定读哪个目录的 3 个 .hbm。norm_stats 默认取 --model-dir/norm_stats.json,必须含 state。
  • 按 stride=30 抽取的 GT 观测彼此不连续。默认 RTC 会沿用上一段预测缓存,开环误差会被放大。上面的 --model-config-patch 只在这次独立样本开环里关闭 RTC,真机仍使用 configs/runtime.json 里的 RTC 配置。
  • 开环耗时是 runtime 内部计时,脚本汇总全部样本的 profile,输出平均耗时与 P95。
  • 命令带 --no-plots,输出预测记录和误差统计,不调用旧绘图脚本; 不带 --no-plots 才会出下图所示的预测曲线。

板端开环测试结果:Calib openloop: CT action vs pred (chunk step 0) 各关节 GT 动作与预测动作对比曲线

真机运行​

客户端目录为 /root/VLA/pi0_s600/case_client。连接、归位和运行统一使用 ../configs/client.yaml。连接会自动使能双臂。现场确认急停、工作区和归位路径后再执行。

cd /root/VLA/pi0_s600/case_client
./run_arm.sh --list
# 按现场映射启用两路 1 Mbps CAN,例如:
# sudo ip link set can_left up type can bitrate 1000000
# sudo ip link set can_right up type can bitrate 1000000
./run_arm.sh --config ../configs/client.yaml
./run_arm.sh --config ../configs/client.yaml --home

归位并铺平毛巾后,先启动客户端监听,再启动 runtime。

# 板端终端 1
cd /root/VLA/pi0_s600/case_client
./run_client.sh --config ../configs/client.yaml \
--prompt "Fold the towel from bottom to top twice, then from right to left." \
--record-commands --duration 120 --log-level INFO
# 板端终端 2
export SDK=/root/VLA/oe_llm_s600/pi0_sdk/D-Robotics_LLM_2.0.0_S600_S100_SDK
cd "$SDK/oellm_runtime_s600/examples/vla_demo/pi0"
source "$SDK/oellm_runtime_s600/env.sh" 4
export LD_LIBRARY_PATH="$PWD/dist:${LD_LIBRARY_PATH:-}"
./dist/vla_sdk_demo --oellm_config /root/VLA/pi0_s600/configs/runtime.json \
--demo_config /root/VLA/pi0_s600/configs/network.json --mode network
  • 配置:configs/client.yaml(服务端端口、相机后端与顺序、14 维关节名、图像尺寸、RTC、下发滤波、退出归位,以及 arms 节中的 CAN 口、夹爪行程与力、sleep_position)。
  • 重点核对相机序列号,以及机械臂 CAN 口名称与现场一致。
  • 客户端进程监听 server.host:server.port(默认 0.0.0.0:30005),板端 runtime 主动连入。

网络通信协议

  • 角色方向:板端 runtime(dist/vla_sdk_demo --mode network)是 TCP client,主动 connect() 到客户端机器。客户端侧(arm_interface 部署链的相机/臂节点所在机器,参考实现 deploy/src/inference_runner/inference_runner/oellm_tcp.py::OellmTcpClient)是 TCP server。地址和端口在 configs/network.json 的 network.server_ip、network.server_port、network.timeout(默认 127.0.0.1:30005)。

  • 分帧:每帧 = [4 字节大端序长度][protobuf 字节流]。板端 vla_demo_network.h 用 htonl/ntohl 读写长度头、SerializeToString/ParseFromString 收发。客户端用 struct.pack(">I", len) 对齐。请求与响应同为一种消息(MultiModalInput)。

  • 消息定义(oellm_runtime/examples/vla_demo/common/src/msg.proto):

    message Time  { int64 sec = 1; int32 nsec = 2; }
    message Header {
    uint32 seq = 1; // 自增序列号
    Time stamp = 2; // 采集时刻
    string frame_id = 3;
    uint32 type = 4; // 图像类型 (RGB/BGR)
    bool reset = 5; // 重置推演 (RTC: 任务开始清缓存)
    bool view_only = 6;
    }
    message Tensor {
    enum DataType { FLOAT64=0; UINT8=1; STRING=2; FLOAT32=3; INT32=4; FP16=5; }
    DataType dtype = 1;
    repeated int32 shape = 2; // int32(跨架构)
    bytes data = 3; // 原始字节
    }
    message MultiModalInput {
    Header header = 1;
    repeated Tensor images = 2; // 3 张,shape=[3,H,W] CHW,DT_UINT8
    repeated Tensor languages= 3; // prompt 文本,DT_STRING(板端内部 tokenize)
    repeated Tensor states = 4; // state 向量,DT_FLOAT64(pi0 走此张量做 state_proj)
    }
  • 请求 → 响应:客户端发 images/languages/states(+可选 field 5/6 RTC 约束),板端回同型 MultiModalInput,动作放 languages[0](dtype 为 FLOAT64 或 FP16,shape = [action_horizon, action_dim] 的原始维度)。

  • pi0 的 state 走 states 张量(inject_state=false,不进 prompt):客户端发原始值,由 runtime host 侧归一化(见下)。

请求格式与归一化​

字段dtypeshape内容是否归一化
images[i]DT_UINT8(也接受 FLOAT32/FP16)[3, H, W](CHW,RGB)相机原图像素 0–255未归一化;UINT8 时 runtime 内部统一做 ResizeWithPad(→224×224) + 归一化
languages[0]DT_STRING(或 DT_INT32)[](或 [token_len])prompt 原文(UTF-8);或已分词 token ids不涉及(文本);runtime 内部 tokenize(pi0 inject_state=false,不把 state 注入 prompt)
states[0]DT_FLOAT64[14]原始关节值(双臂 6 关节+夹爪,raw 物理量)未归一化;runtime host 侧按 norm_stats_runtime.json 的 state.mean/std 归一化后再喂 HBM(preproc=true)
prev_actions(field 5)DT_FLOAT64[n*14] 展平上一 chunk 未执行的尾巴(raw)未归一化;engine 侧按 action 统计处理
注意
  • preproc=true(默认):states 必须发 raw(DT_FLOAT64)——网络层据 dtype 置 state_raw=true,runtime 才做归一化。发 DT_FLOAT32 会被当作“已归一化”(state_raw=false),不要在客户端预归一化。
  • preproc=false:输入须已预处理——states 必须是已归一化值且长度 == state_size(=14),languages 必须发 DT_INT32 token ids(prompt 文本无法离线转 tokens),否则报错。
  • 图像若设 obs.image_size=[0,0](不缩放),客户端把相机原图(如 1280×720)交给 runtime,由 runtime 统一 ResizeWithPad 到 224×224 再归一化。
  • 响应侧 languages[0] 的动作是模型输出空间的绝对关节动作(配合 use_absolute_action/filter 等 processing),与上述请求张量的原始量纲不同。

问题与解决方法​

训练 / 数据​

现象根因解决
加载卡死 / 访问 huggingface 超时离线环境仍尝试联网HF_HUB_OFFLINE=1;base 权重走 hf-mirror 镜像本地化
tokenizer 加载失败(gated)google/paligemma-3b-pt-224 需授权本地下载并改 pi0_base/policy_preprocessor.json 路径;训练后 ckpt 自带 tokenizer/
v2.1 数据集读不了lerobot ≥0.4 抛 BackwardCompatibilityError训练/校准用 v3.0;v2.1 用 drrm(0.3.3) 或先转 v3.0(格式差异见 数据集格式说明)
数据集 key 是扁平名训练特征名与策略不符--rename_map 映射 head_cam→observation.images.head_cam 等

板端运行时​

现象根因解决效果
动作完全不对num_flow_steps 未设为 10(velocity 当 x_t 回灌)model.num_flow_steps=100.4955 → 0.4172
动作完全不对use_absolute_action 缺省 false(又叠一次 state)use_absolute_action=true量级 1.99 → 1.00
norm_stats 初始化失败norm_stats.json 只有 actions 缺 state用 norm_stats_runtime.json(含 state)—

数据 / 配置​

项训练侧板端
归一化MEAN_STD;state/action 14 维norm_stats_runtime.json(state+actions)
动作空间绝对动作use_absolute_action=true
state连续输入,补零到 32 维inject_state=false + state_size=14
图像480×640 存储,模型内 resize 224先 openpi resize_with_pad 到 224

验收指标参考​

量化精度​

提示

仅作为本任务的参考,校准集 30 样本,伪量化 vs float。

阶段指标值
calib_evalaction MAE0.001846
calib_evalaction RMSE0.003304
calib_evalaction cosine0.999772
calib_evaltensor MAE0.002611
calib_evaltensor cosine0.999775
说明

以上为 runtime 内计时:preproc 0.1ms + visual ~20ms + lm ~100ms + action ~125ms + postproc 0.1ms ≈ 250-270ms。此处为高精度版本,若欲提升推理性能,可参考 OELLM 工具链中的均衡版本以及高性能版本。

实机资源占用​

折毛巾实机运行时,RDK S600 的 CPU 与四核 BPU 占用如下。

CPU 占用​

Pi0 实机运行的 CPU 占用时序

四核 BPU 使用率​

Pi0 实机运行的四核 BPU 使用率

分阶段 CPU 占用​

Pi0 与 Pi05 分阶段 CPU 占用率