专栏 Minis 拆解

多模态:图片如何进入上下文

截了一张报错对话框的图,两兆,问她 这是什么意思 。

多模态:图片如何进入上下文

截了一张报错对话框的图,两兆,问她”这是什么意思”。

她答得很好。又贴了两张,继续问。到第五轮,界面弹出一行字:请求过大。

图加起来不到十兆。上下文里真正算 token 的部分,可能还没到一个长文件。

一、图片不是附件,是正文

聊天软件里的图片是上传后存一个链接,对方点开才加载。

模型 API 不是这样。绝大多数接口要求图片内联在请求体里,用 base64 编码成字符串,和文字混在一起发过去。

所以图片的体积会直接计入请求体积。你发的不只是一张图,是这张图编码后的全部字节。

而且 base64 有个固定代价:每三个原始字节编成四个字符,膨胀三分之一。一张 25 MB 的图,出现在请求里大约是 33 MB。

二、两套口径,互不成比例

这里有个让很多人踩坑的地方:图片的成本有两个完全不同的计量方式。

传输按字节。 网关限的是请求体大小,几十兆就到顶。

计费按 token。 模型把图片切成小方块,每块折算成一定数量的 token,和分辨率相关,和文件大小无关。

一张高清但压缩得很好的图,可能只有几百 KB,却因为分辨率高而算很多 token。反过来,一张体积很大但内容简单的图,token 反而不多。

两个口径不成比例,所以需要两套预算:一套管字节,一套管 token。只算其中一套,另一半迟早出事。

三、字节预算最容易错的地方

有个真实的案例。

系统给图片设了上限,单位是”原始字节”。看起来没问题,直到有人传了一张 25 MB 的图——按原始大小没超,编码之后 33 MB,撞上网关 32 MB 的上限,请求被拒,报的还是”请求过大”,但前面的文字历史还没算进去。

修法有两种。一种是把所有上限除以 1.333,另一种是在比较的时候才换算。

实现选了后者,理由写在注释里:膨胀是按张发生的,N 张图就有 N 次舍入。把常量缩小,每张图都会在边界上多留一点或少留一点,误差累积;而在比较时精确换算,只有一个地方需要推理。

这是一条通用的工程原则:当两个单位必须共存时,把换算放在边界上,不要把换算结果存进常量。存进去的那一刻,常量就有了两个含义。

四、图片会一直留在历史里

字节预算解决的是”一次请求装不装得下”,还有一个更麻烦的问题:图片会在每一轮被重发。

Agent 的循环是每轮重发全部历史。第一轮贴的图,第二轮还在,第十轮还在。每发一次,就要为它付一次传输成本。

一张 1 MB 的图,二十轮对话就是 20 MB 的流量,而且用户早就忘了自己贴过。

所以成熟的实现不会把图片字节永久留在历史里。常见做法是:落盘存成文件,上下文里放一个引用(路径 + 尺寸 + 简短描述),需要细节时再读回来。

代价是模型默认看不到细节,要多一轮工具调用。收益是上下文里不再背着那些字节。

这和第三篇讲的”大工具结果外置”是同一个策略:能挪到磁盘的,就别放在上下文里。

五、工具产生的图片更麻烦

用户贴图是显式的,有数量上限。工具返回的图是隐式的,可能一次来一堆。

截图、读图、图表渲染,每一次工具调用都可能带回一张图。它们在历史里的地位和用户贴的图一样,也一样要占体积。

所以工具在返回图片前要做降采样:把长边压到合理尺寸,转成合适的格式,实在太大就只给路径和一句说明。

这里的取舍是:宁可让模型多看一轮,也不要让一张图把整个会话撑爆。

六、图片和缓存的关系

第十二篇讲过,缓存按精确字节前缀匹配。

图片是字节,而且是最容易变动的那部分字节。同一张图,重新编码一次,哪怕像素完全一样,字节也可能不同——压缩参数、元数据、色彩配置,任何一处变化都会让前缀失效。

所以图片一旦进入历史,就应该原样保留,不要每次发送前重新处理。想压缩,在第一次入库时就压好。

这个约束和”图片外置”是配套的:存成文件之后,每次读回来的字节是稳定的,缓存前缀也稳定。

七、它其实没有”看见”

最后说一个容易被高估的能力。

模型处理图片的方式,是把图切成小块,每块编码成一个向量,再和文字一起送进注意力计算。它没有眼睛,没有”看”这个动作。

这带来几个实际限制:

  • 小字容易糊。截图里的日志、代码、表格,字号小到一定程度就识别不了。
  • 密集信息会丢。一张 A4 排版的表格,不如直接把表格文字贴过去。
  • 空间关系弱。它能说出”左上角有个按钮”,但精确的坐标和布局关系经常错。

所以正确用法不是”把图丢给它”,而是把图里关键的信息用文字补一遍。图用来提供整体印象,文字用来保证精确。

多模态不是让模型变全知,是把另一种模态接进了同一个上下文预算里。它没有绕过任何限制,只是让限制变得更难算。