周五晚上,餐厅接到一张 1000 份宫保鸡丁的团购订单。主厨一个人就算手脚再快,也要忙到天亮。聪明的做法是把订单按份数拆成四堆,交给四位帮厨,每人在自己的灶台上同时开工——四倍灶台,四倍出锅速度。可问题也随之而来:帮厨们各炒各的,最后整桌菜的口味怎么统一?酱汁要怎么保证都是一个味儿?这份“把大工作拆开、同时干、再对齐结果”的智慧,正是 JAX 里 pmap 多设备并行计算的核心。本文的主角 pmap,就像那个“派单的老板”:host(宿主)负责接单和分堆,GPU/TPU 这些设备(帮厨)在自己的灶台上翻炒数据,而 lax.pmean 这类通信原语,就是那口把酱汁混匀再分回去的盆。读完这篇文章,你会彻底搞懂多设备训练是怎么切数据、怎么通信、怎么跟 jit 组合的。

岗位关联

掌握 pmap 就等于学会给 JAX 的分布式训练“派单”,能在多卡环境中把训练吞吐提升数倍,是走向大规模 AI 工程化的必修课。

目录
  1. 可视化演示
  2. 并行基础:订单太大,分给帮厨做
  3. 设备映射:帮厨各就各位
  4. 通信原语:那口混酱汁的盆
  5. 手动并行:老板亲自下场分堆
  6. jit组合:主厨的预制菜谱
  7. 训练实战:一整桌宴席的演练
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示JAX pmap如何实现跨多GPU/TPU设备的数据并行计算,包括设备映射、数据分片、all-reduce/all-gather通信原语与多设备训练流程。

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

JAX pmap多设备并行计算 设备映射:pmap将函数编译为多设备并行版本,数据沿并行轴分片到各设备 数据并行:输入数据按设备数切分,每设备独立计算前向/反向传播 梯度同步:all-reduce聚合各设备梯度,确保参数同步更新 pmap(f)(x_sharded) → [f(x₀), f(x₁), ..., f(xₙ)] pmap实现数据并行计算与梯度同步

并行基础:订单太大,分给帮厨做

周五晚上,餐厅接到一张 1000 份宫保鸡丁的团购订单。主厨一个人就算手脚再快,也要忙到天亮。最简单的解法,是把订单按份数拆成四堆,让四位帮厨各拿一堆,在自己的灶台上同时开工——四倍的灶台,四倍的出锅速度。这种“把一份大工作拆成几份小工作、同时干”的思路,放在 AI 训练里就叫数据并行(data parallelism)。JAX 里负责实现这一步的 API,就是今天的主角:pmap。

在 JAX 的世界里,程序永远分为两种角色。一种是 host(宿主),通常指 CPU 和主内存,它像餐厅老板:负责接单、派单、收账,但绝不亲自下厨。另一种是 device(设备),也就是 GPU 或 TPU,它们像后厨的帮厨:重活全在它们手里。pmap 这个名字拆开是 parallel + map,意思是“把一个函数并行地映射到多台设备上执行”。你写一个函数 f,pmap 会把这个函数复制到每一台设备上,大家步调一致地跑同一段代码。

不过帮厨开工前,得先把食材分好。pmap 默认沿着输入数组的第一个维度——也就是样本(batch)维度——把数据切成 D 份,每台设备只拿到一份。切分之后每台设备处理多少样本,由一个非常简单的除法决定:

订单拆成几堆,帮厨谁也不用等谁,各自锅里的菜互不干扰。这就是数据并行最舒服的地方:切分是自动的,计算是独立的。唯一需要大家坐下来商量的事,到第三节再讲。先把“分堆”这个动作刻在脑子里——pmap 的一切,都从这一刀开始。

每个子批次的大小=样本总数 B设备数量 D\displaystyle \text{每个子批次的大小} = \frac{\text{样本总数}\, B}{\text{设备数量}\, D}
一批 1000 张图片、4 台设备,每台设备拿 250 张。pmap 会在内部自动完成这次“分堆”,你不用手写任何切片代码。
  1. 样本总数批次中全部样本的数量,例如一批图片有 1000 张。
    B=1000B = 1000
  2. 设备数量参与计算的 GPU/TPU 台数,等于后厨帮厨的人数。
    D=4D = 4
  3. 子批次大小每台设备分到的样本数,它必须是一个整数,所以批次大小最好能被设备数整除。
    S=10004=250S = \frac{1000}{4} = 250

设备映射:帮厨各就各位

开饭前,老板要做的第一件事是数人头:今天几位帮厨上班?各站哪口灶台?在 JAX 里,对应两行代码:jax.devices() 查看当前可见的全部设备,jax.local_device_count() 数出本机一共有几台。它们返回的信息,就是 pmap 分配任务时手里那张“排班表”。

有了排班表,pmap 开始干活。它做了一件很有意思的事:把“设备”这个概念,变成数组的一个新维度。当函数被 pmap 包装后,输入数组的第一个维度被解释为设备轴(parallel axis):下标为 d 的那一块数据,交给第 d 台设备去算。你可以把这个轴想象成帮厨工位上贴的编号——1 号灶台拿第 1 堆食材,2 号灶台拿第 2 堆。

计算结束后,pmap 会把所有设备的输出按设备编号堆叠起来,返回一个前导维度大小为 D 的数组。这个数组长得和普通 JAX 数组一模一样,却暗藏玄机:每一层切片都来自不同的设备。

从外部看,你只是调用了 pmap(f);但从设备的角度看,每个帮厨都守着自己那口灶,锅里的菜只属于自己。设备映射的本质,就是让“多台设备”这件事在数组形状上显形——看一眼 shape 的第一个维度,就知道老板今天派了几位帮厨。

yd=f(xd),X∈RD×S, Y∈RD×S′\displaystyle y_d = f(x_d), \quad X \in \mathbb{R}^{D \times S}, \ Y \in \mathbb{R}^{D \times S'}
第 d 台设备读自己那份输入 x_d,算出结果 y_d;D 台设备的结果按编号摞在一起,像四位帮厨把炒好的菜按灶台编号排成一排。
  1. 设备下标d 表示第几台设备,取值从 0 到 D-1,相当于灶台的编号。
    d∈{0,1,…,D−1}d \in \{0, 1, \ldots, D-1\}
  2. 子批次输入x_d 是第 d 台设备拿到的数据分片,形状由每台样本数决定。
    xd∈RSx_d \in \mathbb{R}^{S}
  3. 计算函数f 是你要并行的函数,比如一层神经网络或一个损失函数,所有设备执行同一份代码。
    f:RS→RS′f: \mathbb{R}^{S} \to \mathbb{R}^{S'}
  4. 结果堆叠每台设备的 y_d 按 d 顺序堆叠成输出数组,前导维度恰好等于设备数。
    Y=[y0,y1,…,yD−1],Y∈RD×S′Y = [y_0, y_1, \ldots, y_{D-1}], \quad Y \in \mathbb{R}^{D \times S'}

通信原语:那口混酱汁的盆

帮厨们各炒各的,可宫保鸡丁讲究“整桌一个味儿”。于是主厨定下规矩:所有人把调好的酱汁倒进一个大盆,搅匀,再各舀一勺回去。这个“倒进去—搅匀—舀回来”的动作,在并行计算里叫 all-reduce(全归约):所有设备先把自己的数值贡献出来,汇总成一个结果,再广播给每一台设备。

JAX 里最常用的通信原语有两个。lax.pmean(mean 是平均)做的事正如其名:跨设备求平均值再广播回所有设备,是 all-reduce 家族里专做“平均”的那一款。lax.all_gather 则像拼盘环节:每个帮厨把自己做的特色配菜交出来互相分享,最后每台设备手里都有一份拼好的完整数据。前者让数值变得一致,后者让数据变得完整。

为什么训练离不开 pmean?因为梯度更新要求所有设备使用同一个平均梯度。假设设备 d 算出了梯度 g_d,全局平均梯度就是:

那口盆就是 pmean,规矩就是 axis_name——所有帮厨得先知道“往哪口盆里倒”,动作才能统一。没有这口盆,每个帮厨按自己的口味调酱,整桌菜就成了大杂烩。

gˉ=1D∑d=1Dgd\displaystyle \bar{g} = \frac{1}{D}\sum_{d=1}^{D} g_d
pmean 把每台设备的梯度相加,除以设备总数,再把平均值送还给每一台设备,保证大家参数更新的方向完全一致。
  1. 局部梯度g_d 是第 d 台设备在自己的子批次上算出的梯度,代表这台设备对更新方向的“个人意见”。
    gd=∇θLdg_d = \nabla_\theta \mathcal{L}_d
  2. 跨设备求和把 D 台设备的梯度逐分量相加,相当于所有帮厨把酱汁倒进同一口盆。
    ∑d=1Dgd\sum_{d=1}^{D} g_d
  3. 取平均总和除以设备数,得到全局平均梯度,这是大家最终认可的“统一口味”。
    gˉ=1D∑d=1Dgd\bar{g} = \frac{1}{D} \sum_{d=1}^{D} g_d
  4. 广播回去pmean 把 ḡ 复制到每台设备上,大家拿着同一份梯度更新参数。
    ∀d: gd←gˉ\forall d: \ g_d \leftarrow \bar{g}

手动并行:老板亲自下场分堆

大多数时候 pmap 会自动切分数据,但有些时候帮厨太忙,需要老板先把食材按堆分好再递过去。这种“亲自下场分堆”的做法叫手动并行。它通常只有一步:把输入 reshape 成(设备数,每台样本数)的形状,像把 1000 份食材装进 4 个贴着编号的筐里。

手动并行还要求你理解一个隐藏的规矩:通信即同步。当某台设备执行到 lax.pmean 或 lax.all_gather 时,它必须停下来,等其他所有设备都执行到同样的位置,交换完数据,才能继续往前走。这个“必须一起到齐”的点叫同步屏障(synchronization barrier)——就像混酱汁时,最后一个帮厨没到,谁也不能先开饭。

同步是要花时间的。并行训练一个 step 的总时间可以拆成两块:

手动并行的精髓,是让老板对“分多少、传什么、等多久”心里有数。数据分得越细,计算越轻;设备越多,通信越重。懂得在两者之间找平衡,才算真正会派单。

T总=T串行D+αD+βM\displaystyle T_{\text{总}} = \frac{T_{\text{串行}}}{D} + \alpha D + \beta M
计算时间随设备数增加而变小(活被摊薄了),通信时间却可能随设备数增加而变大(更多的人要排队倒酱汁)。设备翻倍,总时间不可能等比缩小。
  1. 本地计算时间每台设备炒自己那份菜的时间,理想情况下是串行时间的 1/D。
    T计算≈T串行D\displaystyle T_{\text{计算}} \approx \frac{T_{\text{串行}}}{D}
  2. 通信固定开销α 是每台设备参与一次同步的固定成本,设备越多累计越大。
    αD\alpha D
  3. 数据传输时间β 是传输每字节数据的时间,M 是要交换的数据量,数据越大传得越久。
    βM\beta M
  4. 总时间一个训练 step 的墙钟时间等于计算与通信之和。
    T总=T串行D+αD+βMT_{\text{总}} = \frac{T_{\text{串行}}}{D} + \alpha D + \beta M

jit组合:主厨的预制菜谱

假设帮厨做菜全凭记忆,每炒一份都要停下来回忆下一步——太慢了。高明的做法是,主厨把菜谱提前印好,贴在每口灶台前,帮厨照方抓药,流畅得像肌肉记忆。这个“提前印好的菜谱”,在 JAX 里就是 jit 编译。

jit 会把函数编译成底层的 XLA 程序,跳过 Python 的解释开销。和 pmap 组合时,标准写法是把 jit 放在里层:pmap(jit(f))。意思是:先把菜谱印好(jit 编译一份 XLA 程序),再分发给所有设备(pmap 分发到 D 个灶台)。每台设备执行的,都是同一份编译好的程序——完全符合“先印菜谱,再派给帮厨”的顺序。

组合之后,自动微分依然畅通。要在 pmap(jit(f)) 里用 grad,JAX 会在每台设备上独立计算损失对参数的梯度,再靠 pmean 跨设备取平均。要保证这一切成立,函数必须是纯函数:不能读写外部变量,就像帮厨只看自己灶台上的食材,不能偷看别人的锅——一切输入输出都通过参数和返回值传递。

预制菜谱加帮厨分发,再加酱汁共识,这套组合拳让多设备训练兼顾了编译速度和并行效率。jit 负责“无脑快”,pmap 负责“人多力量大”,pmean 负责“心往一处想”。

∇θL=1D∑d=1D∇θLd\displaystyle \nabla_\theta L = \frac{1}{D}\sum_{d=1}^{D}\nabla_\theta L_d
每个设备用自己的子批次算出一个梯度“意见”,大家把意见平均一下,才得到共识梯度,用于更新参数。
  1. 局部损失L_d 是第 d 台设备用自己的子批次算出的损失,表示这台设备觉得现在的模型差多少。
    Ld=1S∑i=1Sℓθ(xd,i, yd,i)\displaystyle L_d = \frac{1}{S}\sum_{i=1}^{S}\ell_\theta(x_{d,i},\, y_{d,i})
  2. 局部梯度对参数 θ 求导,得到该设备对更新方向的建议。
    ∇θLd\nabla_\theta L_d
  3. 平均融合跨设备取平均,得到大家共识的更新方向。
    ∇θL=1D∑d=1D∇θLd\displaystyle \nabla_\theta L = \frac{1}{D}\sum_{d=1}^{D}\nabla_\theta L_d

训练实战:一整桌宴席的演练

最后,把所有环节串成一次完整的宴席。后厨收到订单(一批数据),第一步永远是确认灶台数量,然后把订单按灶台数分堆:x.reshape(设备数, 每台样本数)。准备妥当,pmap 包装的训练函数就开火了。

每轮训练 step 里发生的事,与前面几节完全吻合:每台设备用自己的子批次做前向计算得到损失,反向传播算出梯度;pmean 把梯度平均并广播;所有设备用同一个平均梯度,更新自己手里那份参数副本。因为每台设备都持有参数副本,且每步都用相同的梯度更新,所以参数始终保持同步。

最后一件事是验收。衡量并行训练值不值,看两个指标:吞吐量(每秒处理多少样本)和扩展比(D 台设备相对 1 台设备的加速倍数)。理想扩展比是线性,实际因为通信开销总会差一点。更新参数的公式长这样:

从派单、分堆、炒菜、混酱,到按同一份菜谱改良口味,一整桌宴席就这样有条不紊地出锅了。pmap 把“人多”这件事变得安全可预期,而你要做的,只是记住那口盆(pmean)和那张菜谱(jit),其余交给 JAX。

θ←θ−ηgˉ\displaystyle \theta \leftarrow \theta - \eta \bar{g}
每个设备把参数朝平均梯度的反方向挪一小步,步长由学习率 η 控制;因为梯度是全局共识,所以所有设备的参数保持一致。
  1. 参数副本每台设备持有一份完全相同的参数 θ,保证大家从同一起点出发。
    θd=θ, ∀d\theta_d = \theta, \ \forall d
  2. 学习率η 是超参数,决定每次挪动参数的步子迈多大。
    η>0\eta > 0
  3. 同步更新用平均梯度同步更新所有设备上的参数,模型在任何时刻都保持一致。
    θ←θ−ηgˉ\theta \leftarrow \theta - \eta \bar{g}

AI 应用场景

多卡图像分类训练加速

阶段:模型训练

解决的问题:单张 GPU 显存和算力有限,大批次训练耗时;手写多卡同步逻辑(切分、梯度同步)容易出错。用 pmap 把 batch 沿第一维切到多张卡,每个 step 用 lax.pmean 平均梯度,核心代码只要几行。

具体案例:某团队在 8 张 A100 上用 JAX 训练 ResNet-50,将每批 1024 张图 reshape 成 (8, 128) 传入 pmap 包装的训练函数,梯度经 all-reduce 平均后同步更新参数,吞吐从约 600 张/秒提升到 4000+ 张/秒。

多环境并行采样(强化学习)

阶段:数据采集

解决的问题:强化学习里模型要不断与环境互动采集样本,单环境串行太慢。用 pmap 让每台设备跑一个环境副本,并行执行 step,再用 all-gather 把各环境的(状态,动作,奖励)收集回 host 组成训练批次。

具体案例:机器人仿真训练中,用 pmap 在 4 台 TPU 上并行跑 4 个环境副本,每轮采样时间从约 2 秒降到 0.55 秒,让同一批数据更快喂给策略网络。

大规模批量推理提速

阶段:模型推理

解决的问题:线上批量推理(如视频审核、批量翻译)时单卡吞吐不足,请求排队严重。用 pmap 把待推理的样本按设备数切分,每个设备独立做前向,一次完成整批推理。

具体案例:在 4 张 V100 上对 1 万条文本批量翻译,pmap 并行推理整体耗时约为单卡的 1/3.5,把深夜积压的翻译任务缩短到分钟级。

Python 演示

python59 行
import jaximport jax.numpy as jnpfrom jax import grad, pmap, jitfrom jax import lax  def main():    device_count = jax.local_device_count()    print("设备数量:", device_count)    print("设备清单:", jax.devices())     # 1. 模拟一批 8 条样本的特征数据    x = jnp.arange(8.0)    y = 3.0 * x + 1.0    print("原始批次 x:", x)     # 2. pmap: 沿第一个维度把批次切分到各设备    def compute(v):        return v * 2 + 1     mapped = pmap(compute)(x)    print("各设备计算结果:", mapped)     # 3. all-reduce: 跨设备求平均并广播    def compute_with_mean(v):        return lax.pmean(v, axis_name="data")     averaged = pmap(compute_with_mean, axis_name="data")(x)    print("all-reduce 全局平均:", averaged)     # 4. all-gather: 收齐所有分片并拼接成完整批次    def collect(v):        return lax.all_gather(v, axis_name="data", tiled=True)     gathered = pmap(collect, axis_name="data")(x)    print("all-gather 拼接形状:", gathered.shape)     # 5. 数据并行训练: 每台设备只看自己的子批次    per = x.shape[0] // device_count    x_shards = x[:device_count * per].reshape(device_count, per)    y_shards = y[:device_count * per].reshape(device_count, per)    w = jnp.array([0.0])     def loss_fn(w, x_local, y_local):        return jnp.mean((w * x_local - y_local) ** 2)     def train_step(w, x_local, y_local):        g = grad(loss_fn)(w, x_local, y_local)        avg_g = lax.pmean(g, axis_name="data")        return w - 0.01 * avg_g     step = pmap(train_step, axis_name="data")    for _ in range(300):        w = step(w, x_shards, y_shards)    print("训练后的参数 w:", w)  if __name__ == "__main__":    main()
目的:演示 pmap 的四种核心用法——批量切分、all-reduce 平均、all-gather 拼接,以及一次完整的数据并行梯度下降训练。
  1. device_count = jax.local_device_count()数一数后厨有几口灶:查询本机可用的加速设备数量。
  2. pmap(compute)(x)把 compute 函数映射到所有设备上,并沿第一个维度把 x 切成设备数份。
  3. lax.pmean(v, axis_name="data")混酱汁:跨设备求平均,再把平均值广播回每台设备。
  4. lax.all_gather(v, axis_name="data", tiled=True)拼盘:把各设备的分片拼回完整批次,让每台设备都持有全量数据。
  5. x_shards = x[:device_count * per].reshape(device_count, per)手动分堆:把批次切成“设备数 × 每台样本数”的块。
  6. g = grad(loss_fn)(w, x_local, y_local)每台设备只用自己的子批次算出梯度,互不偷看别人的数据。
  7. avg_g = lax.pmean(g, axis_name="data")梯度跨设备平均,保证所有设备用同一个“共识梯度”更新参数。

常见误区

误区pmap 会把一份数据完整复制到每台设备上,这样每台设备都能看到全部样本,训练更充分。

正解pmap 默认沿第一个维度把数据切成互不重叠的多份,每台设备只拿到属于自己的子批次,像把订单分堆而不是复印订单。真正被“复制”的是模型参数:每台设备都持有一份参数副本,而数据是切分的。二者一结合,才实现“数据不同、模型同步”的数据并行。

为何常见:容易把“并行”直觉成“复制几份分别算”,忽略了切片和广播是两种完全不同的语义。

误区只要 GPU 数量翻倍,训练速度就翻倍;上 8 张卡一定比 4 张卡好,设备越多越好。

正解每个 step 的时间 = 计算 + 通信。设备多了,本地计算变少,但梯度平均、参数同步的通信开销会上升;如果总批次固定,每台设备分到的样本太少,GPU 利用率反而下降。扩展比要实测,通常 8 卡能到 6~7 倍就算不错。

为何常见:只盯着算力看,忽略了设备间通信是分布式计算绕不开的固定成本。

误区在 pmap 函数里可以随便用 lax.pmean,不用额外声明任何东西,它会自动知道在哪条轴上通信。

正解通信原语靠 axis_name 识别“在哪条轴上做通信”。必须把 axis_name 传给 pmap,并保证两边名字一致,否则 JAX 会直接报错。它就像后厨暗号——“往哪口盆里倒”要先说清楚,动作才能统一。

为何常见:很多示例代码里 axis_name 恰好同名,新手照抄时没意识到 pmean 和 pmap 的这两个参数是绑定的。

误区pmap 和 jit 只能二选一,或者必须把 pmap 包在 jit 外面才有效。

正解二者可以组合,常见写法是把 jit 放在里层:pmap(jit(f)),每个设备执行的都是编译好的 XLA 程序;jit(pmap(f)) 同样可行。先用 jit 编译再用 pmap 分发,更符合“先印菜谱再派给帮厨”的思路。

为何常见:对两者的分层关系不清楚,误以为编译优化和并行分发是互斥的两条路。

面试题

Q1. pmap 会把输入批次按哪个维度切分到多台设备?

单选题 · easy

  • A.沿第一个(批次)维度切分← 正确答案
  • B.沿最后一个特征维度切分
  • C.随机挑选某个维度切分
  • D.不切分而是复制到所有设备

答案:A。pmap 默认沿输入数组的第一个维度(样本维度)切分,每台设备拿到互不重叠的一个子批次;这与把数据广播复制给所有设备完全不同,因此 B、C、D 均错误。

Q2. pmap 计算完成后,返回数组的第一个维度代表什么?

单选题 · easy

  • A.代表样本特征的维度
  • B.代表设备数量的维度← 正确答案
  • C.代表训练轮数的维度
  • D.代表标签类别的维度

答案:B。pmap 会为每台设备的结果保留一个前导维度,其大小等于设备数,就像帮厨把菜按灶台编号排成一排。样本特征、训练轮数、标签类别都不是 pmap 输出的第一维含义。

Q3. 在 pmap 函数体内调用 lax.pmean 之前,必须完成哪一步?

单选题 · mid

  • A.在 pmap 中声明 axis_name← 正确答案
  • B.先调用 lax.all_gather
  • C.把数据拷贝回主机内存
  • D.先关闭 jit 编译功能

答案:A。lax.pmean 这类通信原语靠 axis_name 识别并行轴,必须把 axis_name 传给 pmap 且两边名字一致,否则 JAX 报错。pmean 不需要先 all-gather,也不需要拷贝回主机或关闭 jit。

Q4. 在 JAX 中,jax.devices() 的作用是什么?

单选题 · mid

  • A.创建虚拟的加速设备
  • B.查看当前可见的设备← 正确答案
  • C.修改 GPU 的显存大小
  • D.设置默认的数据类型

答案:B。jax.devices() 返回当前可见的加速设备列表,相当于老板看排班表。它不创建设备、不改显存、不设数据类型,因此 A、C、D 都超出了它的职责。

Q5. 关于 all-reduce 与 all-gather 的异同,说法正确的有哪些?

多选题 · mid

  • A.都包含跨设备的数据通信← 正确答案
  • B.都要求所有设备同步参与← 正确答案
  • C.都让每台设备获得聚合结果← 正确答案
  • D.都用来减少通信的数据量

答案:ABC。二者都是集合通信,需要全员同步参与并相互交换数据,最终每台设备都能拿到结果,所以 A、B、C 正确。all-gather 是拼接,数据量不会减少反而变大,D 错误。

Q6. 关于 pmap 与 jit 的组合,哪种说法正确?

单选题 · mid

  • A.可以先编译再分发到设备← 正确答案
  • B.两者互斥只能选其一
  • C.先分发再编译效果更好
  • D.组合后梯度无法再传播

答案:A。pmap(jit(f)) 是把函数先编译成 XLA 程序再分发到各设备,jit(pmap(f)) 也可行。B 认为互斥是错的;C 的“效果更好”没有依据;D 错在组合后自动微分依然正常工作。

Q7. pmap 数据并行训练中,正确的做法包括哪些?

多选题 · mid

  • A.批次大小能被设备总数整除← 正确答案
  • B.各设备独立计算本地梯度← 正确答案
  • C.用 pmean 聚合梯度再同步更新← 正确答案
  • D.每台设备采用不同的模型结构

答案:ABC。数据并行要求批次能被设备数整除、各设备独立算梯度、梯度平均后同步更新,因此 A、B、C 正确。所有设备必须使用相同的模型结构,D 错误。

Q8. 总批次不变而设备数翻倍时,单设备批次规模和通信开销占比如何变化?

单选题 · hard

  • A.批次变小,通信占比变大← 正确答案
  • B.批次变大,通信占比变小
  • C.批次不变,通信占比不变
  • D.批次变小,通信占比变小

答案:A。总批次固定时,设备越多每台分到的样本越少,本地计算时间变短;而梯度平均、参数同步的通信开销仍在,甚至因参与设备更多而增大,所以通信占比变大。

Q9. 关于多设备训练中梯度聚合与参数更新,正确的流程描述有哪些?

多选题 · hard

  • A.每个设备独立算出自己的梯度← 正确答案
  • B.pmean 生成全局平均梯度← 正确答案
  • C.所有设备用同一平均梯度更新← 正确答案
  • D.每台设备用本地梯度单独更新

答案:ABC。标准数据并行流程是:各设备先算本地梯度,pmean 求全局平均并广播,所有设备再用同一个平均梯度更新参数,因此 A、B、C 正确。D 会让各设备参数发散,模型失去同步。

Q10. 在 pmap 函数内调用 lax.pmean 却未声明 axis_name,会发生什么?

单选题 · extreme

  • A.抛出异常并中止执行← 正确答案
  • B.静默跳过该通信操作
  • C.自动回退到单设备运行
  • D.把结果变成随机数值

答案:A。通信原语必须知道在哪条轴上通信,pmap 没有收到 axis_name 就遇到 pmean 时,JAX 会抛出异常明确提示轴名不匹配。它不会静默跳过、不会回退单设备,更不会产出随机数。