LeRobot 全链路原理:用一个“1+1=2“的例子讲透机械臂与模型如何接入

本文用一个贯穿到底的例子,讲清楚 LeRobot 是什么、它定义了哪些"协议"、一台新机械臂和一个新模型分别要怎么接入,以及 6 个舵机和 12 个舵机的机器人如何被同一种模型适配。文中结论对照过 LeRobot 源码(基于 LeRobot 0.6.x 的分支阅读);为了讲清楚,例子里虚构了一个新模型 "MiniVLA" 和一台新机械臂 "双臂 12 舵机"。代码是简化版,但类名、方法名与真实 LeRobot 一致。

一、前世今生:LeRobot 是什么

LeRobot 出现之前(2023 年及以前):每个实验室、每家公司都自己写一套数据格式、训练脚本和机械臂驱动。A 实验室发了个好模型,B 公司想在自家机械臂上用,几乎要重写一遍;你采的数据别人也用不了。
#mermaid-svg-rQrLJKkqvIHWyH9a{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rQrLJKkqvIHWyH9a .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rQrLJKkqvIHWyH9a .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rQrLJKkqvIHWyH9a .error-icon{fill:#552222;}#mermaid-svg-rQrLJKkqvIHWyH9a .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rQrLJKkqvIHWyH9a .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rQrLJKkqvIHWyH9a .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rQrLJKkqvIHWyH9a .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rQrLJKkqvIHWyH9a .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rQrLJKkqvIHWyH9a .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rQrLJKkqvIHWyH9a .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rQrLJKkqvIHWyH9a .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rQrLJKkqvIHWyH9a .marker.cross{stroke:#333333;}#mermaid-svg-rQrLJKkqvIHWyH9a svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rQrLJKkqvIHWyH9a p{margin:0;}#mermaid-svg-rQrLJKkqvIHWyH9a .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rQrLJKkqvIHWyH9a .cluster-label text{fill:#333;}#mermaid-svg-rQrLJKkqvIHWyH9a .cluster-label span{color:#333;}#mermaid-svg-rQrLJKkqvIHWyH9a .cluster-label span p{background-color:transparent;}#mermaid-svg-rQrLJKkqvIHWyH9a .label text,#mermaid-svg-rQrLJKkqvIHWyH9a span{fill:#333;color:#333;}#mermaid-svg-rQrLJKkqvIHWyH9a .node rect,#mermaid-svg-rQrLJKkqvIHWyH9a .node circle,#mermaid-svg-rQrLJKkqvIHWyH9a .node ellipse,#mermaid-svg-rQrLJKkqvIHWyH9a .node polygon,#mermaid-svg-rQrLJKkqvIHWyH9a .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rQrLJKkqvIHWyH9a .rough-node .label text,#mermaid-svg-rQrLJKkqvIHWyH9a .node .label text,#mermaid-svg-rQrLJKkqvIHWyH9a .image-shape .label,#mermaid-svg-rQrLJKkqvIHWyH9a .icon-shape .label{text-anchor:middle;}#mermaid-svg-rQrLJKkqvIHWyH9a .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rQrLJKkqvIHWyH9a .rough-node .label,#mermaid-svg-rQrLJKkqvIHWyH9a .node .label,#mermaid-svg-rQrLJKkqvIHWyH9a .image-shape .label,#mermaid-svg-rQrLJKkqvIHWyH9a .icon-shape .label{text-align:center;}#mermaid-svg-rQrLJKkqvIHWyH9a .node.clickable{cursor:pointer;}#mermaid-svg-rQrLJKkqvIHWyH9a .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rQrLJKkqvIHWyH9a .arrowheadPath{fill:#333333;}#mermaid-svg-rQrLJKkqvIHWyH9a .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rQrLJKkqvIHWyH9a .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rQrLJKkqvIHWyH9a .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rQrLJKkqvIHWyH9a .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rQrLJKkqvIHWyH9a .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rQrLJKkqvIHWyH9a .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rQrLJKkqvIHWyH9a .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rQrLJKkqvIHWyH9a .cluster text{fill:#333;}#mermaid-svg-rQrLJKkqvIHWyH9a .cluster span{color:#333;}#mermaid-svg-rQrLJKkqvIHWyH9a div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rQrLJKkqvIHWyH9a .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rQrLJKkqvIHWyH9a rect.text{fill:none;stroke-width:0;}#mermaid-svg-rQrLJKkqvIHWyH9a .icon-shape,#mermaid-svg-rQrLJKkqvIHWyH9a .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rQrLJKkqvIHWyH9a .icon-shape p,#mermaid-svg-rQrLJKkqvIHWyH9a .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rQrLJKkqvIHWyH9a .icon-shape .label rect,#mermaid-svg-rQrLJKkqvIHWyH9a .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rQrLJKkqvIHWyH9a .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rQrLJKkqvIHWyH9a .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rQrLJKkqvIHWyH9a :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} LeRobot 之前:N × M 次适配
模型 A
机械臂 1
机械臂 2
模型 B
LeRobot 之后:N + M 次适配
模型 A
LeRobot

统一接口
模型 B
机械臂 1
机械臂 2

图 1:没有统一接口时,每个模型和每台机械臂都要两两适配;有了 LeRobot,各自只需对接一次中间层。

2024 年 :Hugging Face 发布了 LeRobot,目标是做机器人领域的 "transformers 库":统一数据格式、统一训练流程、统一硬件接口。再配上一台低成本、可 3D 打印的 SO-100 机械臂,让学生也玩得起。

现在:它成了模仿学习领域事实上的标准。仓库里已经接入的机器人包括:

类型 例子
单臂 SO-101、Koch、NexArm、OpenArm、OMX
双臂 双 SO 臂、双 OpenArm
移动底盘加机械臂 LeKiwi
人形或半人形 宇树 G1、Reachy2、HopeJR
移动小车 EarthRover

所以它不只管机械臂,而是"任何能读出状态、能接收动作的机器人"都能接。

二、LeRobot 到底定义了什么

它定义了 5 类插口 和 1 种数据格式,再加上 3 个现成的流程脚本:
#mermaid-svg-DC1BCqsEhDt5sUb8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DC1BCqsEhDt5sUb8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DC1BCqsEhDt5sUb8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DC1BCqsEhDt5sUb8 .error-icon{fill:#552222;}#mermaid-svg-DC1BCqsEhDt5sUb8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DC1BCqsEhDt5sUb8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DC1BCqsEhDt5sUb8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DC1BCqsEhDt5sUb8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DC1BCqsEhDt5sUb8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DC1BCqsEhDt5sUb8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DC1BCqsEhDt5sUb8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DC1BCqsEhDt5sUb8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DC1BCqsEhDt5sUb8 .marker.cross{stroke:#333333;}#mermaid-svg-DC1BCqsEhDt5sUb8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DC1BCqsEhDt5sUb8 p{margin:0;}#mermaid-svg-DC1BCqsEhDt5sUb8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DC1BCqsEhDt5sUb8 .cluster-label text{fill:#333;}#mermaid-svg-DC1BCqsEhDt5sUb8 .cluster-label span{color:#333;}#mermaid-svg-DC1BCqsEhDt5sUb8 .cluster-label span p{background-color:transparent;}#mermaid-svg-DC1BCqsEhDt5sUb8 .label text,#mermaid-svg-DC1BCqsEhDt5sUb8 span{fill:#333;color:#333;}#mermaid-svg-DC1BCqsEhDt5sUb8 .node rect,#mermaid-svg-DC1BCqsEhDt5sUb8 .node circle,#mermaid-svg-DC1BCqsEhDt5sUb8 .node ellipse,#mermaid-svg-DC1BCqsEhDt5sUb8 .node polygon,#mermaid-svg-DC1BCqsEhDt5sUb8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DC1BCqsEhDt5sUb8 .rough-node .label text,#mermaid-svg-DC1BCqsEhDt5sUb8 .node .label text,#mermaid-svg-DC1BCqsEhDt5sUb8 .image-shape .label,#mermaid-svg-DC1BCqsEhDt5sUb8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-DC1BCqsEhDt5sUb8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DC1BCqsEhDt5sUb8 .rough-node .label,#mermaid-svg-DC1BCqsEhDt5sUb8 .node .label,#mermaid-svg-DC1BCqsEhDt5sUb8 .image-shape .label,#mermaid-svg-DC1BCqsEhDt5sUb8 .icon-shape .label{text-align:center;}#mermaid-svg-DC1BCqsEhDt5sUb8 .node.clickable{cursor:pointer;}#mermaid-svg-DC1BCqsEhDt5sUb8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DC1BCqsEhDt5sUb8 .arrowheadPath{fill:#333333;}#mermaid-svg-DC1BCqsEhDt5sUb8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DC1BCqsEhDt5sUb8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DC1BCqsEhDt5sUb8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DC1BCqsEhDt5sUb8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DC1BCqsEhDt5sUb8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DC1BCqsEhDt5sUb8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DC1BCqsEhDt5sUb8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DC1BCqsEhDt5sUb8 .cluster text{fill:#333;}#mermaid-svg-DC1BCqsEhDt5sUb8 .cluster span{color:#333;}#mermaid-svg-DC1BCqsEhDt5sUb8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DC1BCqsEhDt5sUb8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DC1BCqsEhDt5sUb8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-DC1BCqsEhDt5sUb8 .icon-shape,#mermaid-svg-DC1BCqsEhDt5sUb8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DC1BCqsEhDt5sUb8 .icon-shape p,#mermaid-svg-DC1BCqsEhDt5sUb8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DC1BCqsEhDt5sUb8 .icon-shape .label rect,#mermaid-svg-DC1BCqsEhDt5sUb8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DC1BCqsEhDt5sUb8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DC1BCqsEhDt5sUb8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DC1BCqsEhDt5sUb8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Robot

机器人插口
lerobot-record

采集
Teleoperator

遥操作插口
Camera

相机插口
Dataset

统一数据格式
lerobot-train

训练
Policy

模型插口
训练好的权重
lerobot-rollout

推理

图 2:LeRobot 的整体架构。五类插口和统一数据格式被三个现成脚本串起来:机器人、遥操作、相机供采集使用,数据集和模型供训练使用,机器人和模型再一起用于推理。脚本通过 --robot.type、--policy.type 这类名字在登记表里找到具体实现。

所谓"协议",在这里就是三样具体的东西:

  1. 一组 Python 抽象类 :规定"你必须实现哪几个方法、返回什么格式"。比如所有机器人都必须继承 Robot 类,实现 get_observation() 和 send_action()。
  2. 一种数据格式:规定采集下来的数据怎么存、叫什么名字。
  3. 一个登记机制 :每个机器人、每个模型用一个名字登记,脚本里用 --robot.type=名字、--policy.type=名字 来调用。

这和 USB 很像:USB 规定了接口形状和通信规则,键盘厂商按规则做键盘,电脑厂商按规则做接口,双方不需要认识对方。

三、接入一台新机械臂,要做什么

例子:某厂商出了一台"双臂 12 舵机"的机械臂,左右各 6 个舵机。要接进 LeRobot,只需写一个类,回答"我有什么、怎么读、怎么写":

python 复制代码
@RobotConfig.register_subclass("dual12")          # ① 登记名字
class Dual12Config(RobotConfig):
    port: str
    cameras: dict = {}

class Dual12(Robot):
    config_class = Dual12Config
    name = "dual12"

    JOINTS = ["left_shoulder_pan", "left_shoulder_lift", "left_elbow", "left_wrist_flex",
              "left_wrist_roll", "left_gripper",
              "right_shoulder_pan", "right_shoulder_lift", "right_elbow", "right_wrist_flex",
              "right_wrist_roll", "right_gripper"]

    @property
    def observation_features(self):                 # ② 声明"我能提供什么"
        return {f"{j}.pos": float for j in self.JOINTS} | {"front": (480, 640, 3)}

    @property
    def action_features(self):                      # ③ 声明"我能接收什么"
        return {f"{j}.pos": float for j in self.JOINTS}

    def get_observation(self):                      # ④ 读:调用厂商的舵机协议
        pos = self.bus.read_all_positions()         #    这一行是厂商私有的部分
        return {f"{j}.pos": pos[i] for i, j in enumerate(self.JOINTS)} | {"front": self.cam.read()}

    def send_action(self, action):                  # ⑤ 写:把 12 个目标值发给舵机
        self.bus.write_all_positions([action[f"{j}.pos"] for j in self.JOINTS])
        return action

    # 另有 connect / disconnect / calibrate 等,略

机械臂这边只做三件事 :声明有几个关节和相机,实现怎么读,实现怎么写。真正跟硬件相关的,只有 self.bus 背后那段舵机通信代码。比如幻尔为 NexArm 写的适配代码约 300 行,做的就是这些事。

一个现成的真实例子 :LeRobot 里的"双 SO 臂"就是这么做的。它不重新写驱动,而是把两条 6 舵机的 SO 臂拼在一起,给左臂每个关节名加前缀 left_,右臂加 right_,对外就变成一台 12 个关节的机器人。
#mermaid-svg-c20fKQMbyWrzvv6o{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-c20fKQMbyWrzvv6o .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-c20fKQMbyWrzvv6o .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-c20fKQMbyWrzvv6o .error-icon{fill:#552222;}#mermaid-svg-c20fKQMbyWrzvv6o .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-c20fKQMbyWrzvv6o .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-c20fKQMbyWrzvv6o .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-c20fKQMbyWrzvv6o .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-c20fKQMbyWrzvv6o .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-c20fKQMbyWrzvv6o .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-c20fKQMbyWrzvv6o .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-c20fKQMbyWrzvv6o .marker{fill:#333333;stroke:#333333;}#mermaid-svg-c20fKQMbyWrzvv6o .marker.cross{stroke:#333333;}#mermaid-svg-c20fKQMbyWrzvv6o svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-c20fKQMbyWrzvv6o p{margin:0;}#mermaid-svg-c20fKQMbyWrzvv6o .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-c20fKQMbyWrzvv6o .cluster-label text{fill:#333;}#mermaid-svg-c20fKQMbyWrzvv6o .cluster-label span{color:#333;}#mermaid-svg-c20fKQMbyWrzvv6o .cluster-label span p{background-color:transparent;}#mermaid-svg-c20fKQMbyWrzvv6o .label text,#mermaid-svg-c20fKQMbyWrzvv6o span{fill:#333;color:#333;}#mermaid-svg-c20fKQMbyWrzvv6o .node rect,#mermaid-svg-c20fKQMbyWrzvv6o .node circle,#mermaid-svg-c20fKQMbyWrzvv6o .node ellipse,#mermaid-svg-c20fKQMbyWrzvv6o .node polygon,#mermaid-svg-c20fKQMbyWrzvv6o .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-c20fKQMbyWrzvv6o .rough-node .label text,#mermaid-svg-c20fKQMbyWrzvv6o .node .label text,#mermaid-svg-c20fKQMbyWrzvv6o .image-shape .label,#mermaid-svg-c20fKQMbyWrzvv6o .icon-shape .label{text-anchor:middle;}#mermaid-svg-c20fKQMbyWrzvv6o .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-c20fKQMbyWrzvv6o .rough-node .label,#mermaid-svg-c20fKQMbyWrzvv6o .node .label,#mermaid-svg-c20fKQMbyWrzvv6o .image-shape .label,#mermaid-svg-c20fKQMbyWrzvv6o .icon-shape .label{text-align:center;}#mermaid-svg-c20fKQMbyWrzvv6o .node.clickable{cursor:pointer;}#mermaid-svg-c20fKQMbyWrzvv6o .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-c20fKQMbyWrzvv6o .arrowheadPath{fill:#333333;}#mermaid-svg-c20fKQMbyWrzvv6o .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-c20fKQMbyWrzvv6o .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-c20fKQMbyWrzvv6o .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c20fKQMbyWrzvv6o .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-c20fKQMbyWrzvv6o .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c20fKQMbyWrzvv6o .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-c20fKQMbyWrzvv6o .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-c20fKQMbyWrzvv6o .cluster text{fill:#333;}#mermaid-svg-c20fKQMbyWrzvv6o .cluster span{color:#333;}#mermaid-svg-c20fKQMbyWrzvv6o div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-c20fKQMbyWrzvv6o .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-c20fKQMbyWrzvv6o rect.text{fill:none;stroke-width:0;}#mermaid-svg-c20fKQMbyWrzvv6o .icon-shape,#mermaid-svg-c20fKQMbyWrzvv6o .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c20fKQMbyWrzvv6o .icon-shape p,#mermaid-svg-c20fKQMbyWrzvv6o .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-c20fKQMbyWrzvv6o .icon-shape .label rect,#mermaid-svg-c20fKQMbyWrzvv6o .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c20fKQMbyWrzvv6o .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-c20fKQMbyWrzvv6o .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-c20fKQMbyWrzvv6o :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 厂商私有部分
LeRobot 统一接口(Robot 抽象类)
observation_features

声明能提供什么
action_features

声明能接收什么
get_observation()

读
send_action()

写
舵机总线驱动

self.bus
相机
12 个舵机

图 3:接入新机械臂时,左边是 LeRobot 规定要实现的接口,右边才是和具体硬件相关的代码。

四、12 个舵机和 6 个舵机,怎么"适配"

这是理解全局的关键一步。采集数据时,LeRobot 会把机器人声明的关节自动拼成一个向量:有几个关节,向量就有多长。

6 舵机的机械臂采集后,数据集的说明文件里会写成这样:

json 复制代码
"observation.state": { "shape": [6],  "names": ["shoulder_pan.pos", "shoulder_lift.pos", "elbow_flex.pos",
                                                 "wrist_flex.pos", "wrist_roll.pos", "gripper.pos"] },
"action":            { "shape": [6],  "names": [ 同上 ] },
"observation.images.front": { "shape": [480, 640, 3] },
"observation.images.wrist": { "shape": [480, 640, 3] }

双臂 12 舵机采集后,同一位置就变成 "shape": [12],名字是 left_... 和 right_... 共 12 个。

模型怎么"理解"6 和 12? 说实话,模型不理解,它只认数字的个数和位置:

  • 它看到的不是"肩关节转 30 度",而是"一个长度为 6 的数字列表,第 1 个数是 2048"。
  • 关节名字是给人看的,也是为了保证数据和硬件对得上号,模型本身不读名字。
  • 每个数代表什么,是模型从示范数据里"统计出来"的:它发现"每次夹东西前,第 6 个数都会变小",于是学会在该夹的时候把第 6 个数调小。

模型的网络大小怎么跟着变? 分两种:

模型类型 怎么处理 6 和 12 源码依据
从头训练的小模型,如 ACT 训练开始时读数据集说明文件,看到 shape: 6 就把输入输出层建成 6,看到 12 就建成 12 LeRobot 有专门的函数把数据集特征转成模型的输入输出形状
有预训练基座的大模型,如 SmolVLA 基座尺寸固定,统一把向量补零到 32 位:6 个关节就是 6 个真实数加 26 个 0,12 个关节就是 12 个真实数加 20 个 0 SmolVLA 配置里最大状态维度和最大动作维度都是 32,模型代码里有补零函数

所以,同一种模型可以用在 6 舵机或 12 舵机上,但训练出来的那份权重只属于那一台机器:在 6 舵机数据上训练的,第 6 位是夹爪;在双臂上训练的,第 6 位是左夹爪、第 12 位是右夹爪,含义完全不同,不能混用。
#mermaid-svg-SFc15dNVTW8SjWmp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SFc15dNVTW8SjWmp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SFc15dNVTW8SjWmp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SFc15dNVTW8SjWmp .error-icon{fill:#552222;}#mermaid-svg-SFc15dNVTW8SjWmp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SFc15dNVTW8SjWmp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SFc15dNVTW8SjWmp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SFc15dNVTW8SjWmp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SFc15dNVTW8SjWmp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SFc15dNVTW8SjWmp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SFc15dNVTW8SjWmp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SFc15dNVTW8SjWmp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SFc15dNVTW8SjWmp .marker.cross{stroke:#333333;}#mermaid-svg-SFc15dNVTW8SjWmp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SFc15dNVTW8SjWmp p{margin:0;}#mermaid-svg-SFc15dNVTW8SjWmp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SFc15dNVTW8SjWmp .cluster-label text{fill:#333;}#mermaid-svg-SFc15dNVTW8SjWmp .cluster-label span{color:#333;}#mermaid-svg-SFc15dNVTW8SjWmp .cluster-label span p{background-color:transparent;}#mermaid-svg-SFc15dNVTW8SjWmp .label text,#mermaid-svg-SFc15dNVTW8SjWmp span{fill:#333;color:#333;}#mermaid-svg-SFc15dNVTW8SjWmp .node rect,#mermaid-svg-SFc15dNVTW8SjWmp .node circle,#mermaid-svg-SFc15dNVTW8SjWmp .node ellipse,#mermaid-svg-SFc15dNVTW8SjWmp .node polygon,#mermaid-svg-SFc15dNVTW8SjWmp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SFc15dNVTW8SjWmp .rough-node .label text,#mermaid-svg-SFc15dNVTW8SjWmp .node .label text,#mermaid-svg-SFc15dNVTW8SjWmp .image-shape .label,#mermaid-svg-SFc15dNVTW8SjWmp .icon-shape .label{text-anchor:middle;}#mermaid-svg-SFc15dNVTW8SjWmp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SFc15dNVTW8SjWmp .rough-node .label,#mermaid-svg-SFc15dNVTW8SjWmp .node .label,#mermaid-svg-SFc15dNVTW8SjWmp .image-shape .label,#mermaid-svg-SFc15dNVTW8SjWmp .icon-shape .label{text-align:center;}#mermaid-svg-SFc15dNVTW8SjWmp .node.clickable{cursor:pointer;}#mermaid-svg-SFc15dNVTW8SjWmp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SFc15dNVTW8SjWmp .arrowheadPath{fill:#333333;}#mermaid-svg-SFc15dNVTW8SjWmp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SFc15dNVTW8SjWmp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SFc15dNVTW8SjWmp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SFc15dNVTW8SjWmp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SFc15dNVTW8SjWmp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SFc15dNVTW8SjWmp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SFc15dNVTW8SjWmp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SFc15dNVTW8SjWmp .cluster text{fill:#333;}#mermaid-svg-SFc15dNVTW8SjWmp .cluster span{color:#333;}#mermaid-svg-SFc15dNVTW8SjWmp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SFc15dNVTW8SjWmp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SFc15dNVTW8SjWmp rect.text{fill:none;stroke-width:0;}#mermaid-svg-SFc15dNVTW8SjWmp .icon-shape,#mermaid-svg-SFc15dNVTW8SjWmp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SFc15dNVTW8SjWmp .icon-shape p,#mermaid-svg-SFc15dNVTW8SjWmp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SFc15dNVTW8SjWmp .icon-shape .label rect,#mermaid-svg-SFc15dNVTW8SjWmp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SFc15dNVTW8SjWmp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SFc15dNVTW8SjWmp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SFc15dNVTW8SjWmp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 12 舵机双臂
6 舵机单臂
取前 6 个
取前 12 个
6 个关节读数
补 26 个 0
12 个关节读数
补 20 个 0
统一的 32 维向量
预训练 VLA

如 SmolVLA
输出 32 维动作
发给 6 个舵机
发给 12 个舵机

图 4:预训练大模型的输入输出尺寸固定为 32 维,不同关节数的机器人通过补零接入,输出时只取前 N 个。ACT 这类从头训练的小模型没有这一步,直接按关节数建网络。

五、接入一个新模型,要做什么

例子 :某实验室发布了 "MiniVLA",效果很好。要接入 LeRobot,需要写三个文件,每个文件只关心数据,不关心机器人是谁:

python 复制代码
# 文件 1:configuration_minivla.py ------ 登记名字、声明超参数
@PreTrainedConfig.register_subclass("minivla")
class MiniVLAConfig(PreTrainedConfig):
    chunk_size: int = 50          # 一次输出多少步动作
    max_action_dim: int = 32      # 像 SmolVLA 一样,统一补到 32 位

# 文件 2:modeling_minivla.py ------ 实现协议规定的方法
class MiniVLAPolicy(PreTrainedPolicy):
    config_class = MiniVLAConfig
    name = "minivla"

    def __init__(self, config):
        # 从 config 里读到"动作有几维"(6 或 12),据此搭网络
        action_dim = config.output_features["action"].shape[0]
        self.net = 实验室原来的网络代码(...)

    def forward(self, batch):            # 训练用:给一批数据,返回误差
        pred = self.net(batch["observation.images.front"], batch["observation.state"])
        return loss(pred, batch["action"]), {}

    def select_action(self, batch):      # 推理用:给当前观测,返回下一步动作
        return self.net(batch["observation.images.front"], batch["observation.state"])[:, 0]

    def reset(self): ...                 # 每轮开始清空缓存
    def get_optim_params(self): return self.parameters()

# 文件 3:processor_minivla.py ------ 前后处理(归一化、反归一化、图片缩放)
def make_minivla_pre_post_processors(config, dataset_stats): ...

模型这边只做三件事 :登记名字;把作者原来的网络包进 forward 和 select_action 两个方法;定义前后处理。它只认 observation.state、observation.images.xxx、action 这些标准名字,不管背后是哪台机械臂。

写好后有两条路发布:合并进官方 LeRobot;或者打包成一个名字以 lerobot_policy_ 开头的插件(比如 lerobot_policy_minivla),LeRobot 启动时会自动发现并加载。
#mermaid-svg-NygDEBi2nBKp86ub{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NygDEBi2nBKp86ub .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NygDEBi2nBKp86ub .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NygDEBi2nBKp86ub .error-icon{fill:#552222;}#mermaid-svg-NygDEBi2nBKp86ub .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NygDEBi2nBKp86ub .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NygDEBi2nBKp86ub .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NygDEBi2nBKp86ub .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NygDEBi2nBKp86ub .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NygDEBi2nBKp86ub .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NygDEBi2nBKp86ub .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NygDEBi2nBKp86ub .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NygDEBi2nBKp86ub .marker.cross{stroke:#333333;}#mermaid-svg-NygDEBi2nBKp86ub svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NygDEBi2nBKp86ub p{margin:0;}#mermaid-svg-NygDEBi2nBKp86ub .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NygDEBi2nBKp86ub .cluster-label text{fill:#333;}#mermaid-svg-NygDEBi2nBKp86ub .cluster-label span{color:#333;}#mermaid-svg-NygDEBi2nBKp86ub .cluster-label span p{background-color:transparent;}#mermaid-svg-NygDEBi2nBKp86ub .label text,#mermaid-svg-NygDEBi2nBKp86ub span{fill:#333;color:#333;}#mermaid-svg-NygDEBi2nBKp86ub .node rect,#mermaid-svg-NygDEBi2nBKp86ub .node circle,#mermaid-svg-NygDEBi2nBKp86ub .node ellipse,#mermaid-svg-NygDEBi2nBKp86ub .node polygon,#mermaid-svg-NygDEBi2nBKp86ub .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NygDEBi2nBKp86ub .rough-node .label text,#mermaid-svg-NygDEBi2nBKp86ub .node .label text,#mermaid-svg-NygDEBi2nBKp86ub .image-shape .label,#mermaid-svg-NygDEBi2nBKp86ub .icon-shape .label{text-anchor:middle;}#mermaid-svg-NygDEBi2nBKp86ub .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NygDEBi2nBKp86ub .rough-node .label,#mermaid-svg-NygDEBi2nBKp86ub .node .label,#mermaid-svg-NygDEBi2nBKp86ub .image-shape .label,#mermaid-svg-NygDEBi2nBKp86ub .icon-shape .label{text-align:center;}#mermaid-svg-NygDEBi2nBKp86ub .node.clickable{cursor:pointer;}#mermaid-svg-NygDEBi2nBKp86ub .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NygDEBi2nBKp86ub .arrowheadPath{fill:#333333;}#mermaid-svg-NygDEBi2nBKp86ub .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NygDEBi2nBKp86ub .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NygDEBi2nBKp86ub .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NygDEBi2nBKp86ub .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NygDEBi2nBKp86ub .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NygDEBi2nBKp86ub .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NygDEBi2nBKp86ub .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NygDEBi2nBKp86ub .cluster text{fill:#333;}#mermaid-svg-NygDEBi2nBKp86ub .cluster span{color:#333;}#mermaid-svg-NygDEBi2nBKp86ub div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NygDEBi2nBKp86ub .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NygDEBi2nBKp86ub rect.text{fill:none;stroke-width:0;}#mermaid-svg-NygDEBi2nBKp86ub .icon-shape,#mermaid-svg-NygDEBi2nBKp86ub .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NygDEBi2nBKp86ub .icon-shape p,#mermaid-svg-NygDEBi2nBKp86ub .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NygDEBi2nBKp86ub .icon-shape .label rect,#mermaid-svg-NygDEBi2nBKp86ub .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NygDEBi2nBKp86ub .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NygDEBi2nBKp86ub .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NygDEBi2nBKp86ub :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 新模型需要写的三个文件
标准输入(与机器人无关)
配置
observation.state
observation.images.*
configuration_xxx.py

登记名字、超参数
processor_xxx.py

归一化 / 反归一化
modeling_xxx.py

forward / select_action
processor_xxx.py

反归一化
action

图 5:接入新模型时,模型只认标准名字的输入输出,不关心背后是哪台机械臂。

六、1 + 1 = 2:把它们拼起来

已知:

  • 机械臂 = NexArm(6 舵机),已实现机器人插口,登记名 nexarm_follower
  • 模型 = MiniVLA,已实现模型插口,登记名 minivla

第 1 步:采集。用主动臂示范"抓小件挂到钩上"100 次:
#mermaid-svg-H3frIckqfHfWqEUc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-H3frIckqfHfWqEUc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-H3frIckqfHfWqEUc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-H3frIckqfHfWqEUc .error-icon{fill:#552222;}#mermaid-svg-H3frIckqfHfWqEUc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-H3frIckqfHfWqEUc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-H3frIckqfHfWqEUc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-H3frIckqfHfWqEUc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-H3frIckqfHfWqEUc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-H3frIckqfHfWqEUc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-H3frIckqfHfWqEUc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-H3frIckqfHfWqEUc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-H3frIckqfHfWqEUc .marker.cross{stroke:#333333;}#mermaid-svg-H3frIckqfHfWqEUc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-H3frIckqfHfWqEUc p{margin:0;}#mermaid-svg-H3frIckqfHfWqEUc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-H3frIckqfHfWqEUc .cluster-label text{fill:#333;}#mermaid-svg-H3frIckqfHfWqEUc .cluster-label span{color:#333;}#mermaid-svg-H3frIckqfHfWqEUc .cluster-label span p{background-color:transparent;}#mermaid-svg-H3frIckqfHfWqEUc .label text,#mermaid-svg-H3frIckqfHfWqEUc span{fill:#333;color:#333;}#mermaid-svg-H3frIckqfHfWqEUc .node rect,#mermaid-svg-H3frIckqfHfWqEUc .node circle,#mermaid-svg-H3frIckqfHfWqEUc .node ellipse,#mermaid-svg-H3frIckqfHfWqEUc .node polygon,#mermaid-svg-H3frIckqfHfWqEUc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-H3frIckqfHfWqEUc .rough-node .label text,#mermaid-svg-H3frIckqfHfWqEUc .node .label text,#mermaid-svg-H3frIckqfHfWqEUc .image-shape .label,#mermaid-svg-H3frIckqfHfWqEUc .icon-shape .label{text-anchor:middle;}#mermaid-svg-H3frIckqfHfWqEUc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-H3frIckqfHfWqEUc .rough-node .label,#mermaid-svg-H3frIckqfHfWqEUc .node .label,#mermaid-svg-H3frIckqfHfWqEUc .image-shape .label,#mermaid-svg-H3frIckqfHfWqEUc .icon-shape .label{text-align:center;}#mermaid-svg-H3frIckqfHfWqEUc .node.clickable{cursor:pointer;}#mermaid-svg-H3frIckqfHfWqEUc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-H3frIckqfHfWqEUc .arrowheadPath{fill:#333333;}#mermaid-svg-H3frIckqfHfWqEUc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-H3frIckqfHfWqEUc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-H3frIckqfHfWqEUc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-H3frIckqfHfWqEUc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-H3frIckqfHfWqEUc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-H3frIckqfHfWqEUc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-H3frIckqfHfWqEUc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-H3frIckqfHfWqEUc .cluster text{fill:#333;}#mermaid-svg-H3frIckqfHfWqEUc .cluster span{color:#333;}#mermaid-svg-H3frIckqfHfWqEUc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-H3frIckqfHfWqEUc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-H3frIckqfHfWqEUc rect.text{fill:none;stroke-width:0;}#mermaid-svg-H3frIckqfHfWqEUc .icon-shape,#mermaid-svg-H3frIckqfHfWqEUc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-H3frIckqfHfWqEUc .icon-shape p,#mermaid-svg-H3frIckqfHfWqEUc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-H3frIckqfHfWqEUc .icon-shape .label rect,#mermaid-svg-H3frIckqfHfWqEUc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-H3frIckqfHfWqEUc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-H3frIckqfHfWqEUc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-H3frIckqfHfWqEUc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 人拖动主动臂示范
lerobot-record

采集
标准数据集

state:6 action:6

视频 + 统计值
lerobot-train

训练 MiniVLA
权重 + 配置

  • 归一化统计值
    lerobot-rollout

推理
NexArm 自主完成挂件

图 6:从采集到推理的完整链路。

bash 复制代码
lerobot-record --robot.type=nexarm_follower --robot.port=/dev/ttyUSB0 \
  --robot.cameras="{front: {...}, wrist: {...}}" \
  --teleop.type=nexarm_leader --teleop.port=/dev/ttyUSB1 \
  --dataset.repo_id=yourname/hang_v1 --dataset.num_episodes=100 \
  --dataset.single_task="hang the part on the hook"

脚本每秒 30 次调用 NexArm 的 get_observation(),得到 6 个数和 2 张图,再记录主动臂的 6 个数作为"正确动作"。最终存成标准数据集,说明文件写明 state: [6]、action: [6],并算好每个关节的均值和方差。

第 2 步:训练。

bash 复制代码
lerobot-train --dataset.repo_id=yourname/hang_v1 --policy.type=minivla \
  --output_dir=outputs/hang_minivla

脚本读数据集说明文件,发现动作是 6 维,于是告诉 MiniVLA "输出 6 维"(MiniVLA 内部再补零到 32 位)。然后反复调用 forward:喂进图片和 6 个关节数,让它预测 6 个动作数,和人的示范对比,算误差、调参数。结束后保存权重、配置和归一化统计值。

第 3 步:推理。

bash 复制代码
lerobot-rollout --robot.type=nexarm_follower --robot.port=/dev/ttyUSB0 \
  --robot.cameras="{front: {...}, wrist: {...}}" \
  --policy.path=outputs/hang_minivla/checkpoints/last/pretrained_model

每秒 30 次循环:
后处理 MiniVLA 前处理 NexArm 后处理 MiniVLA 前处理 NexArm #mermaid-svg-6rVVzU7DKuirHVTX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6rVVzU7DKuirHVTX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6rVVzU7DKuirHVTX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6rVVzU7DKuirHVTX .error-icon{fill:#552222;}#mermaid-svg-6rVVzU7DKuirHVTX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6rVVzU7DKuirHVTX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6rVVzU7DKuirHVTX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6rVVzU7DKuirHVTX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6rVVzU7DKuirHVTX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6rVVzU7DKuirHVTX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6rVVzU7DKuirHVTX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6rVVzU7DKuirHVTX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6rVVzU7DKuirHVTX .marker.cross{stroke:#333333;}#mermaid-svg-6rVVzU7DKuirHVTX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6rVVzU7DKuirHVTX p{margin:0;}#mermaid-svg-6rVVzU7DKuirHVTX .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6rVVzU7DKuirHVTX text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-6rVVzU7DKuirHVTX .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6rVVzU7DKuirHVTX .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-6rVVzU7DKuirHVTX .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-6rVVzU7DKuirHVTX .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-6rVVzU7DKuirHVTX #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-6rVVzU7DKuirHVTX .sequenceNumber{fill:white;}#mermaid-svg-6rVVzU7DKuirHVTX #sequencenumber{fill:#333;}#mermaid-svg-6rVVzU7DKuirHVTX #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-6rVVzU7DKuirHVTX .messageText{fill:#333;stroke:none;}#mermaid-svg-6rVVzU7DKuirHVTX .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6rVVzU7DKuirHVTX .labelText,#mermaid-svg-6rVVzU7DKuirHVTX .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-6rVVzU7DKuirHVTX .loopText,#mermaid-svg-6rVVzU7DKuirHVTX .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-6rVVzU7DKuirHVTX .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6rVVzU7DKuirHVTX .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-6rVVzU7DKuirHVTX .noteText,#mermaid-svg-6rVVzU7DKuirHVTX .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-6rVVzU7DKuirHVTX .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6rVVzU7DKuirHVTX .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6rVVzU7DKuirHVTX .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6rVVzU7DKuirHVTX .actorPopupMenu{position:absolute;}#mermaid-svg-6rVVzU7DKuirHVTX .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-6rVVzU7DKuirHVTX .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6rVVzU7DKuirHVTX .actor-man circle,#mermaid-svg-6rVVzU7DKuirHVTX line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-6rVVzU7DKuirHVTX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop每秒 30 次 get_observation() 6 个关节读数 + 2 张图归一化后的观测select_action() 输出归一化动作反归一化为舵机目标值,send_action()

图 7:推理时的实时控制循环。

复制代码
NexArm.get_observation()      →  [2048, 1530, 2900, 1800, 2048, 3100] + 两张图
前处理:按训练时的统计值归一化  →  [0.01, -0.83, 1.21, ...]
MiniVLA.select_action()        →  [0.02, -0.80, 1.18, ..., -1.40]   (第 6 位变小:要夹了)
后处理:反归一化               →  [2052, 1545, 2880, 1810, 2048, 2200]
NexArm.send_action()           →  6 个舵机转到这些位置,夹爪闭合

结果 :NexArm 夹住小件、挂上钩子。整个过程里,机械臂厂商没写过一行 MiniVLA 的代码,MiniVLA 的作者也没见过 NexArm。这就是 1 + 1 = 2。

换成双臂 12 舵机,会怎样? 把三条命令里的 nexarm_follower 换成 dual12,重新采一批双臂数据,说明文件就会变成 [12],训练出的 MiniVLA 自动输出 12 维。代码一行都不用改,但必须重新采数据、重新训练,因为 6 维那份权重学的是另一副身体。

七、总结:谁负责什么

角色 负责写什么 不需要关心什么
机械臂厂商 机器人插口:声明关节和相机,实现读、写 用什么模型
模型作者 模型插口:配置、forward、select_action、前后处理 用什么机械臂
Hugging Face / LeRobot 数据格式、采集训练推理脚本、登记机制 具体的硬件和模型
落地团队 选机械臂和模型;采高质量的任务数据;训练;做评测;放到真实工位 底层接口

接口已经被别人铺好了,落地团队真正的价值在于任务数据、评测方法和现场落地。

相关推荐
GlobalInfo6 小时前
全球及中国风电叶片检测机器人行业研究报告(2026版):市场现状、行业前景与占有率分析
运维·网络·人工智能·机器人
、达西先生12 小时前
rucking插值调试记录,运动控制速度和加速度的理解
运动控制·具身智能
天树星智源15 小时前
相机轨迹为什么对不上:问题不在控制模块,而在两段噪声不是同一份
论文阅读·机器人·大模型
Wanda121416 小时前
机器人与机电一体化3D数字孪生机器-Day7
人工智能·3d·机器人·数字孪生
ergevv18 小时前
如何使用 NIS 判断观测是否可靠:从卡尔曼滤波推导到机器人实践
机器人·卡尔曼滤波·nis·观测可靠性
todoitbo1 天前
在 Ubuntu ARM64 上运行 Q1 V3.0 MuJoCo 模型:加载、步进和单执行器响应
linux·运维·ubuntu·ai·机器人·仿真教程·启元
dghongcheng1 天前
喷涂机器人品牌推荐:六轴 vs 协作机器人怎么选
机器人·喷涂·喷漆设备·自动喷涂线·自动喷涂
todoitbo1 天前
Q1 MuJoCo 模型里的 49 项传感器定义与 60 维数据:一次关节和 IMU 输出核验
人工智能·ai·机器人·1024程序员节·启元机器人
SteveSenna1 天前
aubo学习:启动真实机械臂
机器人