模型:gemma-4-31B-it gemma-4-31B-it
URL:http://localhost:8005/
model = "gemma-4-31b"
日志查看:docker logs -f gemma4-vllm
1 部署
第1步:先从modelscope上下载模型
进入spark目录:/home/admin/models/modelscope/
git clone https://www.modelscope.cn/google/gemma–4–31B–it.git
第2步:拉取gemma4–cu130镜像
docker pull vllm/vllm–openai:gemma4–cu130
或
docker pull docker.1ms.run/vllm/vllm–openai:gemma4–cu130
第3步:部署gemma–4–31B–it
docker run –d \\
—gpus all \\
—name gemma4–vllm \\
—restart unless–stopped \\
–v /home/admin/models:/home/admin/models \\
–p 8005:30000 \\
docker.1ms.run/vllm/vllm–openai:gemma4–cu130 \\
—model /home/admin/models/modelscope/gemma–4–31B–it \\
—served–model–name gemma–4–31b \\
—enable–auto–tool–choice \\
—tool–call–parser pythonic \\
—reasoning–parser gemma4 \\
—gpu–memory–utilization 0.70 \\
—host 0.0.0.0 \\
—port 30000 \\
—kv–cache–dtype fp8 \\
—load–format safetensors \\
—enable–prefix–caching \\
—enable–chunked–prefill \\
—max–model–len 262144 \\
—max–num–seqs 4 \\
—max–num–batched–tokens 8192
第4步:当容器停止后,重新启动即可
补充:
vLLM 部署 Gemma 4 模型的 Docker 命令中每个参数的含义:
Docker 基础参数
| -d | 后台运行(detached mode),容器在后台运行,不占用当前终端 |
| –gpus all | 启用所有 GPU,让容器可以访问主机的所有 NVIDIA GPU |
| –name gemma4-vllm | 容器名称,方便后续管理(如 docker stop gemma4-vllm) |
| –restart unless-stopped | 重启策略:除非手动停止,否则容器崩溃或主机重启后自动重启 |
| -v /home/admin/models:/home/admin/models | 卷挂载:将主机的模型目录映射到容器内相同路径,实现模型持久化 |
| -p 8005:30000 | 端口映射:将主机的 8005 端口映射到容器的 30000 端口 |
vLLM 服务参数
| –model /home/admin/models/modelscope/gemma-4-31B-it | 模型路径,指定 Gemma 4 31B 指令微调版的位置 |
| –served-model-name gemma-4-31b | 对外暴露的模型名称,API 调用时使用的标识名 |
| –enable-auto-tool-choice | 启用自动工具选择,让模型自动决定是否调用工具 |
| –tool-call-parser pythonic | 工具调用解析器格式,使用 Python 风格的工具调用格式 |
| –reasoning-parser gemma4 | 推理解析器,专门用于解析 Gemma 4 模型的推理输出格式 |
| –gpu-memory-utilization 0.70 | GPU 内存使用率上限,限制使用 70% 的显存,预留空间给其他进程 |
| –host 0.0.0.0 | 监听地址,绑定到所有网络接口,允许外部访问 |
| –port 30000 | 服务端口,容器内部监听端口(与 Docker 映射的 30000 对应) |
| –kv-cache-dtype fp8 | KV 缓存数据类型,使用 8 位浮点量化,减少显存占用 |
| –load-format safetensors | 模型加载格式,使用 SafeTensors 格式(更安全、加载更快) |
| –enable-prefix-caching | 启用前缀缓存,对相同前缀的输入复用 KV 缓存,加速推理 |
| –enable-chunked-prefill | 启用分块预填充,将长输入分块处理,减少显存峰值占用 |
| –max-model-len 262144 | 最大上下文长度,支持 262,144 tokens(约 20 万字) |
| –max-num-seqs 4 | 最大并发序列数,同时处理 4 个请求序列 |
| –max-num-batched-tokens 8192 | 最大批处理 token 数,每个批次最多处理 8192 个 token |
关键优化点总结
镜像说明
docker.1ms.run/vllm/vllm-openai:gemma4-cu130 是专门为 Gemma 4 优化的镜像:
- cu130 表示基于 CUDA 13.0
- 预装了 Gemma 4 所需的依赖和补丁
部署后,你可以通过 http://<服务器IP>:8005 访问 OpenAI 兼容的 API 接口。
2 测试接口健康
# 请求
curl http://localhost:8005/v1/models
# 响应:
{
"object": "list",
"data": [
{
"id": "gemma-4-31b",
"object": "model",
"created": 1775196908,
"owned_by": "vllm",
"root": "/home/admin/models/modelscope/gemma-4-31B-it",
"parent": null,
"max_model_len": 262144,
"permission": [
{
"id": "modelperm-a344e56a847117e2",
"object": "model_permission",
"created": 1775196908,
"allow_create_engine": false,
"allow_sampling": true,
"allow_logprobs": true,
"allow_search_indices": false,
"allow_view": true,
"allow_fine_tuning": false,
"organization": "*",
"group": null,
"is_blocking": false
}
]
}
]
}
3 测试对话
# 请求
curl http://localhost:8005/v1/chat/completions \\
–H "Content-Type: application/json" \\
–d '{
"model": "gemma-4-31b",
"messages": [
{
"role": "system",
"content": "你是一个helpful的AI助手。"
},
{
"role": "user",
"content": "你好,介绍自己。"
}
],
"temperature": 0.7,
"max_tokens": 1024
}'
# 响应
{
"id": "chatcmpl-8b42bfc9c3dc3d4c",
"object": "chat.completion",
"created": 1775197098,
"model": "gemma-4-31b",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "你好!很高兴为你服务。\\n\\n我是一个由 Google 训练的大型语言模型。你可以把我当作一个**全能的智能助手**,我旨在通过处理和生成文本,帮助用户高效地完成各种任务。\\n\\n**我能为你做些什么?**\\n\\n1. **回答问题**:无论是科学常识、历史文化、编程技术,还是日常生活中的小疑问,我都可以为你提供详细的解答。\\n2. **文字创作**:我可以帮你写邮件、周报、故事、诗歌、论文大纲,或者润色你的文章。\\n3. **语言翻译**:我精通多种语言,可以帮你进行流畅的互译。\\n4. **逻辑分析与编程**:我可以帮你编写代码(多种语言)、调试 Bug,或者分析复杂的问题并提供解决方案。\\n5. **总结与提取**:如果你有长篇文章没时间看,我可以帮你快速总结核心观点。\\n6. **创意启发**:当你没有灵感时,我可以帮你进行头脑风暴,提供各种创意点子。\\n\\n**我的特点:**\\n\\n* **耐心且客观**:我没有情绪,会尽可能客观地为你提供信息。\\n* **学习能力强**:我基于海量的数据训练,知识面非常广泛。\\n* **高效快捷**:我可以瞬间处理大量信息并给出反馈。\\n\\n**怎么与我更好地协作?**\\n\\n如果你能给我**更具体、更清晰的指令(Prompt)**,我给出的答案会更精准。例如,比起说“帮我写个计划”,说“请帮我写一个为期两周的东京旅游计划,预算5000元,侧重于美食和动漫”会效果更好。\\n\\n**那么,现在有什么我可以帮你的吗?**",
"refusal": null,
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": [
],
"reasoning": null
},
"logprobs": null,
"finish_reason": "stop",
"stop_reason": 106,
"token_ids": null
}
],
"service_tier": null,
"system_fingerprint": null,
"usage": {
"prompt_tokens": 43,
"total_tokens": 430,
"completion_tokens": 387,
"prompt_tokens_details": null
},
"prompt_logprobs": null,
"prompt_token_ids": null,
"kv_transfer_params": null
}
4 测试图片理解
# 请求
curl http://localhost:8005/v1/chat/completions \\
–H "Content-Type: application/json" \\
–d '{
"model": "gemma-4-31b",
"messages": [
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": "https://www.171host.com/wp-content/uploads/2026/05/20260506092428-69fb08cc05e78.jpg"
}
},
{
"type": "text",
"text": "这张图片里有什么?"
}
]
}
],
"max_tokens": 512
}'
# 响应:
{
"id": "chatcmpl-96634f63e8a7aabe",
"object": "chat.completion",
"created": 1775196591,
"model": "gemma-4-31b",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "这张图片展现了一个宁静的自然景观。\\n\\n画面中心是一条由浅色木板铺成的**栈道(木栈道)**,它从前景一直延伸到远方,引导观众的视线向深处移动。\\n\\n栈道两旁是茂盛的**绿色草地或湿地**,长满了高高的绿草。在背景中,可以看到一排低矮的**树木和灌木丛**,将草地与天空分隔开。\\n\\n上方是广阔的**蓝色天空**,点缀着轻盈的白色卷云和散落的云朵,光线明亮,给人一种开阔、清新且平静的感觉。",
"refusal": null,
"annotations": null,
"audio": null,
"function_call": null,
"tool_calls": [
],
"reasoning": null
},
"logprobs": null,
"finish_reason": "stop",
"stop_reason": 106,
"token_ids": null
}
],
"service_tier": null,
"system_fingerprint": null,
"usage": {
"prompt_tokens": 281,
"total_tokens": 427,
"completion_tokens": 146,
"prompt_tokens_details": null
},
"prompt_logprobs": null,
"prompt_token_ids": null,
"kv_transfer_params": null
}
5 测试视频理解
Gemma 4 的视频理解通过逐帧抽帧方式实现,将视频关键帧转为多张图片传入
注意: 视频理解目前 vLLM 不支持直接传视频文件,需要手动抽帧后以多图片形式传入。帧数建议控制在 5~10 帧,避免超出 token 限制。
# 用 ffmpeg 从视频抽取关键帧(每秒1帧,取前5帧)
ffmpeg -i /path/to/video.mp4 -vf "fps=1" -frames:v 5 /tmp/frame%d.jpg
# 将每帧转为 base64
FRAME1=$(base64 -w 0 /tmp/frame1.jpg)
FRAME2=$(base64 -w 0 /tmp/frame2.jpg)
FRAME3=$(base64 -w 0 /tmp/frame3.jpg)
FRAME4=$(base64 -w 0 /tmp/frame4.jpg)
FRAME5=$(base64 -w 0 /tmp/frame5.jpg)
curl http://localhost:8005/v1/chat/completions \\
-H "Content-Type: application/json" \\
-d "{
\\"model\\": \\"gemma-4-31b\\",
\\"messages\\": [
{
\\"role\\": \\"user\\",
\\"content\\": [
{\\"type\\": \\"image_url\\", \\"image_url\\": {\\"url\\": \\"data:image/jpeg;base64,${FRAME1}\\"}},
{\\"type\\": \\"image_url\\", \\"image_url\\": {\\"url\\": \\"data:image/jpeg;base64,${FRAME2}\\"}},
{\\"type\\": \\"image_url\\", \\"image_url\\": {\\"url\\": \\"data:image/jpeg;base64,${FRAME3}\\"}},
{\\"type\\": \\"image_url\\", \\"image_url\\": {\\"url\\": \\"data:image/jpeg;base64,${FRAME4}\\"}},
{\\"type\\": \\"image_url\\", \\"image_url\\": {\\"url\\": \\"data:image/jpeg;base64,${FRAME5}\\"}},
{
\\"type\\": \\"text\\",
\\"text\\": \\"以上是一段视频的连续帧,请描述视频的内容、场景和发生了什么事情。\\"
}
]
}
],
\\"max_tokens\\": 1024
}"
6.python示例
from openai import OpenAI
import base64
client = OpenAI(
base_url="http://192.168.0.254:8005/v1",
api_key="dummy" # vLLM 本地不需要真实 key
)
# —- 1. 健康检查 —-
models = client.models.list()
print("可用模型:", [m.id for m in models.data])
# —- 2. Chat —-
response = client.chat.completions.create(
model="gemma-4-31b",
messages=[{"role": "user", "content": "用一句话介绍你自己"}],
max_tokens=256
)
print("Chat:", response.choices[0].message.content)
# —- 3. 图片理解 —-
with open("荷花.png", "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
response = client.chat.completions.create(
model="gemma-4-31b",
messages=[{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}},
{"type": "text", "text": "详细描述这张图片"}
]
}],
max_tokens=512
)
print("图片理解:", response.choices[0].message.content)
# —- 4. 视频理解(多帧) —-
import shutil
import subprocess
import tempfile
from pathlib import Path
# Windows 上 WinError 2:通常是未安装 ffmpeg 或未加入 PATH(需能直接运行 ffmpeg.exe)
_ffmpeg = shutil.which("ffmpeg")
if not _ffmpeg:
raise SystemExit(
"未找到 ffmpeg:请安装并加入 PATH,例如:\\n"
" winget install Gyan.FFmpeg\\n"
"或从 https://ffmpeg.org/download.html 下载,把 bin 目录加到系统 PATH。"
)
_video = Path("12345.mp4")
if not _video.is_file():
raise SystemExit(f"找不到视频文件: {_video.resolve()},请把 12345.mp4 放在当前工作目录或改成正确路径。")
# 抽帧(不要用 /tmp/,Windows 下一般不存在)
_tmp = Path(tempfile.mkdtemp(prefix="vframes_"))
subprocess.run(
[
_ffmpeg,
"-i",
str(_video),
"-vf",
"fps=1",
"-frames:v",
"5",
str(_tmp / "frame%d.jpg"),
"-y",
],
check=True,
)
frames = []
for i in range(1, 6):
with open(_tmp / f"frame{i}.jpg", "rb") as f:
b64 = base64.b64encode(f.read()).decode()
frames.append({
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{b64}"}
})
frames.append({
"type": "text",
"text": "以上是视频的连续帧,请描述视频内容和发生的事情。"
})
response = client.chat.completions.create(
model="gemma-4-31b",
messages=[{"role": "user", "content": frames}],
max_tokens=1024
)
print("视频理解:", response.choices[0].message.content)




