vLLM 适配 GLM-4.6V 模型
随着 vLLM/SGLang 等推理引擎的更新,下面内容记录的方式可能已经不再适用,可以考虑尝试直接使用最新版本的 vLLM/SGLang 运行
记录使用 vLLM 部署 GLM-4.6V 的适配过程
模型:
- HuggingFace: zai-org/GLM-4.6V
- ModelScope: ZhipuAI/GLM-4.6V
主要针对 vLLM 做适配,目前发现的需要适配的有:
- 升级 transformers 版本大于等于 5.0.0
- 针对 transformers>=5.0.0 修改 vLLM 的一些代码(比较少,不修改在启动模型时会报错)
一、vLLM
基于vllm/vllm-openai:v0.13.0镜像做修改
可以直接使用修改过后的镜像(arm64镜像的没有做验证),后面的步骤可以供参考自行修改其他版本
# 自适配
swr.cn-east-3.myhuaweicloud.com/r4in/vllm/vllm-openai:v0.13.0-transformers-5.0.0rc2
# amd64
swr.cn-east-3.myhuaweicloud.com/r4in/vllm/vllm-openai:v0.13.0-amd64-transformers-5.0.0rc2
# arm64
swr.cn-east-3.myhuaweicloud.com/r4in/vllm/vllm-openai:v0.13.0-arm64-transformers-5.0.0rc2
注意,使用上面的镜像时,需要注意 entrypoint 从 vLLM 镜像原本的vllm serve改成了/bin/bash,所以在运行时需要注意 vllm 启动命令的变化或者修改 entrypoint
1.1 对镜像做修改
由于升级 transformers 时会有报错,使用 Dockerfile 方式去 build 镜像将不能正常构建,所以先使用 vLLM 的镜像起一个容器,在容器中对环境进行修改,验证没问题之后对容器docker commit生成镜像
# 先使用官方镜像起一个容器
# 如果下面的镜像拉取失败,可以换成 swr.cn-east-3.myhuaweicloud.com/r4in/vllm/vllm-openai:v0.13.0
docker run -itd --name vllm \
-v /model:/model \
--ipc=host \
--network host \
--shm-size 500G \
--gpus all \
--entrypoint /bin/bash \
vllm/vllm-openai:v0.13.0
docker exec -it vllm bash
1.1.1 改动1: 升级 transformers
执行下面的命令进行安装时,最后会有一个报错,可以先忽略,实际上通过pip list查看 transformers 版本已经是新版本
# 在容器里面执行
pip install transformers==5.0.0rc2 -i "https://mirrors.aliyun.com/pypi/simple"
安装后会有如下报错,可忽略
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
vllm 0.13.0 requires transformers<5,>=4.56.0, but you have transformers 5.0.0rc2 which is incompatible.
Successfully installed huggingface-hub-1.3.2 transformers-5.0.0rc2 typer-slim-0.21.1

查看 transformers 版本,已经是新版本
pip list | grep transformers
transformers 5.0.0rc2
1.1.2 改动2: 修改 vLLM 代码
需修改如下两个文件:
- /usr/local/lib/python3.12/dist-packages/vllm/config/model.py
- /usr/local/lib/python3.12/dist-packages/vllm/transformers_utils/config.py
将文件中的from transformers.configuration_utils import ALLOWED_LAYER_TYPES修改为from transformers.configuration_utils import ALLOWED_MLP_LAYER_TYPES as ALLOWED_LAYER_TYPES
容器中没有 vim 等命令,直接使用 sed 修改
# 第14行左右
sed -i '/from transformers.configuration_utils import ALLOWED_LAYER_TYPES/s/ALLOWED_LAYER_TYPES/ALLOWED_MLP_LAYER_TYPES as ALLOWED_LAYER_TYPES/' /usr/local/lib/python3.12/dist-packages/vllm/config/model.py
# 第18行左右
sed -i '/from transformers.configuration_utils import ALLOWED_LAYER_TYPES/s/ALLOWED_LAYER_TYPES/ALLOWED_MLP_LAYER_TYPES as ALLOWED_LAYER_TYPES/' /usr/local/lib/python3.12/dist-packages/vllm/transformers_utils/config.py
1.2 启动模型
# --tensor-parallel-size 指定为 8 或 4 都可以
vllm serve /model/ZhipuAI--GLM-4.6V \
--tensor-parallel-size 8 \
--served-model-name glm-4.6v \
--trust-remote-code \
--tool-call-parser glm45 \
--reasoning-parser glm45 \
--enable-auto-tool-choice \
--enable-expert-parallel \
--allowed-local-media-path / \
--mm-encoder-tp-mode data \
--host 0.0.0.0 \
--port 8000 \
--mm-processor-cache-type shm
1.3 打包镜像
在容器中验证没有问题之后,将容器环境直接打包成镜像,以便在其他环境中复用
docker commit vllm vllm/vllm-openai:v0.13.0-transformers-5.0.0rc2
二、测试模型
测试 VL 模型需要传入一个图片,可以是 url地址或者将图片转成 base64 编码
下面使用两种方式进行测试,curl 和 python,如果在 curl 里面直接使用图片的 base64 编码,会提示超出 curl 限制,所以curl直接提供一个图片的 url,使用一个 python 脚本展示 base64 编码的图片
测试图片(仙逆动漫王林):

2.1 方式一:curl
使用 python 运行一个文件服务器,如果有可以直接下载的图片,可以不用自行起服务器
# 准备一张图片
ls
wl.png
# 启动文件服务器
python -m http.server 80
通过 curl 调用文件,注意替换model、url等内容,url是图片url
curl http://x.x.x.x:8810/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "glm-4.6v",
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "描述一下这幅图片" },
{
"type": "image_url",
"image_url": {
"url": "http://x.x.x.x/wl.png"
}
}
]
}
],
"max_tokens": 30000
}'
示例输出(vLLM):
{"id":"chatcmpl-bd1c9459b5162269","object":"chat.completion","created":1768495563,"model":"glm-4.6v","choices":[{"index":0,"message":{"role":"assistant","content":"\n这幅图片呈现了一个**男性动画角色**,整体风格偏向幻想题材,细节刻画精致: \n\n### 角色形象 \n- **发
型与发饰**:他拥有一头长而飘逸的头发,发色以**纯白**为主,右侧发间点缀着**蓝色装饰性发饰**(造型精致,带有卷曲感),左侧则有一枚**白色卷曲发饰**,增添了华丽与神秘感。 \n- **面部特征**:面部轮廓分明,眉毛浓黑,眼睛是**深邃的蓝色**,眼神锐利且带着冷峻感;嘴唇呈淡粉色
,表情严肃,气质冷冽。 \n- **身体状态**:上半身赤裸,展现出健硕的肌肉线条,姿态前倾,似处于动态或蓄势待发的状态,凸显力量感。 \n\n\n### 背景环境 \n背景是**模糊的荒凉山地景观**,色调以暖黄、棕褐为主,营造出苍茫、肃穆的氛围(仿佛黄昏或荒野场景),与角色的冷峻气质形
成呼应,强化了画面的幻想感与张力。 \n\n\n整体画面通过精致的细节(发饰、肌肉线条)、鲜明的色彩对比(白发与蓝饰、暖调背景与冷峻角色),传递出一种**冷峻、神秘且充满力量**的视觉感受,仿佛角色身处一场史诗般的奇幻冒险中。","refusal":null,"annotations":null,"audio":null,"f
unction_call":null,"tool_calls":[],"reasoning":"用户现在需要描述这幅图片,首先看图片内容:是一个男性动画角色,白发,有蓝白渐变?不对,看头发,左边是白色,右边有蓝色装饰?然后发型有精致的设计,比如左边的白色卷曲装饰,右边的蓝色发饰。面部特征:深色眉毛,蓝色眼睛,表情
严肃。上半身赤裸,肌肉线条明显。背景是模糊的荒凉山地,色调偏暖黄和棕褐色,可能是黄昏或荒野场景。\n\n首先分解元素:角色形象(发型、发饰、面部、身体)、背景环境。然后组织语言,描述细节,比如发型的颜色和装饰,面部的表情和特征,身体的姿态,背景的氛围。\n\n现在开始描述:
这幅图片展示了一个男性动画角色,他有着一头长而飘逸的头发,发色以白色为主,右侧发间点缀着蓝色的装饰性发饰,左侧则有一枚精致的白色卷曲发饰,增添了华丽感。他的面部轮廓分明,眉毛浓黑,眼睛是深邃的蓝色,眼神锐利,表情严肃,嘴唇呈淡粉色,整体气质冷峻。上半身赤裸,展现出健
硕的肌肉线条,姿态前倾,似乎处于动态或准备行动的状态。背景是模糊的荒凉山地景观,色调偏暖黄与棕褐,营造出一种苍茫、肃穆的氛围,仿佛是黄昏或荒野中的场景,与角色的冷峻气质相呼应,整体画面充满幻想风格,细节刻画精致,色彩对比鲜明,突出了角色的独特魅力与场景的苍凉感。\n\n
需要检查是否准确:头发颜色,白色和蓝色装饰;面部特征,蓝色眼睛,严肃表情;身体状态,赤裸,肌肉;背景,荒凉山地,暖色调。这样描述应该涵盖主要元素,让读者能想象出画面。","reasoning_content":"用户现在需要描述这幅图片,首先看图片内容:是一个男性动画角色,白发,有蓝白渐变
?不对,看头发,左边是白色,右边有蓝色装饰?然后发型有精致的设计,比如左边的白色卷曲装饰,右边的蓝色发饰。面部特征:深色眉毛,蓝色眼睛,表情严肃。上半身赤裸,肌肉线条明显。背景是模糊的荒凉山地,色调偏暖黄和棕褐色,可能是黄昏或荒野场景。\n\n首先分解元素:角色形象(发
型、发饰、面部、身体)、背景环境。然后组织语言,描述细节,比如发型的颜色和装饰,面部的表情和特征,身体的姿态,背景的氛围。\n\n现在开始描述:这幅图片展示了一个男性动画角色,他有着一头长而飘逸的头发,发色以白色为主,右侧发间点缀着蓝色的装饰性发饰,左侧则有一枚精致的白
色卷曲发饰,增添了华丽感。他的面部轮廓分明,眉毛浓黑,眼睛是深邃的蓝色,眼神锐利,表情严肃,嘴唇呈淡粉色,整体气质冷峻。上半身赤裸,展现出健硕的肌肉线条,姿态前倾,似乎处于动态或准备行动的状态。背景是模糊的荒凉山地景观,色调偏暖黄与棕褐,营造出一种苍茫、肃穆的氛围,
仿佛是黄昏或荒野中的场景,与角色的冷峻气质相呼应,整体画面充满幻想风格,细节刻画精致,色彩对比鲜明,突出了角色的独特魅力与场景的苍凉感。\n\n需要检查是否准确:头发颜色,白色和蓝色装饰;面部特征,蓝色眼睛,严肃表情;身体状态,赤裸,肌肉;背景,荒凉山地,暖色调。这样描
述应该涵盖主要元素,让读者能想象出画面。"},"logprobs":null,"finish_reason":"stop","stop_reason":151336,"token_ids":null}],"service_tier":null,"system_fingerprint":null,"usage":{"prompt_tokens":6044,"total_tokens":6718,"completion_tokens":674,"prompt_tokens_details":n
ull},"prompt_logprobs":null,"prompt_token_ids":null,"kv_transfer_params":null}
2.2 方式二:Python
Python 脚本如下(将本地图片转成 base64 编码,然后发送给模型):
import base64
import requests
from pathlib import Path
BASE_URL = "http://localhost:30000/v1/chat/completions"
MODEL_ID = "glm-4.6v"
# API_KEY 可以是空或 None
API_KEY = None
# 本地图片地址
IMAGE_PATH = "wl.png"
MAX_TOKENS = 30000
REQUEST_TIMEOUT = 30000
def image_to_base64(image_path: str) -> str:
image_path = Path(image_path)
suffix = image_path.suffix.lower().lstrip(".")
with open(image_path, "rb") as f:
image_bytes = f.read()
b64 = base64.b64encode(image_bytes).decode("utf-8")
return f"data:image/{suffix};base64,{b64}"
print(f"准备处理图片: {IMAGE_PATH}")
image_base64 = image_to_base64(IMAGE_PATH)
payload = {
"model": MODEL_ID,
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "描述一下这幅图片"},
{
"type": "image_url",
"image_url": {
"url": image_base64
}
}
]
}
],
"max_tokens": MAX_TOKENS
}
headers = {
"Content-Type": "application/json",
}
if API_KEY:
headers["Authorization"] = f"Bearer {API_KEY}"
print(f"模型信息: MODEL_ID={MODEL_ID} BASE_URL={BASE_URL}")
print("已发送请求,等待模型响应...")
resp = requests.post(
BASE_URL,
headers=headers,
json=payload,
timeout=REQUEST_TIMEOUT
)
resp.raise_for_status()
print("模型响应结果:")
print(resp.json())
示例结果(SGLang推理引擎):
{'id': '09f5f3edfc5346bfa4da4822e8c4a902', 'object': 'chat.completion', 'created': 1768498283, 'model': 'glm-4.6v', 'choices': [{'index': 0, 'message': {'role': 'assistant', 'content': '<think>用户现在需要描述这幅图片,首先看图片内容:是一个男性动画角色,白发,有蓝色和白色的发饰,面部特征精致,眼神锐利,上半身赤裸,背景是模糊的荒凉山地,色调偏暖黄和棕褐色。\n\n现在组织语言:这幅图片展示了一位男性动画角色。他有着一头银白色的长发,发间装饰着精致的白色和蓝色发饰,增添了奇幻感。他的面部轮廓分明,眼神锐利深邃,眉宇间透露出坚毅的气质。上半身赤裸,展现出健硕的体态,肌肉线条流畅。背景是模糊的荒凉山地,色调以暖黄和棕褐色为主,营造出一种苍茫、肃穆的氛围,整体风格偏向奇幻或玄幻题材的动画角色设定。\n\n需要检查是否涵盖主要元素:角色外貌(白发、发饰、面部、体态)、背景环境(荒凉山地、色调)、整体风格(奇幻/玄幻动画角色)。确保描述清晰,细节到位。</think>\n这幅图片展示了一位男性动画角色。他有着一头银白色的长发,发间装饰着精致的白色和蓝色发饰,增添了奇幻感。他的面部轮廓分明,眼神锐利深邃,眉宇间透露出坚毅的气质。上半身赤裸,展现出健硕的体态,肌肉线条流畅。背景是模糊的荒凉山地,色调以暖黄和棕褐色为主,营造出一种苍茫、肃穆的氛围,整体风格偏向奇幻或玄幻题材的动画角色设定。', 'reasoning_content': None, 'tool_calls': None}, 'logprobs': None, 'finish_reason': 'stop', 'matched_stop': 151336}], 'usage': {'prompt_tokens': 6044, 'total_tokens': 6358, 'completion_tokens': 314, 'prompt_tokens_details': None, 'reasoning_tokens': 0}, 'metadata': {'weight_version': 'default'}}