ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

In-Context VLA:为多模态大模型装上“手脚”,实现从感知到行动的跨越

In-Context VLA:为多模态大模型装上“手脚”,实现从感知到行动的跨越 1. 项目概述从“看图说话”到“看图做事”的范式跃迁最近在跟团队复盘多模态大模型Multimodal Large Language Models, MLLMs的落地路径时我们反复讨论一个核心痛点现有的视觉-语言模型Vision-Language Models, VLMs能力边界在哪里它们能精准描述一张图片甚至能进行复杂的视觉推理但当你告诉它“帮我把桌上那杯水拿过来”时它只能生成一段完美的文字指令然后……就没有然后了。这中间的鸿沟就是“感知”与“行动”的割裂。而“In-Context VLA”这个方向正是试图用一套精巧的工程化方案为现有的视觉-语言大模型装上“手”和“脚”让它们不仅能看懂、能说更能直接驱动智能体Agent去执行任务。简单来说In-Context VLA 不是一个从零训练的全新模型而是一种赋能框架。它的核心思想是我们不重新造轮子训练一个昂贵的、数据饥渴的 Vision-Language-Action 模型而是基于一个已经很强的视觉-语言基座模型比如 GPT-4V, LLaVA, Qwen-VL通过一种称为“上下文后训练”In-Context Post-Training的技术让它学会在特定上下文中将视觉理解和语言指令直接映射成可执行的行动序列或工具调用。这就像给一个博学的军师VLM配上了一本万能“工具使用手册”和一套快速学习新任务的方法让它能立刻转型为一位能征善战的将军VLA。为什么这个方向值得深挖从我们实际做项目的经验看至少有三大驱动力。第一是成本与效率。从头训练一个端到端的 VLA 模型需要海量的图像指令动作三元组数据这类数据标注成本极高且动作空间如机械臂的关节角度、游戏的操作指令复杂多样泛化性难保证。In-Context 方法复用现有强大 VLM 的视觉和语言能力只需相对少量的、针对“行动输出”格式的调优数据性价比极高。第二是灵活性。它本质上让模型学会了“使用工具”。工具可以是代码解释器、API 调用、机器人控制指令甚至是调用另一个专用模型。这种模块化设计使得系统能力可以像搭积木一样扩展而无需动基座模型。第三是涌现的智能体Agent能力。当模型能可靠地输出结构化动作后它自然就成为了一个规划与执行模块的核心能够处理更复杂的、多步骤的任务实现真正的自主智能体。接下来我将结合我们对相关论文的复现和实践经验深入拆解 In-Context VLA 的两大核心技术支柱In-Context Post-Training和Agentic Tool Use并分享一套从零搭建原型系统的实操指南与避坑心得。2. 核心架构与设计思路拆解要理解 In-Context VLA必须跳出“单一模型”的思维将其视为一个系统。这个系统的输入是视觉观察图像/视频帧和自然语言指令输出是具体的动作。其设计精髓在于“分而治之”和“上下文学习”。2.1 基座模型的选择与考量一切始于选择一个合适的视觉-语言基座模型。这不是一个可以随意替换的组件它的能力直接决定了天花板上限。我们的选型主要围绕几个维度视觉编码器质量模型如何理解图片是简单的网格特征还是基于 Vision Transformer (ViT) 的细粒度特征对于需要精确定位如“点击左上角的按钮”或理解复杂场景的任务强大的视觉编码器是关键。例如CLIP-ViT-L/14 是许多开源 VLM 的标配而商用 API 如 GPT-4V 则使用了更强大的私有视觉编码器。语言模型的能力这是逻辑推理、任务分解和工具调用指令生成的核心。我们需要模型有强大的上下文理解能力、指令跟随能力和结构化输出能力如 JSON。因此Llama 3、Qwen 2.5 等最新一代的 7B/8B 参数模型因其在指令微调和推理上的优秀表现成为了热门基座选择。更大的模型如 70B当然更好但推理成本是必须考虑的工程约束。多模态对齐效果视觉特征和语言特征是否在同一个语义空间内对齐良好这决定了模型是否能准确地将视觉信息用语言表达出来进而指导行动。通常在高质量图文对如 LAION上预训练过的模型对齐效果更好。开源与可控性对于研究和深度定制开源模型如 LLaVA-NeXT, CogVLM2是首选。它们允许我们深入模型内部修改输入输出格式并进行针对性的后训练。在我们的实践中对于原型验证我们选择了LLaVA-NeXT (基于 Vicuna-7B)。原因在于其生态成熟有丰富的微调教程和社区支持且 7B 参数规模在消费级 GPU如 RTX 4090上即可进行高效的参数高效微调PEFT。2.2 In-Context Post-Training 的本质教模型“答题格式”这是整个框架的第一个魔法点。传统的微调是教模型“新的知识或技能”而 In-Context Post-Training 更像是教模型“在新的考试中如何按照给定的格式答题”。核心思想我们准备一系列“示例对”Demonstrations。每个示例对包括查询Query一张图片 一个语言指令。上下文Context一组固定的、预先定义好的“工具描述”或“动作模板”。答案Answer模型应该输出的、符合上下文的动作序列。这个答案必须严格遵循某个模板比如一个 JSON 对象{“action”: “click”, “coordinates”: [x, y]}或一段可解析的 Python 代码。然后我们不是让模型死记硬背这些示例而是通过轻量级微调通常是 LoRA让模型学会一个模式当看到类似的视觉-语言查询并且系统提示Context中给出了可用的工具格式时它应该生成类似格式的输出。举个例子假设我们要教模型操作一个图形界面GUI。传统VLM微调给模型看成千上万个截图 “点击登录按钮”的配对希望它学会“登录按钮”长什么样以及“点击”是什么意思。In-Context Post-Training我们给模型一个“工具说明书”上下文“可用动作1. CLICK(x, y): 在坐标(x,y)处点击。2. TYPE(text): 输入文本。3. PRESS(key): 按下键盘键。”然后我们只提供少量几十到几百个示例比如登录界面截图 “请登录”CLICK(320, 150)。模型通过微调学习的不是“登录”这个概念而是“当指令涉及‘登录’且上下文有 CLICK 工具时我应该输出 CLICK 指令并推理出按钮坐标”。实操心得这里的“少量”示例数据构造是关键。它们必须覆盖你想要模型学会的各种输出格式而不是各种任务场景。任务场景的泛化能力由基座 VLM 的视觉-语言能力承担。你的数据要确保模型学会“调用工具 A 时应输出 JSON 格式 A调用工具 B 时应输出代码块”。2.3 Agentic Tool Use 的设计将模型转化为调度中心模型学会了输出结构化动作后下一步就是让它真正“用”起来。这就是 Agentic Tool Use 部分。此时VLM 扮演的是“大脑”或“规划器”的角色。一个典型的智能体工作流如下感知VLM 接收当前视觉观察屏幕截图、摄像头画面和用户指令。规划与工具调用VLM 结合内置的“工具列表”以系统提示词或上下文示例的形式提供决定下一步该调用哪个工具并生成准确的调用参数。执行一个独立的执行器Executor模块解析 VLM 的输出。如果输出是CLICK(320,150)执行器就调用自动化库如 PyAutoGUI执行点击如果输出是{“api”: “get_weather”, “params”: {“city”: “Beijing”}}执行器就调用相应的天气 API。观察结果执行动作后环境状态改变。可能需要获取新的视觉观察如点击后跳转的新页面或获取 API 返回的文本结果。循环将新的观察可能是图像文本反馈给 VLM进行下一步决策直到任务完成或达到步骤限制。这个设计的美妙之处在于解耦。VLM 只负责高级的感知和规划它不需要知道 PyAutoGUI 的具体函数签名或 API 的网络地址。它只需要遵循约定的格式“说话”。执行器是鲁棒的、确定性的程序。任何工具的更新或新增只需修改执行器和提供给 VLM 的“工具描述”上下文而无需重新训练模型。3. 从零搭建 In-Context VLA 原型系统理论说得再多不如动手搭一个。下面我将以“创建一个能自动化操作桌面计算器应用的智能体”为例展示一个最小可行系统MVP的构建全过程。这个任务直观且易于验证让模型看着计算器截图听从“计算 123 乘以 456”这样的指令并自动点击按钮完成计算。3.1 环境准备与基座模型部署首先搭建基础环境。我们使用 Python并假设你有一张至少 16GB 显存的 GPU如 RTX 4080/4090。# 创建环境 conda create -n invla python3.10 -y conda activate invla # 安装核心依赖 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 根据你的CUDA版本调整 pip install transformers accelerate peft bitsandbytes pip install pillow opencv-python # 用于后续的演示和数据收集 pip install pyautogui pyscreenshot接下来加载基座模型。我们使用 LLaVA-NeXT 的 7B 版本并通过 4-bit 量化来减少显存占用。import torch from transformers import AutoProcessor, LlavaForConditionalGeneration from peft import LoraConfig, get_peft_model model_id llava-hf/llava-v1.6-mistral-7b-hf # 加载模型和处理器使用4-bit量化 model LlavaForConditionalGeneration.from_pretrained( model_id, torch_dtypetorch.float16, low_cpu_mem_usageTrue, load_in_4bitTrue, # 4-bit量化是关键大幅降低显存 device_mapauto ) processor AutoProcessor.from_pretrained(model_id) # 配置LoRA为后续的In-Context Post-Training做准备 lora_config LoraConfig( r16, # LoRA秩 lora_alpha32, target_modules[q_proj, v_proj, k_proj, o_proj], # 通常注意力模块是有效的目标 lora_dropout0.1, biasnone, task_typeCAUSAL_LM ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 此时只有LoRA参数可训练占比通常1%3.2 构造 In-Context 训练数据这是最需要匠心的一步。我们需要制作一个微型数据集教模型如何将“计算 123*456”这样的指令转化为一系列点击坐标。步骤1定义工具动作格式。我们定义计算器智能体的动作空间为CLICK(x, y)。为了让模型更好理解我们将其包装成更自然的描述。我们设计一个“系统提示词”作为上下文的一部分你是一个桌面计算器控制智能体。你可以通过CLICK动作来操作计算器界面。 CLICK动作的格式是CLICK(x, y)其中x和y是屏幕上的整数坐标。 你看到的图像就是当前计算器界面的截图。 你的目标是根据用户指令输出一系列CLICK动作来完成计算。步骤2收集演示数据。我们需要手动或半自动收集一些截图指令动作序列三元组。例如截图Windows 计算器在初始状态的截图。用户指令“计算 123 乘以 456。”动作序列CLICK(100, 200) CLICK(150, 250) CLICK(100, 200) ...这里需要你实际打开计算器记录下点击数字‘1’、‘2’、‘3’、‘乘号’、‘4’、‘5’、‘6’、‘等号’的坐标。如何获取坐标可以用一个简单的 Python 脚本import pyautogui import time print(“将鼠标移动到目标按钮上5秒后获取坐标...”) time.sleep(5) x, y pyautogui.position() print(f”坐标: ({x}, {y})“)我们可能需要 20-50 个这样的三元组覆盖不同的数字、运算符和清零操作。数据越多越杂模型泛化能力越强但初期验证时10个左右精心设计的例子也能看到效果。步骤3格式化训练数据。将每个样本格式化为模型接受的对话格式。以 LLaVA 为例它通常接受[“image”, “USER: ...\nASSISTANT: ...”]的格式。我们需要把“系统提示词”和“示例”都整合进 USER 的输入中。一种有效的 In-Context Learning 格式是 Few-shot Prompting但对于 Post-Training我们将其扁平化。训练时一个样本的构造如下输入文本(由处理器处理):你是一个桌面计算器控制智能体。你可以通过CLICK动作来操作计算器界面。CLICK动作的格式是CLICK(x, y)其中x和y是屏幕上的整数坐标。你看到的图像就是当前计算器界面的截图。你的目标是根据用户指令输出一系列CLICK动作来完成计算。 USER: 这是当前计算器界面。请计算 123 乘以 456。 ASSISTANT:注意图像会通过处理器单独编码并与文本拼接。标签Target Labels:CLICK(100, 200) CLICK(150, 250) CLICK(200, 300) CLICK(250, 350) CLICK(100, 200) CLICK(150, 250) CLICK(200, 300) CLICK(400, 450)这里假设了点击 ‘1’, ‘2’, ‘3’, ‘*’, ‘4’, ‘5’, ‘6’, ‘’ 的坐标在训练时只有ASSISTANT:之后的部分即动作序列参与损失计算模型的任务是学会补全这部分。3.3 执行 In-Context Post-Training有了数据和模型就可以开始微调了。我们使用标准的因果语言模型Causal LM训练方式。from transformers import TrainingArguments, Trainer import torch from PIL import Image import os # 假设我们已经将数据整理成列表 train_dataset每个元素是字典 # { # ‘image’: PIL.Image对象, # ‘input_ids’: 输入文本的token id, # ‘labels’: 目标动作序列的token id (与input_ids等长非目标部分用-100填充) # } training_args TrainingArguments( output_dir“./llava-calculator-lora”, num_train_epochs3, # 对于少量数据可以稍多一些轮次 per_device_train_batch_size4, # 根据显存调整 gradient_accumulation_steps4, warmup_steps50, logging_steps10, save_steps100, learning_rate2e-4, # LoRA学习率可以稍高 fp16True, # 混合精度训练 remove_unused_columnsFalse, push_to_hubFalse, # 本地保存 ) trainer Trainer( modelmodel, argstraining_args, train_datasettrain_dataset, data_collatorcollate_fn, # 需要自定义一个collate_fn来处理图像和文本 ) trainer.train()关键细节与避坑指南图像处理LLaVA 处理器会默认将图像 resize 到 336x336。确保你的训练截图和推理时的截图分辨率、计算器窗口大小保持一致否则坐标会完全对不上。最好在数据收集和推理时固定计算器窗口的位置和大小。坐标归一化一个更鲁棒的做法不是使用绝对屏幕坐标而是使用相对于截图图像的归一化坐标。例如将截图宽高归一化到 [0, 1]模型输出CLICK(0.25, 0.6)再由执行器根据当前实际截图尺寸换算回绝对坐标。这能大大提高模型对不同分辨率、不同窗口大小的泛化能力。在我们的训练数据构造时就应该使用归一化坐标。损失计算确保labels中只有我们希望模型学习生成的部分即动作序列 tokens是有效的 token ID其他部分如图像 token、系统提示词、用户指令都应用-100填充这样它们在计算损失时会被忽略。过拟合由于数据量小很容易过拟合。监控训练损失和验证集如果有的话上的表现。如果模型在训练数据上完美但在新指令上胡言乱语就是过拟合了。可以增加数据多样性或使用更强的数据增强如对截图进行轻微的色偏、模糊处理。3.4 构建智能体执行循环训练完成后我们得到一个具有“计算器操作”能力的 VLM。现在需要构建智能体循环。import pyautogui import pyscreenshot as ImageGrab from PIL import Image import re class CalculatorAgent: def __init__(self, model, processor): self.model model self.processor processor self.system_prompt “你是一个桌面计算器控制智能体...同上” # 系统提示词 self.calculator_bbox (100, 100, 500, 600) # 预设计算器窗口区域 (left, top, right, bottom) def get_screenshot(self): 获取计算器区域截图 im ImageGrab.grab(bboxself.calculator_bbox) return im def parse_action(self, response_text): 从模型回复中解析出CLICK指令 # 使用正则表达式查找所有 CLICK(x, y) pattern r‘CLICK\((\d\.?\d*),\s*(\d\.?\d*)\)’ matches re.findall(pattern, response_text) actions [] for x_str, y_str in matches: # 假设我们训练时用了归一化坐标这里需要转换回绝对坐标 x_norm, y_norm float(x_str), float(y_str) width, height self.calculator_bbox[2] - self.calculator_bbox[0], self.calculator_bbox[3] - self.calculator_bbox[1] x_abs int(self.calculator_bbox[0] x_norm * width) y_abs int(self.calculator_bbox[1] y_norm * height) actions.append((“click”, x_abs, y_abs)) return actions def execute_actions(self, actions): 执行动作序列 for action_type, x, y in actions: if action_type “click”: pyautogui.click(x, y) pyautogui.sleep(0.5) # 每次点击后等待让计算器界面反应 def run(self, user_instruction): 智能体运行一次循环 # 1. 感知 image self.get_screenshot() # 2. 规划与生成 prompt f“{self.system_prompt}\n\nUSER: 这是当前计算器界面。{user_instruction}\nASSISTANT:” inputs processor(textprompt, imagesimage, return_tensors“pt”).to(model.device) # 生成动作序列 with torch.no_grad(): output_ids model.generate(**inputs, max_new_tokens100, do_sampleFalse) response processor.decode(output_ids[0], skip_special_tokensTrue) # 提取ASSISTANT的回复部分 assistant_response response.split(“ASSISTANT:”)[-1].strip() print(f“模型原始输出: {assistant_response}”) # 3. 解析与执行 actions self.parse_action(assistant_response) print(f“解析出的动作: {actions}”) if actions: self.execute_actions(actions) # 4. 可以在这里加入循环比如判断是否计算完成通过OCR读取结果但本例中我们假设一次生成所有动作。 # 使用智能体 agent CalculatorAgent(model, processor) agent.run(“请计算 789 加上 321。”)这个简单的智能体已经具备了感知-规划-执行的基本框架。在实际复杂任务中循环和状态判断会更加复杂。4. 核心挑战、优化策略与扩展方向搭建出原型只是第一步要让 In-Context VLA 稳定可靠地工作会遇到一系列工程和算法上的挑战。4.1 幻觉与输出格式不稳定这是小样本微调最常见的问题。模型可能会“胡编乱造”坐标或者输出不符合CLICK(x, y)格式的文本。解决方案强化格式训练在训练数据中严格统一输出格式。除了动作序列不要有任何其他废话。可以在 ASSISTANT 的回复开头就强制是动作。约束解码Constrained Decoding在模型生成时使用外部的文法或正则表达式来约束每一步生成的 token确保输出一定符合预定格式。Hugging Face 的transformers库对此支持有限但可以自己实现一个简单的后处理或使用像Guidance、Outlines这样的库来引导生成。后处理与重试机制像上面代码一样用正则表达式解析输出。如果解析失败可以将解析失败的信息如“你的输出格式不正确请严格按照 CLICK(x, y) 格式输出”作为反馈连同原图原指令再次输入模型进行重试。这模仿了人类纠正错误的过程。增加数据多样性在数据中故意加入一些“干扰项”比如指令是“描述这个界面”而目标是输出“无操作”。让模型学会区分何时该输出动作何时该进行普通对话。4.2 视觉 grounding 不准确模型可能理解了“点击等号按钮”但预测的坐标偏差很大。这源于视觉编码器对细粒度定位能力的不足或者训练数据中坐标标注的噪声。解决方案归一化坐标如前所述使用相对坐标而非绝对坐标。数据增强对训练截图进行随机的裁剪、缩放、平移同时对应调整坐标标签可以极大地提升模型对目标位置变化的鲁棒性。引入定位辅助对于需要高精度点击的任务可以不用模型直接回归坐标。而是让模型先输出一个描述如“点击左上角那个红色的圆形按钮”然后使用一个额外的、专门的物体检测模型或像素级特征匹配来找到该按钮的精确坐标。这样将“语义理解”和“精确定位”解耦通常更可靠。多模态提示在系统提示词中加入对界面元素的描述甚至用文字标注关键区域如“数字0-9位于下方区域运算符位于右侧”给模型更强的先验知识。4.3 复杂任务的规划与工具链调用我们的计算器例子是线性序列。真实任务如“在网上找到最便宜的某商品并加入购物车”需要复杂的规划、条件判断和多种工具浏览器导航、搜索、解析、点击的协作。解决方案分层规划让 VLM 先输出一个高级计划比如[“打开浏览器”, “访问电商网站”, “搜索商品”, “排序按价格”, “点击第一个商品”, “查看详情”, “点击加入购物车”]。然后每个高级步骤再触发一个子智能体或工具调用。这降低了单次生成的难度。ReAct 范式让模型以Thought: ... Action: ... Observation: ...的格式进行交互。Thought是模型的内部推理Action是工具调用Observation是工具返回的结果。这种格式通过微调很容易让模型学会并能处理长序列的决策。工具库管理维护一个结构化的工具库每个工具都有名称、描述、参数格式和示例。在每次调用模型时动态地将相关工具的描述插入上下文。这要求模型有较强的上下文理解能力。4.4 扩展到真实世界机器人控制从桌面自动化到物理机器人如机械臂挑战在于动作空间的连续性和安全性。实践路径动作抽象化不让模型直接输出关节角度连续、高维、危险而是输出高级技能Skill如PickUp(object“红色方块”),MoveTo(location“桌子B”)。这些技能由底层、安全的控制器来执行。仿真环境预训练在 MuJoCo、Isaac Sim 等仿真环境中大量生成视觉观察 语言指令 技能序列的数据对模型进行 In-Context Post-Training。仿真可以低成本获取大量数据并确保安全。真实世界微调将在仿真中学到的策略通过少量真实世界数据通过演示学习或人工遥控收集进行微调以弥补仿真与现实之间的差距Sim2Real。5. 评估、迭代与未来展望如何判断你的 In-Context VLA 智能体是否有效不能只看生成文本是否漂亮必须看任务完成率。构建评估流水线定义测试集收集一批未见过的图像 指令对并标注黄金标准动作序列或期望的最终状态如计算器显示的结果。自动化执行与验证编写脚本让智能体在测试集上自动运行。对于计算器可以 OCR 识别最终屏幕结果与预期结果对比。对于 GUI 自动化可以检测目标窗口是否跳转到了正确页面。关键指标格式合规率模型输出能正确解析的比例。动作准确率解析出的动作序列中每个动作坐标在目标按钮容错范围内的比例。任务完成率最终成功完成指令的比例。这是最重要的指标。迭代循环 根据评估结果分析失败案例。是视觉理解错了还是规划逻辑有问题或是工具调用格式不对针对性地补充训练数据到你的微调数据集中然后进行新一轮的 Post-Training。这个过程是迭代和工程驱动的。在我和团队的实践中In-Context VLA 展现出的最大魅力是其敏捷性。当我们需要为内部一个老旧、无 API 的桌面软件添加自动化功能时我们没有时间也没有权限去修改软件本身。通过一周左右的努力定义动作空间、收集几百张截图和对应的操作序列、对开源 VLM 进行微调我们就得到了一个能理解自然语言指令操作该软件的智能体原型。虽然它还不完美需要一些后处理和容错但已经极大地提升了特定岗位的工作效率。这个领域的未来是“通用智能体”的雏形。随着多模态模型视觉和理解能力的持续进步以及工具调用范式的标准化也许未来会涌现出类似 OpenAI 的function calling这样的多模态工具调用标准In-Context VLA 有望成为连接强大感知模型与万千具体应用场景的“万能适配器”。一个模型通过不同的“上下文”即工具包定义就能扮演软件自动化助手、机器人遥控器、数据分析向导等不同角色。要实现这一点社区还需要在标准化评估基准、共享的工具定义格式以及更高效的上下文学习算法上继续努力。
返回列表