我们的推理服务器装备了24颗CPU核心,GPU几乎一直闲在那里。当时系统只挂着10个并发请求,可平均延迟却一路飙升到8秒以上。这个数字让人难以置信——明明负载不高,响应时间却像被什么拖进了泥潭。
当时我们用一套Rails后台任务系统,把嵌入和分类请求分发给内部的Python推理服务。后台队列开始积压后,团队做了任何一个理智的团队都会做的事:提升后台工作进程的并发数,同时扩展推理用的Web服务器。我们等着吞吐量线性增长,结果整个系统直接僵住。延迟从毫秒级蹿到8秒开外,后台队列越排越长,服务器的CPU利用率被死死压在100%。可去看GPU的遥测数据,它几乎在空转,等着活干。
CPU并没有忙着做矩阵乘法,它把时间全花在决定下一个该跑哪个线程上。顺着这条线索深挖,我们才意识到,自己撞上了一个隐匿的性能杀手:线程过度订阅。
为了把瓶颈找出来,我们隔离了推理服务,跑了一轮受控负载测试。模拟10个并发请求,一共向Python推理服务器发出100次API调用。单次推理请求大约需要912毫秒,原本不成问题,可一旦并发量仅仅10,平均响应时间就被推到8秒以上。更糟的是,CPU核心被彻底饱和,GPU利用率却只有15%。
在一个健康的系统里,CPU跑到100%意味着吞吐量也该被拉满。可眼前这套服务器,CPU累死累活,几乎产不出多少有效输出。
我们原本以为祸首可能是GPU饱和——模型太重,硬件扛不住;或者需要继续提高Rails并发数,把更多的活塞进去;又或者嵌入算法本身就慢;再不然就是Rails和推理API之间的数据传输拖了后腿。结果全都不对。
在压测期间,我们跳进推理服务器,打开htop查看CPU到底在忙什么。屏幕上,24个物理核心(48个逻辑线程)被红绿长条撑满,每一颗都被逼到极限。接着检查Python推理进程的活跃线程数,用ps -o nlwp一看,返回的数字是240。一个处理着10个并发Web请求的进程,居然生出了240条活跃线程。到底从哪来的?
我们的Python代码很简单——一个用Uvicorn跑的标准FastAPI应用。我们没有手动启动任何线程,只是用PyTorch加载了一个sentence-transformer模型,然后跑推理。那个看起来人畜无害的端点背后,藏着这240条线程的谜团。
热门跟贴