本文用一个贯穿到底的例子,讲清楚 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 这类名字在登记表里找到具体实现。
所谓"协议",在这里就是三样具体的东西:
- 一组 Python 抽象类 :规定"你必须实现哪几个方法、返回什么格式"。比如所有机器人都必须继承
Robot类,实现get_observation()和send_action()。 - 一种数据格式:规定采集下来的数据怎么存、叫什么名字。
- 一个登记机制 :每个机器人、每个模型用一个名字登记,脚本里用
--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 | 数据格式、采集训练推理脚本、登记机制 | 具体的硬件和模型 |
| 落地团队 | 选机械臂和模型;采高质量的任务数据;训练;做评测;放到真实工位 | 底层接口 |
接口已经被别人铺好了,落地团队真正的价值在于任务数据、评测方法和现场落地。