想象你是一家顶级餐厅的主厨,接到一道需要四位厨师通力合作的千年大菜——菜谱厚得像一本电话簿,任何一位大厨的口袋都塞不下。于是你们把菜谱撕成四份,每人保管一份,这就叫参数分片。开火之前,每个人把自己那几页拍照发到工作群,大家拼出完整菜谱,这就是 all-gather(全收集)。等到调整口味时,每位大厨都要写下整页的修改建议,这些建议必须先按章节合并成一份,再分回给负责对应章节的人,这就是 reduce-scatter(归约分片)。FSDP(Fully Sharded Data Parallel)正是这样一套让大模型在 GPU 集群上协同训练的策略:用一点通信开销,换取 N 倍的显存节省。

岗位关联

掌握 FSDP 的通信机制,是分布式训练工程师的必修课。理解 all-gather 与 reduce-scatter,你能看得懂通信瓶颈、算得清带宽开销,还能亲手把千亿级模型的训练成本降下来。

目录
  1. 可视化演示
  2. 单卡内存瓶颈:大厨的围裙口袋
  3. 参数分片原理:菜谱撕成四份
  4. all-gather 通信:开工前互相传阅菜谱
  5. reduce-scatter 聚合:修改意见汇总后各领各的
  6. 计算通信重叠:边焖边备料
  7. 通信量分析:传菜的账单
  8. 与 ZeRO 对比:两种分菜模式
  9. 通信优化技巧:给传菜减负
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态演示FSDP通信的核心机制,通过基础/核心/应用/扩展四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

FSDP通信:FSDP FSDP参数分片机制与全分片原 all-gather参数收集与 核心机制可视化 FSDP通信整体架构与关键组件

单卡内存瓶颈:大厨的围裙口袋

云顶餐厅接下一道“千层智慧羹”。菜谱打印出来比餐桌还大,最资深的大厨翻开围裙口袋,发现连半页都塞不下。这里的“大厨”就是 GPU,“菜谱”就是大模型的参数——而现实中,单个 GPU 的显存真的放不下越来越大的模型。

训练一个大模型时,需要同时保存三类东西:模型参数、梯度、优化器状态。以 70 亿参数的模型为例,仅参数一项用 32 位浮点数存储就要约 28GB,加上梯度和 Adam 优化器状态,总需求超过 100GB。而常见的 A100 显卡也只有 80GB 显存,普通消费级显卡更是只有 24GB。单卡训练很快就触碰天花板。

其中 P 为参数份数(1),G 为梯度份数(1),O 为 Adam 优化器状态份数(2),每个参数用 4 字节(float32)表示。

一份菜谱就要配三张复写纸(梯度和优化器状态),围裙口袋当然装不下。于是团队开始认真思考:如果四张嘴一起背,是不是每个人就轻松了?

Mtotal=(P+G+O)×4 bytes\displaystyle M_{\text{total}} = (P + G + O) \times 4\ \mathrm{bytes}
  1. 份数标识训练时显存里要同时保留参数、梯度、优化器状态三类数据
    P=1,G=1,O=2\displaystyle P = 1,\quad G = 1,\quad O = 2
  2. 每参数字节float32 精度下每个参数占用 4 字节
    4 bytes\displaystyle 4\ \mathrm{bytes}
  3. 总显存三者合起来,每个参数平均需要 16 字节,再乘以参数量
    Mtotal=(1+1+2)×4=16 bytes/param\displaystyle M_{\text{total}} = (1+1+2) \times 4 = 16\ \mathrm{bytes/param}

参数分片原理:菜谱撕成四份

餐厅经理做了个大胆决定:把菜谱按页码交错撕成四叠,每位大厨只贴身保管其中一叠。虽然每个人看到的都不完整,但四个人合起来就是整本菜谱——这正是 FSDP 参数分片的核心思想。

FSDP 把模型参数沿第一维均匀切成 N 份,N 等于 GPU 数量。第 i 张 GPU 只保存参数中下标为 i, i+N, i+2N, ... 的部分。同样,梯度也按这个规则切片,优化器状态更是只存在于各自分片所在的卡上。这样一来,每张卡的显存占用从“全量”降为“全量的 1/N”。

分片后的显存需求等于总显存除以 GPU 数量。FSDP 还会维护一张“分片地图”,记录每个参数块住在哪张卡上,这就是元数据,它的开销通常只有参数本体的千分之一,可以忽略不计。

四位大厨各揣四分之一菜谱,桌面一下子空出四分之三。可做菜时需要看完整菜谱啊——于是,通信就登场了。

Mshard=MtotalN\displaystyle M_{\text{shard}} = \frac{M_{\text{total}}}{N}
  1. 分子 M_total单卡全量训练时所需的总显存
    Mtotal=(P+G+O)×4 bytes\displaystyle M_{\text{total}} = (P+G+O) \times 4\ \mathrm{bytes}
  2. 分母 NGPU 数量,也是参数分片的份数
    N=GPU 数量\displaystyle N = \text{GPU 数量}
  3. 分片显存每张卡只需要承担总显存的 N 分之一
    Mshard=MtotalN\displaystyle M_{\text{shard}} = \frac{M_{\text{total}}}{N}

all-gather 通信:开工前互相传阅菜谱

上灶前,大厨们把手中的菜谱部分拍照发进工作群,谁需要全套谁就看群文件。这个“各自上传、全员下载”的过程,就是集合通信里的 all-gather:每张卡持有一份唯一的数据分片,通信结束后每张卡都拥有完整数据。

FSDP 在前向传播计算某个 Transformer 层之前,先对该层的权重做一次 all-gather,把分散在 N 张卡上的分片拼成完整权重,才能进行矩阵乘法。在反向传播中,为了再次计算梯度,需要再一次 all-gather 同一份权重。也就是说,每一个训练 Step 中,all-gather 至少发生两次。

其中 S_model 是模型权重的字节数。由于自己已保存 1/N,每张卡只需要接收其余 (N-1)/N 的数据;前面的系数 2 代表前向与反向各做一次。

传阅菜谱看似热闹,但好在它只是“先发出去”,厨师不必干等——菜在锅里焖着的时候,正好可以把下一道菜的资料传出去。这个伏笔,就是后面要讲的“通信与计算重叠”。

Callgather=2×N−1N×Smodel\displaystyle C_{\text{allgather}} = 2 \times \frac{N-1}{N} \times S_{\text{model}}
  1. 通信次数前向传播和反向传播各需要一次完整权重的重建
    2×\displaystyle 2 \times
  2. 缺失比例本卡已有 1/N,还需从其他卡接收 (N-1)/N
    N−1N\displaystyle \frac{N-1}{N}
  3. 模型字节数完整权重的大小等于参数量乘以每参数字节数
    Smodel=Nparams×4 bytes\displaystyle S_{\text{model}} = N_{\text{params}} \times 4\ \mathrm{bytes}

reduce-scatter 聚合:修改意见汇总后各领各的

每道菜试吃完,四位大厨都会写下自己的修改建议。有趣的是,这些建议必须先按章节合并,再分回到负责对应章节的大厨手里——这就是 reduce-scatter 的直觉。

反向传播结束后,每张卡其实都算出了整个模型的梯度,但 FSDP 并不需要每张卡都保存完整梯度。它的做法是:把梯度按分片位置切块,将所有卡对第 i 块的梯度求和,得到第 i 块最终梯度,然后只发给持有第 i 块参数的 GPU。第 i 张卡拿到自己的梯度分片后,立刻更新本地参数。

这里 g_{k,i} 表示第 k 张卡计算出的第 i 块梯度,G_i 是归约(求和)后第 i 张卡最终拿到并用于更新的梯度分片。

四个人各写一整页意见,合并后每人只拿回自己负责的那段——这样每张卡既完成了梯度的全局聚合,又只更新自己保存的参数分片。内存节省了,通信也花了,值不值?

Gi=∑k=1Ngk,i\displaystyle G_i = \sum_{k=1}^{N} g_{k,i}
  1. 单卡梯度块第 k 张卡算出的完整梯度中,属于第 i 个分片的那一块
    gk,i\displaystyle g_{k,i}
  2. 求和归约把所有卡对同一分片块的梯度加在一起,这就是 reduce 操作
    ∑k=1N\displaystyle \sum_{k=1}^{N}
  3. 分片梯度归约结果只保留与本地参数对应的那一片,用于参数更新
    Gi\displaystyle G_i

计算通信重叠:边焖边备料

聪明的大厨绝不会傻等汤在锅里焖好再去拿调料。趁着等待,他会把下一道配菜切好。FSDP 的通信优化也遵循同样的智慧:把通信藏到计算背后。

具体来讲,FSDP 不会等到某一层算完才开始收集下一层的参数,而是把模型按层切成若干“微批次”,采用流水线式的调度:计算第 k 层时,后台提前发出第 k+1 层的 all-gather 请求;计算第 k 层的反向时,后台同时进行第 k-1 层的梯度 reduce-scatter。这样,网络传输与 GPU 计算并行执行,通信延迟被计算时间“遮盖”住了。

T_重叠 是被计算掩盖掉的通信时间。重叠越充分,整体训练时间越接近“计算时间”这一项,通信几乎变成免费午餐。

焖菜的时间被用来切菜,出餐时间自然接近“最慢环节”,而不是一段段排队。不过,即便重叠做得再好,通信总量是客观存在的——因为它要付“传菜账单”。

T整体≈T计算+T通信−T重叠\displaystyle T_{\text{整体}} \approx T_{\text{计算}} + T_{\text{通信}} - T_{\text{重叠}}
  1. 纯计算时间GPU 执行矩阵乘法和激活函数等算子所花的时间
    T计算\displaystyle T_{\text{计算}}
  2. 纯通信时间权重和梯度在网络中传输、等待数据到齐的时间
    T通信\displaystyle T_{\text{通信}}
  3. 重叠收益被计算时间“隐藏”掉的通信部分,越小越好
    T重叠→T计算\displaystyle T_{\text{重叠}} \rightarrow T_{\text{计算}}

通信量分析:传菜的账单

请客吃饭要算传菜成本,分布式训练更要算带宽账单。FSDP 每轮训练到底要在集群里搬多少数据?答案是:前向一次 all-gather、反向一次 all-gather、一次 reduce-scatter,共三次集合通信。

作为对比,标准的 DDP(Data Parallel)每个 Step 只需要一次 allreduce。一次 allreduce 在通信量上约等于两次数据传输(求和与广播),所以 DDP 的通信量近似为 2×(N-1)/N×S。而 FSDP 的三次通信加起来是 3×(N-1)/N×S,两者相除,正好是 1.5 倍。

看起来 FSDP 的“传菜人力”更贵,但它让每位厨师只需要一张小桌子(显存),省下的是宝贵的 N 倍内存。这笔买卖到底划不划算,取决于你是“内存紧张”还是“带宽紧张”——而 ZeRO 家族给出了另一套方案。

CFSDP=3(N−1)NSmodel,CDDP=2(N−1)NSmodel\displaystyle C_{\text{FSDP}} = \frac{3(N-1)}{N} S_{\text{model}},\qquad C_{\text{DDP}} = \frac{2(N-1)}{N} S_{\text{model}}
  1. FSDP 三次通信前向 all-gather、反向 all-gather、reduce-scatter 各一次
    3×N−1NSmodel\displaystyle 3 \times \frac{N-1}{N} S_{\text{model}}
  2. DDP 两次通信一次 allreduce 的通信量等价于两次传输
    2×N−1NSmodel\displaystyle 2 \times \frac{N-1}{N} S_{\text{model}}
  3. 两者之比FSDP 通信量约为 DDP 的 1.5 倍
    CFSDPCDDP≈1.5\displaystyle \frac{C_{\text{FSDP}}}{C_{\text{DDP}}} \approx 1.5

与 ZeRO 对比:两种分菜模式

ZeRO 像一个先把食材按菜式分配的中央厨房,FSDP 则像一个把整本菜谱拆开分发的自助餐厅。两者最终效果相似,但设计哲学略有不同。

DeepSpeed 提出的 ZeRO 分三个阶段推进:ZeRO-1 只分片优化器状态;ZeRO-2 更进一步,把梯度也分片;ZeRO-3 将参数也分片,达到与 FSDP 相同的“全分片”效果。FSDP 本质上是 ZeRO-3 思想的 PyTorch 原生实现,但它在框架集成、自动分层分片、与 torch.compile 协同等方面更省心。

中央厨房和自助餐厅的争论没有绝对赢家,关键是看你的厨房(集群)是缺桌子还是缺传菜员。而在实践中,我们还有一些“传菜减负”的妙招。

MFSDP≈MDDPN,CFSDP≈1.5×CDDP\displaystyle M_{\text{FSDP}} \approx \frac{M_{\text{DDP}}}{N},\qquad C_{\text{FSDP}} \approx 1.5 \times C_{\text{DDP}}
  1. 显存下降从 DDP 到 FSDP/ZeRO-3,每卡显存从 O(P) 降为 O(P/N)
    MFSDP=MDDPN\displaystyle M_{\text{FSDP}} = \frac{M_{\text{DDP}}}{N}
  2. 通信上升通信量从约 2P 变成约 3P
    CFSDP=1.5×CDDP\displaystyle C_{\text{FSDP}} = 1.5 \times C_{\text{DDP}}
  3. 权衡公式显存的 N 倍节省,是用 1.5 倍的通信换来的
    显存×N⟷通信×1.5\displaystyle \text{显存} \times N \longleftrightarrow \text{通信} \times 1.5

通信优化技巧:给传菜减负

聪明的餐厅会在后厨先把几道小菜拼成一盘再端出去,还可以用保温箱一次装更多。通信优化也一样:给数据“压缩”和“拼车”。

最常见的优化手段有三类。第一,梯度压缩:把 32 位浮点梯度量化成 8 位甚至 4 位,通信字节数直接除以压缩比,精度损失可通过误差回传补偿。第二,调整分片粒度:每次通信的启动开销是固定的,把分片调大一些,减少通信次数,就能摊薄启动成本。第三,HSDP(混合分片):在节点内部用高效的 allreduce 通信,节点之间才用 FSDP 风格的分片,兼顾带宽利用率和扩展性。

其中 R 是压缩比。例如 float32 压缩成 int8,R=32/8=4,通信量变为原来的四分之一。

把四盒菜装进一个保温箱,再让顺路的车一起送,传菜成本肉眼可见地降了下来。分布式训练的世界里,通信从来不是免费的——但只要算得清、藏得住、压得下,它就能成为你手里最有力的杠杆。

C压缩=C原始R\displaystyle C_{\text{压缩}} = \frac{C_{\text{原始}}}{R}
  1. 原始通信量未做任何压缩时传输的梯度字节数
    C原始\displaystyle C_{\text{原始}}
  2. 压缩比原比特数除以压缩后的比特数
    R=328=4\displaystyle R = \frac{32}{8} = 4
  3. 压缩后通信量带宽占用降为原来的四分之一
    C压缩=C原始4\displaystyle C_{\text{压缩}} = \frac{C_{\text{原始}}}{4}

AI 应用场景

千亿参数大模型训练

阶段:分布式训练优化

解决的问题:单卡显存无法容纳大模型参数,模型体积超过单卡物理极限时训练无法启动。

具体案例:某团队用 8 张 A100(80GB)训练 50B 参数的 LLM。使用 FSDP 后,每张卡只需保存约 6.25B 参数的本地分片,加上梯度和优化器状态也能塞进 80GB 显存。训练时,all-gather 重建权重、reduce-scatter 回收梯度,配合通信与计算重叠,整体吞吐量比 DDP 提升了 3 倍以上。

多机多卡大模型微调

阶段:模型微调与部署

解决的问题:LoRA 微调时基础模型很大,但实际可训练的参数很少,若用 DDP 会导致显存浪费。

具体案例:算法工程师用 FSDP 微调 13B 模型,只对 LoRA 参数保持梯度,基础模型权重仍然分片存储。每次前向用 all-gather 收集权重,反向用 reduce-scatter 汇总 LoRA 梯度,微调显存占用降至原来的 1/4,同时支持在 4 张消费级显卡上完成训练。

断点续训与多租户集群

阶段:训练资源调度与容灾

解决的问题:集群多任务抢占时,模型状态太大导致 checkpoint 保存和恢复非常缓慢。

具体案例:云平台 AI 团队用 FSDP 的“状态分片存储”功能,把 checkpoint 按分片写入各卡本地盘,保存速度接近单卡写盘的 N 倍。训练中断后从分片 checkpoint 恢复,只需要 all-gather 拼装一次元数据,几分钟内即可回到断点状态。

Python 演示

python66 行
"""FSDP 通信模拟:all-gather 与 reduce-scatter"""  class FSDP:    def __init__(self, world_size, params):        self.world_size = world_size        self.num_params = len(params)        # 按 GPU 数分片:GPU i 保存索引为 i, i+N, i+2N... 的参数        self.local_shards = [            list(params[i::world_size]) for i in range(world_size)        ]     def all_gather(self):        """每张卡广播自己的分片, 拼出完整参数"""        full_params = []        for i in range(self.world_size):            full_params.extend(self.local_shards[i])        return full_params     def reduce_scatter(self, grads):        """按分片求和, 并分发回对应的 GPU"""        shard_len = len(self.local_shards[0])        reduced = []        for i in range(self.world_size):            idxs = list(range(i, self.num_params, self.world_size))            shard_sum = [0.0] * shard_len            for g in grads:                for j, idx in enumerate(idxs):                    shard_sum[j] += g[idx]            reduced.append(shard_sum)        return reduced     def update(self, reduced, lr=0.1):        for i in range(self.world_size):            for j in range(len(self.local_shards[i])):                self.local_shards[i][j] -= lr * reduced[i][j]  if __name__ == "__main__":    world_size = 4    params = [float(i) * 0.5 for i in range(8)]    model = FSDP(world_size, params)     print("=== FSDP 通信模拟 ===")    print("初始分片:")    for i in range(world_size):        print(f"  GPU{i}: {model.local_shards[i]}")     full = model.all_gather()    print("all-gather 后完整参数:")    print(" ", full)     # 模拟 4 张卡各自算出的完整梯度, 这里用不同数值    grads = [        [float((i + 1) * j) for j in range(len(params))]        for i in range(world_size)    ]    reduced = model.reduce_scatter(grads)    print("reduce-scatter 后梯度分片:")    for i in range(world_size):        print(f"  GPU{i}: {reduced[i]}")     model.update(reduced, lr=0.1)    print("梯度下降一步后的本地分片:")    for i in range(world_size):        print(f"  GPU{i}: {model.local_shards[i]}")
目的:用纯 Python 模拟 FSDP 的 all-gather 和 reduce-scatter 两个核心通信过程,帮助你直观理解“分片拼接”和“分片归约”的数据流。
  1. self.local_shards = [list(params[i::world_size]) for i in range(world_size)]每个 GPU 只拿到总参数的 1/N 分片
  2. full_params.extend(self.local_shards[i])all-gather 后把各卡分片按顺序拼回完整参数
  3. idxs = list(range(i, self.num_params, self.world_size))计算第 i 张卡负责的下标集合
  4. shard_sum[j] += g[idx]对所有卡同一下标的梯度做累加,等价于 reduce
  5. model.update(reduced, lr=0.1)每张卡只用自己的梯度分片更新本地参数

常见误区

误区既然 FSDP 把参数分片了,每张卡只需要存 1/N,通信量应该也变成 DDP 的 1/N 才对,训练应该更快更省带宽。

正解通信量不降反升。FSDP 每个 Step 要做前向 all-gather、反向 all-gather、reduce-scatter 共三次集合通信,总量约为 DDP 的 1.5 倍。它真正的收益不是省通信,而是把显存需求从 O(P) 降为 O(P/N)。用 1.5 倍的通信换 N 倍的显存,这笔交易才是 FSDP 的设计初衷。

为何常见:初学者容易把“参数分片”和“通信减少”画等号,忽略了重建完整参数本身需要多次传输。

误区以为模型参数在训练一开始收集一次,之后训练过程中就一直用这份完整参数,不用再通信了。

正解每个训练 Step 中,前向传播需要 all-gather 一次重建权重,反向传播为了算梯度又要 all-gather 一次同一份权重。由于每步更新后参数分片内容都变了,下次训练必须重新收集,所以 all-gather 是“每步两次”的高频操作。

为何常见:把“模型加载过程”和“训练迭代过程”混为一谈,忽略了参数每步都在变化。

误区梯度聚合不就是把所有卡的梯度求平均,然后让大家都有完整梯度吗?FSDP 应该也是这样。

正解allreduce 是让每张卡都拿到完整平均梯度;reduce-scatter 则先做求和归约,再把结果切片分发,最终每张卡只保留自己负责的那一片。正因为只留一片,每张卡才能省下存储完整梯度的显存。保留了完整梯度,内存分片的优势就全没了。

为何常见:DDP 中的 allreduce 太常见,导致人们忽略了 scatter 分片这个关键动作。

误区把通信和计算重叠在一起,通信时间就变成零了,训练速度只取决于计算。

正解重叠只是把通信隐藏在计算背后。如果单次通信时间小于计算时间,通信可以被完全掩盖;但大模型场景下通信经常超过计算时间,多出来的部分依然会暴露。所以优化者还要同时做压缩、调分片大小来降低通信本身。

为何常见:把“隐藏延迟”误解成“消除延迟”,忽略了重叠的前提是计算时间足够长。

误区既然 FSDP 就是 ZeRO-3 的实现,那直接用 DeepSpeed 的 ZeRO-3 不就好了,功能更强、速度更快。

正解两者底层通信模式相同,但 FSDP 是 PyTorch 原生组件,兼容性、调优生态和各种工具链集成更流畅;ZeRO-3 的优势在于更细粒度的内存控制(如 CPU offload、跨节点 NVMe 卸载)。选型取决于框架生态,而不是“谁更高级”。

为何常见:看到两者机制相似,就忽略了工程集成成本和场景适配度。

面试题

Q1. FSDP 的英文全称是什么?

单选题 · easy

  • A.Fully Sharded Data Parallel← 正确答案
  • B.Fast Sharded Data Parallel
  • C.Fully Synchronized Data Parallel
  • D.Fast Synchronized Data Parallel

答案:A。FSDP 是 Fully Sharded Data Parallel 的缩写,中文叫“全分片数据并行”。核心动作是把参数、梯度、优化器状态全部按 GPU 数量分片,而不是像 DDP 那样每张卡都保存完整副本。

Q2. FSDP 的核心目标是什么?

单选题 · easy

  • A.显存装下超大模型← 正确答案
  • B.加快单卡推理速度
  • C.减少标注样本量
  • D.提升模型准确率

答案:A。FSDP 的主要动机是突破单卡显存上限。通过参数分片,可以把超大模型塞进多张卡拼接出的“组合显存”里。它既不能直接提升准确率,也不负责推理加速和数据标注。

Q3. FSDP 前向传播中重建完整权重用的是哪种通信操作?

单选题 · mid

  • A.reduce-scatter 归约分片
  • B.allreduce 全量归约
  • C.all-gather 全量收集← 正确答案
  • D.broadcast 单项广播

答案:C。all-gather 让每张卡把自己的分片广播出去,同时接收别人的分片,最终每张卡都有完整权重。reduce-scatter 是反向传播中处理梯度的操作,broadcast 只是数据从一张卡发给所有卡,都不符合“全员收集”的语义。

Q4. reduce-scatter 与 allreduce 的核心区别是什么?

单选题 · mid

  • A.通信次数直接减半
  • B.每卡只保留一个分片← 正确答案
  • C.梯度不再做数学求和
  • D.只能适用于多机训练

答案:B。allreduce 结束后每张卡拥有完整梯度,reduce-scatter 结束后每张卡只保留自己负责的那片梯度。正是这个“只留一片”的动作,让 FSDP 省下了存储完整梯度的显存,是它与 DDP 最本质的差别。

Q5. 下列哪些属于 FSDP 通信优化手段?

多选题 · mid

  • A.梯度量化压缩← 正确答案
  • B.加大批处理量
  • C.通信计算重叠← 正确答案
  • D.调整分片粒度← 正确答案

答案:ACD。量化压缩减少通信字节数,通信计算重叠把延迟藏到计算背后,调整分片粒度摊薄通信启动开销,这三者都是 FSDP 的经典优化手段。加 batch size 更多影响吞吐和收敛,而非直接优化通信模式。

Q6. 相比 DDP,FSDP 每轮的通信量通常在什么水平?

单选题 · hard

  • A.约为 DDP 的 0.5 倍
  • B.与 DDP 基本持平
  • C.约为 DDP 的 1.5 倍← 正确答案
  • D.约为 DDP 的 3 倍

答案:C。DDP 每个 Step 做一次 allreduce,通信量约 2×(N-1)/N×S;FSDP 做前向 all-gather、反向 all-gather 和 reduce-scatter 共三次通信,约 3×(N-1)/N×S,两者比值约 1.5。FSDP 用这多出的 50% 通信换来 N 倍显存节省。

Q7. FSDP 一个训练 Step 中,all-gather 在前向和反向各发生几次?

单选题 · mid

  • A.前向 0 次,反向 1 次
  • B.前向 1 次,反向 0 次
  • C.前向 1 次,反向 1 次← 正确答案
  • D.前向 1 次,反向 2 次

答案:C。前向传播要用完整权重做矩阵乘法,所以收集一次;反向传播为了计算梯度需要再次用到完整权重,所以再一次。一个 Step 内 all-gather 共发生两次,这是 FSDP 通信量比 DDP 高的直接原因。

Q8. 将 32bit 梯度量化为 8bit 后,通信量变为原来的多少?

单选题 · hard

  • A.压缩比 R=2, 变为 1/2
  • B.压缩比 R=4, 变为 1/4← 正确答案
  • C.压缩比 R=8, 变为 1/8
  • D.压缩比 R=16, 变为 1/16

答案:B。量化压缩把每个梯度从 32 位变成 8 位,压缩比 R=32/8=4,通信量变为原来的 1/4。这是最直观的通信减负手段,代价是梯度精度降低,需要配合误差补偿机制。

Q9. ZeRO 三个阶段中,参数也被分片的是哪个阶段?

单选题 · easy

  • A.ZeRO-0 阶段
  • B.ZeRO-1 阶段
  • C.ZeRO-2 阶段
  • D.ZeRO-3 阶段← 正确答案

答案:D。ZeRO-1 只分片优化器状态,ZeRO-2 增加梯度分片,ZeRO-3 才把模型参数也分片,达到与 FSDP 等效的“全分片”状态。FSDP 本质上就是 ZeRO-3 思想的 PyTorch 原生实现。

Q10. 关于 FSDP 与 ZeRO-3,哪些说法是正确的?

多选题 · hard

  • A.两者都使用 all-gather 和 reduce-scatter← 正确答案
  • B.FSDP 是 PyTorch 原生的实现方案← 正确答案
  • C.ZeRO-3 一定比 FSDP 训练速度更快
  • D.FSDP 的显存节省接近 GPU 数量 N 倍← 正确答案

答案:ABD。两者都基于同样的通信模式:all-gather 收参数、reduce-scatter 分梯度,所以 A 正确。FSDP 由 PyTorch 官方提供,B 正确。显存理论节省约 N 倍,D 正确。C 错在“一定更快”——实际性能取决于集群拓扑、通信实现和框架优化程度,不能一概而论。