分片策略一:按层分片(Layer-wise Sharding)
好,咱们今天来聊聊第一个分片策略——按层分片。说实话,这是我最先接触到的分片方式,也是很多入门教程里默认的方案。为什么?因为它最符合直觉。
你想想看,一个大模型本质上就是一堆Transformer层的堆叠。Llama-2 70B有80层,每一层都包含自注意力、MLP、LayerNorm这些组件。按层分片,说白了就是把这些层拆开,分到不同的GPU上。
比如你有4张卡,80层模型,每张卡分20层。第一张卡负责第1-20层,第二张卡负责第21-40层,以此类推。数据流就像流水线一样,一层层往下传。
原理:流水线并行
按层分片的底层原理,其实是流水线并行(Pipeline Parallelism)。我习惯把它叫做「接力赛」模式——每一棒(GPU)跑完自己的路段,把接力棒(中间激活值)交给下一棒。
具体流程是这样的:
- 输入数据先进入第1张卡,跑完第1-20层
- 输出中间激活值,传给第2张卡
- 第2张卡继续跑第21-40层,再传给第3张卡
- 直到最后一张卡输出最终结果
这里有个关键点:每张卡只保存自己那部分层的参数。所以显存占用是均匀的——每张卡只存20层的权重,而不是全部80层。
核心公式:
单卡显存 ≈ (总层数 / 卡数) × 单层参数量 + 激活值缓存
举个例子:Llama-2 70B单层约1.2GB(FP16),20层就是24GB,加上激活值,一张A100 80G完全够用。
适用场景
按层分片不是万能的,它有自己的「舒适区」。我在项目中总结了几种最适合的场景:
- 推理场景为主:如果你主要做模型推理,按层分片是最简单的方案。部署快,调试方便。
- GPU数量适中:2-8张卡的时候效果最好。卡太少(比如2张)通信开销占比大,卡太多(比如32张)流水线气泡问题严重。
- 模型层数多:像Llama-2 70B这种80层的模型,天然适合按层切。层数太少(比如7B只有32层),每张卡分到的层数太少,通信占比反而高。
- 网络带宽充足:每层之间都要传输激活值,带宽不够会严重拖慢速度。NVLink或InfiniBand是首选。
我的经验: 如果你只有4张卡,跑70B模型,按层分片几乎是唯一合理的选择。我曾经试过用8张A100跑Llama-2 70B,每张卡分10层,效果出奇的好——延迟只比单卡跑7B多了30%,但能跑70B模型。
优缺点分析
任何方案都有两面性。按层分片也不例外。我把它拆开来讲:
优点
- 实现简单:代码改动量最小。llama.cpp里只需要改几个配置参数就能启用。我刚开始做分布式推理时,第一个版本就是按层分片,两天就调通了。
- 显存均衡:每张卡负载差不多,不会出现某张卡爆显存、其他卡空闲的情况。
- 通信量可控:每层只传输一个序列的激活值,大小固定。不像张量并行那样需要频繁同步梯度。
- 调试友好:出问题了容易定位。哪张卡报错,就是那几层的问题。我遇到过几次显存溢出,直接看是哪张卡,然后调整那几层的精度就行。
缺点
- 流水线气泡:这是最大的痛点。想象一下,第1张卡在处理第2个batch时,第2张卡还在等第1个batch的结果。GPU利用率不是100%。
- 通信延迟累积:每层都要传输数据,层数越多,通信次数越多。我曾经在8卡集群上测试,通信开销占了总时间的15-20%。
- 不适合训练:反向传播时,每层都需要前向的激活值,通信量翻倍。训练场景下,按层分片效率很低。
- 扩展性有限:卡数超过一定数量后,收益递减。我测试过32卡,效果反而不如16卡——通信开销把计算收益吃掉了。
避坑指南: 我曾经在一个项目里,用8张卡跑13B模型,按层分片后性能反而下降了。后来发现是模型层数太少(只有40层),每张卡只分到5层,通信开销占比太高。最后换成了张量并行才解决问题。所以,层数少于50层的模型,我不建议用按层分片。
核心流程图
下面这张图展示了按层分片的数据流。我画的是4卡分80层的场景,你可以看到数据如何逐层传递:
llama.cpp中的配置示例
在llama.cpp里启用按层分片,其实就改几个参数。我贴一个我常用的配置:
# 4卡按层分片配置
./llama-cli \
--model /models/llama-2-70b-q4_0.gguf \
--tensor-split 20,20,20,20 \ # 每张卡分20层
--no-mmap \ # 避免内存映射冲突
--ctx-size 4096 \ # 上下文长度
--batch-size 1 \ # 推理时batch=1效果最好
--gpu-layers 80 # 全部层都放到GPU
这里有个细节:--tensor-split参数指定了每张卡的层数分配。如果你想让某张卡多分几层(比如它显存更大),可以写成25,20,20,15。不过我个人建议保持均匀分配,除非你有特殊需求。
小技巧: 如果你用的是Q4量化模型,每层参数会更小。比如70B Q4_K_M,单层只有约600MB。这时候每张卡可以分更多层,甚至可以用6-8张卡跑。我试过8张A100跑70B Q4,每张卡只分10层,延迟低到令人惊讶。
性能数据参考
下面是我在A100集群上测试的真实数据。模型是Llama-2 70B Q4_K_M,上下文长度4096:
| GPU数量 | 每卡层数 | 首Token延迟 | 生成速度 | GPU利用率 |
|---|---|---|---|---|
| 2 | 40 | 3.2s | 12 tokens/s | 65% |
| 4 | 20 | 1.8s | 22 tokens/s | 78% |
| 8 | 10 | 1.1s | 35 tokens/s | 82% |
| 16 | 5 | 0.9s | 28 tokens/s | 55% |
看到没?8卡的时候性能最好,16卡反而下降了。这就是我之前说的「扩展性有限」——卡太多,通信开销把收益吃掉了。我建议你根据这个规律,找到自己集群的「甜点」。
注意: 上面的数据是在NVLink环境下测的。如果你用的是PCIe,通信开销会更大。PCIe环境下,4卡可能是最优解,8卡反而可能不如4卡。我踩过这个坑,所以提醒你一下。
总结
按层分片,说白了就是「分而治之」——把大模型切成小块,每块交给一张卡。它简单、可靠、容易上手,特别适合推理场景。但如果你追求极致性能,或者卡数特别多,可能需要考虑其他策略。
我个人习惯把按层分片作为「保底方案」——先跑通,再优化。毕竟,能跑起来比跑得快更重要,对吧?
一句话总结: 按层分片是分布式推理的「入门砖」,简单有效,但别指望它解决所有问题。适合2-8卡推理场景,层数越多效果越好。