现场是这样的。把 Flask 封装的推理服务从 1 个进程改成 4 个 gunicorn worker,24G 卡上本来占 15G,改完直接 OOM,nvidia-smi 显示有 4 个进程各占 14G 多,配置文件里没有开 preload。

中间走过弯路,把「CUDA 上下文不支持跨 fork 共享,即使 copy-on-write 也共享不了显存」当成主因,改完现象没变。

排查方向其实有限,先把可能性排一排。worker 是 fork 出来的独立进程,各自 import 一次模型就在显存里各放一份;preload_app 为 False 时模型在 worker 初始化阶段加载,父进程不持有副本;worker 数按 CPU 核数自动算,机器 8 核就起了 8 个,超出显卡容量。

动手时先确认当前启动参数里 worker 数与 preload 的取值

grep -nE 'workers|preload_app' gunicorn.conf.py

然后改成单进程多线程,把并发交给推理引擎自己调度

gunicorn -w 1 --threads 32 -b 0.0.0.0:8000 app:app --timeout 120

随后观察进程数与显存占用的对应关系

nvidia-smi --query-compute-apps=pid,used_memory --format=csv

压测确认单进程多线程没有把事件循环卡住

python loadtest.py --url http://127.0.0.1:8000/v1/chat/completions --concurrency 16 --duration 120

怎么算真的好了,看的是同一批现象。nvidia-smi 里只出现一个计算进程且显存占用与单模型体积一致,压测 QPS 不低于改造前,超时计数为 0。

补一句。推理服务和普通 Web 服务不一样,扩 worker 等于复印模型,并发应该交给引擎的连续批处理,进程数保持 1 才是常态。同一份配置在测试环境跑通,不代表线上没事,两边的默认值经常不一样。

作者 9823189