多模態算法QwenVL、KimiVL等算法原理
最新內容:https://www.big-yellow-j.top/posts/2025/08/28/MultiModal2.html
對于多模態系列模型大致的多模態大語言模型的通用模型框架和每個模塊的一些實現方法[1]:

基本上就是對于圖片/視頻等通過不同的視覺編碼器(Vit/Clip等)進行編碼,對于text通過編碼器進行編碼,而后將視覺模態信息通過映射層(q-former/mlp等)將兩部分維度對齊而后丟到LLM中輸出結果。簡單總結常用的多模態模型。
QwenVL系列
目前QwenVL迭代更新迭代到2.5(截至2025.09.21)主要介紹QwenVL、QwenVL2、QwenVL2.5這3個多模態模型,其中對于這3個模型其中最大的改進都集中在視覺模態處理上
QwenVL
在QwenVL[2]中在論文里面作者提到的其模型的整個訓練過程如下:

僅從提供的不同階段還是很容易發現QwenVL還是是采用和BLIP相似的使用 learned-query來對齊模態信息
語言模型使用(7.7B):Qwen-7B
視覺編碼器(1.9B):Vit-bigG
融合器(0.08B):Learnable Query
不過論文里面對于模型細節介紹不是很多,從代碼角度出發窺其模型結構:
模型視覺編碼器:視覺編碼器使用的是ViT架構(Vision Transformer),ViT的網絡設置和初始化參數使用了OpenCLIP預訓練好的ViT-bigG模型。具體的代碼處理過程(代碼),其中模型輸出維度變化過程:1x3x448x448-->1x1664x32x32(首先卷積處理)-->1x1024x1664(拉平交換維度)
特征融合器:上述ViT處理后,對于\(448\times 448\)分辨率的圖像,生成一個 [1024, 1664]的序列,也就是向量維度為1664的長度為1024的序列。為了壓縮視覺token的輸入長度,Qwen-VL引入了一個Adapter來壓縮圖像特征。這個Adaper就是一個隨機初始化的單層Cross-Attention模塊。該模塊使用一組可學習的query向量,將來自ViT的圖像特征作為Key向量。通過Cross-Attention操作后將視覺特征序列壓縮到固定的256長度(也就是將視覺特征壓縮到 256 1644)
此外,考慮到位置信息對于精細圖像理解的重要性,Qwen-VL將二維絕對位置編碼(三角位置編碼)整合到Cross-Attention的 \(q,k\)中,以減少壓縮過程中可能丟失的位置細節。隨后將長度為256的壓縮圖像特征序列輸入到大型語言模型中。
QwenVL-2
對于QwenVL-2[3]其模型的基本結構如下:

1、使用動態分辨率(也就是說輸入圖像不需要再去改變圖像尺寸到一個固定值),于此同時為了減少 visual-token數量,將2x2的的相鄰的token進行拼接到一個token而后通過MLP層進行處理。

動態分辨率處理如上,通過指定[mix_pixels, max_pixels]范圍然后將圖像保持原始的縱橫比去縮減圖像到上面的范圍中(處理過程,首先計算原始圖像的像素數量,而后判斷和上面指標的范圍,如果超出范圍就去計算需要修改的比例,在將整個比例去處理到分辨率上)
在通過使用動態分辨率處理圖像之后會在單一圖片增加時間維度也就是將:CHW-->TCHW(這點是為了和視頻處理過程進行對齊),在源碼中T選擇數值為2也就是將圖片“復制一次”,而后對幀序列進行Patchification操作
def _preprocess():
......
channel = patches.shape[1]
grid_t = patches.shape[0] // self.temporal_patch_size
grid_h, grid_w = resized_height // self.patch_size, resized_width // self.patch_size
patches = patches.reshape(
grid_t, # 0
self.temporal_patch_size, channel, # 1 2
grid_h // self.merge_size, # 3
self.merge_size, self.patch_size, # 4 5
grid_w // self.merge_size, # 6
self.merge_size, self.patch_size, # 7 8
) # self.merge_size=2 self.patch_size=14 self.temporal_patch_size=2
### 將2x2的鄰域Patch放到一起,方便后續做領域的Patch過Projector層做聚合壓縮
patches = patches.transpose(0, 3, 6, 4, 7, 2, 1, 5, 8)
### Patch序列化,并保留Patch位置信息(時間,高,寬)
flatten_patches = patches.reshape(
grid_t * grid_h * grid_w, channel * self.temporal_patch_size * self.patch_size * self.patch_size
)
上面過程也就是進行所謂的“2x2的相鄰token拼接”,最后得到[grid_t * grid_h * grid_w, channel * temporal_patch_size(2) * patch_size(14) * patch_size(14)](其中grid_h=resized_height // self.patch_size(14))
2、多模態的旋轉位置編碼(M-RoPE),也就是將原來位置編碼所攜帶的信息處理為:時序(temporal)、高度(height)、寬度(width)。比如下圖中對于文本處理直接初始化為:\((i,i,i)\)。但是對于圖片而言就是:\((i,x,y)\) 其中 \(i\) 是恒定的,而對于視頻就會將 \(i\) 換成視頻中圖像的順序
總結處理過程:動態分辨率處理-->復制時間維度-->將序列切割為patch。這樣一來就會直接將圖像處理為:[grid_t * grid_h * grid_w, channel * temporal_patch_size(2) * patch_size(14) * patch_size(14)](其中grid_h=resized_height // self.patch_size(14))除此之外而后去計算 3d-RoPE最后通過一層線性層處理就得到最后的視覺token。
QwenVL-2.5
在QwenVL2.5中[4]模型具體的代碼處理過程參考Blog[5]具體模型結構:

在圖像處理過程上和QwenVL2差異不大都是直接:動態分辨率處理-->復制時間維度-->將序列切割為patch,對比兩個模型差異:

1、采用 RMSNorm 替換了所有 LayerNorm;2、ViT中每一個VisionBlock中的MLP換成了SwiGLU 結構。只從模型結構上差異不到,在QwenVL2.5中主要進行改動:1、使用window-attention(對應上述結構中的Qwen2_5_VLVisionAttention)對于具體的劃分window方法(代碼):根據輸入的圖像大小 (gird_t, grid_h, grid_w)去得到窗口索引 (window_index) 和 累積序列長度 (cu_window_seqlens)。具體例子如下:
# 數據數據特征
[ [ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23],
[24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35] ]
# 保證可以被window_size劃分需要進行填充
[ [ 0, 1, 2, 3, 4, 5, X, X],
[ 6, 7, 8, 9, 10, 11, X, X],
[12, 13, 14, 15, 16, 17, X, X],
[18, 19, 20, 21, 22, 23, X, X],
[24, 25, 26, 27, 28, 29, X, X],
[30, 31, 32, 33, 34, 35, X, X],
[ X, X, X, X, X, X, X, X],
[ X, X, X, X, X, X, X, X] ]
# 而后直接更具window大小得到每個需要計算注意力的window
# window-0
[ 0, 1, 2, 3]
[ 6, 7, 8, 9]
[12, 13, 14, 15]
[18, 19, 20, 21]
# 展平重新排列得到:
# window-0
[0, 1, 2, 3, 6, 7, 8, 9, 12, 13, 14, 15, 18, 19, 20, 21]
# window-1
[4, 5, 10, 11, 16, 17, 22, 23]
# 計算累計長度
seqlens = (index_padded != -100).sum([2, 3]) # 計算有效長度:window-0:16 window-1:8.....
cu_seqlens_tmp = seqlens.cumsum(0) * 4 + cu_window_seqlens[-1]
cu_window_seqlens.extend(cu_seqlens_tmp.tolist())
# [0, 64, 96, 128, 144]
# 得到最后返回結果window_index, cu_window_seqlens
在得到window_index和cu_window_seqlens之后就是計算注意力過程
for i in range(1, len(cu_seqlens)):
attention_mask[..., cu_seqlens[i - 1] : cu_seqlens[i], cu_seqlens[i - 1] : cu_seqlens[i]] = 0
q = q.transpose(0, 1)
k = k.transpose(0, 1)
v = v.transpose(0, 1)
attn_weights = torch.matmul(q, k.transpose(1, 2)) / math.sqrt(self.head_dim)
attn_weights = attn_weights + attention_mask
attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(q.dtype)
總結
從QwenVL到QwenVL2.5視覺編碼器處理過程:
QwenVL:將圖像轉化為固定的分辨率而后將輸入到Vit-bigG進行處理得到視覺特征之后再去使用類似Q-former處理過程(QwenVL中使用的是一個隨機初始化的單層Cross-Attention模塊)使用learned-query(壓縮到固定的256長度的token)將視覺token進行壓縮而后輸入到LLM中。
QwenVL2:首先使用動態分辨率(將圖像除以固定的factor而后保持橫縱比將其縮減到 [mix_pixels, max_pixels]中)去處理圖像而后將其輸入到視覺編碼器中,而后將2x2的的相鄰的token進行拼接(也就是將圖像補充一個時間幀得到TCHW,而后再去在THW三個維度劃分得到不同的patch:grid_t,grid_h,grid_w)到一個token而后通過MLP層進行處理。
QwenVL2.5:整體框架上和QwenVL2差異不大,區別在于使用了window-attention以及2D-RoPE

浙公網安備 33010602011771號