想象你是一家超大型连锁餐厅的主厨,要在晚市高峰同时为几千桌客人出菜。每桌的菜谱相同,但口味要求略有差异。你不能自己一个人从头做到尾——你必须把菜谱分给一百位厨师,让他们每人负责几桌,同时开火;还得安排好谁切配、谁掌勺、谁装盘,确保所有厨师出锅时间一致,最后统一上桌。这台协同作战的“厨房”,就是 TPU(张量处理器),而 JAX——这套新兴的深度学习框架——就是你的总指挥,让你用几行命令,就能把训练任务撒向成百上千颗 TPU 芯片,让它们像训练有素的厨师团队一样集体出菜。
这篇文章不讲高深理论,只讲实际操作:怎么认领 TPU 设备、怎么用 pmap 把数据分给每位“厨师”、怎么用 jit 把整个流程彩排成肌肉记忆、怎么让所有人的“调味心得”(梯度)在厨房里高效同步、又怎么调试整个厨房的瓶颈。读完你就知道,在 TPU 集群上跑大模型训练,跟指挥一群厨师同步出菜,本质上是一回事——把一盘菜的工序拆成无数份,让人人有活干,步调一致,菜才不会凉。
对 AI 从业者而言,掌握 JAX + TPU 分布式训练意味着能把百亿参数大模型的预训练周期从“周”缩短到“天”,这是大模型时代最稀缺的工程能力之一。
目录
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
pmap: 数据沿batch维度切分到各TPU核心 all-reduce: 梯度同步pmap核心要点1. pmap(fn): 函数映射到多设备并行执行C = A \u00D7 B (输出流)1. v4-2048 Pod: 512板 x 4核 = 2048核,全互联...
设备初始化:上岗前先认识厨房和厨师
你第一次走进这间巨大的中央厨房,不能抓起锅就炒菜。你首先得知道:这间厨房里有多少位厨师?他们站在哪几个灶台边?每个灶台有多少可用空间?
在 JAX 里,这个过程叫设备初始化。jax.devices() 会返回当前进程能看到的所有加速器设备列表——就像总指挥拿着花名册,逐一清点这一班上岗的厨师。如果是 TPU v4 单卡,你会看到 8 颗核心(chip);如果连上了 Pod 集群,你会看到几十甚至上百颗核心。每颗核心还有自己的“工位编号”,这就是设备坐标(device coordinates),它对应 TPU 的物理拓扑位置:哪些芯片直接相连、哪些芯片相距较远需要绕路通信。
初始化时还有一个关键动作:内存分配。JAX 默认采用“按需申请 + 预分配缓冲池”的策略,就像厨师上岗前先把砧板、刀具、备菜盆按固定位置摆好,而不是每次切菜都重新找工具。这样减少了 TCMalloc 频繁分配带来的开销,也让你对每颗芯片的峰值内存有更清晰的预期。
内存分片的思想可以写成:把一份完整的全局训练数据,按设备数量切成等宽的“菜分量”。
做完这一步,你就认识了全部厨师和灶台,可以开始分配任务了。记住:初始化永远是你踩的第一个坑——如果连设备都没认清,后面所有并行操作都像在空荡荡的厨房里喊“开饭”。
x_global 被切成 N 块,编号 i 的设备拿到第 i 块。b 是每块的大小(batch size)。厨师 i 只负责自己的那片食材,互不串味。- 全局张量切块
x_global是全体厨师共用的完整食材堆,现在按设备数量 N 切成 N 份。每份大小相同,都是b。 - 设备专属切片编号为
i的厨师只取从i \times b开始到(i+1) \times b结束的那一段,保证每个灶台的活儿互不重叠。 - 并行前提公式背后的前提是
N必须能整除总样本数,否则有的厨师拿到的菜量不一样,就会有人提前干完空等,造成资源浪费——这就是需要调整 batch size 对齐的原因。
pmap并行化:把菜谱分给每位厨师
厨房里一百位厨师同时开工。你站在中央,手头只有一份完整的菜谱,而每位厨师需要独立复制一份、对着同一流程做自己那份菜。pmap 就是这个“复印机”。
pmap(f) 会把函数 f 编译成一个可以并置(shard)在多台设备上的版本。你给它一个形状为 (N, ...) 的输入数组,它就沿第一个维度把数组切开,把第 i 片送到设备 i 上,让每台设备独立执行 f,最后把结果收集回来——就像你递过去一百份菜谱,每位厨师对着自己的备菜做出一盘成品,再统一端回传菜口。
这就是单程序多数据(SPMD)的核心:同一份代码,多份数据,全套设备并行跑。pmap 在语义上等价于“把 f 同时运行在每个设备的本地分片上”,它自动帮你完成了设备间的数据搬运和结果拼接,你不需要手动写 for 循环去逐个设备分发。
pmap 适合数据并行场景,也就是每颗芯片持有完整模型副本、只分发数据。不过要注意:pmap 对函数内部的所有操作一视同仁——如果你在函数里写了跨设备的归约操作,它会默默帮你插入通信。这既方便也危险,因为通信不是免费的——它需要厨师们停下炒菜、伸手交换食材。当模型大、同步频繁时,通信开销就成了主角,这是后面第四节要展开的。
pmap 把一个函数作用在多设备张量的每个切片上,结果自动沿设备数维度拼回完整张量。就像把“切土豆丝”这件事同时交给一百位厨师,最终收回一百份土豆丝。- 设备分片输入把多设备张量
x按第一维拆开,第i片交给设备i。 - 逐设备独立执行每个设备都运行同一份函数
f,f只处理自己手里的x_i,不偷看别人的。 - 结果拼接把 N 份输出沿设备维度重新拼起来,外部调用者拿到的仍是完整张量。
jit编译优化:提前彩排出肌肉记忆
新来的厨师一开始炒菜要看菜谱,每步都要停下来确认。但如果你让他连续三天做同一道菜,他会形成肌肉记忆,动作一气呵成,出菜时间大大缩短。jit 就是这么一位“培训总监”,它把你写好的 Python 函数,通过 XLA 编译器一次性编译成针对 TPU 硬件高度优化的指令序列。
关键在于,Python 代码本身是解释执行的,一行一行跑很慢。jit 会先把整段计算构建成一张计算图,然后交给 XLA 去做算子融合:把多个小算子(乘法、加法、归一化)合并成一个大的融合算子,每个融合算子在 TPU 上只启动一次内核,把中间结果留在片上寄存器里,不反复读写 HBM 内存。这就像厨师把“洗菜—切菜—配菜”三个步骤并成一个连贯动作,不再每步都把手里的刀放下。
jit 对输入参数有严格约束:数组的形状和类型(dtype)必须稳定。因为编译是“静态”的——它像一个在开工前就把每个人的动作精确编排好的舞谱,不允许中途临时改队形。如果形状变了,JAX 会重新编译,这个“重新彩排”的开销可能高达数十秒,跑一次就换一次形状的训练循环会慢得让人崩溃。
彩排的价值不仅在于快,还在于可预期。一旦编译完成,每次调用都是直接执行机器码,不再有 Python 解释器的额外开销。实践中的经验是:能用 jit 包裹的循环就尽量包,但要把可变形状的参数从函数签名里赶出去——它们会让“总指挥”以为菜谱变了,不得不重新彩排一遍。
- 算子分组融合把计算图中的多个算子按依赖关系分组,把同一组内的算子合并成一个融合算子
O'_j。 - 延迟求和所有融合算子的执行延迟加起来,得到整段流程的总耗时。
- 最小化目标XLA 的优化器以最小化该总耗时为终极目标,求解一个组合优化问题,选出最优融合方案。
分布式训练:让所有厨师交换调味心得
每位厨师做的是同一道菜,但每个人尝过自己的成品后,会得到不同的反馈——有的觉得咸了,有的觉得淡了。要让整道菜的口味最终统一,所有厨师必须把各自的调味心得汇总起来,取一个平均值,然后按这个平均值调整自己的下一步操作。
这就是分布式训练的本质:每台设备用自己那份数据算出本地梯度(调味心得),然后通过All-reduce操作把所有人的梯度求和取平均,得到全局梯度,再用全局梯度更新模型参数。梯度同步保证了:虽然每个人只看了自己那份食材,但最终模型学到的是“全体数据”的规律。
这个“交换心得”的过程在算法上等价于:
别忘了,梯度同步的前置条件是数据加载要跟上。TPU 算得快,但如果数据管道的吞吐量喂不饱所有核心——比如磁盘读太慢,或者 CPU 端的预处理成了瓶颈——再多的厨师也只能握着刀等食材。训练循环里的每一步,都像流水线:数据加载(备菜)→ 前向(炒制)→ 反向(尝味)→ 梯度同步(交流)→ 更新(调整)。任何一环卡住,整个周期都变长。JAX 的 jit + pmap 会把单向数据加载尽量异步化,但开发者仍要关注 tf.data 或 jax.experimental 里的数据流水线设计,确保“菜料充足、厨师不停火”。
- 本地梯度计算每台设备 i 在自己的数据切片上计算损失函数对参数的梯度。
- 全局梯度聚合把 N 个梯度加起来除以 N,得到平均梯度。这里的求和需要跨设备通信,正是 All-reduce 的用武之地。
- 参数更新用平均梯度沿着梯度反方向更新参数,步长由学习率
η控制。
TPU Pod集群:整个厨房连锁集体出菜
单台 TPU 有 8 颗核心,像一家中型餐厅。但当你做的是千亿参数大模型,一家餐厅不够——你得把几十家餐厅联合成连锁集团,共享同一个中央厨房的标准菜谱。这就是 TPU Pod:由数百颗 TPU 核心通过高速互联网络(如 ICI、数据中心网络)组成的超级集群。
在 Pod 上写 JAX 代码,和单机 TPU 的思路没有本质区别:pmap 一样用,jit 一样包。但多了两个必须面对的新问题:
第一,拓扑连接决定通信代价。Pod 内部,芯片之间的 ICI 带宽有快慢之分,相邻芯片通信远快于跨机柜通信。JAX 允许你查看 jax.devices() 的坐标信息来判断远近。把梯度同步的通信量安排在物理相邻的设备上,就像连锁餐厅的食材尽量从隔壁仓库调拨,而不是空运。
第二,通信开销的占比随规模爆炸式增长。100 颗核心做一次全量梯度 All-reduce,消息需要层层汇聚和重分发,时延会随设备数对数增长。一个经典的估算模型是:
应对集群通信瓶颈的常用策略包括:梯度压缩(只传低精度量)、梯度累积(多个 step 的梯度攒一起再同步一次)、以及把 All-reduce 改成 Ring 或 Tree 的拓扑结构。这些优化的核心思想,都是减少“厨师们放下锅、转身握手”的次数——能少交流就少交流,能攒一批再交流就攒一批。
α 是单次通信的固定时延(厨师喊一嗓子的时间),β 是带宽(传菜通道的粗细),G 是梯度总字节数。设备越多,固定时延 α 的项越致命。- 时延累加项All-reduce 的通信轮数与设备数量成正比,每轮都有固定的消息启动开销
α。 - 带宽传输项数据总量 G 在 N 个设备间均摊传输,带宽越大、传输时间越短。
- 总时间估计两项相加,就能预估一次梯度同步的耗时,从而判断通信是否已成为训练瓶颈。
调试与优化:把厨房调成米其林水准
菜做出来了,但上菜速度不理想。这时候不能凭感觉乱改配方,得先找出瓶颈在哪一环——是备菜慢、炒制慢,还是传菜慢?为此,你需要一把“厨房监控器”:TPU Profile、jax.profiler 这类性能剖析工具。
jax.profiler.start_trace() 会在训练循环里采集每个算子的耗时、内存占用、通信等待事件,最后生成一份时间线报告。你看着报告,就像看着厨房里每个环节的秒表记录:哪个厨师动了 10 秒,哪道菜在传菜口等了一分钟。
常见的调优路径是分阶段逼近:
1. 数据加载阶段:看 DataFeed 是否占用训练周期。如果比例过高,加大预取缓冲区(buffer size)、用 jax.device_put 提前把数据放到设备内存。
2. 计算阶段:看 TPU 利用率是否接近 100%。若低于 60%,说明算子融合不够或有小算子拖后腿。把 pmap 里的逐元素操作尽量合并,减少内核启动。
3. 通信阶段:看 All-reduce 的等待时间。占比高就尝试梯度压缩、减少同步频率,或使用 pjit(分区并行)把通信拓扑安排得更合理。
内存优化同样关键。TPU 的 HBM 容量是硬上限,模型、梯度、优化器状态三者加起来不能超。JAX 的内存剖析公式可以帮你预估峰值占用:
调优的目标,是让整条流水线里没有“空转”的环节——每位厨师、每把刀、每块砧板都在持续工作。做到数据不断料、计算不空转、通信不排队,你的 TPU 集群训练效率就离理论峰值不远了。
- 算子体积每个算子
op_k的输出张量占用的字节数,由形状和 dtype 决定。 - 存活期因子
liveness_k取 1 表示该张量在某一时刻必须同时驻留显存,取 0 表示已可释放。 - 峰值求和把所有存活张量的大小加起来,得到训练过程的显存峰值,用来指导你是否需要开启重计算或梯度检查点。
AI 应用场景
场景一:百亿参数大模型预训练
阶段:分布式训练
解决的问题:大模型参数量超过单卡显存,传统单机训练需要数周。用 JAX 将模型参数分片到 TPU Pod 的数百颗芯片上,配合 pmap 做数据并行、jit 做算子融合,把预训练时间从数周压缩到数天。
具体案例:某团队用 JAX 在 256 核 TPU v4 Pod 上训练 130 亿参数语言模型。他们先用 jax.devices() 确认可用核数,再用 pmap 并行化数据加载和前反向传播,梯度同步交给 XLA 自动插入的 All-reduce,最终吞吐达到每核每秒 12 万 token——相当于 256 位厨师同时颠勺,菜还一盘接一盘地出。
场景二:大规模推荐系统全量模型更新
阶段:数据并行训练与模型迭代
解决的问题:推荐模型需要每天在数十亿用户行为数据上重新训练。用 JAX 的 jit 把特征处理、Embedding 查询、深度网络整合成单一计算图,每次更新只编译一次,后续训练循环全速运行;pmap 把用户数据分片到多台 TPU,保证全量数据每轮都参与学习。
具体案例:某电商平台把 CTR 预估模型迁到 TPU + JAX 后,训练周期从每天凌晨 3 点跑完缩短到午夜前跑完,给次日策略留出了更多实验时间。他们利用性能剖析发现数据加载占 40% 时延,通过加大预取 buffer 并启用 jax.device_put 预置数据,直接把训练周期再缩短 25%。
Python 演示
"""TPU 多核心梯度同步的简化模拟:展示 all-reduce 的核心逻辑"""import numpy as np class TPUCluster: def __init__(self, num_devices=4): self.num_devices = num_devices self.device_grads = [] def compute_local_grad(self, param, data_slice): # 每台设备用自己的数据切片计算本地梯度(简化版损失: 0.5 * ||w - target||^2) target = data_slice * 2.0 return param - target def all_reduce_mean(self, grad_list): # All-reduce: 收集所有设备的梯度, 求平均后广播回每台设备 total = np.zeros_like(grad_list[0]) for g in grad_list: total += g return total / len(grad_list) def train_step(self, param, data_batch): # 把 batch 平均分给每台设备 shards = np.array_split(data_batch, self.num_devices) self.device_grads = [ self.compute_local_grad(param, s) for s in shards ] avg_grad = self.all_reduce_mean(self.device_grads) lr = 0.1 return param - lr * avg_grad, avg_grad if __name__ == "__main__": cluster = TPUCluster(num_devices=4) param = np.array([1.0, 2.0, 3.0]) data = np.random.rand(64, 3) # 64 个样本, 每个样本 3 维特征 for step in range(5): param, grad = cluster.train_step(param, data) print(f"step {step+1}: param={np.round(param, 4)}, grad_mean={np.round(grad, 4)}")
pmap 数据并行加 All-reduce 梯度同步的完整一轮训练,帮助你直观理解多设备训练的核心流程。shards = np.array_split(data_batch, self.num_devices)把一整个 batch 的数据平均切成 N 块,对应 JAX 中pmap对输入张量沿第一维做设备切分。self.device_grads = [self.compute_local_grad(param, s) for s in shards]每个“设备”只用自己的数据切片算梯度,对应每颗 TPU 本地前向反向。total += g把所有设备的梯度逐元素相加,对应lax.psum的求和语义。return total / len(grad_list)求平均梯度,对应 All-reduce 的最终结果,广播回每台设备。return param - lr * avg_grad每台设备拿着平均梯度更新自己的参数副本,对应θ ← θ - η·ḡ的权重更新公式。np.random.rand(64, 3)随机生成一批训练数据,模拟真实场景中的输入 batch。
常见误区
误区我用 `jax.numpy` 写了训练代码,TPU 应该会自动用上所有核心吧?毕竟 JAX 不是很智能吗?
正解JAX 默认只使用单设备执行。要让多颗 TPU 核心协同工作,必须显式使用 pmap 或 shard_map 指定并行策略,就像厨师不会因为菜谱写在厨房就自动一起动手——总指挥必须明确下令“每人做一桌”。不写 pmap,哪怕你有 8 颗核心,也只有 1 颗在干活。
为何常见:JAX 的 API 设计很简洁,jax.numpy 写起来和普通 NumPy 几乎一样,容易让人误以为底层也自动并行。实际上并行需要编程者显式表达意图。
误区`pmap` 是专门的并行工具,性能肯定比 `jit` 好,所以训练代码统统用 `pmap` 包起来就对了。
正解pmap 的本质是“带设备分片语义的并行执行”,它内部同样依赖 XLA 编译;如果模型本身不大,单设备 jit 反而可能更快,因为不需要承担数据切分和结果收集的开销。就像一个人炒一盘菜可能比十个人协调分工更快——分工本身也有沟通成本。在设备数量少或模型小时,jit 更优,pmap 的优势在大规模数据并行时才显现。
为何常见:很多人把“并行”和“更快”划等号,忽略了并行引入的通信开销和调度复杂度。
误区JAX 会在需要时自动插入梯度同步操作,我要做的只是把代码写完,同步耗时应该可以忽略吧。
正解自动插入不等于没有成本。All-reduce 涉及跨设备数据传输,在大规模集群上通信可能占据训练总时间的 30%-50%。JAX 的自动性让你省去了手写通信的麻烦,但你仍需监控通信占比,主动通过梯度压缩、降低同步频率、优化拓扑来减少开销。忽略通信开销,就像菜都做好了但传菜通道太窄,菜全堵在窗口。
为何常见:高层框架屏蔽了底层细节,让初学者容易把“透明”误解为“免费”。
误区我在 GPU 上数据加载用 `torch.utils.data.DataLoader`,TPU 上 JAX 也差不多吧?直接套用原来的数据管道不就行了。
正解TPU 是外部加速器,数据和计算分离,CPU 到 TPU 的传输链路和 GPU 不同。如果数据管道没有异步预取、没有把数据放上设备内存,训练循环会出现严重的“等待食材”现象,TPU 算力大量空转。在 JAX 中应使用 tf.data + jax.device_put 或 jax.experimental 中的分片数据管道,确保数据喂送速度匹配 TPU 的计算速度。
为何常见:很多人把不同加速器的编程模型混为一谈,忽略了 TPU 的“设备内存由主机远程管理”这一关键差异。
误区`jit` 报错说形状不匹配或者出现了不支持的操作,那肯定是我的算法写错了,我得去改训练逻辑。
正解jit 的报错往往不是算法错误,而是“无法被编译器静态处理”——比如 Python 原生 if 依赖运行时数值、动态 shape、或者用了非 JAX 原生控制流。解决办法不是改算法,而是改用 jax.lax.cond、jax.lax.scan 等函子式控制流,或统一形状。就像彩排要求所有人动作一致,有人临时改队形,导演就会喊停——不是菜谱错,是有人在排练时自由发挥了。
为何常见:异常报错容易让人直觉联想到代码 bug,而编译器的静态性约束是更深层、更难直觉理解的原因。
面试题
Q1. jax.devices() 返回的是什么?
- A.当前进程可见的全部加速器设备列表← 正确答案
- B.全局所有 TPU Pod 的完整设备清单
- C.当前程序已加载的张量分布位置
- D.所有 CPU 核心的逻辑处理器列表
答案:A。jax.devices() 返回当前进程可见的加速器设备列表,相当于总指挥手中“这一班厨师的花名册”。它不会返回所有 Pod 里的设备,也不会列举张量或 CPU 核心,所以 B、C、D 都不对。
Q2. pmap 的核心作用是什么?
- A.把 Python 代码编译成 C++ 提高单核速度
- B.让同一函数在多台设备上并行处理不同数据分片← 正确答案
- C.自动增大 batch size 以提升准确率
- D.把模型参数压缩成低精度以减少显存
答案:B。pmap 做的是 SPMD 并行——同一份函数、不同的数据分片、多设备同时执行,对应把菜谱同时分给多位厨师。A 是编译的作用,C 是调参行为,D 是量化,都与 pmap 核心语义无关。
Q3. XLA 编译器在 jit 中的主要优化手段是?
- A.将浮点运算全部降到 fp16 以加速
- B.算子融合减少内核启动次数← 正确答案
- C.运行时动态调整 TPU 核心数量
- D.自动替换优化器为 Adam
答案:B。XLA 的核心优化是算子融合——把多个小算子合并成大算子,减少内核启动与访存。A 是精度策略不是编译优化,C 是资源调度,D 是算法改动,都不属于编译器职责。
Q4. 在 TPU 上训练时,数据管道的第一优先级是:
- A.减少数据增强算子以降低 CPU 负担
- B.确保吞吐量能喂饱所有 TPU 核心← 正确答案
- C.一次性把全部数据集读入内存
- D.使用最小 batch size 以节省内存
答案:B。TPU 计算极快,数据管道若吞吐不足会导致核心空等,像厨师等食材。A 是优化局部,C 会遇到内存上限,D 过小 batch 降低效率;只有“喂饱核心”才是数据管道的核心目标。
Q5. 关于 jit 对输入参数的约束,正确的说法是:
- A.参数类型可以是任意 Python 对象
- B.数组形状和 dtype 必须保持静态稳定← 正确答案
- C.参数中可以包含动态生成的 list
- D.每次调用允许不同的输入形状
答案:B。jit 编译出的程序是静态的,要求数组形状、dtype 稳定,否则会触发重新编译,拖慢循环。A、C、D 都是动态性表现,与 XLA 静态编译模型矛盾。
Q6. 当一个 All-reduce 中的设备数 N 增大时,主导耗时来自:
- A.单设备上的矩阵乘法运算
- B.通信轮次累积的固定时延← 正确答案
- C.数据增强的 CPU 计算量
- D.TPU 芯片上 L2 缓存的容量
答案:B。All-reduce 时延中有一项 2(N-1)α,N 越大,固定同步轮次越多,启动开销线性累积。B 正确。A 是本地计算,与设备间通信无关;C 发生在 CPU 端;D 是硬件缓存问题,都不直接由设备数主导。
Q7. TPU Pod 集群相比单机 TPU,最典型的训练差异是:
- A.单颗芯片算力提升一倍
- B.跨机通信开销显著上升← 正确答案
- C.梯度同步变为完全免费
- D.无需重新设计数据管道
答案:B。Pod 集群的核心差异在互联网络层级增多,通信拓扑更复杂,All-reduce 耗时占比上升。A 是硬件升级而非集群差异,C 明显错误,D 与事实相反。
Q8. 下列哪些操作会触发跨设备通信?
- A.单设备
jit内的局部张量乘法 - B.
pmap内对所有设备张量求和归约← 正确答案 - C.设备本地完成的前向传播计算
- D.训练循环中的权重均值聚合← 正确答案
答案:BD。B 中 lax.psum 求和需要跨设备搬运数据,D 中权重均值聚合本质是 All-reduce,都产生通信。A 和 C 只涉及本地计算,不跨设备。就像本地厨师切自己的菜不需要和其他厨师打招呼,但交换调味心得就必须伸手。
Q9. 关于 pmap 与 jit 的关系,正确的是:
- A.
pmap内部依赖 XLA 编译执行← 正确答案 - B.两者可以组合使用提升效率← 正确答案
- C.
pmap完全替代jit的所有功能 - D.
jit内部自动包含设备分片逻辑
答案:AB。pmap 是对并行语义的封装,底层仍调用 XLA 编译,所以 A 对;pmap(jit(f)) 可以组合,B 对。C 错在 pmap 没有覆盖所有 jit 用法(如单设备最佳化),D 错在 jit 默认不感知设备分片。组合使用就像先彩排(jit)再分人执行(pmap)。
Q10. 下列哪些策略能有效降低 TPU Pod 上的通信瓶颈?
- A.将梯度量化为低精度再同步← 正确答案
- B.多个训练步累积梯度后统一同步← 正确答案
- C.把 All-reduce 换成单机循环计算
- D.调整设备映射使亲密通信更靠近← 正确答案
答案:ABD。梯度量化 A 减少数据量,梯度累积 B 降低同步频率,拓扑感知 D 利用相邻高速链路,都能缓解通信。C 把 All-reduce 换成循环在单机做,等于放弃并行,违背前提。像厨师减少亲手递菜的次数、把菜放传送带,而不是每盘菜都自己跑一趟。