每两分钟卡 7 秒,原来怪宿主机

用 4 张 V100 跑 Qwen3.8-Flash-Next 有一阵了,一直有个怪毛病:服务时不时整个停住几秒,所有请求一起不动,然后又自己恢复。查了一整天,最后发现问题根本不在推理服务里,改一行配置就好了。

现象

  • 停顿大约每两分钟一次,每次约 7 秒。内存 KV 缓存从 64GB 改成 32GB 以后,缩短到约 3.5 秒。
  • 只在处理新提示词(prefill)时出现,单纯生成文字时基本碰不到。
  • 刚启动时没有,等内存缓存写满以后才开始。

先排除的

一开始以为是推理引擎自己的问题,先后怀疑过 Python 垃圾回收、显存分配器、内存回收、PCIe 带宽。一个个打点测下来,都不是。

真正有用的线索有三条:

  1. 停顿那几秒,GPU 是闲着的,4 张卡轮流空闲,每张约 0.8 秒。
  2. 停顿间隔非常准,每次相隔 130.5 秒。
  3. 卡住的那个进程,主线程停在一个很小的 mmap 或 munmap 系统调用上,一停就是 0.8 秒。

申请或释放几 KB 内存却要等 0.8 秒,只能是在等锁。于是到宿主机上抓现场,看是谁在占着这个进程的内存映射锁。

元凶

答案是宿主机上 Proxmox 自带的 ksmtuned。它负责调节 KSM(内存页合并),每一轮都会执行:

ps -C kvm -o pss

本意只是统计 kvm 虚拟机进程占了多少内存。但 ps 为了算 PSS,会去读所有进程的 /proc/<pid>/smaps_rollup,过滤是读完之后才做的。读这个文件时,内核要锁住目标进程的内存映射,并把它的页表从头遍历一遍。

我的推理进程每个占 50 多 GB 内存,页表有 110MB,遍历一次要 0.8 秒。这期间,进程里任何申请或释放内存的操作都得等着。4 个推理进程被逐个扫一遍,GPU 就轮流空转,其余的卡在通信里等它,加起来就是好几秒。

这也解释了另外两个现象:内存缓存写满后页表变大,扫一次的时间才长到看得出来;缓存从 64GB 降到 32GB,停顿也差不多减半。

修复

ksmtuned 本身就有这个配置项。在 /etc/ksmtuned.conf 里加一行,再重启它:

KSM_PS_METRIC=rss

ps -o rss 只读进程自带的计数器,不遍历页表:在这台机器上,-o pss 跑一次要 5.2 秒,-o rss 只要 0.08 秒。kvm 进程的 RSS 和 PSS 只差约 0.1%,KSM 的调节不受影响。

改完以后停顿再也没出现过。我又手动执行了一次旧命令,停顿立刻复现,被卡住的位置和之前一模一样。

记两条

  • 容器里的毛病不一定出在容器里。GPU 闲着、主线程又卡在很小的系统调用上,就该去宿主机上看看,是谁在读这个进程的 /proc。
  • 在 Proxmox 上跑大内存的 GPU 容器,建议检查一下 ksmtuned 的配置。宿主机上的大内存虚拟机,每一轮也一样会被扫一遍。