多模态:图片如何进入上下文
截了一张报错对话框的图,两兆,问她 这是什么意思 。
多模态:图片如何进入上下文
截了一张报错对话框的图,两兆,问她”这是什么意思”。
她答得很好。又贴了两张,继续问。到第五轮,界面弹出一行字:请求过大。
图加起来不到十兆。上下文里真正算 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 排版的表格,不如直接把表格文字贴过去。
- 空间关系弱。它能说出”左上角有个按钮”,但精确的坐标和布局关系经常错。
所以正确用法不是”把图丢给它”,而是把图里关键的信息用文字补一遍。图用来提供整体印象,文字用来保证精确。
多模态不是让模型变全知,是把另一种模态接进了同一个上下文预算里。它没有绕过任何限制,只是让限制变得更难算。