2、VLA核心架构解析:多模态编码器、跨模态融合模块、动作解码器,以及端到端训练范式
好,咱们直接切入正题。VLA模型,说白了就是把视觉(Vision)、语言(Language)、动作(Action)这三样东西揉在一起,让车能看懂路、听懂指令、然后自己动起来。这一章我重点拆解它的内部构造——多模态编码器、跨模态融合、动作解码器,还有那个让人又爱又恨的端到端训练。
核心观点:VLA不是简单的“视觉+语言+动作”拼接,而是通过跨模态对齐,让三个模态在特征空间里“说同一种语言”。
2.1 多模态编码器:让车看懂世界、听懂人话
先说说编码器这块。我个人习惯把多模态编码器拆成两条独立的流水线:视觉编码器和语言编码器。它们各干各的,但最终要输出统一维度的特征向量。
2.1.1 视觉编码器
视觉部分,现在主流用的是Vision Transformer(ViT)或者它的变体。为什么不用CNN?我在项目中遇到过,CNN对全局上下文的理解确实弱一些,尤其是在复杂路口,车需要同时关注远处的红绿灯和近处的行人。ViT的自注意力机制天然适合干这个。
举个例子,输入一张640×480的图像,ViT会把它切成16×16的patch,每个patch映射成一个token。最后输出一个序列,比如196个token,每个token是768维的向量。
# 伪代码示意:视觉编码器
class VisionEncoder(nn.Module):
def __init__(self):
self.patch_embed = PatchEmbed(img_size=640, patch_size=16, in_chans=3, embed_dim=768)
self.blocks = nn.ModuleList([TransformerBlock(dim=768) for _ in range(12)])
def forward(self, x):
# x: [B, 3, 640, 480]
tokens = self.patch_embed(x) # [B, 196, 768]
for block in self.blocks:
tokens = block(tokens)
return tokens # [B, 196, 768]
避坑指南:我曾经在视觉编码器的分辨率选择上吃过亏。分辨率太高,推理速度跟不上;分辨率太低,远处的小目标(比如30米外的锥桶)根本看不清。我建议根据实际场景做权衡,城市道路用640×480够用,高速场景可以降到512×384。
2.1.2 语言编码器
语言部分,现在基本都用预训练的LLM来做编码器,比如LLaMA、Qwen的轻量版。输入是自然语言指令,比如“前方50米右转,注意避让行人”。输出是一串token序列,维度跟视觉token对齐。
嗯,这里要注意:语言编码器的输出维度必须和视觉编码器一致,否则后面没法融合。我一般把语言token也映射到768维,序列长度根据指令长短动态调整,但会padding到固定长度(比如64个token)。
# 伪代码示意:语言编码器
class LanguageEncoder(nn.Module):
def __init__(self):
self.llm = AutoModel.from_pretrained("Qwen/Qwen2-0.5B")
self.proj = nn.Linear(4096, 768) # 维度对齐
def forward(self, input_ids, attention_mask):
# input_ids: [B, L]
hidden = self.llm(input_ids, attention_mask).last_hidden_state
tokens = self.proj(hidden) # [B, L, 768]
return tokens
2.2 跨模态融合模块:让视觉和语言“对上话”
这是VLA最核心的部分。说白了,就是让视觉token和语言token在特征空间里互相“看”一眼,然后融合成一个统一的表示。
目前主流做法有两种:
- 交叉注意力(Cross-Attention):视觉token作为Query,语言token作为Key和Value,让视觉特征根据语言指令做加权。
- 统一Transformer:把视觉token和语言token拼成一个长序列,然后一起过Transformer层,让它们自己学怎么交互。
我个人更倾向于第二种。为什么?因为统一Transformer能让两个模态在每一层都充分交互,而不是只在最后一层做一次交叉注意力。我在项目里试过,统一Transformer在复杂场景(比如“左转后立即靠边停车”)下的表现明显更好。
关键设计:融合后的特征维度保持768不变,但序列长度变成了视觉token数+语言token数。比如196个视觉token + 64个语言token = 260个token,每个768维。
# 伪代码示意:跨模态融合
class CrossModalFusion(nn.Module):
def __init__(self):
self.transformer = nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model=768, nhead=8),
num_layers=6
)
def forward(self, vis_tokens, lang_tokens):
# vis_tokens: [B, 196, 768], lang_tokens: [B, 64, 768]
combined = torch.cat([vis_tokens, lang_tokens], dim=1) # [B, 260, 768]
fused = self.transformer(combined) # [B, 260, 768]
return fused
注意:融合模块的层数不是越多越好。我试过12层Transformer,训练时loss降得很快,但实际部署时推理延迟增加了30%,而且过拟合严重。6层是个不错的平衡点。
2.3 动作解码器:从特征到方向盘和油门
融合后的特征怎么变成车的动作?这就是动作解码器干的事。它把260个token(每个768维)映射成具体的控制信号,比如方向盘转角、油门开度、刹车力度。
我常用的做法是:先用一个全局池化层把260个token压缩成一个向量,然后接一个MLP(多层感知机)输出动作值。
# 伪代码示意:动作解码器
class ActionDecoder(nn.Module):
def __init__(self):
self.pool = nn.AdaptiveAvgPool1d(1) # 全局平均池化
self.mlp = nn.Sequential(
nn.Linear(768, 512),
nn.ReLU(),
nn.Linear(512, 256),
nn.ReLU(),
nn.Linear(256, 3) # 方向盘、油门、刹车
)
def forward(self, fused_tokens):
# fused_tokens: [B, 260, 768]
pooled = self.pool(fused_tokens.transpose(1, 2)).squeeze(-1) # [B, 768]
action = self.mlp(pooled) # [B, 3]
return action
不过,直接回归连续值有个问题——动作抖动。我记得有一次测试,车在直道上走S形,方向盘输出一直在±2度之间跳。后来我加了一个时序平滑模块,用过去5帧的动作做加权平均,才把这个问题压下去。
2.4 端到端训练范式:从数据到模型的一体化流程
端到端训练,说白了就是整个VLA模型一起训练,不拆成视觉、语言、动作三个独立模块分别训。这样做的好处是梯度可以反向传播到每一个参数,让模型自己学会怎么协同工作。
训练流程大致如下:
- 数据准备:采集大量驾驶数据,包括摄像头图像、驾驶员语音指令(或文本指令)、以及对应的方向盘/油门/刹车动作。
- 损失函数:动作预测的L1损失或MSE损失,加上一些辅助损失(比如语义分割损失、深度估计损失)来帮助视觉编码器学得更好。
- 优化策略:先用小学习率(1e-4)预训练视觉和语言编码器,然后全模型用大学习率(1e-3)微调。
训练技巧:我建议在训练初期冻结语言编码器,只训练视觉编码器和动作解码器。等视觉部分收敛后,再解冻语言编码器一起微调。这样可以避免语言模型“带偏”视觉特征。
# 伪代码示意:端到端训练
model = VLA(vision_encoder, language_encoder, fusion_module, action_decoder)
optimizer = AdamW(model.parameters(), lr=1e-4)
for epoch in range(100):
for batch in dataloader:
images, instructions, actions = batch
pred_actions = model(images, instructions)
loss = F.l1_loss(pred_actions, actions)
# 辅助损失:语义分割
seg_loss = compute_seg_loss(model.vision_encoder, batch.seg_masks)
total_loss = loss + 0.1 * seg_loss
optimizer.zero_grad()
total_loss.backward()
optimizer.step()
注意:端到端训练对数据质量要求极高。我曾经因为数据里混入了10%的噪声标签(比如方向盘角度标错了),导致模型在弯道上的表现直接崩了。数据清洗和异常检测是端到端训练的前提。
2.5 本章知识体系总览
下面这张图把VLA的核心架构串起来了。你可以看到数据从摄像头和麦克风流入,经过编码、融合、解码,最终输出控制信号。整个流程是端到端的,中间没有手工设计的规则。
好了,这一章的内容就到这里。VLA的核心架构其实不复杂,但每个模块的细节都值得深挖。下一章我会聊聊训练数据怎么准备、怎么清洗,以及那些踩过的坑——嗯,到时候再细说。
公众号:蓝海资料掘金营,微信deep3321