单卡 MI300X 跑 Gemma 4 E2B:64 流吞吐破万,每美元 2713 tok/s
AIAI Summary (BLUF)
本文提供了一份在 AMD Instinct MI300X 上部署 Gemma 4 E2B 模型的完整指南。通过 AMD Developer Cloud 获取 GPU 资源,并使用 vLLM 进行服务化,同时配套了一套基于 Python MCP 的工具集来简化部署管理。文章详细记录了从环境准备、镜像选择、服务启动到性能测试的全过程,并给出了关键的性能数据。
核心洞察
这篇文章最有意思的地方在于它把"部署"和"算账"绑在了一起。大多数人写部署教程只告诉你跑通了,但这里作者把每一档并发下的吞吐、延迟、每美元能换多少 token 全摆出来了。我比较欣赏的是它没有回避一个尴尬事实:如果按 spot 价格算,TPU v5e 在低并发下其实更便宜。
这篇文章是一份把 Gemma 4 E2BGoogle 开源的小型大语言模型,本教程中被部署为推理服务的模型本体,权重挂载路径为 /mnt/models/gemma-4-E2B-it。 部署到 AMD Instinct MI300XAMD 推出的 GPU 加速卡,基于 CDNA 3 架构,拥有 191.7 GiB HBM3 内存,适用于高性能计算和 AI 推理。 的完整操作指南。整套 Python MCPModel Context Protocol - a protocol that enables AI models to access external tools, data sources, and services to enhance their capabilities and context awareness. 工具用来简化 vLLM一个高性能的LLM推理和服务库,为DeepSeek-OCR提供优化的推理能力,支持流式输出和批量处理。 部署的管理。显卡通过 AMD Developer CloudAMD 提供的云平台,用于访问 GPU 资源,底层基于 DigitalOcean,提供 GPU droplet 实例。 访问,底层是 DigitalOcean,而驱动它的工作站本身完全没有 AMD GPU。
代码仓库地址:github.com/xbill9/gemma4-dev/tree/main/gpu-vllm-mi300x-2b
| 模型 | google/gemma-4-E2B-it,bf16 参考版本 |
| 硬件 | 1x AMD Instinct MI300X,gfx942,CDNA 3,191.7 GiB HBM3 |
| 主机 | gpu-mi300x1-192gb-devcloud — 20 vCPU,240 GB 内存,720 GB 磁盘,区域 atl1 |
| 控制面 | AMD Developer Cloud / DigitalOcean v2 API — 不用 gcloud,不用 EC2 |
| 软件 | vllm/vllm-openai-rocm:nightly-rocm100,vLLM 0.3.1.dev3+g0bfc7a15d |
| 费率 | 每小时 1.99 美元,按需 |
| 结果 | 单流 305 tok/s,64 流 10,284 tok/s,每美元小时 2,713 tok/s |
核心结论
单张 AMD Instinct MI300X 部署 Gemma 4 E2B 的实测吞吐:1,024 上下文下单流达 305 tok/s(首 token 延迟 32.67 毫秒),64 流、128 上下文下达 10,284 tok/s;计入 prefill 后最忙格子每秒处理 48,583 个 token。
性价比在匹配按需定价下领先:MI300X 按需费率 1.99 美元/小时,实测每秒每美元小时可换 2,713 个 token,是 TPU v5e-1 按需价(1.20 美元/小时)的 1.4 到 2.9 倍(四档并发分别为 1.53x、1.42x、1.76x、2.86x)。
长上下文场景的瓶颈是 prefill 而非显存:引擎分配 KV 池 9,026,017 个 token,最重格子(64 流 × 8,192 上下文)仅占用 5.8%;每输出 token 时间从 128 上下文的 2.85 毫秒仅升至 8,192 上下文的 3.9 毫秒,而首 token 时间从 12.97 毫秒升至 169.61 毫秒。
启动到就绪存在显著延迟:
docker run约 1 秒返回,但端点需 160 秒才应答,期间权重加载、torch.compile与计算图捕获依次进行,因此容器运行状态与模型服务状态必须分开报告。音频能力在 ROCm vLLM 镜像上不可用:文本、思考、工具调用、视觉四项能力均在实时端点验证通过,但没有任何 ROCm vLLM 镜像包含
vllm[audio]扩展,故--limit-mm-per-prompt中 audio 必须设为 0。
从哪里开始
这张卡是通过 AMD Developer Cloud(devcloud.amd.com)访问的 DigitalOcean GPU droplet,用的是同一套 v2 API、同样的 droplet id,token 从 My AMD Team 账户获取。创建和销毁都是控制台操作,这是故意的:两者都是按小时计费的决策,不该放进 agent 能调用的工具里。
创建之后的所有步骤都是脚本化的。droplet 带一个标签,所有工具都限定在这个标签范围内,工具箱里的任何东西都碰不到没有这个标签的实例。
到这一步你应该已经有了
- 一个运行中的 GPU droplet,已打标签,可以通过 SSH 密钥访问
DIGITALOCEAN_ACCESS_TOKEN放在服务器旁边一个权限为 0600 的.env文件里- droplet 上装了 Docker,
/dev/kfd和/dev/dri都在 - droplet 上有一个 Hugging Face token,用于访问受限的 Gemma 4 权重
- 工作站上有 Python 3,别指望它里面有 GPU
你运行这套东西的机器上没有 GPU
rocm-smi、amd-smi、rocminfo 和 hipcc 在本地不存在,以后也不会有。在本地 shell 里跑 ROCm 命令是 bug,不是检查步骤。这篇文章里的每一个读数都是通过 SSH 或 DigitalOcean API 拿回来的。
这个约束塑造了整个工具箱。MCP 服务器跑在工作站上,不持有任何设备,访问硬件的方式和你手动操作一模一样。
MCP 服务器走 stdio
一个文件,server.py,通过 stdio 暴露 21 个工具:droplet 生命周期、镜像检查、部署、日志、基准测试单元,以及下面的 SRE 探针。每个子进程都走同一个 run_command(cmd: list[str]) 辅助函数,用的是 asyncio.create_subprocess_exec,从不走 shell。
$ grep -c "^@mcp.tool" server.py
21
droplet 是什么
$ droplet_status
📡 debian-gpu-mi300x1-192gb-devcloud-atl1 (601418522)
- status: `active`
- size: `gpu-mi300x1-192gb-devcloud` — 20 vCPU, 240 GB RAM, 720 GB disk
- region: atl1
- cost: $1.99/hr — billed while powered off, too
费率来自 DigitalOcean v2 API 自己的 droplet.size.price_hourly 字段,在运行时读取,不是从定价页面抄的。
什么都还没跑之前的卡
$ gpu_status
✅ GPU reporting on `debian-gpu-mi300x1-192gb-devcloud-atl1`.
| Card | Product | GPU use % | VRAM used % |
| card0 | Aqua Vanjaram [Instinct MI300X VF] | 0 | 0 |
它呈现为一个 SR-IOV 虚拟功能,看起来像分区但并不是:全部 304 个计算单元和完整的 191.7 GiB 都在。rocm-smi 和 amd-smi 都不设置有用的退出码,所以工具直接解析输出,完全忽略状态码。
用哪个镜像
vllm/vllm-openai-rocm:nightly-rocm100。版本号要从运行中的容器里读出来,别信 tag,因为 nightly tag 会变:
$ docker exec vllm python3 -c "import vllm;print(vllm.__version__)"
0.3.1.dev3+g0bfc7a15d
不是每个 ROCm vLLM 镜像都能加载这个 checkpoint。Gemma 4 的滑动注意力层用 256 宽的头,全注意力层用 512 宽,vLLM 版本早于这个拆分点的镜像解析不了配置。三个镜像的实测对比在配套文章里。这里的实用规则就够了:锁定你测过的版本,采纳新镜像之前先检查。
启动服务器
$ deploy_vllm
✅ Started `vllm` serving `google/gemma-4-E2B-it`.
- image: `vllm/vllm-openai-rocm:nightly-rocm100`
- context: 32768, gpu-memory-utilization 0.90
- multimodal: `{"image": 4, "audio": 0}`
完整的 serve 参数:
vllm serve google/gemma-4-E2B-it --host 0.0.0.0 --port 8000 \
--max-model-len 32768 --gpu-memory-utilization 0.90 \
--enable-auto-tool-choice --reasoning-parser gemma4 --tool-call-parser gemma4 \
--chat-template /app/vllm/examples/tool_chat_template_gemma4.jinja \
--limit-mm-per-prompt '{"image": 4, "audio": 0}' --async-scheduling
audio 设成 0 是有意的。E2B 有一个 conformer 音频编码器,而没有任何 ROCm vLLM 镜像带 vllm[audio] 扩展,所以设成非零值只会为一条走不通的路径分配编码器内存。
起来了不等于就绪了
docker run 大约一秒就返回了。端点再过两分半才应答,这期间权重在加载、torch.compile 在跑、计算图在捕获。
# 从 docker run 返回的那一刻起,每 10 秒轮询 /v1/models
READY after 160s
{"object":"list","data":[{"id":"google/gemma-4-E2B-it","max_model_len":32768,...}]}
这就是为什么 serving_status 把容器和端点报告为两个独立的事实。容器在跑不等于模型在服务,把两者混为一谈会把一次正常的启动变成一场幽灵般的卡死。
$ serving_status
✅ container: Up 3 minutes | endpoint: answering on `127.0.0.1:8000`
served: `google/gemma-4-E2B-it`
它真的能服务吗
一个参数被接受了不等于它起了作用,所以每个模态都用答案已知的请求探测一遍。视觉探针用的是一张生成的棋盘格而不是下载的照片,这样预期答案是关于图像本身的事实,而不是一句描述。
$ verify_capabilities
✅ 4/4 capabilities verified.
| text | ✅ | The AMD MI300X is based on the CDNA 3 architecture. |
| thinking | ✅ | 1404 chars, 447 reasoning tokens |
| tool calling | ✅ | tool_calls → get_weather{"city": "Reykjavik"} |
| vision | ✅ | alternating bright red and royal blue squares |
📡 Audio is not probed — no ROCm vLLM image ships the `vllm[audio]` extras.
引擎分配了什么
在规划任何容量之前,启动日志值得读一读,因为引擎自己会说出它的算术:
Available KV cache memory: 155.04 GiB
GPU KV cache size: 9,026,017 tokens
Maximum concurrency for 32,768 tokens per request: 275.45x
一张卡上九百万 token 的 KV。记住这个数字,它决定了你实际会撞上下面两个天花板中的哪一个。
扫描测试
四档并发对四档上下文长度,全程 128 个输出 token,每个格子重复三次,负载由 vLLM 自带的 bench 客户端在第二个容器里生成,不挂载 GPU 设备。
$ python3 benchmarking_suite.py --droplet debian-gpu-mi300x1-192gb-devcloud-atl1 \
--run-id 2026-09-17-vllm-sweep-mi300x-rerun --repeat 3 --seed-base 27000
16 cells, 12 runnable, 4 infeasible at max_model_len 32768
32,768 那一行是不可行而不是缺失:32,768 输入加 128 输出放不进 32,768 的上下文,一个不可能存在的格子被记录为不可行,而不是被丢掉。
有一个细节很关键。vllm bench serve 从 --seed 推导 prompt,默认值是 0,而这个部署会缓存前缀,所以两次共享同一个 seed 的运行测的是缓存,不是显卡。这里每个格子和每次重复用的 seed 在整个扫描中都是唯一的。
原始每秒 token 数
输出 token 每秒,三次重复的中位数,最差格子的变异系数 7.96%:
| 上下文 | 1 流 | 4 | 16 | 64 |
|---|---|---|---|---|
| 128 | 340.9 | 1,124.1 | 3,569.1 | 🥇 10,284.0 |
| 1,024 | 305.1 | 969.1 | 2,653.6 | 5,398.2 |
| 8,192 | 192.8 | 445.5 | 688.2 | 702.7 |
把 prefill 也算进去,最忙的格子每秒处理 48,583 个 token:64 流、1,024 上下文。1,024 上下文下的单流延迟是首 token 32.67 毫秒,之后每 token 3.04 毫秒。
天花板在哪里
上下文增长时会发生两件事,只有一件是人们规划时会想到的。
每输出 token 的时间几乎不动:128 上下文时 2.85 毫秒,1,024 时 3.04 毫秒,8,192 时 3.9 毫秒。首 token 时间急剧上升:12.97 毫秒、32.67 毫秒、169.61 毫秒。解码一直很便宜,prefill 越来越贵。
所以 8,192 那一行走平了,16 流时 688 tok/s,64 流时 703,而 128 那一行一路爬到 10,284。长上下文下的瓶颈是 prefill,不是内存。 网格里最重的格子需要 64 x 8,192 = 524,288 个 KV token,对比引擎分配的 9,026,017 个,按算术算是池子的 5.8%。
这颠覆了这个 monorepo 里 TPU 机器遵循的容量规划规则,那些机器上 KV 容量是你最先耗尽的东西。在一张 192 GB 的卡上服务一个 2B 模型,它从来不会成为约束。
那性价比呢
吞吐除以小时费率,输入 1024,输出 128。每一行都是同一个 checkpoint 在 vLLM 下跑的,各自来自自己符合 schema 的报告:
| 机器 | $/hr | 计费方式 | 1 | 4 | 16 | 64 |
|---|---|---|---|---|---|---|
| MI300X | 1.99 | 按需 | 153 | 487 | 1,333 | 🥇 2,713 |
| TPU v5e-1 | 0.5779 | spot | 🥇 208 | 🥇 711 | 🥇 1,572 | 1,972 |
| TPU v6e-1 | 2.97 | 按需 | 67 | 233 | 587 | 719 |
| NVIDIA L4 | 0.94 | spot | 49 | 187 | — | — |
每秒每美元小时能换多少 token。MI300X 在最忙的那一列完胜,v5e 赢了另外三列,但这两行的定价方式不一样,这正是下一节的全部内容。
同一张表,同样的定价方式
拿 spot 价格对按需价格是一种折扣,不是硬件结果。把三者都放到按需列表价上,排名就统一了:
| 机器 | $/hr | 1 | 4 | 16 | 64 |
|---|---|---|---|---|---|
| 🥇 MI300X | 1.99 | 153 | 487 | 1,333 | 2,713 |
| 🥈 TPU v5e-1 | 1.20 | 100 | 342 | 757 | 950 |
| 🥉 TPU v6e-1 | 2.70 | 74 | 257 | 646 | 790 |
在匹配定价下,MI300X 每美元返回的 token 是 v5e 的 1.4 到 2.9 倍:四档并发下分别是 1.53x、1.42x、1.76x 和 2.86x,这是对上面两行实测数据做算术得出的。
spot 的星号
这个优势有一个实实在在的限制条件:DigitalOcean 没有为 GPU droplet 提供可抢占层,所以 1.99 美元是底价。v5e 的 spot 价格每芯片小时 0.5779 美元是一个可以买到的选项,在 16 流或更少时,它每美元买到的 token 比这张卡多。
所以这个发现的诚实形式是有条件的。如果你要一台不可抢占的机器,或者你让几十条流一直忙着,这是这些机器里测到的最佳每美元 token 数。如果你的流量很轻,又能容忍被抢占,spot TPU 更便宜。
什么能让计费停下来
把 droplet 关机不能让 DigitalOcean 停止计费。资源仍然被保留,小时费率继续走,只有销毁 droplet 才能停表。
这就是为什么这个工具箱不提供 create 也不提供 destroy 工具。两者都是按小时计费的决策,它们保持在控制台里作为一个刻意的步骤,而一个按小时报出的费率在你搞清楚自己在为哪些小时付费之前毫无意义。
$ stop_vllm
✅ `docker rm -f vllm` exited 0.
这释放了显卡的内存。它不释放你的钱包。
总结
这篇文章的目标是把 Gemma 4 E2B 部署到一张 AMD Instinct MI300X 上,并测量它的小时费率能换回多少 token。方案的关键是一个按标签限定的 MCP 工具箱,通过 SSH 和 DigitalOcean API 访问显卡,因为运行它的机器没有 GPU。实测结果是:
- 1,024 上下文下单流 305 tok/s,首 token 32.67 毫秒
- 64 流、128 上下文下 10,284 tok/s;算上 prefill 是 48,583 tok/s
- 每秒每美元小时 2,713 个 token,在匹配按需定价下是 TPU v5e 的 1.4 到 2.9 倍
- 从
docker run到端点应答用了 160 秒 - KV 池 9,026,017 个 token,最重的格子用了 5.8%,长上下文的天花板是 prefill 不是内存
- 文本、思考、工具调用和视觉都在实时端点上验证通过;音频在所有试过的 ROCm 镜像上都走不通
一个 droplet,一张 MI300X,区域 atl1,每个格子三次重复,每次用唯一的 prompt seed,最差格子变异系数 7.96%。对比行和这一行有几个值得点名的差异:TPU 和 L4 的报告是每个格子单次运行,用的是更早的 vLLM 版本;v5e 和 L4 的数字是在 spot 容量上测的,而 MI300X 和 v6e 是按需;这个 monorepo 里没有别的东西在 AMD 上服务这个 checkpoint,所以没有可以对照的孪生数据。
用 MCP 做 AMD Instinct 部署和基准测试的策略,是通过增量式的分步方法验证的。
常见问题(FAQ)
在 AMD MI300X 上部署 Gemma 4 E2B 需要哪些前提条件?
需要 AMD Developer Cloud 的 GPU droplet(已打标签、SSH 密钥访问)、DIGITALOCEAN_ACCESS_TOKEN 放在 0600 权限的 .env 文件、Docker 及 /dev/kfd 和 /dev/dri 设备、Hugging Face token 用于受限权重,工作站有 Python 3 但无 GPU。
为什么本地没有 GPU 还能管理 MI300X 上的 vLLM 部署?
因为 MCP 服务器通过 stdio 暴露 21 个工具,所有硬件操作都通过 SSH 或 DigitalOcean API 远程执行。本地不运行 ROCm 命令,每个读数都从远程获取,所以工作站无需 GPU。
Gemma 4 E2B 在 MI300X 上的性能与成本如何?
单流 305 tok/s,64 流 10,284 tok/s,每美元小时 2,713 tok/s。使用 vLLM 0.3.1.dev3 和 nightly-rocm100 镜像,按需费率 1.99 美元/小时。低并发下 TPU v5e 按 spot 价格可能更便宜。
版权与免责声明:本文仅用于信息分享与交流,不构成任何形式的法律、投资、医疗或其他专业建议,也不构成对任何结果的承诺或保证。
文中提及的商标、品牌、Logo、产品名称及相关图片/素材,其权利归各自合法权利人所有。本站内容可能基于公开资料整理,亦可能使用 AI 辅助生成或润色;我们尽力确保准确与合规,但不保证完整性、时效性与适用性,请读者自行甄别并以官方信息为准。
若本文内容或素材涉嫌侵权、隐私不当或存在错误,请相关权利人/当事人联系本站,我们将及时核实并采取删除、修正或下架等处理措施。也请勿在评论或联系信息中提交身份证号、手机号、住址等个人敏感信息。



