并行计算如何真正“跑满”GPU:一个推理工程师的实战手记
去年冬天,我在调试一个面向客服场景的7B模型API服务时,遇到个典型问题:单卡吞吐只有38 tokens/sec,P99延迟飙到1.2秒——用户还没打完一句话,回复就卡住了。日志里反复出现cudaMalloc failed和ncclTimeout,GPU利用率却始终在45%上下徘徊。那一刻我意识到,不是模型太重,而是我们没让硬件真正“动起来”。
这不是算力不够,是并行没用对。
为什么“开了4张卡,速度却不到单卡的3倍”?
很多团队第一反应是堆数据并行(Data Parallelism):把模型拷四份,请求分四路走。听起来很美,但现实很快打脸——显存瞬间告急,通信开始拖后腿,最后四卡跑得比两卡还慢。
根本原因在于:数据并行只解决了“能不能算”,没解决“怎么算得快”。
它把压力全扔给显存带宽和PCIe总线。比如LLaMA-7B在FP16下权重约13GB,4卡就是52GB显存占用;而A100-80G实际可用约72GB,留给KV Cache的空间只剩20GB。一旦用户发来长prompt+生成512 token,KV Cache直接撑爆,系统被迫降级为CPU offload,延迟翻三倍。
更隐蔽的问题是通信伪共享:哪怕只是做一次AllGather拼接logits,NCCL也要在所有卡间同步一个1.2MB的float16 logits张量(batch=8, vocab=32k)。在跨节点部署时,InfiniBand延迟虽低,但每次通信仍要消耗0.8~1.2ms——而整个前向计算才8.3ms。相当于每10次计算就有1次在等网络。
所以,单纯复制模型 ≠ 并行生效。真正的并行,是让每一纳秒的GPU计算单元、每一条NVLink通道、每一个HBM内存控制器,都处在被驱动的状态。
模型切开,才能跑通:从“复制”到“拆解”的思维转变
当数据并行碰壁后,我们转向了模型并行(Model Parallelism)——不是把模型“搬”到多卡,而是把它“剖”开,让每张卡只干自己最擅长的一段活。
流水线并行:用时间换空间的精妙平衡
我们先试了流水线并行(Pipeline Parallelism),把32层Transformer切成4段,每段8层,部署在4张A100上。
效果立竿见影:显存占用从52GB降到18GB,KV Cache终于能塞进显存。但新问题来了——首token延迟从320ms涨到680ms。
为什么?因为流水线有固有气泡(Bubble)。
简单说:第1段刚算