模型:gemma-4-31B-it gemma-4-31B-it
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 |
关键优化点总结
- 显存优化 :
fp8量化 + 70% 显存限制 + 分块预填充,适合大模型部署 - 性能优化:前缀缓存 + SafeTensors 格式,提升重复查询和加载速度
- 功能特性:支持工具调用(Tool Calling)和思维链解析,适配 Gemma 4 原生能力
- 长上下文:26万 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://vllm-public-assets.s3.us-west-2.amazonaws.com/vision_model_images/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.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)`
`