多模态接入实战:一张图片到底吃掉多少 token
很多团队接多模态大模型时,都是这样翻车的:
- 按「一张图 ≈ 一次请求」做成本预算,上线后账单是预估的好几倍
- 图片里的表格小字死活识别不出来,以为是模型不行
- 把图片调清晰之后识别准了,成本又炸了
这三个现象背后是同一件事:图片不是一张「图」,它是一串长度可计算的 token。 搞不清这件事,成本和效果都调不动。
1. 图片是怎么变成 token 的
语言模型只认识向量序列。要让模型「看图」,得先把图切碎、编码、再对齐到语言模型的维度上:
关键是最后一步的数量关系:
视觉 token 数 ≈ (图片高度 ÷ patch 边长) × (图片宽度 ÷ patch 边长)以 patch = 14×14 为例(CLIP ViT-L/14 这类常见配置):
| 图片分辨率 | patch 数 | 视觉 token |
|---|---|---|
| 224 × 224 | 16 × 16 | 256 |
| 336 × 336 | 24 × 24 | 576 |
| 448 × 448 | 32 × 32 | 1024 |
| 1024 × 1024 | 73 × 73 | 5329 |
这是平方增长,不是线性
边长从 336 提到 1024,只涨了 3 倍;token 从 576 涨到 5329,涨了 9.3 倍。
换句话说,你为了「看得更清楚」把图放大一倍,成本要乘以四。 这是多模态成本失控的第一大来源。
别把这张表当精确计费依据
上面是按 patch 切分的理想估算,用来建立量级直觉。各家 API 的视觉 token 计算规则不同(有的按 512px 瓦片切分、有的会做 token 合并、有的对长边有额外缩放),而且规则会变。做预算时以你的供应商官方文档为准,本文的公式只用来判断「哪个方向能省钱、大概省多少」。
2. 一个直观的量级感受
576 个视觉 token 是什么概念?
- 一篇 400 字的中文文章,大约就是 500~700 个 token
- 也就是说,一张 336×336 的图 ≈ 一篇文章
- 而一张 1024×1024 的图(5329 token),已经超过很多模型单次请求的上下文预算
再考虑常见的多图场景:
| 场景 | 图片数 | 估算视觉 token |
|---|---|---|
| 单图问答 | 1 × 336² | 576 |
| 商品图 + 细节图 | 2 × 448² | 2048 |
| 合同逐页识别 | 10 × 1024² | 53,290 ❌ 直接超限 |
最后一行就是典型的翻车现场:按「10 张图」估成本觉得没事,按 token 一算根本放不下。
3. 把 token 压下来的四个手段
按性价比从高到低排列,不要一上来就动分辨率:
3.1 先裁剪,只传需要的区域(最高杠杆)
绝大多数业务场景里,整张图的信息密度都很低——一张商品图你可能只关心标签,一张发票你只关心金额区域。
❌ 传整张 1024×1024 → 5329 token
✅ 裁出标签区域 224×224 → 256 token 省 95%裁剪的另一个好处是效果反而更好:patch 粒度相对目标区域更细,小字更容易认出来。这是少见的「又省钱又提效果」的操作。
3.2 控制分辨率上限
如果必须传整图,就在上传前统一压到模型的有效输入上限。
from PIL import Image
from io import BytesIO
MAX_SIDE = 1024 # 超过这个边长,token 数增长得毫无性价比
def normalize(path: str) -> bytes:
img = Image.open(path).convert("RGB")
w, h = img.size
if max(w, h) > MAX_SIDE:
scale = MAX_SIDE / max(w, h)
img = img.resize((int(w * scale), int(h * scale)), Image.LANCZOS)
buf = BytesIO()
# 质量 80 对多数视觉任务已足够,体积能再降一半
img.save(buf, format="JPEG", quality=80, optimize=True)
return buf.getvalue()💡 注意顺序:先裁剪再缩放。反过来会先把细节抹掉,再裁出来也是糊的。
3.3 同一张图别重复传
多轮对话里最容易浪费的地方:用户第一轮发了图,后面每轮追问都重新带上图片。视觉 token 会被重复计费。
优先用支持「引用已发送图片」的会话机制;如果 API 不支持,就在第一轮把图片相关的信息抽成文本(比如让模型先输出图片描述或结构化字段),后续轮次只用文本对话。
3.4 选对模型
不同模型的视觉 token 压缩策略差别很大,有的会在 projector 阶段做 token 合并。同样分辨率的图,不同模型消耗的 token 可能差好几倍。
选型时把「单位图片的 token 成本」和「识别准确率」放在一起比,而不是只比每百万 token 的单价。
4. 结构化输出:多模态最容易崩的地方
视觉模型在「按格式输出」这件事上,比纯文本模型更不稳定——因为它多了一层不确定的输入。
4.1 三个稳定化手段
| 手段 | 做法 | 适用 |
|---|---|---|
| JSON Mode / 结构化输出 | 用 API 原生的 JSON schema 约束 | 模型支持时首选 |
| Function Calling | 把要抽取的字段定义成工具参数 | 字段较多、需要枚举值时 |
| 提示 + 校验重试 | 明确给出字段与示例,解析失败就重试 | 兜底方案 |
4.2 一定要做校验和重试
不要假设模型一定返回合法 JSON:
import json
from pydantic import BaseModel, ValidationError
class Invoice(BaseModel):
invoice_no: str
total: float
date: str
def extract(client, image_bytes: bytes, retries: int = 2) -> Invoice | None:
for attempt in range(retries + 1):
resp = client.chat.completions.create(
model="your-vlm",
response_format={"type": "json_object"}, # 支持时优先用
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "抽取发票号、金额、日期,返回 JSON。"},
{"type": "image_url", "image_url": {"url": to_data_url(image_bytes)}},
],
}],
)
raw = resp.choices[0].message.content
try:
return Invoice.model_validate_json(raw)
except (ValidationError, json.JSONDecodeError):
if attempt == retries:
return None
# 把校验错误回灌给模型,比盲目重试有效得多
continue
return None4.3 把「顺序」说清楚
多图对比时,模型很容易张冠李戴。必须在文本里显式标号,别指望模型自己对齐:
❌ "比较这两张图"
✅ "图1 是改造前,图2 是改造后。请逐项对比图1和图2的差异。"5. 什么时候不该降本
降 token 不是无脑操作,以下几类场景降了就是错:
| 场景 | 为什么不能降 |
|---|---|
| 医疗影像、工业缺陷检测 | 细节本身就是判据,压缩即丢失信息 |
| 手写体、密集表格 | patch 粒度不够就直接认错 |
| 需要读小字的合同/票据 | 应该裁剪后放大,而不是整体降分辨率 |
| 颜色/纹理判别 | JPEG 有损压缩会改变色值,建议用 PNG 或高质量 JPEG |
判断准则
问自己一句:「我降分辨率之后,人还能看出结论吗?」 人能看出来的,模型大概率也能;人已经看不清的,别指望模型能补回来。
6. 踩坑清单
| 现象 | 原因 | 解决 |
|---|---|---|
| 账单是预估的好几倍 | 按「图片张数」估成本,没按 token 估 | 用 (边长/patch)² 先估量级 |
| 图片调清晰后成本失控 | token 随边长平方增长 | 只放大目标区域,别放大整图 |
| 图里小字识别不出来 | 有效分辨率不足,patch 粒度太粗 | 裁剪目标区域后再放大 |
| 多轮对话成本异常高 | 每轮都重新传图,视觉 token 重复计费 | 首轮抽成文本,或用图片引用机制 |
| 结构化输出时好时坏 | VLM 的格式遵循能力弱于纯文本模型 | JSON Mode / Function Calling + 校验重试 |
| 多图对比张冠李戴 | 视觉 token 之间缺乏显式边界 | 文本里显式标「图1/图2」并说明对应关系 |
| 先缩放后裁剪,结果很糊 | 顺序错了,细节已被抹掉 | 先裁剪,再缩放 |
| 同一张图不同模型 token 数差很多 | 各家瓦片/patch 规则与压缩策略不同 | 以官方文档为准,横向比「单位图片成本」 |
7. 小结
| 要点 | 说明 |
|---|---|
| 图片 = 一串可计算的 token | (高 ÷ patch) × (宽 ÷ patch),与内容复杂度无关 |
| 增长是平方级的 | 边长翻倍,token 翻 4 倍——这是成本失控的根源 |
| 最高杠杆是裁剪 | 只传需要的区域,既省钱又提准确率 |
| 顺序很重要 | 先裁剪、再缩放,反过来细节就没了 |
| 多轮别重复传图 | 视觉 token 会被重复计费,首轮抽成文本 |
| 结构化输出要兜底 | VLM 格式遵循弱,必须校验 + 回灌错误重试 |
| 有些场景不能降 | 细节即判据的场景,裁剪放大优于整体降分辨率 |