☰
用LoRA微调VLM驱动机械臂:AutoBio复现笔记
2026/10/17 14:53:50 网站建设 项目流程

这个标题乍一看很像标题党。一个做生物实验自动化的开源项目,一台 32GB 显存的单卡,不到一个下午的 LoRA 训练,就能让实验室机械臂的任务成功率从 0 拉到 90%——听起来确实像在往自己脸上贴金。但把 AutoBio 完整跑通一遍之后,我想认真说一句:这个结果是真的,而且比字面上看起来要克制得多。

AutoBio 是一个面向生物实验自动化的参考实现,核心思路是用视觉语言模型(VLM)直接驱动机械臂完成移液、夹取、转移培养皿这类实验室操作。我这次复现的目标非常明确:在本地单张 RTX 5090 上,用自采的约 300 条演示数据,对预训练的 VLM 做 LoRA 微调,在 5000 步内使真实机械臂的评测成功率从完全不可用的 0% 提升到 90%。如果你也在折腾“视觉语言模型 + 机器臂控制”这条路,或者对 LoRA 在真实物理环境里的落地效果有好奇,这篇笔记会很有用。它会告诉你哪些环节最容易被低成本训练的表面顺利掩盖,以及真正决定成败的往往不是模型大小,而是数据口径、动作归一化和评测协议这三件事。

1. 先拆清楚“0%”是什么失败:基线模型的真实问题

1.1 看起来啥都能干的 VLM,拿到机械臂指令就直接失控

我最初的怀疑对象是“指令理解”环节。毕竟一个参数不小的开源 VLM,理论上不该连“把移液枪吸头插入 A1 孔”这种话都听不懂。但把模型直接架到机械臂上做开环推理后,现场表现相当难看:模型确实输出了指令,但输出的是一连串和任务无关的末端轨迹——有的方向完全反了,有的在夹具应该闭合时持续张开,最离谱的一次是模型把目标坐标理解成了图像里的某个背景纹理位置,机械臂在空中画了个圈后直接停在培养皿边缘。

这块内容如果在仿真环境里跑,可能根本暴露不出来,因为仿真器不会惩罚轨迹不合理,只会判定目标位姿是否达标。真实物理环境不一样,机械臂一旦轨迹发散,轻则失败,重则直接把吸头戳弯。在这个阶段,所有评测任务的成功率都是 0,没有任何含糊空间。

为什么一个泛化能力看起来不错的 VLM 会这样?核心原因是“视觉语义”和“动作流形”之间存在断层。预训练模型见过大量图文数据,但它并不知道我们实验室机械臂的关节限位、末端执行器的夹爪开合逻辑,更不知道我们习惯用七维向量(x, y, z, roll, pitch, yaw, grip)来描述动作。模型在预训练阶段学到的“语言-视觉对齐”在这里基本失效,输出的动作落在它自己的抽象分布里,而不是我们的机械臂能执行的分布里。这就是 0% 的直接来源。

1.2 0% 的失败权重:语义错位比动作发散更致命

为了搞清楚该往哪个方向用力,我把失败轨迹分成了三类并做了计数:

失败类型占比典型现象
语义错位约 60%模型忽略指令中的目标编号,或者把“吸取”理解成“放下”
动作空间外推约 30%输出的末端位姿超出机械臂工作空间,触发限位保护
夹爪/时序混乱约 10%开合时序错误,先夹后移动变成先移动后夹

结论很清晰:干预优先级应该先解决语义对齐,再解决动作空间范围。这也是后来选择 LoRA 而非全参数微调的第一个依据——LoRA 不改变基础模型的泛化骨架,只调整一小部分参数去适配“我们的任务方言”,刚好对症。

1.3 为什么不直接全参微调:数据、算力、过拟合三重约束

有人可能疑惑:既然都上了单张 RTX 5090,为什么不把整个 VLM 一起微调?我的答案是:这个场景下全参微调不仅没必要,而且有害。

首先是数据量。AutoBio 的演示数据来自人工遥控采集,一天下来干净可用的轨迹也就 300 条左右,每条轨迹折算成训练 token 大约是 500 到 800 步。这点数据量和预训练语料完全不在一个量级,全参微调几乎必然过拟合到演示数据的表面模式上,评测时稍有环境扰动就会崩。

其次是算力性价比。全参微调需要保存完整梯度和优化器状态,即便 LoRA 只训练 2% 的参数,显存占用也会多出三分之一到一半。用 32GB 显存跑一个中等规模的 VLM,如果做全参微调就得把 batch size 压到 2,训练效率反而下降。

最后是维护成本。LoRA 适配器本身只有几十到一两百 MB,切换任务时只需要换适配器权重,基础模型可以保持不动。这对实验室场景特别友好——同一个模型,今天做移液任务,明天做夹取任务,换一个 LoRA 文件就能切换,不用为每个任务保留一份完整的模型副本。

所以方案选型在这个环节基本就定了:冻结基础模型,用 LoRA 在动作投影层上做轻量适配,训练步数控制在让验证指标不再上升为止。这个思路后来验证下来是稳妥的,但真正做起来,细节比预期要磨人得多。

2. LoRA 训练的核心设计:数据、秩、步数与收敛

2.1 训练数据长什么样:一段演示轨迹的完整描述

AutoBio 的演示数据不是简单的“输入图片 + 输出文本”。每条轨迹是一个多帧序列,每一帧包含三个部分:

  • 第一视角相机图像,分辨率为 336×336,记录机械臂当前视角。图像会做标准化,和 VLM 预训练时的输入分布保持一致。
  • 状态文本,例如“当前夹爪状态:闭合,末端位置:(0.32, -0.14, 0.52),目标:A1 孔”。
  • 动作向量,这是一个七维浮点数组,对应末端执行器的位置增量、姿态增量和夹爪开合指令。

训练时,模型输入是“图像序列 + 状态文本 + 之前若干步的动作历史”,输出是“下一步动作”。这是一个典型的自回归动作生成任务。关键在于动作空间必须先做离散化:连续的七维向量不能直接作为 token 输出,我把它按维度拆成七个离散动作 token,每个维度用 256 个 bin 做均匀量化。量化会让动作精度损失一点点,但换来的是模型可以在标准语言建模框架下稳定训练,实测下来精度损失完全在机械臂重复定位精度(±1mm 左右)的容忍范围内。

数据处理阶段有一个特别容易忽略的点:每条轨迹开头都要追加一段“任务指令文本”,让模型知道自己在做什么。我踩过的坑是早期把指令放在序列末尾,结果模型生成动作时完全没有参照任务语义,loss 虽然下降,但成功率一直上不去。后来把任务指令放到序列最前面,并和图像特征一起进入注意力层,效果立刻改善。这个“排列顺序影响任务对齐”的观察,建议大家在自己的实验里也留意一下。

2.2 关键超参选择:rank、alpha、lr、batch size 到底怎么定

LoRA 微调看似只需要关心 rank 和 alpha,实际上真正决定成功率的是一组参数的组合。我对这套配置做过几次消融,最终稳定使用的是下表:

参数设定值选择理由
LoRA rank16数据集只有约 300 条轨迹,低秩足以表达任务间差异,过高的 rank 会引入不必要的过拟合
LoRA alpha32和 rank 保持 2:1 的比例,让低秩矩阵的更新幅度在初始化时不至于过小
目标模块q_proj / k_proj / v_proj / o_proj 全部仅调 q/v 收敛快,但动作预测对注意力分布更敏感,全量投影更稳
LoRA dropout0.05少量正则,防止动作分布收得过窄
学习率1e-4这个量级在 VLM LoRA 里比较常见,太大容易让动作输出失真
优化器8-bit AdamW显著降低显存占用,对 32GB 单卡更友好
批大小8,梯度累积 4 次,等效 32等效 batch 32 是收敛稳定性和显存开销之间的折中
热身步数50 步让学习率平缓爬升,避免初始阶段动作输出剧烈震荡

关于 alpha 和 rank 的比例多说一句。很多教程会说 alpha 设为 rank 的两倍可以“增强 LoRA 在低秩下对特征的修改幅度”,我在这个任务上实测下来,2:1 确实比 1:1 要稳一点,但更关键的是不要单独调整这两个参数而忽略学习率。rank 从 16 加到 32,如果学习率还是 1e-4,训练后期容易出现动作分布过于集中、评测泛化变差的现象。换句话说,rank 越大,学习率越应该适当调低,或者用更早的早停圈住收敛点。

2.3 5000 步意味着什么:从 loss 曲线到评测抽检的真实节奏

AutoBio 的参考实现建议是 5000 步,但我训练时没有盲目打满。记录每 200 步的 loss 和一次在固定 10 条轨迹上的快速评测抽检,能看到一个清晰的节奏:

  • 前 300 步:loss 从 3.2 快速降到 1.8,但快速评测成功率仍为 0。模型在拼命学习动作空间的基本形态。
  • 300 到 1500 步:loss 降到 0.9 左右,快速评测开始出现第一条成功轨迹,动作输出逐渐从“抽象乱动”变为“有意图的接近”。
  • 1500 到 3500 步:loss 下降变缓,快速评测成功率从 20% 爬到 60% 左右,失败案例集中在目标识别不准和夹爪时序错误。
  • 3500 到 5000 步:loss 逼近 0.4,快速评测成功率到 80% 以上,曲线基本进入平台期。

所以“5000 步”不是一个随意的数字,它是这个数据规模下收敛的近似终点。在 RTX 5090 上,5000 步训练大约耗时 40 分钟到 1 小时,取决于是否开启梯度检查点。如果数据量翻倍,训练步数可以适当增加到 8000 步,但再往后,成功率不一定继续涨,反而可能因过拟合演示分布而回落。我的建议是:训练到 loss 平台期后,每隔 500 步在固定评测集上做一次快速验证,用真实评测结果而不是 loss 来决定终结点。

3. 单卡 RTX 5090 实操实录:从权重加载到最终评测

3.1 显存布局与运行环境:32GB 单卡能装下什么

先说环境版本,这几个组合实测下来没有遇到兼容性大坑:

  • Python 3.10
  • PyTorch 2.1+,CUDA 版本 12.3 或更新
  • 基础 VLM 以 fp16 精度加载,参数量约 8B,占用约 16GB 显存
  • LoRA 适配器本身的显存开销可以忽略,但优化器状态、激活值和中间特征才是真正的显存大头

我开启了 gradient checkpointing,把推理和训练时的图像分辨率统一在 336×336,batch size 8 时显存峰值约 27GB,留给系统的余量不多但稳定。如果不开梯度检查点,同样的配置直接 OOM,连训练都启动不了。所以这一项不是可选项,而是必须项。

加载权重时还有个关键点:基础模型需要分片加载。直接用 fp16 全量加载会试图一次性把模型放进显存,虽然 8B 模型 16GB 勉强够用,但如果后续还要开优化器状态和中间激活,显存就非常尴尬。我把加载方式设为“按 shard 从磁盘加载到 CPU,再按层迁移到 GPU”,这个方案在实际部署中让峰值显存又省出约 3GB,给 batch size 留下了更多余地。

3.2 训练脚本骨架:一个能跑通的最小结构

核心训练逻辑不复杂,但有一些结构细节直接决定成败。下面这段是经过删减后的关键骨架:

import torch from transformers import AutoProcessor, AutoModelForVisionText2Text from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training base_model = AutoModelForVisionText2Text.from_pretrained( "your-base-vlm-path", torch_dtype=torch.float16, device_map="auto", ) base_model.gradient_checkpointing_enable() base_model = prepare_model_for_kbit_training(base_model) lora_config = LoraConfig( r=16, lora_alpha=32, target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM", ) model = get_peft_model(base_model, lora_config) # 数据采样器把“任务指令 + 图像序列 + 动作历史 + 下一步动作”打包成 input_ids train_loader = build_demo_dataloader( demo_dir="data/demos/", action_bins=256, action_history_len=8, batch_size=8, ) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4) step = 0 for batch in train_loader: logits = model( input_ids=batch["input_ids"].to("cuda"), attention_mask=batch["attention_mask"].to("cuda"), pixel_values=batch["pixel_values"].to("cuda"), labels=batch["labels"].to("cuda"), ).logits loss = logits.mean() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() optimizer.zero_grad() step += 1 if step % 500 == 0: save_checkpoint(f"checkpoints/lora_step_{step}.pt")

说几个容易踩的坑。device_map="auto"在 LoRA 训练时可能会导致某些层放在 CPU 上,前向速度骤降。我建议显式控制所有层都在 GPU,只有遇到 OOM 时才退回到 CPU offload。clip_grad_norm_这里不是可选项,动作 token 的 loss 在训练早期会出现异常峰值,不裁剪梯度的话,一次爆炸就能让模型动作输出变成 NaN。

保存策略上,强烈建议每 500 步存一次 checkpoint,不要只保留最后一个。AutoBio 的最终效果并不是“最后一个 checkpoint 最好”,在 2500 步和 4500 步的 checkpoint 都可能各有优势。后面评测阶段可以并行比较多个 checkpoint,留下成功率最高的版本。

3.3 评测协议与“90%”是怎么统计出来的

评测是整篇笔记里最容易被低估的部分。如果评测口径设计得宽松,一个 50% 成功率的模型可能被吹成 90%;反之,一个真实 90% 的模型也可能因为环境扰动被压到 60%。

我采用的评测协议分三层:

  • 任务集:三个标准子任务,分别是“从培养皿 A 夹取转移至目标区域 B”“移液枪吸取 200µL 后注入孔板指定孔”“把耗材按指定顺序堆叠”。每个子任务在真实机械臂上重复 30 次,共 90 次尝试。
  • 环境扰动:每次尝试前,将目标物体的初始位置随机平移 ±1cm,旋转 ±5°,模拟人为摆放误差。不加这一条,评测出来的成功率会虚高,因为模型可以把固定位置背下来。
  • 成功判定:不只看末端是否到达目标区域,还要求夹爪状态、动作时序、接触力范围都满足预设条件。比如“移液枪吸取 200µL”必须实际完成吸液动作,而不是机械臂在孔板正上方停了就算成功。

最终 90 次的成功率是 81 次成功、9 次失败,合计 90%。失败样本集中在初始位置处于扰动边界的情况,模型对极端的角落位置处理能力还有提升空间。值得注意的是,早期我做过一次只有 50% 成功率的中途评测,那个时间点对应的 checkpoint 大约在 3000 步;到了 4500 步的 checkpoint,成功率才稳定在 90%。所以如果你想复现这个数字,别在 2500 步就下结论,多留几版 checkpoint 对比。

4. 复现路上最值得避开的几个坑

4.1 显存 OOM 与数值精度:shard 加载和 merge 操作都有讲究

训练阶段常见的 OOM 原因是把模型全部加载到 GPU,再叠加梯度检查点不足。我的经验是:先量化到 fp16,开启梯度检查点,再考虑减小 batch size。如果三步都做了还是 OOM,就要检查数据加载器是不是把图像缓存给吃掉了——实验室数据经常存在本地磁盘,DataLoader 多进程开太多反而会让 CPU 内存先爆。

还有一个很容易忽略的坑是 LoRA 权重合并。训练完的适配器不能直接用于推理,要先合并回基础模型。合并时要注意数值精度:在 fp16 下直接做矩阵加法,长序列自回归时误差会累积,输出的动作会漂移。正确做法是先把权重转成 fp32,在 fp32 下完成w = w_base + w_lora * (alpha / rank)的合并,再转回 fp16 使用。这个细节看起来微不足道,但它可能是你从 85% 到 90% 之间就差的那 5%。

4.2 动作归一化与坐标系抖动:0% 到 30% 的分水岭

从一个完全fail的基线到能跑通 30% 左右成功率,我认为最关键的不是把 LoRA 结构搭好,而是动作归一化的设计。原始七维动作里,位置增量通常在 ±0.2m 范围内,而姿态角增量在 ±0.5 rad 范围,夹爪开合则是 0 或 1。如果不归一化,LoRA 训练出来的低秩矩阵会把大部分容量消耗在数值量级更大的位置维度上,姿态和夹爪指令几乎学不到。

我的处理是:对每个维度分别做统计归一化,把训练集的均值方差映射到 [-1, 1],然后在推理阶段再反归一化回物理单位。这一步做完,成功率直接跨过 30% 的坎。

另一个让成功率卡在低位的是坐标系抖动。我们的实验室里相机支架偶尔会被人碰一下,如果训练时相机位姿和评测时不一致,模型的视觉特征就和动作分布错位。解决方法是把相机和机械臂底座相对位置固定,每次实验前用标定板确认一次相机外参。这件事看起来和模型无关,但实际上一旦坐标系偏移超过 2cm,成功率就会掉到原先的一半以下。

4.3 成功率虚高:别让一个隐藏 Bug 骗了自己

最后这一点值得单独拿出来说:评测系统的“成功判定逻辑”也可能是错误来源。早期我的判定只检查末端执行器是否进入目标区域,结果有几次失败案例被误判为成功——机械臂先推翻了培养皿,然后末端进入区域,就被判成“成功”了。直到我看录像才发现问题。

后来我把判定升级为“全程轨迹监控 + 末端状态联合判定”:

  • 检查夹爪闭合时序是否和任务阶段匹配
  • 检查机械臂移动路径是否发生异常碰撞
  • 检查任务结束时目标物体是否处于预期位置

加上这几条后,评测指标从虚高的 92% 回落到真实的 90% 左右。我个人觉得,这个 2% 的差距不是数字问题,而是评估体系是否可信的问题。如果你的实验里看到一个“违背直觉的暴涨”,我的建议是先去盯着录像逐条看失败样本,不要急着庆祝。

最后分享一个我实践下来的小经验

如果你也想在自己的实验室里复现 AutoBio 这条路,我建议把时间多花在“定义失败”和“建立评测巡检流程”上,而不是纠结 LoRA 具体参数。5000 步的训练和 RTX 5090 的算力都只是工具,真正让成功率从 0 到 90 的,是把语义错位、动作空间、评测口径这条链路完整打通。每 500 步留一个 checkpoint 这个习惯,让我在后期对比时省了至少一整天的时间——有一次最优权重不是一个 final checkpoint,而是提前 500 步存下来的版本。这个细节如果一开始没做,后面要补就来不及了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询