使用 AMD GPU 部署 LMDeploy 推理服务时,OOM 往往是并发请求在短时间同时进入,GPU 显存一次性分配给多个请求的 KV Cache 和激活值,超出显存物理上限导致的。LMDeploy 虽然会尽力复用显存,但并发数一旦超过设计上限,仍会触发分配失败。优先控制服务可见的并发请求数量,比单纯调小模型参数更直接。
处理方向是限制 LMDeploy 服务端批量大小和请求并发数,同时在上游加一层队列或限流器,避免瞬时流量直接命中 GPU。验证方式是用压力工具观察显存峰值和 OOM 日志,逐步压低并发直到显存余量稳定。注意 AMD GPU 的 ROCm 显存管理与 CUDA 有差异,需要结合驱动和实际显存占用再做微调。
为什么并发过高会直接触发 OOM
LMDeploy 在推理请求到达时,会为目标 batch 预先分配连续显存,用于 KV Cache 和激活值。每个请求占用的显存不是固定的,而是与输入长度、输出长度、模型大小、batch 内总 token 数相关。并发数越高,batch 越大,单次分配的显存块就越大。GPU 显存不足时,分配失败并不总是抛出 OOM,有时表现为 ROCm 报错或服务进程被系统杀掉,需要先盯住日志里的显存分配记录。
AMD GPU 上使用 ROCm 时,显存碎片化和分配粒度可能与 CUDA 不同。即使配置文件写了相似参数,也不能直接套用 NVIDIA 环境下的数值,需要先跑一轮压测确认当前卡的真实显存余量。
限流配置:从服务端和入口双层下手
第一层限制 LMDeploy 自身的并发能力。参考 LMDeploy 的启动参数,可以设置 `--max-batch-size` 控制单次推理的最大 batch 数,以及 `--max-concurrent-requests` 控制同时处理的请求数量(如果当前版本支持)。这两个参数直接决定单次显存分配的上限,优先调小它们。配置示例:
lmdeploy serve api_server /path/to/model \
`--server-port` 8080 \
`--max-batch-size` 8 \
`--max-concurrent-requests` 16
第二层在服务入口加流量控制。用 Nginx 做反向代理时,给 /v1/chat/completions 添加请求频率限制和队列长度限制,让超出服务处理能力的请求在代理层排队或被丢弃,不进入 GPU。Nginx 配置片段:
limit_req_zone $remote_addr zone=llm_limit:10m rate=10r/s;
location /v1/chat/completions {
limit_req zone=llm_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:8080;
}
也可以把队列放到应用网关,例如 Java 的阻塞队列或 Redis 列表,由业务进程按固定速率消费请求。这样即使外部流量突然飙升,GPU 看到的并发也是稳定的。
需要确认的是,LMDeploy 不同版本对并发参数的命名可能不同。建议先执行 lmdeploy serve api_server `--help` 查看当前版本支持的参数名,再按实际参数调整。
验证限流是否有效
验证时不要只看请求成功率,要同时观察三个指标:
- GPU 显存峰值是否稳定在预留的安全水位内,可以用
rocm-smi周期性记录显存使用。 - 服务日志中是否还有 OOM 或显存分配失败记录。
- 压测时的请求超时率和错误码分布,确认丢弃的是过载请求而不是正常请求。
建议压测脚本从一个低并发开始,比如 1 并发起步,每次增加 2,每个档位持续 5 分钟,记录显存峰值和错误日志。找到显存余量开始大幅下降的临界值,再将并发上限设为其 60% 左右,留出余量。
如果限流后仍 OOM,检查这些点
限流只解决了并发数量,不一定解决单请求占显存过大的情况。若单个请求输入很长,也会撑爆显存。这时需要处理长上下文请求,可以设置 `--session-len` 限制最大序列长度,或在业务侧截断输入。另一个点是用 LMDeploy 的 PagedAttention 功能,它能把 KV Cache 分页管理,减少碎片。AMD GPU 上同样适用,但需要确认当前版本的 ROCm 后端是否完全支持,不能凭经验直接开启。
如果服务仍然崩溃,优先查看 ROCm 驱动和 LMDeploy 依赖的版本组合是否匹配。遇到版本不兼容问题,不要盲目升级,而是记录当前版本组合,在官方 issue 中搜索是否有类似现象。