Qwen2.5-VL图像Token数计算分析:patch步长、空间合并因子与动态分辨率下的视觉Token估算

vLLM 推理框架—Qwen2.5-VL token数分析

在使用 vLLM 部署 Qwen2.5-VL-32B 模型时,一张图片被编码成的 token 数量并不是一个固定的值,它会主要取决于输入图片的分辨率。模型会根据图片的分辨率动态地生成不同数量的视觉 token。可以通过以下方式进行部署:

1
python3 -m vllm.entrypoints.openai.api_server --served-model-name Qwen2.5-VL-32B-Instruct --port 8080 --model /data/modelRepository/qwen-vl-model/qwen-vl-model_2-5-32b --tensor-parallel-size 2 --max-model-len 65536 --gpu-memory-utilization 0.85 --disable-log-stats --limit_mm_per_prompt image=10,video=1 --api-key <YOUR_API_KEY> --host 0.0.0.0 --enable-auto-tool-choice --tool-call-parser hermes --trust-remote-code

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sudo docker run -itd --gpus '"device=3, 4"' \
-v /data/models/:/data/models/ \
-p 8901:8901 \
--ipc=host \
--restart=always \
-e TZ=UTC \
vllm/vllm-openai:v0.9.1 \
--model /data/models/Qwen2.5-VL-32B-Instruct \
--served-model-name Qwen2.5-VL-32B-Instruct \
--tensor-parallel-size 2 \
--max-model-len 65536 \
--gpu-memory-utilization 0.85 \
--disable-log-stats \
--limit_mm_per_prompt image=10,video=1 \
--api-key <YOUR_API_KEY> \
--host 0.0.0.0 \
--port 8901

🔢 Token数量计算方法

Qwen2.5-VL 模型会通过视觉编码器(ViT)将图像分割成 patches(图像块),并进一步处理这些 patches 来生成视觉 token。其计算可大致表示为:

单图 Token 数 ≈ (图像高度 / Patch步长) × (图像宽度 / Patch步长) / (空间合并因子)²

其中:

  • Patch步长(Patch Stride):通常为 14 像素。这意味着图像会被划分成一系列 14x14 像素的块(但最终 token 数并非简单等同于块的数量)。
  • 空间合并因子(Spatial Merge Size, M):在视觉编码过程中,相邻的 2x2 个空间 patch 特征会被合并成一个 token。该因子默认为 2
  • 图像的高度和宽度在预处理时通常会被调整为 28 的倍数,以满足模型架构的要求。

📊 常见分辨率下的Token估算

根据上述规则,一些常见分辨率下的图片大致会产生如下数量的视觉 token:

图像分辨率预估 Token 数量说明
224x224~ 256 tokens较低分辨率,处理速度快
448x448~ 1024 tokens中等分辨率,细节和计算量的平衡
672x672~ 2304 tokens较高分辨率,能保留更多细节
896x896~ 4096 tokens高分辨率,需要更多计算资源

请注意,表中的 token 数量是估算值。实际 token 数量可能会因模型的具体预处理步骤(如调整图像尺寸以符合模型要求)而略有波动

💡 注意事项

  • 动态分辨率:Qwen2.5-VL 支持动态分辨率处理。这意味着不同大小、不同比例的图片产生的 token 数量是不同的。较高分辨率的图像会产生更多的视觉 token,从而保留更多的细节信息,但也会增加计算开销和内存占用。
  • 上下文长度:Qwen2.5-VL-32B 模型支持长达 128K 的上下文长度。单张图片产生的视觉 token(通常几百到几千)会与文本 token 共同占用这个上下文窗口。你需要确保图片 token 加上文本 token 的总和不超过模型的最大上下文限制。
  • 实际测试:要获取精确的 token 数量,最可靠的方法是在你的 vLLM 部署环境中实际处理一张图片,并通过模型的 API 或日志输出查看实际消耗的 token 数。

💎 高分辨率图片示例

如果是一张 2550*3300 和一张 1920×1280 的图片呢,分别有多少 token?


Qwen2.5-VL 模型在处理图像前,会先将其分辨率调整(Resize)为 28 的倍数,以满足其视觉编码器(ViT)的架构要求。这是计算的关键第一步。

其 Token 数量的计算公式可以简化为:

Token数量 ≈ (调整后的图像高度 / 28) × (调整后的图像宽度 / 28)


1. 对于 2550 × 3300 的图片

这张图片非常高,类似于一张海报或传单。

  1. 调整分辨率
    • 模型需要将尺寸调整为 28 的倍数。
    • 2550 ÷ 28 ≈ 91.07 -> 向下取整为 91 倍 -> 91 * 28 = 2548
    • 3300 ÷ 28 ≈ 117.86 -> 向上取整为 118 倍 -> 118 * 28 = 3304
    • 因此,调整后的分辨率大约为 2548 × 3304
  2. 计算Token数量
    • Token数 = (2548 / 28) × (3304 / 28) = 91 × 118 = 10,738 tokens

结论:这张 2550 × 3300 的图片经过预处理后,大约会产生 10,738 个视觉 Token。


2. 对于 1920 × 1280 的图片

这张图片是接近 16:10 的宽屏比例。

  1. 调整分辨率
    • 1920 ÷ 28 ≈ 68.57 -> 通常向下取整为 68 倍 -> 68 * 28 = 1904
    • 1280 ÷ 28 ≈ 45.71 -> 通常向下取整为 45 倍?但 4528=1260,与 1280 相差较大。更合理的做法是寻找最接近的倍数。1288 (4628) 比 1260 更接近 1280。
    • 因此,调整后的分辨率可能为 1904 × 1288
  2. 计算Token数量
    • Token数 = (1904 / 28) × (1288 / 28) = 68 × 46 = 3,128 tokens

结论:这张 1920 × 1280 的图片经过预处理后,大约会产生 3,128 个视觉 Token。

测试验证的代码如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import requests
import json
import base64
from datetime import datetime

url = "http://qwen-vl-tc-aigcprd.toolchain.zvalley.com/v1/chat/completions"
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer <YOUR_API_KEY>'
}

def encode_base64_content_from_url(image_path: str) -> str:
with open(image_path, "rb") as image_file:
base64_data = base64.b64encode(image_file.read()).decode('utf-8')

return base64_data

def video_to_data_url(video_path):
with open(video_path, "rb") as video_file:
base64_video = base64.b64encode(video_file.read()).decode('utf-8')
return f"data:video/mp4;base64,{base64_video}"

model = "Qwen2.5-VL-32B-Instruct"

# video_path = "./datasets/space_woaudio.mp4"
# video_data_url = video_to_data_url(video_path)


SYSTEM_PROMPT = f"""You are a helpful assistant.
"""

USER_PROMPT = """
请描述一下视频内容
"""

# messages = [
# {"role": "system", "content": SYSTEM_PROMPT},
# {
# "role": "user",
# "content": [
# {
# "type": "text",
# "text": USER_PROMPT,
# },
# # {"type": "image_url", "image_url": {
# # "url": f"data:image/jpeg;base64,{image_base64}"}
# # },
# {"type": "video_url", "video_url": {
# "url": video_data_url}
# },
#
# ],
# },
#
# ]

messages = [
{'role': 'user', 'content': [
{'type': 'text', 'text': '请描述一下图片内容'},
{'type': 'image_url', 'image_url': {'url': 'https://qmcrm.zoomlion.com/cmm-nginx-beta/document/file/v1/unsigned/download?fileId=574ac51a54b142bc912ed62a480efca0'}},
# {'type': 'image_url', 'image_url': {'url': 'https://qmcrm.zoomlion.com/cmm-nginx-beta/document/file/v1/unsigned/download?fileId=48b0e6fecf8f415b978d414a9daf1fc9'}},
# {'type': 'image_url', 'image_url': {'url': 'https://qmcrm.zoomlion.com/cmm-nginx-beta/document/file/v1/unsigned/download?fileId=1996c765b14641ee923480a2f73df2d0'}},
# {'type': 'image_url', 'image_url': {'url': 'https://qmcrm.zoomlion.com/cmm-nginx-beta/document/file/v1/unsigned/download?fileId=619c296005654d9fb09ce38014eea01f'}}
]}]

data = {"model": model, "messages": messages, "temperature": 0.15, "max_tokens": 1024}

start_time = datetime.now()
response = requests.post(url, headers=headers, data=json.dumps(data))
end_time = datetime.now()
print("end_time-start_time=", end_time - start_time)
print(response.json())
print("="* 20)
# print(response.json()["choices"][0]["message"]["content"])

print(response)


------------------------------------------------------------------------
end_time-start_time= 0:00:25.723125
{'id': 'chatcmpl-b1d6c2ef2662220d69be061b60ce809a', 'object': 'chat.completion', 'created': 1757473751, 'model': 'Qwen2.5-VL-32B-Instruct', 'choices': [{'index': 0, 'message': {'role': 'assistant', 'reasoning_content': None, 'content': '### 图片内容描述\n\n这张图片展示了 **最全初中数学知识点全总结** 中的一部分,具体是 ******', 'tool_calls': []}, 'logprobs': None, 'finish_reason': 'stop', 'stop_reason': None}], 'usage': {'prompt_tokens': 10764, 'total_tokens': 11570, 'completion_tokens': 806, 'prompt_tokens_details': None}, 'prompt_logprobs': None}
====================
<Response [200]>

💎 高分辨率视频示例

如果是一条长度为 106 秒、12 帧每秒、分辨率为 2504*1390 的视频,以及一条长度为 106 秒,30 帧每秒,分辨率为 1280×720 的视频呢,分别有多少 token?

1. 对于长度为 106 秒、12 帧每秒、分辨率为 2504*1390 的视频

  • Token数 = 71000

2. 对于长度为 106 秒,30 帧每秒,分辨率为 1280*720 的视频

  • Token数 = 19163 『其中(25041390/(1280720))*19163约等于71000』

3. 对于一张 1280*720 的图片,对应的 token 为 1223,视频约采样 15~16 帧图片

本文结束 感谢您的阅读