第 10 章 并行与拆解:切开之后,代价在哪
第 3 章算过,70B 模型 FP16 权重 130 GiB,一张 80GB 卡装不下。这不是调参能解决的问题,必须跨卡。
跨卡听起来像"把模型分成几份放上去",实际做起来要回答三个问题:按什么切,切完之后每一步怎么同步,同步的代价会不会把收益吃掉。这一章讲这三个问题,以及一种更激进的做法——把预填充和解码也拆开。
先把同步这件事说清楚,因为后面所有的取舍都建立在它上面。
先垫一块砖:集合通信
多张卡协同算一件事,中间必须交换数据。点对点通信是一张卡发给另一张卡;集合通信是一组卡一起参与的操作。
最常用的是 all-reduce。它的含义是:每张卡各有一份数据,操作之后,每张卡都拿到所有卡数据的和。比如八张卡各有一个向量,all-reduce 之后,八张卡上的向量都变成这八个向量的和。
为什么需要它?因为张量并行会把一个矩阵乘切开:每张卡算一部分乘积,但最终结果要把各部分加起来。这个"加起来"就是 all-reduce。切得越细,需要同步的次数越多,这就是并行的固定成本。
集合通信由专门的库实现,NVIDIA 平台上叫 NCCL。它会在 NVLink、PCIe、InfiniBand 之间自动选路。做并行时讨论"通信开销",说的基本都是 NCCL 在这个拓扑上跑多快。
三种切法
| 方式 | 切什么 | 适合 | 代价 |
|---|---|---|---|
| 张量并行 TP | 层内的矩阵 | 单节点内,延迟优先 | 每层两次 all-reduce,必须走 NVLink |
| 专家并行 EP | MoE 的专家 | MoE 大模型,吞吐优先 | 路由时 all-to-all,负载可能不均 |
| 流水线并行 PP | 按层切成段 | 跨节点,模型极深 | 有气泡,段数越多浪费越大 |
这三种可以组合。一个 70B 模型常见的配法是节点内 TP=8,跨节点再叠 PP 或者用更多节点做 TP。
张量并行:切得最细,同步最频繁
张量并行的做法是把一个矩阵按列或者按行切开,每张卡算一部分,最后把结果拼起来或者加起来。
以注意力为例,八个头分给八张卡,每张卡算一个头。算完输出投影时,每张卡只算了一部分和,需要 all-reduce 把八部分加起来。前馈网络同理:先把中间维度切开,各算各的,再同步一次。
所以每一层有两次 all-reduce,一次在注意力之后,一次在前馈之后。70B 有 80 层,一个 token 要走 160 次同步。
这个频率决定了张量并行只能在节点内做。节点内 NVLink 是 900 GB/s,一次同步几微秒;跨节点走 InfiniBand 是 50 GB/s,延迟高一个数量级,160 次同步累积起来就是几十毫秒,比计算本身还慢。
也有好处:切得细,每张卡的显存压力小,单步计算时间也短。延迟敏感的负载,节点内 TP 是最直接的手段。
通信量到底有多大
算一遍,看看通信是不是真的可以忽略。
70B 模型,隐藏维度 8192,FP16,TP=8,批大小 1。每层两次 all-reduce,每次同步一个 8192 维的向量,16 KiB。八卡 ring all-reduce 每张卡要搬大约两倍的数据量,算 32 KiB。一层 64 KiB,80 层 5 MiB。
按 NVLink 900 GB/s 算,5 MiB 只要 6 微秒,可以忽略。真正的成本是延迟:160 次集合通信,每次几微秒的固定开销,合计一到两毫秒。
对照计算的账:70B 每张卡分到 16 GiB 权重,每步读 16 GiB,按 2039 GB/s 是 8 毫秒。所以同步的固定开销占每步的百分之十几到二十。
这个比例说明两件事。第一,节点内 TP 的通信带宽不是瓶颈,延迟才是;优化方向是减少同步次数(比如融合两次 all-reduce),而不是提高带宽。第二,跨节点做 TP 会把这个比例放大十倍,同步开销直接超过计算,所以不划算。
流水线并行:切得最粗,浪费在气泡
流水线并行按层切:前二十层放第一张卡,中间二十层放第二张卡,依次类推。数据从第一段流到第二段,像工厂流水线。
问题是填充和排空。第一批数据进入时,后面的卡在等;最后一批数据流出时,前面的卡已经空了。这段空闲叫气泡。段数越多、批越小,气泡占比越大。
所以流水线并行适合跨节点,而且要用较大的批来填满流水线。它的优点是通信量小:只在段与段之间传激活值,不像张量并行那样每层同步。
判断顺序通常是:先节点内张量并行,装不下再叠流水线,最后才考虑跨节点张量并行。
专家并行:MoE 专用
MoE 模型的前馈层由很多专家组成,每个 token 只走其中几个。专家并行就是把不同的专家放到不同的卡上。
好处很直接:2350 亿参数的模型,权重 440 GiB,一张卡装不下,但每个专家单独看都不大,分散到多张卡上就装得下。每个 token 只需要访问被路由到的那几个专家所在的卡。
代价是 all-to-all:每个 token 要被发送到它选中的专家那里,算完再送回来。这个通信模式和 all-reduce 不同,它的数据量取决于路由结果,可能很不均匀——热门专家被挤爆,冷门专家闲着。
这就是第 3 章说的"MoE 省算力不省显存"的另一面:显存分散了,但通信变复杂了。批处理时几乎全部专家都会被激活,all-to-all 的数据量随之上升。
拆解:把预填充和解码分开
前面三种是切模型。还有一种做法是切阶段:把预填充和解码放到不同的机器池里。
理由是两个阶段的资源需求相反。预填充是算力受限,要 FLOPS 高的卡;解码是带宽受限,要带宽大的卡。放在一起,两种负载互相干扰:长预填充一进来,解码就卡顿。第 9 章的切块预填充缓解了干扰,但没有消除资源竞争。
拆解之后,两个池子各自扩缩。预填充池按输入流量扩,解码池按并发扩,成本更可控。
代价是缓存要搬。预填充算出的 KV cache 必须传给解码池,这是一大块数据:一条 8K 请求 1 GiB。跨机传 1 GiB,按 50 GB/s 算是 20 毫秒,比预填充本身还慢。所以拆解的前提是网络够快,或者缓存压得够小——这也是为什么 KV 量化对拆解特别重要,Inference Engineering 明确说过这一点。
拆解不是默认选项。只有预填充和解码的比例严重失调、或者流量波动很大时,它才划算。流量平稳、模型不大的场景,拆解带来的复杂度和传输开销不划算。
什么时候不该并行
并行是放大器,也是负债。三种情况不该用它。
模型本来就装得下,而且单卡速度够。并行的第一收益是容量,第二才是速度;如果容量不缺,多卡带来的只有同步开销。一张卡能跑 8B,就不要为了"看起来更强"切四张。
流量很低。每秒几条请求,单卡的算力都用不满,多卡不会让单条更快,只会让成本翻倍。低流量的正确做法是选一张合适的卡,把余量留住,而不是堆机器。
延迟已经达标。并行的收益在吞吐,不在单条延迟。如果 P99 已经满足要求,加卡只会提高成本。扩容是在容量不够时做的,不是在指标好看时做的。
反过来,三种情况该用:模型装不下(唯一选择)、并发上不去(容量问题)、单卡吞吐撑不住峰值(吞吐问题)。
判断标准始终是第 11 章那三个数:到达率、单条耗时、单机并发。三个数算出来的卡数不够,才谈并行。
一次判断:70B 该怎么放
把这一章串起来,做一个判断。
70B,FP16,130 GiB 权重。目标是一台八卡 80GB 的机器,总显存 640 GiB。
第一步,权重放得下,八张卡每张 16 GiB,剩 64 GiB 给缓存。按 GQA 结构,每 token 320 KiB,8K 请求 2.5 GiB,能并二十多条。
第二步,选切法。节点内 TP=8,每层两次同步,延迟可接受;不需要 PP,因为一台机器够。
第三步,算通信。每步 160 次同步,一到两毫秒,相对 8 毫秒的读取时间占两成。可接受,但不能再加。
第四步,如果并发上不去,考虑拆解或者加机器。加机器意味着跨节点,跨节点意味着 PP 或者跨节点 TP,两者都会带来新的开销,需要重新算。
这四步的顺序不能反。先确认装得下,再选切法,再算通信,最后才谈扩展。很多失败案例是反过来的:先买了多机,再发现通信把收益吃光了。
并行的边界
并行能解决容量,不能解决效率。
八张卡的总吞吐不是一张卡的八倍。同步开销、气泡、负载不均,都会吃掉一部分。节点内 TP 靠 NVLink,掉得少一些;跨节点要过 InfiniBand,掉得更多。具体掉多少必须实测,它取决于模型、批大小、拓扑和 NCCL 的版本,没有可以照抄的比例。
并行也不能解决单条请求的延迟下限。一条请求只能用到有限的并行度,切得再细,它还是要一层一层走完。想让它更快,靠的是更快的卡,不是更多的卡。
下一章讲评测和成本。前面所有的账,最终都要落到两个问题上:这个服务能接多少请求,每个 token 花多少钱。