这个坑值得记一笔。推理服务用 FastAPI 加 uvicorn 起单 worker,路由写成 async def 但里面直接调同步的 model.generate,压测并发 4 时第一个请求 1.1 秒,第四个要 4.4 秒,CPU 几乎空闲,显存也没满。

刚开始盯着「没设并发上限,客户端超时后重试又叠了一层」,折腾了半个多小时,排掉了。

把日志和配置对完,能解释这个现象的基本就这几处。同步阻塞调用写在 async def 里,直接把事件循环占住;单 worker 只能有一个事件循环,后续请求全部排队;uvicorn 默认 backlog 较小,连接堆积在 accept 队列。

第一件事是用并发 4 复现排队现象

wrk -t2 -c4 -d30s --latency http://127.0.0.1:8000/v1/chat

再往下把阻塞调用丢到线程池

python -c "import asyncio;print(asyncio.get_event_loop().run_in_executor(None, lambda: 1+1))"

或者把路由改成普通 def 交给线程池

python -c "print('def route(): return model.generate(...)')"

末了按显存能承受的量限制并发

python -m uvicorn app:app --workers 1 --limit-concurrency 8 --backlog 256

怎么算真的好了,看的是同一批现象。并发 8 时每个请求延迟接近单请求水平,事件循环不再被占住,健康检查在压测期间仍能秒回。

容易忽略的是,异步框架里混同步调用是最常见的性能陷阱,要么全异步,要么显式丢线程池,别让一个 generate 堵住整个进程。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。

作者 9823189