这篇文章对VLM的架构解释得非常清楚。
一种方法是使用适配器将图片转换为tokens,例如LLaVA使用的prompt based适配器。这种方法类似于RAG的形式,将图片理解的内容补充在对话的上文中。这种适配器会占用LLM的上下文长度,因为图片的tokens会被放入LLM的上文中。目前来说性能会好一些。
另一种方法是基于交叉注意力的适配器,这种方法不会占用LLM的上下文长度,但需要大量参数来达到良好的质量。Llama3.2就是这种结构。
关于Llama3.2本身,它使用了GQA,将kv head分组,多头查询将原本的K和V头分成组并为每个组生成一个共享的Head,这样可以减少kv cache而不太丧失精度(相较于MQA这种只共享一个KV头的方法)。因此,分组多头查询在多头查询注意力和正常多头注意力之间维持了平衡,既考虑了速度,又考虑了输出质量。另一个优化是对一个上下文中的不同文档进行mask处理。由于大模型的上下文现在很长,会将多个文档放入一个上下文中进行训练,但为了避免文档之间的相互影响,需要在文档级别进行mask处理,即当前token不能看到之后的token,也不能看到同一上下文中其他文档的token。其他改动主要是训练规模的调整。
根据Llama3.2的技术报告,里面的image encoder用的是ViT架构。适配器在语言模型和图像编码器之间引入交叉注意力层(cross-attention layers),以提高模型的效率和准确性。交叉注意力层使用通用查询注意力(GQA)并在核心语言模型每四层之后应用。交叉注意力层增加了大量可训练参数,例如Llama 3 405B中约有100B个参数。
本质上,图片编码器的输出通过适配器后作为交叉注意力层的K,文本作为Q,V也来自图片适配器,从而计算文字和图片之间的注意力关系,然后与LLM的输出进行交叉注意力。在训练Llama3.2的适配器时,同时更新了图像编码器的参数,但刻意不更新语言模型的参数。这意味着在适配器训练过程中,Meta只关注图像编码器和适配器的学习,而不影响语言模型的预训练知识。
简而言之,这个适配器在功能上类似于最初的encoder-decoder Transformer中的encoder部分。
在具体的以vLLM推断过程的实现为例,对话的API中会包含{"type":"image","image_url":"uri_of_the_image"}
,在应用对话模板以后会插入占位符,比如llama3.2用的就是<|image|>
,原始的训练中的文本内容会变成类似"<|image|>If I had to write a haiku for this one"
,以此标记图片的位置信息,实际上需要图片会通过uri_of_the_image
被加载到encoder中并携带<|image|>
所代表的位置信息编码。
总的来说,VLM的计算过程和推断中的处理方式通过引入适配器和交叉注意力层,实现了图片和文本的高效融合,为多模态任务提供了强大的支持。