三周前,我们的AI服务还在一台单卡4090上"苟延残喘":8个并发就把机器打满,单用户等回复要十几秒,被业务同事吐槽"人工智障"。这三周我做了一件事——把推理服务从简单的Transformers脚本迁移到vLLM,并做了一轮深度调优,最终把吞吐量从约30 token/s 干到了800 token/s(8卡并行),延迟从十几秒压到2秒内。这篇把踩坑和调优过程完整写下来。
最初的架构很简单:Docker里跑Transformers的generate接口,单卡4090,模型是Qwen2.5-14B(4bit量化)。问题是:
把Transformers换成vLLM,同样的单卡,效果立竿见影:
vllm serve ./qwen-14b-awq \
--max-model-len 8192 \
--gpu-memory-utilization 0.92 \
--port 8000vLLM的PagedAttention(按页管理KV Cache,消除显存碎片)和Continuous Batching(动态把多个请求拼进一个batch)两个机制,让单卡4090的并发吞吐直接翻了5倍,从30 token/s 提到约150 token/s。这是投入产出比最高的一步——换框架,零代码改动。
单卡再优化也有限,我们把服务扩到8卡(2台4卡服务器),用vLLM的张量并行(TP):
vllm serve ./qwen-14b-awq \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.92 \
--port 8000注意是每台机器4卡张量并行,两台机器分别起服务,前面挂负载均衡,而不是跨机器的张量并行——跨机TP的网络开销会吃掉收益,这是第一个大坑。
8卡之后,实测并发吞吐达到约800 token/s,单请求延迟稳定在2秒以内,总算能满足业务了。
多卡之后还有几个可以榨的油水:
在vLLM参数里加--enforce-eager的反面——默认开CUDA Graph捕获,减少kernel启动开销。单请求延迟能再降10-15%。
同样是4bit,AWQ量化的吞吐和显存效率比GGUF高一个档次(GGUF主要在llama.cpp系生态里更有优势)。迁移后同样卡数吞吐又涨了一截。
业务里大部分请求上下文在2K以内,我把max-model-len从8192降到4096,显存占用下降,能塞进更大的batch,吞吐再涨约10%。注意:改这个要评估好业务最长上下文,别为了性能砍掉长文档场景。
相同前缀的请求(比如同样的系统提示词)可以复用KV Cache,实测相同系统提示词的场景下吞吐提升15-20%。vLLM的--enable-prefix-caching直接开启。
这轮调优能顺利推进,离不开监控。我配了:
调优决策全部基于监控数据:比如看到"GPU利用率只有60%",说明batch没打满,就调大max-num-seqs(batch上限);看到"队列堆积",说明并发超过能力,就加机器而不是硬调参数。
阶段 | 吞吐量 | P99延迟 | 变化 |
|---|---|---|---|
初版(Transformers单卡) | ~30 token/s | >15s | 基线 |
迁移vLLM(单卡) | ~150 token/s | ~6s | 5倍 |
8卡并行 | ~800 token/s | <2s | 累计27倍 |
量化+前缀缓存微调 | ~900 token/s | ~1.5s | 再涨10-15% |
这次调优给我最大的启发是:大模型推理性能是个系统工程,框架选型(vLLM vs Transformers)是第一杠杆,并行规模是第二杠杆,量化、缓存、参数是第三杠杆。每一步都有数据支撑,每一步都踩了真实存在的坑。如果你的AI服务也面临"慢、并发低"的问题,建议按这个顺序排查:先换框架,再上并行,最后微调参数——大概率能让你省下几个通宵。