RFM 微调与评测:从预训练模型到可部署策略¶
目标:掌握 Robot Foundation Model 的三种微调策略(全量微调、LoRA、冻结主干+适配器),理解 SmolVLA 与 OpenVLA 的数据格式与超参配置,并能用本仓库
evaluate.py完成离线与闭环评测、语言消融与泛化评测。
Tags: #fine-tuning #LoRA #evaluation #LeRobot #RLDS #ablation
Related Docs: - 24-action-representation-and-tokenization.md — 归一化与动作块 - 21-vla-dataset-organization.md — 数据集组织 - 20-vla-deployment-guide.md — 部署优化 - 06-evaluation-metrics.md — 评测指标体系
目录¶
- 三种微调策略
- SmolVLA 微调详解
- OpenVLA LoRA 微调详解
- 评测协议总览
- 离线评测 (Offline Evaluation)
- 闭环评测 (Closed-Loop Evaluation)
- 语言消融 (Language Ablation)
- 泛化评测 (Generalization Eval)
- 常见问题
1. 三种微调策略¶
预训练的 Robot Foundation Model(如 SmolVLA 450M、OpenVLA 7B)已在海量机器人数据上训练,但针对特定任务/机器人仍需微调 (fine-tuning)。三种主流策略:
| 策略 | 可训练参数 | 显存需求 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 全量微调 (Full FT) | 100% | 高 | 任务与预训练差异大、算力充足 | 性能上限最高 | 灾难性遗忘、昂贵 |
| LoRA | ~1% (低秩矩阵) | 低 | 消费级 GPU、多任务适配 | 高效、可插拔 | 性能略低于全量 |
| 冻结主干 + Adapter | 仅 adapter 层 | 最低 | 快速实验、保预训练知识 | 防遗忘、极快 | 表达力受限 |
graph TD
PRE[预训练模型权重] --> FT{微调策略}
FT -->|全量| F1[更新所有参数]
FT -->|LoRA| F2[冻结原权重<br/>插入低秩 A,B 矩阵]
FT -->|冻结+Adapter| F3[冻结主干<br/>只训练 adapter 层]
F1 --> M1[高性能<br/>高显存]
F2 --> M2[高效适配<br/>可保存多个 LoRA]
F3 --> M3[快速实验<br/>防遗忘]
1.1 LoRA 原理速览¶
LoRA (Low-Rank Adaptation) 把权重更新 ΔW 分解为两个低秩矩阵的乘积:ΔW = A · B,其中 A ∈ R^{d×r}、B ∈ R^{r×d},秩 r << d。原权重 W 冻结,只训练 A 和 B。
- 参数量从
d²降到2dr(如 r=32 时减少数十倍)。 - 多个任务的 LoRA 可随时切换,像“插件”一样。
2. SmolVLA 微调详解¶
SmolVLA 是 450M 参数的轻量 VLA,适合消费级 GPU 微调。本仓库配置在 examples/robot_foundation_models/smolvla/finetune_config.yaml。
2.1 数据格式:LeRobot¶
SmolVLA 使用 HuggingFace LeRobot 数据集格式,每条样本是一个字典:
episode = {
"observation.images.front": np.ndarray, # (H, W, 3) RGB
"observation.state": np.ndarray, # (state_dim,) 机器人状态
"action": np.ndarray, # (action_dim,) 专家动作
"task": "push the red cube to the target", # 语言指令 (LeRobot 用 "task" 字段)
}
配置文件中对应字段:
dataset:
repo_id: "local/pushcube_demo" # 本地或 HF Hub 数据集 ID
root: "data/pushcube_lerobot" # 本地路径
image_keys:
- "observation.images.front"
state_key: "observation.state"
action_key: "action"
language_key: "task" # LeRobot 用 "task" 存语言
2.2 数据量建议¶
官方建议每个任务变体至少 50 条高质量 episode(见配置文件注释)。质量比数量更重要——50 条精准遥操数据胜过 200 条噪声数据。
2.3 关键超参¶
training:
batch_size: 16
num_epochs: 50
learning_rate: 1.0e-4 # 全量微调典型 LR
weight_decay: 0.01
warmup_steps: 500
lr_scheduler: "cosine" # 余弦退火
grad_clip_norm: 1.0
chunk_size: 10 # 预测未来 10 步
temporal_ensemble:
enabled: true
decay: 0.01 # 指数加权衰减
augment: # 数据增强防过拟合
brightness: 0.1
contrast: 0.1
saturation: 0.1
hue: 0.05
random_crop: true
hardware:
device: "cuda"
mixed_precision: "bf16" # Ampere+ 用 bf16
| 超参 | 值 | 说明 |
|---|---|---|
learning_rate |
1e-4 | 全量微调;LoRA 可用更高 (5e-4) |
num_epochs |
50 | 配合早停 |
chunk_size |
10 | Action Chunking horizon |
mixed_precision |
bf16 | 省显存、防溢出 |
augment |
启用 | 颜色抖动 + 随机裁剪 |
2.4 评测与保存¶
logging:
eval_every_n_epochs: 5
save_every_n_epochs: 10
save_best_model: true
best_metric: "eval_success_rate" # 按闭环成功率保存最优
注意
best_metric优先选eval_success_rate(闭环成功率)而非eval_loss(训练损失),因为低损失不等于高成功率——模型可能学到了平均动作而无法完成任务。
3. OpenVLA LoRA 微调详解¶
OpenVLA 是 7B 参数的通用 VLA,全量微调需多卡。LoRA 让单卡 24GB 即可微调。配置在 examples/robot_foundation_models/openvla/lora_config.yaml。
3.1 数据格式:RLDS¶
OpenVLA 使用 RLDS (Reinforcement Learning Datasets) 格式,与 LeRobot 不同:
dataset:
rlds_dir: "data/rlds"
dataset_name: "pushcube"
image_key: "observation/image"
state_key: "observation/state"
action_key: "action"
language_key: "language_instruction"
norm_stats_path: "data/norm_stats/pushcube_norm.json" # 归一化统计量
关键:RLDS 格式的归一化统计量单独存于 JSON,推理时通过
unnorm_key加载(见 24-action-representation-and-tokenization.md 第 4 节)。
3.2 LoRA 配置¶
lora:
r: 32 # 低秩矩阵的秩
lora_alpha: 16 # 缩放因子
target_modules: # 在哪些层插入 LoRA
- "q_proj"
- "k_proj"
- "v_proj"
- "o_proj"
- "gate_proj"
- "up_proj"
- "down_proj"
lora_dropout: 0.05
bias: "none"
task_type: "CAUSAL_LM"
| 参数 | 值 | 说明 |
|---|---|---|
r |
32 | 秩,越大表达力越强但参数越多 |
lora_alpha |
16 | 缩放,实际更新幅度 = alpha/r |
target_modules |
7 个 | 覆盖 attention + MLP 全部投影层 |
lora_dropout |
0.05 | 防过拟合 |
3.3 训练超参与显存¶
training:
batch_size: 2
num_epochs: 20
learning_rate: 5.0e-4 # LoRA 用比全量更高的 LR
warmup_ratio: 0.03
lr_scheduler: "cosine"
grad_clip_norm: 1.0
gradient_accumulation_steps: 8 # 有效 batch = 2×8 = 16
hardware:
device: "cuda"
mixed_precision: "bf16"
num_gpus: 1
# OpenVLA-7B + LoRA 约需 24GB VRAM
显存估算:7B 模型 bf16 权重约 14GB + 激活值 + LoRA 参数 + 优化器状态 ≈ 24GB。gradient_accumulation_steps=8 让有效 batch 达到 16,弥补单卡 batch_size=2 的不足。
graph LR
subgraph 24GB 显存分配
W[模型权重 bf16 ~14GB] --> MEM
A[激活值 ~4GB] --> MEM
L[LoRA 参数 ~50M] --> MEM
O[优化器状态 ~4GB] --> MEM
end
4. 评测协议总览¶
微调后必须评测。本仓库 examples/robot_foundation_models/smolvla/evaluate.py 实现了两类评测:
| 评测类型 | 是否需环境 | 指标 | 回答的问题 |
|---|---|---|---|
| 离线 (Offline) | 否(只需专家数据) | Action MAE, L2, 方向一致性 | 模型预测准不准? |
| 闭环 (Closed-Loop) | 是(PushCube env) | 成功率、选错率、选择准确率 | 模型能完成任务吗? |
graph TD
EVAL{评测} --> OFF[离线评测]
EVAL --> CL[闭环评测]
OFF --> O1[Action MAE]
OFF --> O2[L2 距离]
OFF --> O3[方向一致性]
CL --> C1[正确成功率]
CL --> C2[错误成功率]
CL --> C3[选择准确率]
CL --> C4[平均步数]
5. 离线评测 (Offline Evaluation)¶
离线评测把模型预测与专家示范逐帧对比,无需运行环境,速度快、可复现。
5.1 指标定义¶
来自 evaluate.py 的 run_offline_eval:
- Action MAE (Mean Absolute Error):预测动作与专家动作逐元素绝对误差的均值。
- Action L2:预测与专家动作向量的欧氏距离。
- Direction Consistency (方向一致性):预测方向与专家方向夹角 < 90°(即余弦相似度 > 0)的样本比例。
# 来自 evaluate.py run_offline_eval
mae = np.mean(np.abs(pred - expert))
l2 = np.linalg.norm(pred - expert)
# 方向一致性
if np.linalg.norm(pred) > 1e-6 and np.linalg.norm(expert) > 1e-6:
cos_sim = np.dot(pred, expert) / (
np.linalg.norm(pred) * np.linalg.norm(expert)
)
if cos_sim > 0:
direction_matches += 1
5.2 运行方式¶
# Mock 模式(CI 用,无需 GPU/模型下载)
python evaluate.py --mode offline --mock
# 真实模型 + 专家数据
python evaluate.py --mode offline --data results/benchmarks/pushcube_expert.json
5.3 指标解读¶
| 指标 | 好的范围 | 含义 |
|---|---|---|
| Action MAE | < 0.05 (归一化后) | 数值误差小 |
| Action L2 | < 0.1 | 整体偏差小 |
| Direction Consistency | > 0.8 | 80% 以上步骤方向正确 |
注意:离线指标好不等于闭环成功。模型可能 MAE 很低但无法完成任务(如卡在局部最优)。离线评测是必要不充分条件。
6. 闭环评测 (Closed-Loop Evaluation)¶
闭环评测在 PushCube 环境中真实 rollout 模型,测量任务完成度。这是最终的“黄金标准”。
6.1 PushCube 双方块任务¶
PushCube 是本仓库的标准评测环境(见 README.md):场景中有两个不同颜色的方块,语言指令指定推动哪一个。这天然测试了模型的语言 grounding 能力。
6.2 指标定义¶
来自 evaluate.py 的 run_closed_loop_eval:
| 指标 | 含义 | 计算方式 |
|---|---|---|
correct_success |
正确方块被推到目标的成功率 | active cube 距目标 < threshold |
wrong_success |
错误方块被推到目标的成功率 | other cube 距目标 < threshold |
selection_accuracy |
选对方块的比例 | active 距离 < other 距离 |
avg_steps |
平均完成步数 | 总步数 / episode 数 |
# 来自 evaluate.py run_closed_loop_eval
active_dist = float(np.linalg.norm(active_cube - target))
other_dist = float(np.linalg.norm(other_cube - target))
if active_dist < env.goal_threshold:
correct_success += 1
if other_dist < env.goal_threshold:
wrong_success += 1
if active_dist < other_dist:
selection_accuracy += 1
6.3 运行方式¶
# 闭环评测(mock 模式)
python evaluate.py --mode closed_loop --mock --n_episodes 5
# 闭环评测(真实模型)
python evaluate.py --mode closed_loop --n_episodes 20
6.4 关键观察¶
wrong_success应趋近 0:若 wrong_success 高,说明模型没听懂语言,在乱推。selection_accuracy是语言 grounding 的直接指标:> 90% 说明模型能根据颜色选择正确方块。avg_steps反映效率:步数过多可能说明模型在打转。
7. 语言消融 (Language Ablation)¶
语言消融用于验证模型是否真的在用语言指令,而非只靠视觉记忆。方法:同一模型、同一场景,只改变语言输入。
7.1 三种语言条件¶
| 条件 | 语言输入 | 预期结果 |
|---|---|---|
| Correct | "push the red cube" (与场景一致) | 高成功率 |
| Swapped | "push the green cube" (颜色对调) | 选错方块、成功率下降 |
| None | 空字符串或无关文本 | 随机选择、成功率 ~50% |
graph TD
SCENE[场景: 红方块 + 绿方块] --> M[同一个微调模型]
M -->|Correct: push red| R1[推红方块 ✓]
M -->|Swapped: push green| R2[推绿方块 ✗]
M -->|None: 无语言| R3[随机选择 ?]
7.2 如何在本仓库实现¶
evaluate.py 的闭环评测已支持不同语言指令(通过 env.get_language_instruction())。要做消融,可在评测循环中替换语言:
# 语言消融示例
conditions = {
"correct": env.get_language_instruction(),
"swapped": env.get_swapped_instruction(), # 颜色对调
"none": "", # 无语言
}
for cond_name, lang in conditions.items():
results[cond_name] = run_closed_loop_eval_with_lang(adapter, lang, n_episodes=20)
7.3 判读标准¶
- 若 Correct 成功率高、Swapped 选错率高 → 模型确实用了语言。
- 若三者成功率接近 → 模型忽略了语言,只靠视觉(需检查训练数据是否语言-动作对齐)。
8. 泛化评测 (Generalization Eval)¶
泛化评测测试模型对分布外 (Out-of-Distribution, OOD) 场景的鲁棒性。
8.1 四个泛化维度¶
| 维度 | 训练分布 | 测试分布 | 测试什么 |
|---|---|---|---|
| 新位置 (New Positions) | 方块在固定区域 | 方块随机位置 | 空间泛化 |
| 新颜色 (New Colors) | 红/绿方块 | 蓝/黄方块 | 颜色不变性 |
| 新背景 (New Backgrounds) | 纯色桌面 | 纹理桌面 | 背景鲁棒性 |
| 改写指令 (Paraphrased) | "push the red cube" | "move the crimson block" | 语言泛化 |
8.2 评测方法¶
每个维度独立评测,保持其他条件不变:
# 泛化评测示例
generalization_tests = {
"new_position": {"seed_range": (3000, 3020), "lang": "push the red cube"},
"new_color": {"color_map": {"red": "blue", "green": "yellow"}},
"new_bg": {"bg_texture": "wood.png"},
"paraphrase": {"lang": "move the crimson block to the target"},
}
for test_name, config in generalization_tests.items():
results[test_name] = run_closed_loop_eval(
adapter, n_episodes=20, **config
)
8.3 结果分析¶
graph LR
TRAIN[训练分布] --> G1[新位置: 80%]
TRAIN --> G2[新颜色: 60%]
TRAIN --> G3[新背景: 70%]
TRAIN --> G4[改写指令: 50%]
- 新位置泛化通常最好(空间不变性较强)。
- 改写指令泛化最难(需要语言理解而非模式匹配),下降明显说明模型在死记短语。
- 颜色泛化下降说明模型过拟合训练颜色——数据增强 (
augment.hue) 可缓解。
9. 常见问题¶
Q1: 50 条 episode 够微调吗?
SmolVLA 官方建议 50 条起步。对于简单任务(如 PushCube)足够;复杂任务(如装配)可能需要 200+。关键是数据质量和多样性(覆盖不同初始位置、光照)。
Q2: LoRA 的 rank 设多少?
r=32 是经验默认值(本仓库 lora_config.yaml)。r=8 适合简单任务,r=64/128 适合复杂任务但更易过拟合。建议从 32 开始网格搜索 {8, 16, 32, 64}。
Q3: 离线 MAE 很低但闭环成功率也低,为什么?
常见原因:(1) 模型预测了“平均动作”,MAE 低但无法完成任务;(2) 复合误差——单步误差小但累积偏离;(3) 安全过滤器过度 CLIP 导致动作被截断。需检查闭环 rollout 轨迹。
Q4: 语言消融中 None 条件应该用什么?
用空字符串 "" 或无关节点(如 "hello")。若用空字符串成功率仍高,说明模型完全靠视觉;若成功率骤降,说明模型强依赖语言。
Q5: 闭环评测为什么用 PushCube 双方块?
双方块场景天然需要语言 grounding——模型必须根据颜色指令选择正确方块,单方块场景无法区分“听懂语言”和“靠视觉记忆”。这正是 evaluate.py 同时测量 correct_success 和 wrong_success 的原因。
小结:微调与评测是 RFM 落地的最后一公里。SmolVLA 用 LeRobot 格式 + 全量微调(1e-4 LR),OpenVLA 用 RLDS 格式 + LoRA(r=32, 24GB)。评测遵循“离线→闭环→消融→泛化”的递进逻辑:离线测数值精度,闭环测任务完成,语言消融测 grounding,泛化测 OOD 鲁棒性。本仓库
evaluate.py已实现离线与闭环两类评测,可直接复用。下一篇 27-embodied-reasoning-and-planning.md 将讨论如何在 RFM 之上加入推理与规划层。