30 分钟,手把手带你从 0 到 1 跑通一个高性能 Token 服务
30 分钟,手把手带你从 0 到 1 跑通一个高性能 Token 服务
用 AMD Radeon 开发者云、vLLM 和 AI Agent,完成一次从服务部署、性能压测、GPU Profiling、Kernel 分析,到自动调优和端到端验证的完整实验。
写在前面
平时我们用大模型写代码、查资料,但能不能让大模型反过来优化大模型推理服务?
AMD 的“AgenticAI 推理自优化”实验给出了一个很有意思的答案:把传统性能工程流程封装成一个 Skill,让 AI Agent 按照预设的步骤启动 vLLM、运行基准测试、采集 GPU Trace、识别瓶颈、调优 GEMM,并用端到端压测验证优化是否真的有效。
这套实验被称为 LLM-for-LLM:使用 LLM Agent 优化 LLM 工作负载。
需要特别说明的是,Agent 并不是凭感觉“魔改”代码。真正起作用的是一个名为 /vllm-optimize 的 Skill。它更像一份可以被 Agent 执行的标准作业程序,明确规定了:
- 要问用户哪些问题;
- 每个阶段运行哪些命令;
- 需要采集哪些性能数据;
- 什么条件下可以接受优化;
- 什么条件下必须回滚;
- 最终报告必须包含哪些内容。
完成本文后,你将跑通下面这条链路:
领取云资源
→ 启动 Notebook 实例
→ 安装并启动 OpenCode
→ 调用 /vllm-optimize Skill
→ 启动 vLLM 服务
→ 并发压测与 GPU Profiling
→ 找到 Kernel 瓶颈和真实 GEMM Shape
→ 离线调优 GEMM
→ 重新进行端到端压测
→ 自动接受或回滚优化
→ 生成 Markdown 分析报告
一、实验环境与前置准备
开始前需要准备:
- AMD 开发者社区账号;
- AMD 开发者云点数;
- 一个可供 OpenCode 使用的 LLM API Key;如果暂时没有,也可以选择平台提供的免费模型;
- 大约 30~60 分钟实验时间。模型下载、服务启动和调优耗时会受到网络、模型大小和参数配置影响。
本实验使用的核心技术包括:
- AMD ROCm:AMD GPU 计算平台;
- vLLM:高吞吐量大模型推理框架,提供兼容 OpenAI API 的服务;
- PyTorch Profiler:采集算子、Kernel 和输入 Shape;
- TunableOps/rocBLAS:针对真实 GEMM Shape 离线选择更快的实现;
- OpenCode + Skill:负责交互、流程编排、命令执行和报告生成。
本文中的“Token 服务”指的是基于 vLLM 的大模型推理服务,核心指标是每秒生成的 Token 数,即 Output TPS。
二、领取并兑换 AMD 开发者云点数
登录 AMD 开发者社区后,点击“我的权益”。

点击“领取”。

滑动到页面中部,点击“立即兑换”。

输入需要兑换的使用时长。

兑换完成后,点击“查看云力券”。

复制兑换链接或兑换码,后面需要在 AMD 开发者云中使用。

返回首页,进入“AMD 开发者云”。

打开 Profile 页面,兑换云平台点数。


输入刚才复制的兑换码并完成兑换。

如果国内站访问较慢,可以尝试国际站:https://radeon.anruicloud.com/。资源兑换流程基本一致。
三、启动“AgenticAI 推理自优化”实验
回到任务列表,选择 AgenticAI 推理自优化。如果页面中的任务顺序没有变化,它通常是第三个任务。

任务卡片通常有两个入口:
- Preview:阅读实验介绍;
- Launch:创建实验实例并启动 Notebook。
点击 Launch。

实例准备完成后,点击 Open Notebook。

进入 JupyterLab 后,可以看到以下核心文件:
assets/ # Notebook 图片和演示材料
skills/
vllm-optimize -> vllm-optimize-v3
vllm-optimize-v2/
vllm-optimize-v3/ # 本实验实际使用的 vLLM 优化 Skill
inferencex-optimize/
tests/ # 端到端测试
GUIDE.md
README.md
install.sh # 安装 Skill
workshop_LLM_for_LLM_cn.ipynb # 中文讲解型 Notebook
这里有一个容易误解的地方:workshop_LLM_for_LLM_cn.ipynb 主要是一份讲解型 Notebook,本身几乎没有可执行代码。实际的自动优化逻辑位于 skills/vllm-optimize-v3/ 中。
实验结束后记得销毁实例,避免继续消耗点数。

四、安装 Skill 并启动 OpenCode
在 JupyterLab 左上角点击加号。

选择 Terminal,打开终端。

执行安装脚本:
bash install.sh


install.sh 会将 inferencex-optimize 和 vllm-optimize 安装到:
~/.claude/skills/
同时还会为 Cursor 创建 Skill 链接和规则。虽然安装目录名中包含 .claude,但这个 Skill 同样兼容 OpenCode。
安装完成后启动 OpenCode:
opencode
Linux 命令区分大小写。如果输入
OpenCode提示找不到命令,请改用小写的opencode。
五、选择 Agent 模型并调用 Skill
进入 OpenCode 后,输入:
/models
选择一个可用的 Agent 模型。如果你有自己的 API Key,可以通过:
/connect
配置对应的模型服务。


然后输入:
/skills
选择:
/vllm-optimize
接着输入需要优化的模型,例如:
Qwen/Qwen3-8B


六、如何选择 Skill 的参数
Agent 会分两轮询问实验配置。第一次运行可以优先选择推荐项,但理解这些参数会让实验更有价值。
第一轮:运行方式
通常包括:
- Run plan:完整优化、仅 Profiling,或使用已有数据继续优化;
- Output directory:新建带时间戳的结果目录,或指定已有目录;
- GPU selection:自动选择空闲 GPU、沿用当前环境变量,或手动指定 GPU ID。
第一次体验建议选择:
Full optimize
New timestamped dir
Auto-select least busy

选择完毕后按回车确认。

第二轮:工作负载参数
Agent 完成 GPU 探测后,还会询问:
- TP:Tensor Parallel 数量;单卡实验选择
1; - ISL × OSL:输入和输出序列长度;推荐先用
1024 × 1024; - Concurrency:并发请求数;标准档通常是
1, 4, 16; - Optimization budget:每个 Kernel 最大优化尝试次数;
- Demo mode:是否在每个步骤完成后暂停。


Demo mode 的区别:
- 选择 Yes:每个关键步骤完成后,Agent 会展示结果并询问是否继续,适合学习;
- 选择 No:Agent 会连续运行所有阶段,适合无人值守执行。
第一次运行建议选择 Yes,这样可以观察每一个中间产物。
七、/vllm-optimize 到底做了什么
整个 Skill 包含 Phase 0~6,共七个阶段。界面中显示“0/6”是因为编号从 0 开始,并不是只有六项工作。
Phase 0:环境检测
Agent 首先检查:
- GPU 厂商、架构和数量;
- AMD
rocminfo、rocm-smi是否正常; - PyTorch 能否识别 GPU;
- vLLM 和 PyTorch 版本。
结果保存到:
env_info.json

Phase 1:启动 vLLM 服务
这一阶段会:
- 清理遗留的 vLLM API Server 和
VLLM::EngineCore; - 自动选择空闲 GPU;
- 检查本地模型,必要时下载 Hugging Face 模型;
- 设置 ROCm、TunableOps 和 Profiler 环境变量;
- 启动兼容 OpenAI API 的 vLLM 服务;
- 验证
/v1/models和 Profiler API 是否可用。
Skill 会先发送 SIGTERM,等待进程优雅退出,只有超时后才使用 SIGKILL。这是因为直接 kill -9 可能留下仍然持有 /dev/kfd 的 EngineCore 子进程,导致显存无法释放。

Phase 2:并发压测与 GPU Profiling
Agent 会在不同并发等级下发送请求,并记录:
- Output TPS:每秒生成 Token 数;
- Total TPS:输入与输出 Token 总吞吐量;
- P50 延迟;
- 成功请求数。
然后在代表性的低并发和高并发负载下启动 PyTorch Profiler。Profiler 使用 record_shapes=True,从实际执行过程记录算子输入 Shape。
默认主要分析 decode 阶段。因为 prefill 和 decode 的执行特征不同:
- prefill 通常处理较大的矩阵,计算密集;
- decode 每次只生成少量 Token,矩阵的
M较小,更容易受到调度和访存影响。

Phase 3:识别性能瓶颈
这一阶段完成两项核心分析。
第一项是 Kernel 时间分布。kernel_breakdown.py 会将 GPU Kernel 分类为:
- GEMM;
- Attention;
- 通信;
- 内存操作;
- 其他 Kernel。
第二项是提取真实 GEMM Shape。extract_shapes.py 会从 Trace 中得到实际出现的 (M, K, N),而不是根据模型配置猜测。
这非常重要,因为 vLLM 使用 continuous batching。运行时的 batch size 会不断变化,静态的模型参数无法准确反映真实服务中的 GEMM Shape。

Phase 4:Kernel 和 GEMM 调优
Skill 默认先尝试 PyTorch TunableOps,而不是立即手写 Triton Kernel。
执行过程是:
- 记录运行中尚未调优的 GEMM Shape;
- 只保留 Profiler Trace 中真实出现的 Shape;
- 针对每个 Shape 离线尝试不同 rocBLAS 实现;
- 将最佳算法写入
tuned_gemm.csv; - 运行微基准,比较优化前后的速度。
如果 TunableOps 在适用 Shape 上的提升不足 10%,Skill 才建议进一步尝试 Triton Kernel 优化。
需要注意:小批量 decode 的 M <= 4 Shape 往往走 wvSplitK 或 LLMM1,可能绕过 TunableOps;M > 4 的 Shape 更可能走 aten::mm 并被 TunableOps 接管。因此,低并发下接近 1.0x 并不一定表示优化没有生效。

Phase 5:注入优化并进行端到端验证
Skill 不会修改 /opt、/usr 或 pip 安装的 vLLM 源码,而是生成一个运行时注入文件:
optimized/pypath/sitecustomize.py
启动优化版服务时,通过下面的方式加载:
PYTHONPATH=optimized/pypath:$PYTHONPATH
随后 Agent 会分别测试:
- 全新启动、未注入优化的 baseline 服务;
- 注入调优配置的 patched 服务。
两次测试必须使用相同模型、序列长度、并发等级和服务参数,才具有可比性。

Phase 6:生成报告并关闭服务
最后生成 Markdown 报告,其中包含:
- 实验环境与配置;
- baseline 吞吐量;
- GPU Kernel 时间分布;
- 真实 Shape 覆盖情况;
- 微基准加速比;
- 各并发等级的端到端加速比;
- 每个优化目标的接受、拒绝或跳过原因;
- 最终是否集成优化。


运行完成后,可以在输出目录中找到 report/final_report.md。

八、为什么“Kernel 快了”不代表服务一定快了
这是整个实验最值得理解的一点。
一个 Kernel 在微基准中提升 1.5 倍,并不能证明真实服务也提升 1.5 倍,原因可能包括:
- 该 Kernel 只占总 GPU 时间的一小部分;
- 优化只覆盖少数 Shape;
- Shape 分派和 Python Hook 引入额外开销;
- 自动调优在运行时反复搜索,抵消了收益;
- 优化了 prefill,但线上瓶颈实际位于 decode;
- 高并发变快,但单用户延迟发生退化。
因此,Skill 设置了五道验证门:
- 服务可用性:vLLM 必须正常启动并能响应请求;
- Shape 覆盖率:优化必须覆盖 Profiler 中出现的真实 Shape;
- 正确性:优化后的输出必须通过数值或确定性检查;
- 全并发 E2E TPS:任一指定并发等级都不能低于 baseline;
- 单用户 decode:并发为 1 时不能退化。
只要关键 Gate 失败,Agent 就不会宣称优化成功,而是自动回滚并在报告中记录原因。
九、如何阅读最终结果
建议重点关注以下指标。
1. Output TPS
Output TPS 表示每秒生成的 Token 数,是生成式推理吞吐量最直观的指标。
Speedup = Optimized Output TPS / Baseline Output TPS
- 大于
1.0x:优化版更快; - 接近
1.0x:基本没有变化; - 小于
1.0x:发生性能退化,应回滚。
2. 不同并发下的变化
不要只看最高并发:
- 并发 1 反映单用户体验和 decode 延迟;
- 中等并发反映日常在线服务;
- 高并发反映 GPU 饱和后的吞吐能力。
3. Kernel 时间占比
如果 GEMM 占 GPU 活跃时间的大部分,TunableOps 或 Triton GEMM 优化更有意义。如果 Attention 或内存操作才是瓶颈,只调 GEMM 很可能无法带来明显的端到端收益。
4. Shape 覆盖率
优化 Shape 数量太少时,即便微基准结果很好,也不代表能够覆盖真实线上负载。
5. Verdict
最终报告中的 Verdict 比单个 Speedup 数字更重要:
- Integrated:所有必要验证通过,可以使用;
- Rolled back:出现端到端退化,已经回滚;
- Skipped:缺少真实 Shape,或该目标不适合当前优化方式;
- Not integrated:完成分析,但没有进入生产式集成。
十、常见问题与排查方法
1. opencode: command not found
先确认安装脚本是否执行成功:
bash install.sh
注意启动命令通常为小写:
opencode
2. 找不到 /vllm-optimize
检查 Skill 是否安装:
ls -la ~/.claude/skills/vllm-optimize
必要时重新运行:
bash install.sh
3. 模型下载失败
可能是 Hugging Face 网络或镜像问题。可以:
- 换用已经缓存到实例中的模型;
- 检查
HF_ENDPOINT; - 使用更小的模型先完成流程;
- 确认实例磁盘空间是否足够。
4. vLLM 启动超时
查看对应输出目录中的服务日志:
tail -n 100 vllm_server.log
常见原因包括显存不足、残留 EngineCore、模型文件不完整、max-model-len 过大。
5. 显存没有释放
不要一开始就直接 kill -9。先向 API Server 和 EngineCore 发送 SIGTERM,等待其清理资源,再对残留进程使用 SIGKILL。
可以检查持有 /dev/kfd 的进程:
for pid in /proc/[0-9]*; do
ls -l "$pid/fd" 2>/dev/null | grep -q '/dev/kfd' && echo "$pid"
done
6. 微基准提升明显,但 E2E 没有提升
这并不反常。重点检查:
- 优化 Shape 是否真实出现;
- 这些 Shape 占总 GPU 时间多少;
- 优化是否只覆盖 prefill;
- 是否引入运行时 autotune 或分派开销;
- 低并发请求是否绕过 TunableOps。
7. 实验运行时间超过 30 分钟
标题中的 30 分钟更适合描述“跑通标准流程”的理想情况。以下配置会显著增加耗时:
- 大模型;
1024 × 8192等长输出;- 完整并发扫描;
- Deep optimization budget;
- 首次下载模型;
- 网络速度较慢。
第一次实验建议使用单卡、标准并发、1024 × 1024 和默认优化预算。
十一、总结
通过这个实验,我们完成的不只是启动一个 vLLM 服务,而是跑通了一套相对完整的推理性能工程闭环:
部署 → 压测 → Profiling → 定位 → 调优 → 集成 → 验证 → 回滚/报告
这套方案最有价值的地方不是“Agent 自动运行了很多命令”,而是把性能工程经验编码进 Skill:只使用真实 Shape、保持实验负载一致、同时验证正确性和端到端性能,并诚实记录失败的优化。
对实际生产环境来说,这种思路也很有启发:Agent 可以提高性能分析和实验执行效率,但最终是否采用优化,仍然必须由可复现的数据和严格的验证门决定。
最后再次提醒:实验完成后,请及时关闭 vLLM 并销毁云实例,避免继续消耗开发者云点数。
更多推荐
所有评论(0)