想象一下:你要跟队友一起爬一座雪山,每个人都要背全套装备——帐篷、睡袋、氧气瓶、急救包。队员越多,带进山的装备就越多,哪怕大家走的是同一条路。大模型训练也是一样:每一张 GPU 显卡都像一个登山队员,都要背上完整的模型参数、梯度和优化器状态。当模型大到 70 亿参数时,光是单卡要背的“装备”就超过 120GB,连一块 80GB 的 A100 都装不下。ZeRO 做的事情,就是把原来“每人一套”的装备,改成“全队合一套、按人头分着背”——谁需要哪件装备,再通过对讲机喊一声,从队友那里借过来用。这样一来,每个队员的背包轻了数倍,整支队伍却能爬上更高的山。这篇文章,我们就从这三张卡的故事讲起,看看 ZeRO-1、ZeRO-2、ZeRO-3 是怎么一步一步把“备用氧气瓶”“路况笔记”和“帐篷杆”都摊到每个人肩上的,以及每一级省下的显存到底有多少。
对 AI 从业者来说,ZeRO 是训练百亿乃至千亿参数大模型的核心显存技术。掌握它的分级原理与通信代价,能帮你在实际项目中快速选型 DeepSpeed 配置,避免“显存爆了不知道砍谁”的窘境。
可视化演示
展示ZeRO三阶段如何将优化器状态、梯度和参数分片到多GPU,交互调节GPU数量和阶段观察显存变化。
本节配有交互动效(进入视口后自动加载,约 3KB)。
ZeRO-1:优化器状态分区 参数(2ψ)与梯度(2ψ)全量保留 优化器状态(12ψ)按N分片 显存 = 2ψ + 2ψ + 12ψ/N 优化器状态分片,显存降至~4x
显存四件套
登山队出发前,队长要统计每个人的负重:帐篷、睡袋、氧气瓶、急救包,一样都不能少。模型训练也一样,一张卡要装下四样东西:模型参数(帐篷)、梯度(路况笔记)、优化器状态(氧气瓶+急救包),以及中间激活值(爬坡时随手堆的物资站)。在这四样里,最沉的不是帐篷,而是氧气瓶——也就是 Adam 优化器里保存的动量 m 和方差 v。
为什么氧气瓶这么沉?因为混合精度训练时,每一份参数都要同时维护 FP16 和 FP32 两种形态:FP16 参数副本 2 字节、FP16 梯度 2 字节、FP32 主权重 4 字节、FP32 动量 4 字节、FP32 方差 4 字节。加在一起,平均每个参数要占 16 字节。也就是说,一个 70 亿参数的模型,单卡想存下全量训练状态,需要 7.5 × 10⁹ × 16 ≈ 120GB——一块 80GB 的 A100 都塞不进去。
这就是第一课:显存不是被“模型大小”吃掉的,而是被“训练状态全家桶”吃掉的。其中优化器状态一家就占了 12ψ,是绝对的大头。
回到我们的登山队:氧气瓶和急救包(Adam 状态)比帐篷加睡袋还沉好几倍。谁要是能把氧气瓶的重量降下来,谁就捏住了显存的七寸。
- 参数量符号$\psi$ 表示模型总参数量。比如 7.5B 模型,就是 $\psi = 7.5 \times 10^9$。
- 六份状态的字节数混合精度下每个参数对应 6 份存储,合计 $(2 + 2 + 4 + 4 + 4) = 16$ 字节。
- 结果含义$M_{\text{base}}$ 是单卡存全量训练状态所需显存,$\psi=7.5B$ 时约为 120GB。
基线数据并行
登山队的原始方案很简单粗暴:每个队员都背一整套装备,谁也别想偷懒。数据并行(DP)就是这个思路——每张卡都复制一份完整的模型参数,喂进去不同批次的数据,各自前向反向算出梯度,然后所有人把梯度拿来求平均(这一步叫 all-reduce,可以理解为全体队员对着对讲机报数,最后每个人都知道平均路况),再用平均后的梯度更新各自的参数。
这个方案的问题肉眼可见:卡越多,总显存越大。N 张卡就要 N 份完整的 16ψ,一点共享都没有。还是那个 7.5B 模型,用 64 张卡做数据并行,总显存需求是 64 × 120GB ≈ 7.7TB。而实际上每张卡的有效计算贡献无非是多吃了一份数据,显存却被复制了 64 份——这是一笔非常不划算的买卖。
登山队这才发现:与其每个人背同样的氧气瓶进山,不如在山脚建一个共享营地,把装备按人头分好,谁要用再来领。这个从“人手一套”到“全队一套”的转变,正是 ZeRO 的出发点。
对登山队来说,这个方案意味着 64 个人背 64 套氧气瓶进山,结果路上还有 63 套是纯浪费。队长开始琢磨:能不能把氧气瓶拆开,一人背一罐,用的时候再互相借?
- 单卡基线$16\psi$ 是上一节算出的单卡训练状态全量显存。
- 乘以卡数$N$ 为 GPU 数量,数据并行要求每卡都有一份完整副本,所以乘法生效。
- 线性增长的含义加卡只能增加吞吐,不能摊薄单卡显存负担,甚至总显存需求反而爆炸。
ZeRO-1切优化器
队长的第一个主意是拿最沉的氧气瓶开刀:把全队 12ψ 的 Adam 优化器状态均匀切成 N 份,每人只负责保管其中一份。训练的时候,每张卡的反向传播照常算出完整梯度,但不需要保留完整梯度——先通过一次 reduce-scatter(全体队员各自报数,最后每个人手里只剩一组平均数的一部分,也可以理解为“把平均路况按路段分给每个人记录”),让自己拿到属于自己那一段的平均梯度,然后立刻用它更新自己保管的那份优化器状态。
这一步切分只动了优化器状态,参数和梯度仍然是每卡全量,所以显存公式变成了 4ψ + 12ψ/N。拿 7.5B、64 卡来算:参数 2ψ(15GB)+ 梯度 2ψ(15GB)+ 优化器分片 12ψ/64(约 1.4GB),每卡约 31.4GB——从 120GB 直接砍到 31.4GB,省掉了四分之三。更妙的是,通信量并没有增加:原来的梯度 all-reduce 被拆成 reduce-scatter(通信量 2ψ)和更新后的梯度 all-gather(通信量 2ψ),总量依然是 2ψ,与基线持平。
ZeRO-1 的朴素智慧是:最重的东西不要人人带一份,而是人人各带一部分。氧气瓶从 90GB 的“人均负重”变成 1.4GB 的“人均份额”,登山队员的背包瞬间轻了四分之三。
氧气瓶问题解决了,但登山队的背包里还有一厚摞路况笔记(梯度)——那也是每人一份全量的。队长眯起眼睛:下一刀,砍它。
- 保留的全量部分$4\psi$ = FP16 参数副本 $2\psi$ + FP16 梯度 $2\psi$,这两样不动。
- 切分的主体$\frac{12\psi}{N}$ 是 Adam 的 FP32 主权重、动量、方差三样各 4ψ,合计 12ψ,被均匀切成 N 份。
- 代入实例$\psi=7.5B, N=64$ 时,$M_{\text{ZeRO-1}}=30+1.4\approx 31.4\text{GB}$。
ZeRO-2切梯度
队长的第二个主意是处理路况笔记:反正每张卡算完梯度后,真正用到的只有自己负责的那一段,那梯度也没必要全量保留。ZeRO-2 在前向/反向算出全量梯度后,用 reduce-scatter 把梯度均分成 N 份,每张卡只留下属于自己的那 1/N 段,然后立刻用它去更新自己保管的优化器状态分片——更新完,这一段梯度就可以释放了。也就是说,梯度显存从全量 2ψ 降到了 2ψ/N。
加上 ZeRO-1 已经切掉的优化器状态,ZeRO-2 的显存公式变成 2ψ + 14ψ/N。还是 7.5B、64 卡:参数 2ψ(15GB)+ 梯度分片 2ψ/64(0.23GB)+ 优化器分片 12ψ/64(1.4GB),每卡约 16.6GB。相比 ZeRO-1 的 31.4GB 又少了一半,相比基线 120GB 已经省下了 86%。
登山队此时的状态是:氧气瓶分着背、路况笔记分着记,只有帐篷还每人一顶。每人的背包已经很轻,但队长盯着帐篷——那是最后一坨全量重量。
路况笔记也不全量了。但队长还有最后一个念头:帐篷那么大,为什么不能让每个人只扛一根帐篷杆呢?
- 剩下的全量$2\psi$ 只指 FP16 参数副本,这是前向计算必须的全量参数。
- 切分的总量$\frac{14\psi}{N}$ = 梯度 $2\psi$ + 优化器状态 $12\psi$,合计 14ψ 均分。
- 通信不变ZeRO-2 的通信仍是 reduce-scatter 加 all-gather 一次系数汇总,总通信量约 $2\psi$,和基线同级。
- 代入实例$N=64$ 时每卡约 $15 + 1.64 \approx 16.6\text{GB}$。
ZeRO-3切参数
ZeRO-3 的答案是:连帐篷也拆了。模型参数被均匀切成 N 份,每张卡只保存自己负责的那 1/N 段参数。但问题来了:前向计算时,每一层都需要完整的权重。怎么办?答案是“用时借”——在计算某一层之前,所有卡通过一次 all-gather(全体队员把各自保管的零件广播一遍,拼成完整帐篷)把该层参数收集齐全,计算完再丢弃。反向传播时,为了算梯度,又得再 all-gather 一次该层参数。这就是 ZeRO-3 的典型特征:参数平时不存在显存里,用到哪层,哪层才“快递”过来。
显存公式变得极其干净:16ψ/N。7.5B、64 卡下每卡约 1.9GB,一张消费级显卡就能塞下 70 亿参数的训练状态。但这世界没有免费的午餐——参数每次前向、反向都要 all-gather,通信量相比基线增加了约 50%。也就是说,ZeRO-3 是用“喊话频率”换“背包重量”。
登山队最终实现了全员轻量化:每个人只扛一根帐篷杆、一瓶氧气、半页路况笔记,走到哪儿需要搭帐篷,就对讲机喊一声,队友把杆子拆过来,用完再还。整支队伍确实能爬更高的山了,只是山里的对讲机频道,明显热闹了不少。
不过,帐篷拆得越碎,搭帐篷时喊话的次数就越多。省下的显存,终究要用额外的通信时间来买单——这就是下一节的天平。
- 分子 16ψ混合精度训练状态六件套全部被纳入切分范围。
- 分母 N所有训练状态均匀分布在 N 张卡上,单卡承担 $1/N$。
- 实例$\frac{120\text{GB}}{64} \approx 1.9\text{GB}$,显存不再是大模型训练的第一瓶颈。
通信换显存
登山队发现,装备分摊到每个人头上之后,队伍虽然能轻装上阵,但队员之间的对讲机通话量急剧上升。ZeRO 的世界里,显存和通信就是这样一个跷跷板:每一级切分都省下一块显存,同时可能多出一笔通信账单。
先看账单明细。基线数据并行:每步要 all-reduce 一次梯度,每卡通信量约 2ψ。ZeRO-1 和 ZeRO-2:把 all-reduce 拆成 reduce-scatter + all-gather 两步,通信总量依然是 2ψ,所以和基线打平——这是 ZeRO-1/2 最划算的地方,显存省了,通信不涨。到了 ZeRO-3:除了梯度通信,每层参数在训练中被 all-gather 两次(前向一次、反向一次),额外多出参数规模的通信,总通信量约为基线的 1.5 倍。
这个 1.5 倍在高速互联的集群里也许只是多等几毫秒,但在千兆以太网或 PCIe 互联的廉价集群里,可能就是训练速度腰斩的导火索。所以选型时要想清楚:你的“山路”是高速公路(NVLink/IB)还是乡间小道(千兆网)?网络越好,越敢用 ZeRO-3。
对讲机喊话次数变多了,但每个队员的负重确实轻了。聪明的领队会根据山路条件做取舍:路好走就多喊话,路难走就少拆装备。
- 基线通信量$V_{\text{base}} \approx 2\psi$,是每步梯度 all-reduce 的字节数。
- ZeRO-1/2 不变改成 reduce-scatter + all-gather 后,总量仍约 $2\psi$。
- ZeRO-3 的增量每层参数前向、反向各 all-gather 一次,额外增加约 $1\psi$ 量级的通信。
- 结论$V_{\text{ZeRO-3}} \approx 3\psi = 1.5 \times 2\psi$。
工程落地
理解了分级原理,落地的选型就变得像按体力分装备一样简单。如果训练的是 7B 以下的小模型,单卡显存够用,直接 ZeRO-1 甚至不用 ZeRO;如果模型到了 13B、33B 级别,ZeRO-2 通常是最佳性价比——显存省得明显,通信又不涨;当模型冲向 100B、1000B 时,必须上 ZeRO-3,同时配合流水并行和张量并行组成“组合拳”,把参数、梯度、优化器状态、激活值四大件全部摊开。
工程上还有两个常用搭配。第一是 offload:把优化器状态甚至参数卸载到 CPU 内存或 NVMe 硬盘,相当于给登山队请了一个山脚仓库管理员,GPU 只管算,存东西找仓库。第二是激活检查点(activation checkpointing):不保存每一层的激活值,反向时重新计算一遍,用算力换显存——这是应对长序列训练时激活爆炸的利器。在 DeepSpeed 里,这一切只需要在配置文件里写 "zero_optimization": {"stage": 3} 这样一行配置即可生效。
最后别忘了那个极限:卡数 N 趋于无穷时,ZeRO-3 每卡显存 16ψ/N 趋近于零,但这只是理论上的“行李消失术”。现实中通信不会归零,激活值也不会归零,所以别把 ZeRO 当成万能药,它只是显存管理这盘大棋里最关键的一步。
登山队明白了一个道理:装备可以无限拆细,但对讲机的频道总会拥堵。聪明的领队,会在背包重量和喊话频率之间,找到属于自己这支队伍的最佳平衡点。
- 极限对象$\frac{16\psi}{N}$ 是 ZeRO-3 的每卡显存公式。
- N 趋向无穷理论上加卡就能无限摊薄显存,训练状态可以无限细分。
- 现实约束通信量不随 N 归零,激活值也不参与切分,所以极限只是“理论背包”,不是实际背包。
AI 应用场景
千卡集群训练千亿级大模型
阶段:大规模预训练
解决的问题:训练 175B+ 参数的模型时,基线数据并行需要每卡存储全量 16ψ≈2.8TB 训练状态,任何单卡都无法容纳。ZeRO-3 将全部训练状态切分到数千张卡上,配合流水并行和张量并行,让千亿模型训练成为可能。
具体案例:某大模型团队用 1024 张 A100 训练一个 175B 模型,采用 DeepSpeed ZeRO-3 + 流水并行。参数、梯度、优化器状态均分为 1024 份,每卡仅需承担约 2.7GB 的训练状态,配合高速 IB 网络将 1.5 倍通信开销控制在可接受范围。
单机四卡微调 13B 开源模型
阶段:监督微调(SFT)
解决的问题:普通团队只有 4 张 RTX 4090(每张 24GB),直接加载 13B 模型做 LoRA 微调或全参微调都会显存溢出。ZeRO-2 + offload 可以把优化器状态卸载到 CPU 内存,让 4 张消费级显卡跑起全参微调。
具体案例:算法工程师在 4×4090 机器上微调 13B Chat 模型,DeepSpeed 配置 stage: 2 并将优化器 offload 到 CPU。每卡显存从全量约 208GB 降到约 34GB,微调 batch size 从 1 提升到 4,训练吞吐提升 2.5 倍。
长序列训练对抗激活显存爆炸
阶段:长上下文训练/微调
解决的问题:训练 128K 长序列时,即便 ZeRO-3 把参数类显存压到极低,中间激活值依然会在单卡上堆积几 GB 到几十 GB。通过 ZeRO-3 搭配激活检查点,用“反向重新计算”换取激活显存的释放。
具体案例:某团队微调 7B 模型支持 128K 上下文,开启 ZeRO-3 + activation checkpointing 后,激活显存峰值从 40GB 降到 8GB,单卡总显存占用稳定在 22GB 以内,成功在 8 张 A100 上完成长序列微调。
Python 演示
def estimate(params_b, gpus, gpu_mem_gb=80.0): psi = params_b * 1e9 base = 16 * psi stages = { 0: (base, 1.0), 1: (4 * psi + 12 * psi / gpus, 1.0), 2: (2 * psi + 14 * psi / gpus, 1.0), 3: (16 * psi / gpus, 1.5), } print("模型 {}B / {} 卡 / 单卡 {}GB".format(params_b, gpus, gpu_mem_gb)) for stage, (mem, comm) in stages.items(): per_gpu = mem ok = "可行" if per_gpu <= gpu_mem_gb * 1e9 else "超显存" print(" ZeRO-{}: 每卡 {:.2f}GB, 通信 {}x -> {}".format( stage, per_gpu / 1e9, comm, ok)) if __name__ == "__main__": estimate(7.5, 64, 80) estimate(13, 4, 24)
psi = params_b * 1e9把“B”为单位的参数量换算成实际参数个数,后续所有显存公式都以它为基准。base = 16 * psi复现 16ψ 公式,混合精度训练状态下每个参数占用 16 字节。1: (4 * psi + 12 * psi / gpus, 1.0)ZeRO-1 显存公式 4ψ + 12ψ/N,通信倍数保持 1.0。2: (2 * psi + 14 * psi / gpus, 1.0)ZeRO-2 显存公式 2ψ + 14ψ/N,通信仍不涨。3: (16 * psi / gpus, 1.5)ZeRO-3 全切分公式 16ψ/N,通信倍数 1.5。per_gpu / 1e9把字节换算成 GB,方便与 GPU 实际显存对比。estimate(7.5, 64, 80)示例:7.5B 模型 64 张 80GB 卡,输出会显示 ZeRO-0/1/2/3 各自是否可行。
常见误区
误区“ZeRO 把参数切到多张卡上,那它不就是张量并行或流水并行吗?和模型并行是一回事。”
正解ZeRO 本质上是数据并行的显存优化方案。每张卡依然处理不同的 batch 数据,只是训练状态的存储从“每卡全量”变成“全网分片”。张量并行是把单层计算拆到多卡,流水并行是按层切分计算流程,这两者切割的是“计算图”,而 ZeRO 切割的是“存储副本”。实际大模型训练常把三者组合使用,但它们解决的问题各不相同。
为何常见:ZeRO-3 确实把参数全网分片了,看起来“很像模型并行”,初学者容易从表象归类,而忽略了每个并行策略切割的对象是计算还是存储。
误区“ZeRO-3 省显存最多,效果最好,所以不管什么场景我都直接开 stage 3 就行。”
正解ZeRO-3 的 1.5 倍通信开销在网络带宽不足时会让训练明显变慢。训练 7B 以下模型时,单卡显存通常够用,ZeRO-2 甚至 ZeRO-1 性价比更高;只有在模型大到单卡放不下全量训练状态时,ZeRO-3 才成为必选项。选型要看“当前瓶颈是显存还是带宽”,而不是盲目追求最高 stage。
为何常见:人容易把“省得更多”等同于“更好”,忽略了显存之外还有通信、延迟这些同样关键的成本维度。
误区“ZeRO-3 把参数都切完了,每卡只用 1.9GB,那再大的模型也能跑,显存问题彻底解决了。”
正解ZeRO-3 只切分了参数、梯度、优化器状态这三类“静态状态”,而中间激活值依然在前向过程中全量存在于显存里。训练长序列或大 batch 时,激活值可能轻松超过 30GB,照样爆显存。必须额外配合激活检查点、梯度累积等手段,才能把激活峰值压下来。
为何常见:显存公式 16ψ/N 太干净、太有说服力,让人忘了公式里根本没包含激活值这一大项。
误区“显存省下来了,每张卡可以塞更大 batch,训练应该又快又省。”
正解ZeRO 省显存不等于省时间。ZeRO-1/2 虽然通信量与基线持平,但新增的分片更新和 all-gather 同步都有额外延迟;ZeRO-3 更是直接多了 50% 通信量。省下来的显存如果用于增大 batch,可以提升吞吐,但通信开销会部分抵消收益。实际工程里经常要调优 batch、梯度累积步数和网络配置来找到吞吐最优解。
为何常见:人容易把“资源占用下降”直接等同于“性能提升”,忽略了分布式训练里通信常常是新的瓶颈。
面试题
Q1. 混合精度(FP16 + FP32 Adam)训练一个 7.5B 参数模型,单卡全量训练状态约需多少显存?
- A.16GB
- B.60GB
- C.90GB
- D.120GB← 正确答案
答案:D。基于 16ψ 公式,ψ=7.5B 时 16×7.5=120GB。其中优化器状态 12ψ=90GB,参数和梯度各 15GB。A 是把 16 字节误当 16GB,C 只是优化器状态那一份。
Q2. ZeRO-1 主要切分的是哪一部分训练状态?
- A.模型参数副本
- B.反向传播梯度
- C.Adam 优化器状态← 正确答案
- D.中间激活张量
答案:C。ZeRO-1 只切分最重的 Adam 优化器状态(12ψ),参数和梯度仍每卡全量。切梯度和切参数分别属于 ZeRO-2 和 ZeRO-3。
Q3. ZeRO-2 相比 ZeRO-1 额外切分了什么?
- A.模型参数副本
- B.反向传播梯度← 正确答案
- C.Adam 优化器状态
- D.训练样本数据
答案:B。ZeRO-2 在优化器状态基础上,把梯度也分区存储,用完即释放。参数切分是 ZeRO-3 做的事,训练样本数据从来不属于切分对象。
Q4. 64 卡训练 7.5B 模型,ZeRO-2 每卡显存约为多少?
- A.约 30GB
- B.约 17GB← 正确答案
- C.约 8GB
- D.约 2GB
答案:B。ZeRO-2 公式 2ψ+14ψ/N = 15 + 105/64 ≈ 16.6GB。A 是 ZeRO-1 的水平,D 是 ZeRO-3 的水平,C 没有对应任何标准档位。
Q5. ZeRO-3 在计算某层前向时,需要把分散在各卡上的该层参数收集齐全,这个操作叫什么?
- A.Reduce-Scatter
- B.All-Gather← 正确答案
- C.All-Reduce
- D.Broadcast
答案:B。参数是分片存储在各卡上的,计算前需要用 All-Gather 把所有人手里的零件拼成完整权重。Reduce-Scatter 用于梯度平均后分片,All-Reduce 是基线 DP 的整段梯度同步操作。
Q6. 关于 ZeRO-3 相比 ZeRO-2 的正确说法有哪些?
- A.参数也被分区存储← 正确答案
- B.前向反向都要通信← 正确答案
- C.通信量显著增加← 正确答案
- D.激活值不再占用显存
答案:ABC。ZeRO-3 把参数也切分了,平时不保有全量权重,前向和反向都要 All-Gather 参数,通信量约为基线 1.5 倍。但激活值不在切分范围内,D 说法错误。
Q7. 根据 DeepSpeed 论文,ZeRO-3 的总通信量约为基线数据并行的多少倍?
- A.约 0.5 倍
- B.约 1.5 倍← 正确答案
- C.约 2.5 倍
- D.约 3.0 倍
答案:B。基线 DP 每步通信约 2ψ(梯度 all-reduce),ZeRO-1/2 保持 2ψ 不变;ZeRO-3 额外增加参数 all-gather 通信,总通信量约为 1.5×基线。这是论文中的经典结论,也是选型时判断是否用 ZeRO-3 的关键依据。
Q8. 关于 ZeRO-1/2 通信量与基线数据并行的关系,正确的说法是?
- A.与基线处于同一量级← 正确答案
- B.通信量降低到一半
- C.通信量直接翻倍
- D.降低到原来四分之一
答案:A。ZeRO-1/2 把梯度 all-reduce 拆成 reduce-scatter 和 all-gather,总通信量仍约 2ψ,与基线持平。这是 ZeRO-1/2 性价比高的核心原因:显存省了,通信账单没涨。
Q9. 下列哪些技术可以和 ZeRO 组合使用以进一步降低显存?
- A.激活检查点降激活← 正确答案
- B.参数卸载到 CPU← 正确答案
- C.梯度累积省显存← 正确答案
- D.关闭混合精度训练
答案:ABC。激活检查点用重计算换激活显存,offload 把优化器状态/参数挪到 CPU,梯度累积通过分步更新降低峰值。混合精度是 ZeRO 显存估算的前提,关闭它会彻底改变公式,不是组合手段。
Q10. 实际训练 100B 以上模型时,关于 ZeRO-3 的说法正确的有?
- A.网络带宽必须充足← 正确答案
- B.激活显存仍需关注← 正确答案
- C.参数分区不再重要
- D.通信开销可以忽略
答案:AB。ZeRO-3 的 1.5 倍通信依赖高速网络(如 IB)才能保证吞吐;激活值不参与切分,长序列下仍是显存杀手。参数分区是 ZeRO-3 的核心,通信开销是它的主要代价,C 和 D 表述相反。