简介视觉语言动作模型VLA是当前具身智能领域的重要技术方向通过融合视觉信息与语言指令驱动机器人和自动驾驶系统完成场景感知、任务规划与动作执行。代码包面向AI开发者、机器人研究者及自动驾驶从业者提供一套轻量级的VLA应用参考实现便于快速理解视觉编码器、语言模型与策略模块之间的衔接以及多模态融合和端到端训练的基本思路。压缩包共3个文件主要包含HTML演示页面、.gitignore版本管理配置和.inscode在线编码环境配置整体仅7KB结构简洁适合入门研读、运行和二次扩展。目前已有378人学习读者可在浏览器中直接查看页面效果对照描述中的技术脉络梳理VLA的完整流程并以该代码为基础快速搭建自己的演示原型为后续在真实机器人或自动驾驶场景中的部署与调参提供起点。1. VLA 到底是什么一条把视觉、语言直接映射成机械臂动作的路线有个做自动化集成的朋友找我排查产线上的视觉分拣问题。他们的方案是“目标检测 机械臂规划”相机识别物料类别和位置再交给运动规划器算轨迹。每个新产品上线工程师要重新标数据、调检测阈值、改抓取位姿一套流程下来两到三周。我问他为什么不试试 VLA他说听过这个词但一直觉得是学术 Demo不敢往产线上放。后来我们花了两周用一条端到端的视觉语言动作模型把“识别、决策、控制”三个环节合成一步新任务只改一句自然语言指令不再改代码。VLAVision-Language-Action Model做的事情就是把相机画面和一条操作指令同时送进同一个模型模型直接输出机械臂动作——末端位姿增量、关节角或者夹爪开合量。它和传统方案的本质区别不是“精度更好”而是“泛化方式变了”传统方案用规则和坐标传递语义VLA 用语言做中间层。适合读这篇笔记的人是手里有示教数据、想用一套模型覆盖多种操作任务、又不想每次重新写视觉规则的团队。带 [代码] 就是因为下面所有步骤都能直接落到脚本里跑不是停留在概念讲解。2. VLA 模型结构拆解VLM 底座、动作头与动作表示怎么选VLA 不是什么全新的网络结构它是把两条成熟技术路线拼在一起一条是视觉语言模型VLM一条是动作解码器。搞清楚这条拼接线很多配置上的困惑会瞬间解开。2.1 为什么不是“视觉模型 规划器”差在泛化方式传统操作流程里感知和规划是两套独立系统。感知系统输出物体的类别、坐标、姿态规划系统把这些结构化信息翻译成轨迹。这套流程在固定场景里很稳但一旦任务变化感知接口和规划规则都要跟着改。比如原来抓“红色方块”现在要“把红色方块放到左侧托盘”传统方案需要额外实现托盘定位和放置点计算这些逻辑散落在不同模块里。VLA 把整个映射过程压进一个网络。输入是图像和文本输出是动作中间的“该看哪里、该往哪动”全部由模型自己决定。训练数据是“图像 指令 动作”的三元组模型学到的是语义层面的行为映射而不是某个物体的坐标。所以换新任务时指令变了输出就跟着变。代价是模型变大了数据要求变高了推理变慢了。选择 VLA 的前提是你能接受 7B 级模型带来的部署成本换来的是任务迁移成本几乎归零。2.2 三个关键组件视觉编码器、VLM 底座、动作头一个可落地的 VLA 通常由三部分组成。首先是视觉编码器负责把原始图像变成视觉 token。常见实现是用 CLIP 风格的编码器输入分辨率一般取 224 到 512 之间。分辨率越高细节保留越好但 token 数量翻倍推理时间和显存占用都涨得很快。其次是 VLM 底座负责融合视觉和语言信息。现在主流方案是拿 7B 量级的开源语言模型做底座把图像 token 和文本 token 拼在同一个序列里让模型在自回归过程中同时“看到”画面和指令。第三部分是动作头也是最容易被忽略的部分。VLM 底座输出的是语义向量不是动作。动作头负责把最后一层隐含状态解码成具体的动作数值。这里的选择会直接影响训练难度和最终控制精度。我在最开始做 VLA 实验时把大部分时间花在调视觉编码器和底座上后来发现动作头选型错了损失函数怎么调都收不住。建议先固定视觉语言部分把动作头确定下来再谈其他。2.3 动作表示连续回归与离散步进 token 的取舍动作头怎么做业界有两条路线。第一条是离散化把每个动作维度切分成固定数量的 bin例如把末端位移映射到 256 个离散区间模型用分类损失去预测每个区间的编号。这条路线的好处是能直接复用语言模型的交叉熵训练方式实现简单稳定性高。代价是精度受 bin 数量限制而且动作维数多了之后离散空间会迅速膨胀。对于夹爪开合这种两态输出离散化很自然对于连续位姿离散化会让动作看起来有“阶梯感”。第二条是连续回归动作头输出动作分布的均值有时也预测方差配合 L2 或者负对数似然损失。精度上比离散化好但训练容易不稳定尤其当数据里存在少量异常轨迹时损失会被极端值拉爆。近两年常见的做法是引入扩散模型做动作头把“从噪声中逐步去噪得到动作”当成生成任务。扩散头效果更稳对多模态动作分布同一个指令存在多种合理走法表达能力更强缺点是训练步数和推理时延都相应增加。我的建议是数据量少于 2 万条先走离散化数据超过 5 万条且任务包含多种操作风格直接上扩散头。2.4 LoRA 微调为什么不能冻结 VLM 底座拿到了预训练的 VLM 底座之后一个很自然的想法是冻结它只训练动作头。这样做训练成本低但效果往往不好。原因是底座里的语义表征并不是为动作任务优化的冻结后模型会“看得懂指令但不知道手往哪放”。会损失一部分语言理解能力也会损失对细微视觉差异的敏感度。常见做法是用 LoRA 做参数高效微调只更新一部分低秩矩阵既保住底座能力又让语义表征向动作任务对齐。用 LoRA 时有两个坑必须注意。第一如果只把 LoRA 挂在语言部分的注意力矩阵上视觉编码器仍然处于冻结状态模型对光照、物体纹理的适应能力会偏弱后面我会在避坑章节具体展开。第二LoRA 的 rank 不是越大越好。我在一个抓取项目里把 rank 从 16 调到 64效果确实涨了一点但显存占用涨了将近一倍训练速度也明显变慢最终在 rank48 左右达到性能拐点。参数选择上没有统一最优解但按 rank 3264、alpha 取 rank 的 2 倍起步是一个不会翻车的起始区间。3. 最小可复现 VLA 训练数据组织、训练脚本与四个关键参数没有开源权重做底座就不要从零训练 VLA那是另一条成本完全不同的路线。我先讲数据怎么组织再给一份能直接改路径运行的训练脚本最后把四个必调参数拆开讲。3.1 训练数据长什么样图像、指令、动作三元组VLA 训练集的最小单元是一条“指令 画面 动作”记录。画面通常是单个视角的 RGB 图指令是自然语言句子动作是一个浮点数组。动作维度取决于机器人构型常见的六轴机械臂加夹爪动作是 7 维——末端 x/y/z 平移增量、绕 x/y/z 轴的旋转增量、夹爪开合量。如果只做平面抓取动作可以降到 4 维x/y/z 加夹爪如果做移动抓取还要加上底盘线速度和角速度。数据里有几点必须统一。第一是坐标系所有动作必须定义在同一个基准坐标系下不能一部分是基座坐标、一部分是相机坐标。第二是单位平移用毫米还是米旋转用弧度还是角度一旦混了loss 会表现出“先降后横盘”的怪状。第三是采样频率动作标签的时间间隔必须和真机控制频率一致比如示教时 10Hz 采一条训练时模型输出的动作就是按 10Hz 的执行周期来理解的。数据采集多花一周时间统一这些元信息后面训练和部署会省两周不止。3.2 数据加载与预处理脚本先统一图像和动作空间import torch from datasets import load_dataset from torch.utils.data import Dataset ACTION_DIM 7 # 末端6自由度量 夹爪开合 class VLADataset(Dataset): def __init__(self, data_path, image_size224): # jsonl: 每行 {instruction: ..., image_path: ..., action: [...]} self.data load_dataset(json, data_filesdata_path)[train] self.image_size image_size def __len__(self): return len(self.data) def __getitem__(self, idx): item self.data[idx] # 1) 统一图像格式RGB 固定尺寸避免换相机后翻车 image item[image].convert(RGB).resize((self.image_size, self.image_size)) # 2) 动作归一化到 [-1, 1]训练更稳推理时再反算回真实量程 action torch.tensor(item[action], dtypetorch.float32) action_min torch.tensor(item[action_min], dtypetorch.float32) action_max torch.tensor(item[action_max], dtypetorch.float32) action_norm 2.0 * (action - action_min) / (action_max - action_min) - 1.0 return {image: image, instruction: item[instruction], action: action_norm}这段代码做了三件事把 PIL 图像统一成 RGB 并缩放到固定尺寸将动作向量归一化到 [-1, 1] 区间返回与训练框架对接的标准字典。归一化这一步不能省未归一化的动作会让模型在训练初期把大量容量浪费在“适应数值尺度”上表现为 loss 掉得很慢。动作的 min/max 值必须来自全部训练数据而不是单条数据并且要在部署时把同一组 min/max 存下来推理端反归一化要用它。很多人训练和部署各存了一份归一化参数结果部署出来的动作幅度差两倍这类问题我会在避坑章节再强调一次。3.3 LoRA 微调训练脚本可直接替换路径运行from transformers import Trainer, TrainingArguments, AutoProcessor from peft import LoraConfig, get_peft_model # 以 7B 级开源 VLM 底座为例实际使用时替换为你的底座路径 model_path vlm-backbone-7b # 1) 加载底座模型 LoRA 配置 model AutoModelForVision2Seq.from_pretrained(model_path) lora_config LoraConfig( r48, lora_alpha96, target_modules[q_proj, k_proj, v_proj, o_proj], lora_dropout0.05, ) model get_peft_model(model, lora_config) # 2) 训练参数8卡 batch 总大小 8*432 training_args TrainingArguments( output_dir./vla-runs, per_device_train_batch_size8, gradient_accumulation_steps4, learning_rate1e-4, num_train_epochs3, warmup_ratio0.03, logging_steps50, save_steps500, bf16True, ) trainer Trainer( modelmodel, argstraining_args, train_datasetVLADataset(data/train.jsonl), ) trainer.train()核心逻辑是两条AutoModelForVision2Seq负责加载视觉语言底座get_peft_model套上 LoRA。训练时只更新 LoRA 参数和动作头底座的原始权重不动。这个脚本默认把 LoRA 挂在注意力层上如果你的底座用了不同的模块命名需要先打印模型结构把target_modules改对否则 LoRA 根本没生效训练半天 loss 纹丝不动。训练参数里有三个最容易影响结果的learning_rate从 1e-4 起步如果 loss 前两百步不降降到 3e-5per_device_train_batch_size受显存限制不够时用gradient_accumulation_steps凑总 batch总 batch 太小会让 VLA 训练很不稳建议总 batch 保持在 32 以上bf16True只在支持 BF16 的 GPU 上开老卡改成fp16True。最后是save_steps我习惯设 500 步存一次 checkpoint因为 VLA 训练经常在第 1000 步附近出现 loss 突然跳变多几个存档点才有后悔药吃。3.4 四个关键参数lr、rank、图像分辨率、动作损失权重第一个是学习率。VLA 是在底座之上做微调学习率太高会把底座原有语义冲坏太低动作头又学不动。一个稳妥做法是给动作头单独设更高的学习率给 LoRA 层用低学习率两个学习率差 3~5 倍。第二个是 LoRA rank我在 2.4 里已经提过rank 从 32 起步数据量大就升到 64显存不够就降到 16 并接受一点性能损失。第三个是图像分辨率。训练时用 224 还是 384直接决定模型能不能看清小目标。之前做某个插座插拔项目224 分辨率下模型根本分不清插孔朝向换到 384 后成功率直接翻倍。但分辨率从 224 提高到 384视觉 token 数量差不多是原来的三倍训练时间和显存都会明显上涨。第四个是动作损失权重。如果整体 loss 里语言建模损失占主导动作部分会被“淹没”模型会变成“能看懂指令但动作幅度很小”的状态。常见做法是把动作损失权重单独调到 1.5~2.5保持语言损失在辅助位置。4. VLA 部署到真机量化、延迟预算与推理服务训练跑通只是第一步VLA 模型从 checkpoint 变成产线上能用的控制器中间还隔着推理延迟、显存占用和接口稳定性三道坎。这一章我按实际部署顺序来讲先定延迟预算再决定量化和硬件最后写推理服务。4.1 先算延迟预算再选硬件机械臂控制周期决定了 VLA 推理必须做多快。常见工业机械臂的控制频率在 10Hz 到 30Hz 之间但 VLA 模型的推理很少能跑进这个周期。一个常见的部署方案是异步执行相机以固定频率采集图像模型持续推理最新一帧画面产生的动作直接覆盖上一帧动作而不是等待当前动作执行完。这样机械臂的执行频率是固定的模型只是不断“刷新”目标动作整体表现取决于模型平均推理延迟而不是单次上限。先根据你的算力量级定一个延迟预算表再决定要不要上量化和裁剪。下面是我在几个项目里积累的典型参考值部署形态硬件类型量化位宽典型推理延迟工作站中端单卡 GPUFP1630~80ms工作站中端单卡 GPUINT820~50ms边缘盒嵌入式 GPUINT8/INT4100~250ms纯 CPU 工控机普通多核INT4500ms 以上这张表的意义是帮你先做乘法如果机械臂控制频率是 10Hz即每 100ms 需要一次新动作那嵌入式边缘盒勉强够如果是 20Hz就必须上工作站 GPU。很多人项目翻车不是因为模型精度不够而是部署前没算延迟预算等到模型上了真机才发现跑不满控制频率整个系统一抖一抖的。4.2 量化方案压视觉语言部分动作头尽量保持原精度7B 模型正常人手里都没有无限显存量化基本是必选项。常见做法是把底座权重量化到 INT8 或者 INT4动作头保持更高精度。原因是动作头对数值误差更敏感它输出的动作数值直接进机械臂控制器哪怕差 2% 都会表现为末端位置的毫米级偏移在精密装配场景里这是致命的。量化用现成库就可以了加载时传一个量化配置模型权重在内存里以低精度存储计算时反量化回浮点。INT8 通常不掉点INT4 会有一点性能损失但对操作类任务往往在可接受范围内。如果你发现 INT4 之后动作明显抖动有一个折中方案底座用 INT4动作头单独保持 FP16。这个混合精度方案在不少开源部署框架里都有支持效果比整体 INT4 好很多部署成本也没涨太多。4.3 一个能直接改的推理服务骨架import torch from transformers import AutoModelForVision2Seq, AutoProcessor class VLARunner: def __init__(self, ckpt_path, action_min, action_max, quantize_bits4): # 加载处理器和模型量化底座权重 self.processor AutoProcessor.from_pretrained(ckpt_path) if quantize_bits 4: model AutoModelForVision2Seq.from_pretrained( ckpt_path, load_in_4bitTrue, device_mapauto ) else: model AutoModelForVision2Seq.from_pretrained(ckpt_path, device_mapauto) self.model model # 训练时用过的归一化参数推理端必须和训练端完全一致 self.action_min torch.tensor(action_min, dtypetorch.float32) self.action_max torch.tensor(action_max, dtypetorch.float32) torch.inference_mode() def predict(self, frame, instruction): # 图像预处理与训练保持一致RGB 224x224 inputs self.processor( textinstruction, imagesframe.convert(RGB), return_tensorspt, ).to(self.model.device) # 模型输出的是归一化动作反算回真实量程 action_norm self.model.generate(**inputs, max_new_tokensACTION_DIM) action (action_norm 1) / 2 * (self.action_max - self.action_min) self.action_min return action.cpu().numpy()这段代码的骨架可以直接抄进项目里但有几个点要格外注意。self.processor的文本和图像预处理参数必须和训练时一致否则模型看到的是“另一种格式的画面”表现会莫名下降。max_new_tokensACTION_DIM要等于你的动作维度这个值设大了模型会继续生成无意义内容设小了动作被截断。最后action_min和action_max必须是从训练数据统计来的同一组值不能用推理时手动指定的范围。延迟优化方面有一个低成本技巧值得先试把推理输入图像的动态尺寸改成固定尺寸。有些开源实现默认按最长边 512 缩放不同帧的 token 数量不同GPU 每次都要重新构图延迟波动很大。固定到 224 或 320 之后延迟能减少 20% 左右而且波动变小。另外torch.inference_mode()要包住整个推理路径它能避免 PyTorch 在推理时保存中间变量用于自动求导这部分能省下不少显存和计算。5. VLA 部署避坑五个高频故障的现象、原因与解法VLA 项目的问题往往不在模型结构而在数据管线、部署环境和接口对接。下面五条是我在实际项目中反复遇到的每条都按现象、原因、解决的顺序写方便你对照排查。5.1 换了一台相机模型动作整体漂移现象同一个模型训练时用的相机和部署时用的相机不是同一型号结果机械臂的动作轨迹整体偏移抓取位置明显偏离物体中心。模型在旧相机数据上测试一切正常。原因VLA 对图像低级特征的敏感度超出预期。换相机带来的变化不只是分辨率还有色彩空间、白平衡、镜头畸变、自动曝光曲线。视觉编码器提取的特征分布发生变化动作输出自然跟着漂移。解决推理端和训练端统一图像预处理是最低要求但往往还不够。我后来总结了一条可复用的习惯在训练数据里混入多种相机的采集画面或者在推理端对图像叠加轻微的颜色扰动亮度 ±10%、对比度 ±5%能显著提升跨相机的鲁棒性。如果项目允许尽量用训练数据同款相机做部署这是最省事的话。5.2 动作数据混用关节角和末端位姿loss 降不下去现象训练集一部分数据来自遥操作示教存的是关节角另一部分来自规划器生成存的是末端位姿。混合训练后 loss 一直横盘或者不断出现尖峰。原因关节角和末端位姿的量纲、数值范围完全不同。关节角在 0~2π 之间末端平移可能到几百毫米旋转又是弧度三种数值混在一起模型相当于在同一个输出空间里学两种完全不同的语义很难收敛。解决统一动作空间。最稳妥的是全部转成末端位姿因为规划器和大多数真机控制接口都用这个格式。统一后重新统计 action_min 和 action_max再跑训练。如果某些数据源确实只能提供关节角需要先用运动学正解转一遍而不是留着一个混合空间让模型自己学。5.3 推理延迟忽高忽低机械臂高频抖动现象模型平均推理延迟在 80ms 左右但偶尔会跳到 300ms。机械臂执行时出现高频抖动看起来像控制不稳定调 PID 参数也没用。原因延迟波动造成的动作刷新间隔不稳定比“慢但稳定”更影响控制稳定性。产生波动的源头可能是系统里其他进程抢占 GPU、显存不足导致的动态换页或者推理输入尺寸不固定导致的构图耗时差异。解决先固定输入尺寸消除构图波动。再给推理服务设置延迟告警记录推理耗时分布当 P95 延迟超过预算的 1.5 倍时主动告警。如果多次出现尖峰检查显存占用和并行任务优先保证推理进程独占 GPU。也可以在服务里加一个“过期动作丢弃”逻辑如果新动作推理时间超过控制周期就沿用上一帧动作而不是插入一个过期的中间动作。5.4 视觉编码器冻结模型看不见小目标现象抓取大物件时成功率不错换成小尺寸目标比如螺丝、小插头时成功率骤降。训练 loss 正常但真机能感觉到“眼神不好”。原因4.3 节里我用 LoRA 只挂了注意力层视觉编码器处于冻结状态。底座预训练时的视觉特征是为图文理解优化的对细小物体的边缘、纹理表达能力不够而 LoRA 没覆盖到这一层。解决把视觉编码器也纳入微调范围。一种做法是单独为视觉层配置更低的 LoRA rank 和更小的学习率让它只做轻微调整另一种做法是解冻视觉编码器的最后几层配合低学习率训练。两个方法我都试过效果接近但前者显存开销更小。注意视觉编码器学习率要设在底座 LoRA 的 1/5 以下否则容易把预训练视觉特征冲掉表现为训练集上动作稳了换场景效果反而更差。5.5 轨迹数据被随机打散连续动作“接不上”现象训练 loss 正常模型单步动作预测准确率也高但真机上连续执行一个多步任务时动作在步骤切换处断档比如从“靠近物体”到“抓取”之间会停顿甚至倒退。原因训练时把同一条示教轨迹里的连续帧当成独立样本随机洗牌了。模型在训练时看到的画面和动作是“跳着”的没有形成连续运动的上下文输出动作自然只针对单帧不针对时序。解决按轨迹分组训练。同一条示教轨迹的样本在同一个 batch 内保持时序顺序不同轨迹之间才允许随机洗牌。实现上可以在数据集的样本里额外存一个traj_id字段采样时按traj_id分组组内顺序不打乱。这个问题在视觉动作模型里很常见但很少被人主动提等真机连续任务跑不通时才暴露出来。6. 进阶先在仿真基准上打一次分再谈真机迁移VLA 项目做到后面你会发现最贵的不是训练而是“评估”。真机试错一次要花几分钟还有安全风险根本不可能频繁迭代。我现在的习惯是所有改动先在仿真基准上打分分数稳定之后再挑其中几条任务上真机验证。仿真不能完全替代真机但能把迭代周期从一天一次压缩到一小时一次。具体做法是找一个开源的机器人操作仿真包里面固定了 100 条左右的操作任务每条任务对应一个自然语言指令和初始场景配置。模型在仿真里跑完所有任务统计一个“指令成功率”作为统一指标。训练过程中每隔几百步就评估一次看曲线走势。在这个阶段我通常还会同时记录两个诊断指标平均步数越少越好失败模式集中在哪几类指令。如果所有失败都集中在“夹取细长物体”这类任务上说明图像分辨率不够如果集中在“把物体放到指定位置”上说明动作回归精度不够优先调动作头而不是盲目加数据。真机迁移前有一组对齐清单值得固化下来每次训完新模型先自查一遍图像尺寸和色彩空间是否与训练一致动作归一化参数是否用了同一组机械臂坐标基准是否和训练动作定义一致控制频率是否符合推理延迟预算。这四项里任何一项不一致模型再准都是白搭。我做过一个项目仿真成功率到 87%上真机只有 40%排查到最后是部署端的图像格式从 RGB 变成了 BGR一次颜色通道翻转直接掉了一半分。最后一个我亲测有效的技巧在真机首次运行前把模型在验证集上的“动作输出分布”画出来和训练集动作分布叠在一张图上。如果输出分布明显偏离训练分布比如动作幅度整体偏小说明模型没有真正学会动作空间只是记住了“图像到指令”的表面对应。这时候直接上真机就是撞运气。这个检查只需要十分钟能帮你省下至少一个下午的现场调试。我自己也翻过好几次车每次都是换相机、改频率、动归一化参数这三件事里栽的。后来把这些检查项做成一个固定流程迁移新任务时先跑一遍再谈调优。希望帮到你。本文还有配套的精品资源点击获取