05 - 看:图像视频怎么被理解
用户拍了张菜单照片,问「这个有什么推荐」。
照片 3024×4032,手机随手拍的。它要怎么进到模型里?
02 篇给过答案的形状:模态编码器把它变成一串向量,输入投影器把这串向量对齐到 LLM 的空间。这一篇拆开「模态编码器」这一格。
先给结论,它是整篇的主线:分辨率会直接变成 token 账单。用户随手拍的一张图,可能比他打的字贵一百倍。
一、把图切成方块
做法叫 ViT(Vision Transformer,视觉 Transformer)。想法很直白:Transformer 只会处理序列,那就把图切成小方块,每块当一个词。
二、分辨率就是账单
上一节那个 1024,是按 448×448 算的。可用户那张照片是 3024×4032。
早期做法是强行缩放到固定尺寸,比如都缩成 448×448。好处是 token 数恒定、显存好估、编码器能捕成 CUDA Graph;坏处是一张竖着的菜单被压扁,小字全糊,OCR 直接废掉。
后来的做法叫动态分辨率:图多大就按多大切,不缩放。Qwen2-VL 这一代开始普遍这么做。
代价立刻显现:
| 输入 | 按固定 448 | 按动态分辨率 |
|---|---|---|
| 一张 448×448 的图 | 1024 块 | 1024 块 |
| 用户那张 3024×4032 | 1024 块(但字糊了) | 约 62000 块 |
| 合成 2×2 之后 | 256 | 约 15500 |
一张手机原图能顶十几篇长文档。 所以所有支持动态分辨率的模型都必须设一个上限,超了就等比缩小 —— 这个上限(通常叫 max_pixels 之类)是部署时最该先确认的参数之一。
固定分辨率时,一张图就是 1024 个 token,容量规划是道算术题。
动态分辨率之后,同样是「一张图」,token 数可能差 60 倍。你没法再按「平均每请求多少 token」去规划,只能按上限规划 —— 于是平时大量浪费,而一旦有人上传高清图,又可能撞穿。
三、另一种接法:把视觉参数塞进 LLM 里
前面讲的都是「编码器出向量 → 投影器对齐 → 塞进序列」,LLM 本身一个参数都不改。
还有一条路:在 LLM 每一层里额外加一套只处理视觉 token 的参数。CogVLM 叫它 visual expert(视觉专家),17B 里有 10B 是视觉参数、7B 是语言参数。
两种接法的差别,在服务上很实际:
| 投影器接法 | 视觉专家接法 | |
|---|---|---|
| LLM 权重 | 完全不动,可以跟纯文本服务共用 | 改了,得单独部署一份 |
| 训练成本 | 只训投影器,便宜 | 要训一套新参数 |
| 效果 | 视觉信息「翻译」进来,有损耗 | 视觉和文本在每一层深度融合,细粒度任务更强 |
| 显存 | 跟原 LLM 差不多 | 显著更大 |
大多数产品用第一种,因为它便宜、灵活、能跟已有的文本服务复用同一套权重。
四、视频:再乘一个帧数
视频没有本质新东西,就是图片乘帧数,但这个乘法很吓人。
一段 10 秒的视频,按每秒抽 2 帧就是 20 帧。每帧哪怕只算 256 块,也是 5120 个 token。按每秒抽 8 帧,就是 20480。
所以视频这边所有工程手段都在做同一件事:少要几帧、每帧少要几块。
- 降抽帧率。多数场景每秒 1~2 帧就够,剧烈运动才需要更高。
- 时间维合并。相邻两帧的相同位置合成一个 token,直接砍一半。
- 按内容抽帧。画面没怎么变的段落少抽几帧。
- 一张图切成多少块(分辨率 ÷ 块边长,再看合不合并)
- 视频每秒抽几帧
- 有没有 token 数上限,上限是多少
这三个数一乘,就是你的账单。模型参数量反而不是主要因素。
五、服务视角:编码器是那个卡住所有人的家伙
前面都是模型视角。换成服务视角,视觉编码器有一个很讨厌的性质。
它不是一步一步算的,是一口气算完。一张图 1024 块,一次前向全算完,这期间 GPU 被它占满。
而同一时刻,可能有三十个用户正在等着模型一个字一个字往外吐。编码器一跑,这三十个人全都得停下等。