
Task6基于 SO-101 数据微调 π0.5前面已经完成了 SO-101 的遥操作、数据采集和 Diffusion Policy 训练。本次继续尝试 π0.5使用已有的 LeRobot 数据通过 LoRA 微调让预训练模型适应自己的机械臂任务。整个流程仍然是准备数据 → 检查输入输出 → 加载预训练模型 → 微调 → 保存 checkpoint → 实机测试。不同之处在于这次需要同时处理图像、机器人状态和任务文字并在部署时正确加载 LoRA 权重。本文的命令按固定版本的 LeRobot 源码重新整理作为复现参考原始命令未完整保留下面的起步参数没有在本次 RTX 4090 环境中重新实测。最后的训练曲线和实机现象来自第六周实验记录不能视为下方参考参数产生的结果。一、任务介绍与实验环境本次使用 SO-101 机械臂训练设备为 RTX 4090。Mac 负责连接机械臂和摄像头服务器负责训练与模型推理。远程部署继续使用前文的链路Mac采集相机图像和机械臂状态发送观测、接收动作 ↕ SSH 隧道 服务器加载策略根据观测生成动作π0.5 属于视觉语言动作模型。对本文任务而言可以把一次策略调用理解为输入当前画面、机械臂状态和任务描述输出后续一段时间的动作。LoRA 则用于减少微调时需要更新的参数保留基础模型在部分层上训练较小的适配器。基础权重仍需加载图像和动作的计算也仍然占用显存所以选择 LoRA 后batch size 和相机数量依然需要根据设备调整。LeRobot PEFT 说明二、沿用前文的数据集示例使用前文公开的数据集feng0724821/so101_test_record。任务描述为Place the bottle next to the national flag即把瓶子放到国旗旁边。为了便于复现本文固定数据 revision 为e78431cbd96856c32822adc73f94ead78ea3d4a1该版本包含50 个 episode共 26,710 帧采集频率为 30 FPS。一路腕部相机observation.images.wrist视频分辨率为 640×480。六维机械臂状态observation.state和六维动作action。LeRobot Dataset v3.0 格式视频编码为 AV1。这里的一个 episode 对应一段示范轨迹一帧则包含某个时刻的状态、动作及对应图像二者不能混为一谈。数据集元数据状态和动作的维度顺序均为shoulder_pan.pos shoulder_lift.pos elbow_flex.pos wrist_flex.pos wrist_roll.pos gripper.pos前五维对应机械臂关节位置最后一维对应夹爪。这里不是末端的六维位姿。训练时保留数据原有的动作语义部署时使用一致的关节顺序、校准和单位不根据数值范围自行转换成角度或弧度。这个公开版本与后续实验需要区分第五周报告记录过扩充到 80 段的数据第六周还涉及双摄采集的方块、盒子任务。本文用前文公开的单摄版本讲解训练流程后面单独讨论第六周的部署现象。三、准备训练环境并检查数据以下命令在 RTX 4090 所在的 Linux 服务器执行。建议建立独立环境避免与前文 Diffusion Policy 的依赖互相影响。conda create-nlerobot-pi05python3.12-yconda activate lerobot-pi05 condainstall-cconda-forgeffmpeg7.1.1-ygitclone https://github.com/huggingface/lerobot.git lerobot-pi05cdlerobot-pi05gitcheckout e0d50211ef236143ae867228662b7dfaba554f02 python-mpipinstall-e.[pi,peft,training]nvidia-smi python-cimport torch; print(torch.__version__, torch.cuda.is_available())其中pi安装 π 系列模型依赖peft提供 LoRA 支持training安装训练相关依赖。固定源码版本是为了让后面的参数名、数据处理和 checkpoint 加载方式保持一致。torch.cuda.is_available()应输出True。该版本依赖配置π0.5 使用 PaliGemma tokenizer。先在 Hugging Face 完成google/paligemma-3b-pt-224的访问授权再执行hf auth login登录自己的账号。π0.5 官方说明1. 下载基础模型在 LeRobot 仓库目录执行exportSO101_DATASETfeng0724821/so101_test_recordexportSO101_REVISIONe78431cbd96856c32822adc73f94ead78ea3d4a1exportPI05_BASE_DIR$PWD/../pi05_models/pi05_basepython -PY import os from huggingface_hub import snapshot_download snapshot_download( repo_idlerobot/pi05_base, revisionb211f3d44c36b6acfcf7ae94a64e8e96f75a64ba, local_diros.environ[PI05_BASE_DIR], ) PYrepo_id指定基础模型revision固定模型版本local_dir指定本地保存位置。后续训练直接读取PI05_BASE_DIR方便保证训练和 adapter 重载使用同一份基础权重。重新打开终端时需要再次设置这三个环境变量。2. 读取一条数据检查字段沿用前文的数据检查方式先确认当前环境能读数据、解码视频再启动训练python -PY import os from lerobot.datasets.lerobot_dataset import LeRobotDataset ds LeRobotDataset( os.environ[SO101_DATASET], revisionos.environ[SO101_REVISION], video_backendpyav, ) print(episodes:, ds.num_episodes) print(frames:, ds.num_frames) print(fps:, ds.fps) print(cameras:, ds.meta.camera_keys) sample ds[0] for key in (observation.images.wrist, observation.state, action): print(key, tuple(sample[key].shape), sample[key].dtype) print(task:, sample[task]) assert sample[observation.state].shape (6,) assert sample[action].shape (6,) for key in (observation.state, action): assert {q01, q99} ds.meta.stats[key].keys() PY预期核对到 50 段、26,710 帧、30 FPS以及单路wrist。视频读取后通常变为通道在前的(3, 480, 640)张量状态和动作均为(6,)。其中video_backendpyav指定解码后端后面的训练也保持一致sample[task]是送入模型的任务文字。最后的断言检查状态和动作是否带有q01、q99即 1% 和 99% 分位数。π0.5 默认使用这些统计量进行归一化这份数据已经包含不必为了切换模型重复计算。已保存的统计量四、启动 LoRA 微调下面给出完整训练入口。第一次执行时先按本节末尾的方法改成 100 步试跑确认链路正常后再使用完整训练设置。mkdir-poutputs/train_logsset-opipefail lerobot-train\--dataset.repo_id$SO101_DATASET\--dataset.revision$SO101_REVISION\--dataset.video_backendpyav\--dataset.eval_split0.1\--policy.typepi05\--policy.pretrained_path$PI05_BASE_DIR\--policy.devicecuda\--policy.dtypebfloat16\--policy.gradient_checkpointingtrue\--policy.chunk_size50\--policy.n_action_steps10\--policy.optimizer_lr1e-4\--policy.scheduler_decay_lr1e-5\--policy.scheduler_warmup_steps500\--policy.scheduler_decay_steps10000\--peft.method_typeLORA\--peft.r16\--peft.lora_alpha32\--accelerator.mixed_precisionbf16\--batch_size1\--num_workers2\--steps10000\--save_freq1000\--log_freq10\--eval_steps1000\--max_eval_samples128\--env_eval_freq0\--policy.push_to_hubfalse\--wandb.enablefalse\--output_diroutputs/train/pi05_so101_lora\21|teeoutputs/train_logs/pi05_so101_lora.log其中各组参数的作用如下。1. 数据与模型参数dataset.repo_id、dataset.revision指定数据集及版本避免仓库更新后示范数量、相机字段发生变化。dataset.eval_split0.1按 episode 留出 10% 数据。该版本会为这份单任务数据留下末尾 5 段做验证其余 45 段训练避免同一段轨迹被随意按帧切开。policy.typepi05选择 π0.5 策略。policy.pretrained_path加载基础模型权重。配合policy.type使用时输入输出字段从当前数据集解析因此仍使用observation.images.wrist不用为了套别的示例改成image或补出双摄。SO-101 的六维动作由模型内部适配不需要手工把数据改成其他机器人的七维动作。特征解析与加载实现2. LoRA 参数peft.method_typeLORA启用 LoRA 微调。peft.r16设置低秩适配器的秩影响可训练参数量和适配容量增大后不一定能提高实机效果。peft.lora_alpha32控制 LoRA 更新的缩放。普通 LoRA 使用alpha/r这里为 2但不能把它直接理解成学习率。当前 π0.5 默认在动作专家的部分注意力投影以及动作、时间投影上加入适配器。基础权重被冻结这不是全参数训练。新建训练时使用peft参数即可不要额外把用于加载已有 adapter 的policy.use_peft设置为true。π0.5 默认适配层3. 显存和数据加载参数policy.devicecuda使用服务器 GPU。policy.dtypebfloat16和accelerator.mixed_precisionbf16分别设置模型计算精度和训练自动混合精度。policy.gradient_checkpointingtrue用反向传播时的部分重计算换取较低的激活显存。batch_size1每次处理一个样本作为 24GB 显存环境的起点。num_workers2使用两个数据加载进程主要影响 CPU 侧读取速度和内存。本例没有开启梯度累积。先通过 100 步试跑观察显存占用更换相机数量或模型实现后也要重新检查。运行配置4. 动作长度与学习率chunk_size50模型一次预测 50 个时间步的动作。n_action_steps10使用标准select_action接口时执行其中 10 步后请求新预测。它不会把训练目标的长度改成 10远程服务如果有独立动作队列还要看服务端实现。optimizer_lr1e-4设置峰值学习率。scheduler_warmup_steps500前 500 步逐渐提高学习率。scheduler_decay_steps10000、scheduler_decay_lr1e-5在给定训练长度内按调度衰减到较低学习率。steps10000本轮训练的迭代次数不是采集了 10,000 段轨迹也不是 10,000 个 epoch。调整学习率和训练长度时同时观察训练、验证 loss并保留几个阶段的 checkpoint 做实机比较。π0.5 参数定义5. 日志、验证与保存log_freq10表示每 10 步打印训练信息save_freq1000表示每 1000 步保存 checkpointeval_steps1000表示每 1000 步计算保留数据上的 loss。max_eval_samples128限制验证样本数量适合快速观察趋势但不能代表完整五段验证轨迹的表现。这个流程仍使用数据集已有的全量统计量也不应称为严格独立的泛化评估。env_eval_freq0关闭仿真环境评估policy.push_to_hubfalse、wandb.enablefalse让模型和日志保留在本地。21 | tee ...同时显示并保存标准输出和错误信息方便后续排查。训练配置6. 先做 100 步试跑复制上面的训练命令将steps、save_freq、scheduler_decay_steps改为100scheduler_warmup_steps改为5eval_steps改为0。输出目录和日志名称分别换成outputs/train/pi05_so101_smoke、outputs/train_logs/pi05_so101_smoke.log。试跑主要检查预训练权重能否加载、数据能否进入模型、loss 是否为有限值以及能否完成反向传播和保存。确认日志出现All keys loaded successfully!若出现Returning model without loading pretrained weights先解决权重加载问题不能因为训练开始输出 loss 就继续。权重加载实现通过后再用正式目录运行完整命令。每个新实验使用独立输出目录保留对应日志便于比较参数变化。五、检查 checkpoint 并恢复训练训练输出在outputs/train/pi05_so101_lora/最新 checkpoint 可以通过checkpoints/last/找到checkpoints/last/ ├── pretrained_model/ │ ├── adapter_config.json │ ├── adapter_model.safetensors │ ├── config.json │ ├── train_config.json │ ├── policy_preprocessor.json │ ├── policy_postprocessor.json │ └── 处理器关联的统计量文件 └── training_state/其中adapter_model.safetensors保存 LoRA 权重adapter_config.json记录适配器配置及基础模型位置处理器文件保存预处理、归一化等信息training_state保存继续训练需要的状态。last指向最新保存结果并不表示实机效果最好。checkpoint 保存实现LoRA checkpoint 仍依赖基础模型不能只拷贝一个 adapter 权重文件就部署。尤其本文使用本地基础模型目录迁移服务器时要同时保留基础权重并检查 adapter 中记录的路径。如果训练中断使用保存的训练配置恢复lerobot-train\--config_pathoutputs/train/pi05_so101_lora/checkpoints/last/pretrained_model/train_config.json\--resumetrue这里config_path指向原训练配置resumetrue恢复已有训练状态。它与仅加载权重开始一轮新训练不同也不等于自动延长已经完成的训练计划。六、推理前先做离线检查接入机械臂前可以先取数据集的一条观测在服务器检查“图像和状态 → 模型 → 六维动作”的链路。以下代码不连接机械臂python -PY import os from pathlib import Path import torch from lerobot.configs import PreTrainedConfig from lerobot.datasets.lerobot_dataset import LeRobotDataset from lerobot.policies import make_policy, make_pre_post_processors ckpt Path(outputs/train/pi05_so101_lora/checkpoints/last/pretrained_model) ds LeRobotDataset( os.environ[SO101_DATASET], revisionos.environ[SO101_REVISION], video_backendpyav, ) cfg PreTrainedConfig.from_pretrained(ckpt) cfg.pretrained_path ckpt cfg.pretrained_revision None cfg.device cuda assert cfg.use_peft policy make_policy(cfg, ds_metads.meta).eval() policy.reset() pre, post make_pre_post_processors( cfg, pretrained_pathckpt, preprocessor_overrides{device_processor: {device: cuda}}, ) sample ds[0] obs {k: v for k, v in sample.items() if k.startswith(observation.) or k task} with torch.inference_mode(): action post(policy.select_action(pre(obs))) print(action shape:, tuple(action.shape)) print(action:, action) assert tuple(action.shape) (1, 6) assert torch.isfinite(action).all() PY其中make_policy负责组合加载基础模型和 adapterpre使用训练保存的处理器整理观测post将模型输出转换回机器人的动作尺度(1, 6)表示一个样本对应六维动作。代码通过只能说明离线调用链正常不能说明动作已经准确。策略与处理器工厂继续采用 Mac 客户端和服务器推理时也要检查服务端是否使用兼容 LoRA 的加载方式。本文固定版本中的旧async_inference/policy_server.py直接调用策略类的from_pretrained()没有经过上述 PEFT 加载分支因此不能只把旧命令里的 checkpoint 换成 adapter 目录。旧服务端加载入口原实验的服务端修改记录没有完整保留这里不拼接一条未经验证的远程部署命令。迁移时应先让服务正确加载模型与处理器再核对相机字段、关节顺序和控制频率。七、第六周的训练结果与实机问题下面是第六周汇报中保存的训练曲线训练 loss 从约 0.64 下降到 0.12 附近图中标注的最佳验证点为第 4000 步验证 loss 为 0.1130曲线展示到了约 5600 步。这个结果可用于筛选待测试的 checkpoint但不能直接换算成实机成功率也没有记录能确认最终部署的就是第 4000 步模型。实验周报第 118 页现有记录未保留这张曲线对应的完整数据版本因此不据此比较单摄与双摄训练效果也不把它标作前面公开 50 段数据的训练结果。1. 双摄训练与单摄推理不一致当时双摄采集的数据包含两个视角但 Mac 通过扩展坞同时接入两个摄像头时会出现卡死推理只能保留腕部wrist相机。实际现象是模型能看到蓝色方块却难以定位盒子。当时的排查记录认为缺失相机通路被填成了黑图输入与训练时的双摄画面不一致同时腕部视野主要覆盖夹爪附近没有有效看到桌面上的盒子。具体的填图和屏蔽行为取决于代码版本但这个现象说明检查时需要查看模型真正收到的画面不能只看客户端能否连接、服务器是否输出动作。双摄训练后简单删除一路摄像头不会自动得到适合单摄的策略。后续应先固定硬件配置保证采集和推理的视角一致并让目标物体和放置区域获得足够的视觉覆盖。实验周报第 117 页2. 单视角实验仍有动作震荡另一组实验使用单视角采集的数据。把物体放在视野中心时能够跑通任务但动作仍有明显震荡执行时间也较长。现有记录不足以把原因完全归到训练不足、过拟合或网络延迟上需要分别检查数据覆盖、模型输出、通信耗时和动作执行过程。下一轮可以固定物体初始位置做重复测试再逐步改变位置记录每次是否完成、完成时间以及失败阶段同时比较不同 checkpoint。周报没有给出完整的成功次数和测试次数本文因此只保留观察到的现象。实验周报第 118 页这次实验已经跑通 π0.5 微调和实机调用。继续提高效果时优先解决相机输入的一致性和任务视野再通过重复测试判断需要增加什么样的数据、调整哪些训练参数。