ARTICLE DETAIL

资讯详情

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

【源码解读】Alpamayo 推理——多视角、多时间步图像的视觉编码

【源码解读】Alpamayo 推理——多视角、多时间步图像的视觉编码 本文基于 Alpamayo 推理源码transformers中的qwen3_vl/qwen2_vl模块梳理了多视角、多时间步原始图像转换为视觉 Token 的过程。一、视觉编码Vision Encoding的作用在视觉语言模型VLM的架构中视觉编码的作用是将原始图像像素转化为语义 Token 序列作为后续 LLM 骨干网络的输入。然而当视觉语言动作模型VLA迈向端侧部署时这一过程便面临严苛的工程矛盾LLM 的计算复杂度随 Token 数量呈二次方增长。因此VLA 中的视觉编码器绝不能仅做无差别的特征提取它必须在最大限度保留环境语义信息的前提下极尽所能地压榨输出 Token 的数量。参考Alpamayo-R1 论文中 arXiv:2511.00088中3.2.1节的详细描述为破解这一“保语义”与“减 Token”的博弈业界衍生出了多种视觉 Token 化方案。这些方案的本质差异聚焦于单次推理步长中的信息压缩比——即以何种架构策略将多少帧多时间步、多少视角多空间步的图像信息压缩至多少个 Token 中。二、视觉编码的实现2.1 论文阐述与开源代码的差异Alpamayo 论文3.2.1 节阐述了三种视觉编码范式Single-Image Tokenization单帧图像 token 化如ViTs 切分图像为patches并编码为1D token 序列Multi-Camera Tokenization多相机图像 token 化如 triplane 三平面法Multi-Camera Video Tokenization多相机视频 token 化如 Flex当前开源代码仅释放了Single-Image 方式类似ViTs的逐张图像独立编码Multi-Camera 相关的核心模块均未释出。这意味着如果把 4 个相机 × 4 个时间步的 16 张图输入给LLM模型它们会被当作 16 张独立的静态图处理Token 数量会随相机数和时间步线性增长。 参考链接Questions about visual tokenizerFeature Request: Release of Triplane Multi-Camera Tokenizer2.2 入口函数和参数配置开源代码采用了 Single-Image 的视觉编码范式图像处理的入口在./alpamayo-main/ar1_venv/lib/python3.11/site-packages/transformers/models/qwen3_vl/processing_qwen3_vl.pyimage_inputsself.image_processor(imagesimages,**output_kwargs[images_kwargs])image_grid_thwimage_inputs[image_grid_thw]虽然入口是Qwen3VLProcessor但底层调用的图像处理器是Qwen2VLImageProcessorFast。它的核心配置决定了后续所有的维度变换参数值含义patch_size16切分 patch 为 16×16merge_size2空间合并组大小resize 对齐 factor 16×2 32temporal_patch_size2时间维度每 2 帧组成一个 temporal patchmin/max_pixels163840 / 196608resize 后总像素数的上下界控制 Token 数量image_mean / image_std0.5 / 0.5将像素映射到 [-1, 1]三、视觉编码流程与图像维度变换以笔者在跟踪Alpamayo推理代码的 case 为例输入16 张 1080×1920 的多视角/多时间步图像最终输出pixel_values.shape [11520, 1536]。其核心维度流转过程如下关键步骤拆解②智能缩放smart_resizeresized_height,resized_widthsmart_resize(height,width,factorpatch_size*merge_size,min_pixelssize[shortest_edge],max_pixelssize[longest_edge],)原图[3, 1080, 1920]207万像素远超max_pixels196608代码按照β √(原图像面积/最大像素数) √(2073600/196608) ≈ 3.25 等比例缩小并对齐到 32 的倍数得到 resized后的图像尺寸 320×576面积 184320宽高比 1.8 ≈ 原图 1.78宽高比几乎无失真。⑤时间维补齐ifpatches.shape[1]%temporal_patch_size!0:repeatspatches[:,-1:].repeat(1,temporal_patch_size-1,1,1,1)patchestorch.cat([patches,repeats],dim1)静态图 T1不能被temporal_patch_size2整除于是复制末帧补成 T2得到 [16, 2, 3, 320, 576]。⑥⑦⑧维度变换view,permute,reshape️patches维度变换示意图batch_size,grid_t,channelpatches.shape[:3]grid_tgrid_t//temporal_patch_size grid_h,grid_wresized_height//patch_size,resized_width//patch_size# [16, 2, 3, 320, 576] -- [16, 1, 2, 3, 10, 2, 16, 18, 2, 16]patchespatches.view(batch_size,grid_t,temporal_patch_size,channel,grid_h//merge_size,merge_size,patch_size,grid_w//merge_size,merge_size,patch_size)# Reorder dimensions to group grid and patch information for subsequent flattening.# (batch, grid_t, grid_h//merge_h, grid_w//merge_w, merge_h, merge_w, channel, temp_patch_size, patch_h, patch_w)# [16, 1, 2, 3, 10, 2, 16, 18, 2, 16] -- [16, 1, 10, 18, 2, 2, 3, 2, 16, 16]patchespatches.permute(0,1,4,7,5,8,3,2,6,9)# [16, 1, 10, 18, 2, 2, 3, 2, 16, 16] -- [16, 720, 1536]flatten_patchespatches.reshape(batch_size,grid_t*grid_h*grid_w,channel*temporal_patch_size*patch_size*patch_size,)patches维度变换步骤 先计算网格大小grid_t 2//2 1grid_h 320//16 20grid_w 576//16 36。 ⑥ view把高度维度拆成宏观网格 10 × merge 2 × patch 16三级、宽度维度拆成18 × 2 × 16三级得到 10 维张量 [16, 1, 2, 3, 10, 2, 16, 18, 2, 16]。 ⑦ permute把宏观网格grid_t, grid_h//2, grid_w//2提到前面、微观维度merge, channel, temporal, patch_h, patch_w放到后面得到 [16, 1, 10, 18, 2, 2, 3, 2, 16, 16]——这样空间上 2×2 相邻的 patch 在 token 序列中保持连续便于后续 patch 合并与位置编码。 ⑧ reshape展平为 [16, 720, 1536]其中 720 1×20×36 为每张图的 token 数1536 3×2×16×16 为 token 维度。⑨token拼接把 16 张图沿 token 维拼接得到最终 pixel_values [11520, 1536]11520 16×720同时为每帧图像写下 [grid_t, grid_h, grid_w] [1, 20, 36]存入 image_grid_thw供模型后续做位置编码和按图拆分 token。四、总结总结整条链路分组 → smart_resize → resize/归一化 → 时间维补齐 → view/permute/reshape → cat将多视角、多时间步的输入图像按照“可变分辨率 最小纵横比失真”的方式编码为图像 token 序列输出 [11520, 1536] 的图像 token 序列和 [1, 20, 36] 的网格元信息。【原创】本文为笔者在详细阅读论文并跟踪推理代码后整理的代码阅读笔记欢迎大家评论区留言讨论。
返回列表