上一篇:《理解多模態大語言模型,主流技術與最新模型簡介》
序言:動手搭建建多模態LLM的兩大通用主流方法是:統一嵌入-解碼器架構和跨模態注意力架構,它們都是透過利用影像嵌入與投影、跨注意力機制等技術來實現的。
- 構建多模態 LLM 的常見方法
構建多模態 LLM(大型語言模型)主要有兩種方法:
方法 A:統一嵌入-解碼器架構 (Unified Embedding Decoder Architecture)
方法 B:跨模態注意力架構 (Cross-modality Attention Architecture)
(順便說一下,我覺得目前這些技術還沒有官方的標準術語,如果你聽說過什麼官方叫法的話,也請告訴我。比如說,簡短一點的描述可能就是“純解碼器結構(decoder-only)”和“基於交叉注意力(cross-attention-based)”的兩種方法。)
這兩種開發多模態 LLM 架構的主要途徑
上面這張圖展示了兩種主要方法。第一種統一嵌入-解碼器架構使用的是單一的解碼器模型,就像一個沒怎麼改過的 LLM 架構,比如 GPT-2 或 Llama 3.2。在這種方法裡,影像會被轉成和文字 token 一樣大小的嵌入向量,然後 LLM 會把文字和影像的輸入 token 串起來一起處理。
而跨模態注意力架構則是在注意力層裡用跨注意力機制來直接融合影像和文字的嵌入。
接下來幾節,我們會先在概念層面探討這些方法是怎麼工作的,然後看看最近的一些論文和研究,看看這些方法在實際中是怎麼用的。
2.1 方法 A:統一嵌入-解碼器架構
咱們先從統一嵌入-解碼器架構說起,下面這張圖又重新畫了這個架構。
圖示:統一嵌入-解碼器架構,也就是一個未修改過的解碼器風格 LLM(比如 GPT-2、Phi-3、Gemma 或者 Llama 3.2),它接收的輸入是包括了影像 token 和文字 token 的嵌入。
在這種統一嵌入-解碼器架構中,一張影像會被轉成一串嵌入向量,就像在標準的文字 LLM 裡文字被轉成嵌入向量那樣。
對一個典型的純文字 LLM 來說,處理文字時通常會先把文字分詞(比如用 BPE),然後送進一個嵌入層,如下圖所示。
圖示:標準的文字分詞和轉成 token 嵌入的流程,然後在訓練和推理時,這些嵌入會傳給 LLM。
2.1.1 理解影像編碼器 (Image encoders)
就像文字在送進 LLM 前要分詞和嵌入一樣,影像的嵌入是透過影像編碼器模組(而不是分詞器)來搞定的,如下圖所示。
圖示:把影像編碼成影像 patch 的嵌入的過程。
上面這個圖裡,影像處理的流程是啥?基本上就是先把影像分成好幾塊小 patch,就像分詞把單詞拆成子詞,然後用一個預訓練的視覺 Transformer(ViT)來對這些小塊進行編碼,就像下面這張圖演示的一樣。
圖示:經典的 Vision Transformer (ViT) 架構,類似 2020 年那篇 “An Image is Worth 16x16 Words” 裡的模型。
注意,ViT 通常用來做分類任務,所以上面那張圖裡還畫了個分類頭。但是這裡我們只需要用到影像編碼器那一部分就行了。
2.1.2 線性投影模組 (linear projection) 的作用
前面圖裡提到的“linear projection”(線性投影)就是一層全連線的線性層。它的目的就是把影像的 patch(已經展平成一個向量)投影到和 Transformer 編碼器匹配的嵌入維度中。這在下圖中演示了。一個影像 patch,原本展平後是 256 維,會被升維到 768 維的向量。
圖示:線性投影層把展平的影像 patch 從 256 維投影到 768 維嵌入空間。
如果你想看程式碼示例的話,用 PyTorch 可以這樣寫這層線性投影:
import torch
class PatchProjectionLayer(torch.nn.Module):
def init(self, patch_size, num_channels, embedding_dim):
super().init()
self.patch_size = patch_size
self.num_channels = num_channels
self.embedding_dim = embedding_dim
self.projection = torch.nn.Linear(
patch_size * patch_size * num_channels, embedding_dim
)
def forward(self, x):
batch_size, num_patches, channels, height, width = x.size()
x = x.view(batch_size, num_patches, -1) # 把每個patch展平
x = self.projection(x) # 投影
return x
使用示例
batch_size = 1
num_patches = 9 # 每張影像的patch數
patch_size = 16 # 每個patch是16x16畫素
num_channels = 3 # RGB影像
embedding_dim = 768 # 嵌入向量維度
projection_layer = PatchProjectionLayer(patch_size, num_channels, embedding_dim)
patches = torch.rand(
batch_size, num_patches, num_channels, patch_size, patch_size
)
projected_embeddings = projection_layer(patches)
print(projected_embeddings.shape)
輸出: torch.Size([1, 9, 768])
如果你看過我那本《Machine Learning Q and AI》書,你可能知道可以用卷積來代替線性層,數學上等價的實現也有。這種方法在這裡很有用,因為可以用兩行程式碼就同時實現patch的建立和投影:
layer = torch.nn.Conv2d(3, 768, kernel_size=(16, 16), stride=(16, 16))
image = torch.rand(batch_size, 3, 48, 48)
projected_patches = layer(image)
print(projected_patches.flatten(-2).transpose(-1, -2).shape)
輸出:torch.Size([1, 9, 768])
2.1.3 影像和文字的分詞對比 (Image vs text tokenization)
咱們已經大概說了影像編碼器和線性投影的用途,現在再回頭看看文字分詞的類比。下圖展示了影像和文字的分詞與嵌入過程的對比。
圖示:左邊是影像的分塊和嵌入,右邊是文字的分詞和嵌入,對比放一起。
你可以看到上面那張圖裡,我還放了個額外的投影器(projector)模組在影像編碼器後面。這玩意兒一般就是另一層線性投影(和剛才說的差不多)。它的目的是把影像編碼器輸出的維度調整到和文字 token 嵌入同樣的維度,如下圖所示。(後面我們會看到,這個 projector 有時候也叫 adapter、adaptor 或 connector。)
圖示:再對比一次,影像和文字的分詞過程。這裡 projector 的作用就是讓影像 token 嵌入的維度和文字 token 嵌入的維度匹配。
現在影像 patch 的嵌入維度和文字 token 嵌入維度相同了,我們就可以把它們簡單地拼接起來,作為輸入給 LLM,如下圖開頭時候展示的那樣。下面是同樣的圖,方便你對照。
圖示:在把影像 patch token 投影到和文字 token 嵌入相同的維度後,我們就能輕鬆地把它們拼接在一起,送進標準的 LLM。
順便說一句,這裡用到的影像編碼器通常是一個預訓練的 Vision Transformer,比如 CLIP 或 OpenCLIP。
不過,也有一些使用方法 A 的模型是直接處理影像 patch 的,比如 Fuyu,就不需要額外的影像編碼器。看下圖:
圖示:Fuyu 多模態 LLM 的示意圖。它直接對影像 patch 進行處理,不需要獨立的影像編碼器。(圖片出自 https://www.adept.ai/blog/fuyu-8b,已做標註。)
上圖中可以看到,Fuyu 是直接把輸入的影像patch送進線性投影(或者embedding層)中,自學自己的影像 patch 嵌入,而不像其他方法那樣依賴一個已經預訓練好的影像編碼器。這讓架構和訓練流程更簡化。
2.2 方法 B:跨模態注意力架構 (Cross-Modality Attention Architecture)
現在我們已經討論了統一嵌入-解碼器架構(方法 A),並理解了影像編碼的基本概念,讓我們來看看另一種實現多模態 LLM 的方法——用跨注意力機制,如下圖總結所示。
圖示:跨模態注意力架構方法的示意圖。
在上面的跨模態注意力架構中,我們依然使用之前討論過的影像編碼器設定。但與把影像的patch嵌入作為LLM輸入不同,這裡是在多頭注意力層中透過跨注意力機制把影像資訊接入進來。
這個點子其實和最初的 transformer 架構有關係,也就是 2017 年那篇 “Attention Is All You Need” 論文中使用的機制。下面的圖高亮了這一點。
圖示:最初的 Transformer 架構裡使用的跨注意力機制。(圖片來自“Attention Is All You Need”論文:https://arxiv.org/abs/1706.03762,有標註)
注意,最初的 Transformer 是用來做語言翻譯的,所以它有一個文字編碼器(圖的左半部分)和一個文字解碼器(圖的右半部分)。在多模態 LLM 的場景下,這裡的“編碼器”就是影像編碼器,而不是文字編碼器,但原理是一樣的。
跨注意力是怎麼工作的?讓我們先看一下常規自注意力(self-attention)是啥樣。下圖是自注意力機制的示意圖。
圖示:常規自注意力機制的流程。(這裡展示的是多頭注意力中的一個注意力頭的處理流程)
上面這張圖裡,x 是輸入,Wq 是生成查詢(Q)的權重矩陣,同理 K 是 keys 的權重,V 是 values 的權重。A 是注意力分數矩陣,Z 是最終的上下文向量輸出。(如果你覺得這有點繞,可以參考我《Build a Large Language Model from Scratch》這本書的第3章,或者看看我的文章《Understanding and Coding Self-Attention, Multi-Head Attention, Cross-Attention, and Causal-Attention in LLMs》。)
在跨注意力(cross-attention)裡,不同於自注意力只有一個輸入序列,我們會有兩個不同的輸入源,如下圖示意。
圖示:跨注意力(cross-attention)的示意圖,有兩個不同的輸入 x1 和 x2
在自注意力中,輸入序列是同一個。在跨注意力中,我們是把兩個不同的輸入序列混合起來。
在最初的 Transformer 架構中,x1 和 x2 對應的是左邊編碼器輸出的序列(x2)以及右邊解碼器正在處理的輸入序列(x1)。在多模態 LLM 的場景中,x2 就是影像編碼器的輸出。(一般來說,query 來自解碼器,而 key 和 value 來自編碼器。)
要注意的是,在跨注意力中,這兩個輸入序列 x1 和 x2 可以有不同的長度。但它們的嵌入維度必須相同。如果我們讓 x1 = x2,那其實就又退化回自注意力了。
總結:本文詳細介紹了構建多模態大語言模型的兩種主流方案:統一嵌入-解碼器架構和跨模態注意力架構,並說明了影像編碼與投影在文字-影像融合中的關鍵作用。透過對基礎概念、典型實現方式及現有研究進行分析,讀者可初步瞭解多模態LLM的設計思路和技術路徑。下一篇將重點探討如何為這兩類架構的方法實際訓練多模態大語言模型,以期在實踐中取得更高效、更穩定的效能表現。