想象一家开在闹市区的“智慧餐厅”。每天饭点,订单像雪片一样飞来:宫保鸡丁、清蒸鲈鱼、佛跳墙……如果只靠一位大厨从洗菜、切菜、炒菜到装盘一路包办,顾客怕是要等到花儿都谢了。聪明的餐厅会这样运营:让擅长大火爆炒的师傅管热菜,让擅长火候的师傅管蒸煮,再安排一位专研秘方的老师傅负责招牌炖品——三位师傅同时开工,同一时刻能端出好几道菜。这,就是“并行执行”。而主厨如何派单、如何协调食材传递、如何让每位师傅都不闲下来,便是“调度”的艺术。AI 系统里的情形如出一辙:CPU、GPU、NPU 就像各有所长的厨师,计算任务就像一道道菜。走进这间“异构厨房”,你会发现:并行执行不是简单地把人招满,而是一场关于分工、协作与等待的精密运营。

岗位关联

理解并行执行与调度逻辑,是 AI 工程化的核心能力。掌握它,你就能在实际部署推理、训练系统时快速定位性能瓶颈,设计出合理的多设备方案,这是迈向高级 AI 系统工程师的必修课。

目录
  1. 可视化演示
  2. 并行执行概念
  3. 硬件并行基础
  4. 调度粒度划分
  5. 运行时调度策略
  6. 同步与通信
  7. 性能分析指标
  8. 典型应用场景
  9. 优化策略
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态展示异构计算中并行执行的分解-分配-编排三步骤、Flynn四类分类、串并行加速对比(4x)及AI训练中的数据/张量/流水线并行组合。

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

并行执行概念

在传统小饭馆里,老板一个人既负责点单又负责炒菜,客人再多也只能一桌一桌慢慢来——这是“串行”(Serial Execution)。而当智慧餐厅开业,老板请来多位厨师,热菜、冷盘、汤品由不同师傅同时制作,翻台率立刻翻了几倍——这就是“并行”(Parallel Execution)。并行的本质,是把“一件接一件地做”变成“多件同时推进”,让时间从加法变成取最大值。

在 AI 计算的世界里,模型推理和训练包含海量矩阵运算、卷积计算和数据处理,等价于一张张“订单”。如果处理器只有一个,这些运算只能排队;而 AI Runtime 系统会把一个大计算图拆成许多相互独立的小块,同时交给多个计算单元处理,让一条条数据流平行推进。这种“同时运行多个计算任务”的能力,正是现代深度学习框架与芯片协同设计的基石。

并行执行要成立,需要两个关键条件:一是“可拆分”,任务之间不能有无法解开的依赖;二是“可同时”,硬件必须提供多个计算通道。理想情况下,n 个任务在 n 个设备上运行,耗时从 n 段之和缩短为其中最长的单段。

就像智慧餐厅的翻台率取决于出菜最慢的那道菜,并行系统的总耗时也由最慢的任务钉死。要想再快,要么把慢任务继续拆分,要么给慢任务指派更快的“师傅”——这是后面每一节都要反复用到的直觉。

Tparallel=max⁡1≤i≤nti  ≤  Tserial=∑i=1nti\displaystyle T_{\text{parallel}} = \max_{1 \le i \le n} t_i \;\le\; T_{\text{serial}} = \sum_{i=1}^{n} t_i
这段公式说明:并行执行的最优时间由最慢的任务决定,而串行时间则是所有任务时间的总和。只要任务能真正同时跑,并行时间一定不大于串行时间。
  1. 串行总耗时串行执行时,所有任务一个接一个地完成,总耗时等于每个任务耗时的累加,就像一位厨师炒完一道菜再炒下一道。
    Tserial=∑i=1ntiT_{\text{serial}} = \sum_{i=1}^{n} t_i
  2. 并行总耗时并行执行时,n 个任务同时启动、互不等待,总耗时由最慢的那个任务决定,就像一桌菜同时上,吃得最久的那位决定了这桌的用餐时长。
    Tparallel=max⁡1≤i≤ntiT_{\text{parallel}} = \max_{1 \le i \le n} t_i
  3. 收益关系因为任意一项 $t_i$ 都不可能超过所有项之和,所以并行时间在数学上一定不劣于串行时间。
    max⁡iti  ≤  ∑iti\max_i t_i \;\le\; \sum_i t_i

硬件并行基础

智慧餐厅的厨房里,灶台各有脾性:炒锅火旺,适合爆炒;蒸箱一次能蒸几十份,适合批量出品;还有一台老火汤煲,专门对付那些要炖上数小时的菜。厨师团队也因“台”制宜——不同的工作台,天生适合不同的菜式。异构计算(Heterogeneous Computing)讲的就是这个道理。

AI 系统里的“工作台”就是各种处理器。CPU 核心数不多,但单个核心能力强、逻辑判断出色,像那位什么都能做的全能大厨;GPU 有成百上千个简单计算核心,像那排能同时蒸几十份点心的蒸箱;NPU 则是为神经网络运算量身定制的专用电路,像那台专炖老火汤的汤煲。三者组合,便构成了一间“异构厨房”。

硬件并行能力决定了系统能同时“开几桌”。多核 CPU 提供 4 到 64 个通用计算通道;GPU 提供数千个并行线程;NPU 内部则排列着大量乘加单元阵列。一个处理器的理论算力,可以近似看作三个因素的乘积:

其中 $N_{\text{cores}}$ 是核心数,$f$ 是时钟频率,$\text{IPC}$ 是每个时钟周期执行的指令数。核心越多、频率越高、单周期干得越多,算力越强。

异构系统的高明之处,是不逼着所有菜都走同一个灶台——把向量密集的矩阵运算交给 GPU 的“蒸箱”,把控制逻辑留给 CPU 的“炒锅”,把神经网络算子交给 NPU 的“汤煲”。各展所长,整体出菜速度自然远超任何一位单干的大厨。

P=Ncores×f×IPC\displaystyle P = N_{\text{cores}} \times f \times \text{IPC}
处理器的“算力”不是单调的数字,而是核心数量、运行频率和每周期指令数的乘积。GPU 之所以快,靠的是海量核心并行,而非单核频率。
  1. 核心数量核心数 $N_{\text{cores}}$ 决定了能够真正同时干活的计算单元数量,如同厨房里灶台的数量,灶台越多,能同时下锅的菜越多。
    NcoresN_{\text{cores}}
  2. 时钟频率频率 $f$ 表示每个核心每秒能推进多少个工作周期,如同厨师手脚的快慢,频率越高,单核动作越快。
    f    (Hz)f \;\;(\text{Hz})
  3. 单周期能力IPC 表示每个时钟周期内能完成的指令数,流水线越宽、向量化越好,IPC 越高,相当于厨师一个动作同时翻炒多道菜。
    IPC\text{IPC}
  4. 综合算力三者相乘得到每秒完成的指令总量 $P$,即整个设备的“出菜能力”上限。
    P=Ncores×f×IPCP = N_{\text{cores}} \times f \times \text{IPC}

调度粒度划分

切菜的刀法决定烹饪的节奏。萝卜切成大块,炖得快但不易入味;切成细丝,容易熟却极费刀工。任务拆分也是同样的道理——粒度太大,并行度不够;粒度太小,拆分和传递的功夫反而拖慢全局。厨房里的“切菜哲学”,正是并行系统中的“粒度权衡”。

调度粒度(Granularity)指的是并行执行的基本任务单元的大小。AI Runtime 系统通常按三种粒度组织计算:最高层是任务级并行,一个模型的前向计算被拆成若干可独立执行的子图;中间是算子级并行,单个算子(如矩阵乘)内部被切分到多个设备上;底层是指令级并行,处理器在单个核心内部同时执行多条指令。

粒度越细,潜在并行度越高,但每个任务对应的调度、分发、通信开销也随之增加。设单任务调度开销为 $O_s$,总工作量为 $W$,切分为 $N$ 个任务后,总耗时可以建模为:

第一项随任务数线性上涨,第二项随任务数下降。最优粒度正是让两者之和最小的平衡点。

回到厨房:切太细,厨师光顾着切菜没空炒菜;切太粗,一个大块头霸占灶台,旁的灶台只能干瞪眼。好的总厨会依据食材性质和人力数量,选择恰到好处的“刀法”,让每个灶台都刚好有活干,这就是粒度调度的艺术。

Ttotal=N×Os+WN\displaystyle T_{\text{total}} = N \times O_{s} + \frac{W}{N}
调度开销与执行开销是一对矛盾:任务切得越细,单个任务越小,但“切”本身越来越贵。总耗时像个倒 U 形,最优点在谷底。
  1. 调度开销项$N \times O_s$ 表示每切一刀都要付出的“切菜成本”,任务数量越多,累积的调度与分发开销越大。
    N×OsN \times O_{s}
  2. 均摊执行项$\frac{W}{N}$ 是每个任务分到的工作量。切得越细,单个任务工作量越小,各设备并行摊薄的时间越短。
    WN\frac{W}{N}
  3. 最优粒度对总耗时求最小值,得到最优任务数 $N^*$,此时的粒度让调度成本与执行成本达到平衡。
    N∗=WOsN^{*} = \sqrt{\frac{W}{O_s}}

运行时调度策略

主厨怎么派单?最简单的做法是开张前就排好班表,定死谁负责哪些桌——这是“静态调度”。但现实总有客人临时加菜、某个灶台突然出故障,于是主厨改为随时观察:哪位厨师闲着,就把菜塞给他——这是“动态调度”。更妙的是,经验丰富的餐厅还会让闲下来的厨师直接去同行面前“偷”半成品来帮炒——这就是工作窃取(Work Stealing)。

AI Runtime 系统中的调度策略,解决的是“哪个任务在哪个设备上、什么时候执行”的问题。静态调度在编译期就生成完整的执行计划,适合结构固定、耗时已知的部署模型,调度零运行时开销;动态调度则根据运行时设备负载、数据位置临时决策,适合输入形状变化大、设备状态波动的推理场景。

工作窃取是动态调度的经典实现:每个设备维护一个本地任务队列,空闲设备从其他设备的队列尾部“偷”任务执行,让负载自动趋于均衡。无论采用哪种策略,最终收益都集中体现在加速比上:

加速比大于 1 说明并行有收益;接近 1 则说明调度与同步开销已经吃掉了并行红利。

好的派单策略,是让每位厨师手上总有活、且不多到做不完。静态调度像固定班表,稳定但死板;动态调度像临场应变,灵活但每次决策都有成本;工作窃取则让闲人自动补位,是现代调度器(如 TBB、Java Fork-Join)的首选方案。

Speedup=TserialTparallel\displaystyle \text{Speedup} = \frac{T_{\text{serial}}}{T_{\text{parallel}}}
加速比是并行系统最直观的成绩单。它把“并行快了多少倍”压缩成一个数字,帮助我们判断当前的调度策略值不值。
  1. 串行基线$T_{\text{serial}}$ 是所有任务在单个设备上依次执行的时间,作为对照的“标准出餐时间”。
    TserialT_{\text{serial}}
  2. 并行耗时$T_{\text{parallel}}$ 是在当前调度策略下多设备协同完成的总耗时。
    TparallelT_{\text{parallel}}
  3. 加速比两者相除得到加速比 Speedup,衡量并行执行比串行快了多少倍,数值越大,调度越成功。
    Speedup=TserialTparallel\text{Speedup} = \frac{T_{\text{serial}}}{T_{\text{parallel}}}

同步与通信

厨房里最怕的不是菜多,而是配合出错。炒好的牛肉要递给负责勾芡的师傅,腌制的时间没到就不能下锅——菜与菜之间存在“先后顺序”和“食材传递”。任何一处等不到位,整条流水线就会卡壳。厨师们在共享一袋盐、一口锅时,还必须遵守默契的“先来后到”。

并行任务之间同样存在两类约束。依赖关系:一条任务的结果是另一条任务的输入,决定任务必须按序执行;同步机制:屏障(Barrier)、互斥锁(Mutex)等保证多个设备在共享数据上达成一致。此外,数据在异构设备间的搬运——从 CPU 内存拷贝到 GPU 显存——是并行执行中最为昂贵的通信成本之一。

这些同步与通信开销,让并行系统的加速比有了硬上限。Amdahl 定律给出了最经典的结论:设程序中可并行部分占比为 $p$,使用的设备数为 $N$,则:

即便设备数 $N$ 趋向无穷大,加速比也无法超过 $1/(1-p)$。那些不可并行的串行部分,就是永远拖后腿的“等菜时间”。

同步与通信就是厨房里的传菜窗口:窗口太少,师傅们互相堵塞;传递太频繁,传菜的功夫又超过了做菜本身。工程上常用“计算通信重叠”来掩盖短板——在等待数据时,先做不依赖它的其他菜,让等待时间与计算时间交错利用。

Smax⁡=1(1−p)+pN\displaystyle S_{\max} = \frac{1}{(1-p) + \frac{p}{N}}
Amdahl 定律是并行计算的“紧箍咒”:就算无穷多个处理器同时工作,只要存在一点串行代码,加速比就有天花板。它提醒我们优化串行部分比堆硬件更本质。
  1. 串行比例$1-p$ 是程序必须串行的部分占比,比如结果汇总、依赖更新,相当于必须由一人完成的收尾工序。
    1−p1-p
  2. 并行时间$\frac{p}{N}$ 是理想情况下并行部分被 $N$ 台设备平分后的时间占比。
    pN\frac{p}{N}
  3. 加速后总时间分母是串行部分与并行部分的时间之和,构成整体加速后的归一化耗时。
    (1−p)+pN(1-p) + \frac{p}{N}
  4. 极限加速比当 $N$ 趋于无穷时,$\frac{p}{N}$ 趋近 0,加速比收敛于 $\frac{1}{1-p}$,这就是硬件堆不出来的天花板。
    lim⁡N→∞Smax⁡=11−p\lim_{N \to \infty} S_{\max} = \frac{1}{1-p}

性能分析指标

怎么判断一家餐厅经营得好不好?菜出得快(吞吐量)、等位时间短(加速比)、厨师不闲着(利用率),三者缺一不可。若只宣传“出餐速度提升十倍”,却忽略一半灶台在闲置,这样的业绩并不是真正的胜利。并行系统同样需要用多维指标来体检。

并行系统的核心指标有三类。加速比衡量“快了多少倍”,是并行最直观的成绩;吞吐量衡量“单位时间完成多少任务”,关注整体处理能力;资源利用率衡量“设备有多少时间在真正计算而非空转等待”。三者相互关联,但也可能彼此矛盾——堆上更多设备可能提高吞吐,却因为同步开销拉低利用率。

在上节 Amdahl 公式的基础上,我们可以引入并行效率 $E$,它表示加入的每台设备平均贡献了多少有效加速:

当 $E$ 接近 1 时,每加一台设备都近乎线性收益;当 $E$ 明显小于 1 时,说明大量时间在空转——此时加机器不如优化算法。

下次再听到“我们系统吞吐量翻倍”时,不妨追问一句:效率掉到了多少?就像餐厅老板不会只为翻台率开心,还会盯着后厨的闲忙比——两个指标齐平,才是健康的并行系统。

E=SN=1N(1−p)+p\displaystyle E = \frac{S}{N} = \frac{1}{N(1-p)+p}
并行效率把“加速比”和“设备数量”放在一起看,防止被绝对加速值迷惑。效率过低意味着资源浪费明显,需要回看调度与同步设计。
  1. 效率定义并行效率 $E$ 是加速比 $S$ 与设备数 $N$ 的比值,表示平均每台设备贡献了多少倍加速。
    E=SNE = \frac{S}{N}
  2. 代入推导把加速比公式代入效率定义,得到一个关于 $N$ 和 $p$ 的显式表达。
    SN=1N(1−p)+p\frac{S}{N} = \frac{1}{N(1-p)+p}
  3. 趋势判断当 $N$ 增大时,分母中的 $N(1-p)$ 随之增大,效率 $E$ 持续下降——这反映了“人越多、协调损耗越大”的常识。
    E→0(N→∞, p<1)E \to 0 \quad (N \to \infty, \ p<1)

典型应用场景

不同餐厅有不同经营模式。连锁快餐店把所有菜品批量复制,来多少客人都照同一配方做——这是“数据并行”;私房菜馆让每位大厨专攻一道工序,协作完成一整桌菜——这是“模型并行”;农家流水席把传菜队伍排成一条线,菜在线上逐站加工——这是“流水线并行”。三种模式,构成了 AI 并行执行的主力阵容。

数据并行把训练样本切分到多台设备,每台设备持有完整模型副本,梯度汇总后统一更新,这是大模型训练的基础;模型并行把模型参数切分到多设备,每个设备只存一部分参数,解决单卡放不下完整模型的问题;流水线并行则把模型按层切分成多个阶段,设备之间像流水线一样接力计算,让计算和数据传输重叠起来。

流水线并行的节奏由最慢的阶段决定。设第 $i$ 个阶段的耗时为 $t_i$,则整条流水线的周期为:

一个阶段慢,整条流水线都跟着慢,出菜节奏被“短板工序”钉死。

三种模式可以叠加使用。大模型训练经常采用“数据并行 × 流水线并行 × 张量并行”的组合拳,把几百亿参数塞进上千张 GPU,靠的正是这几套“厨房经营模式”的排列组合。理解了场景,才能真正理解调度器为何要如此设计。

Tpipeline=max⁡i(ti)\displaystyle T_{\text{pipeline}} = \max_i(t_i)
流水线并行的时间不由总工作量决定,而由最慢工序决定。这就像接力赛中跑得最慢的一棒决定整队成绩,优化必须对准短板。
  1. 阶段时间每个阶段 $i$ 有固定的处理耗时 $t_i$,如同流水线上每道工序的加工时长。
    tit_i
  2. 流水周期流水线每个周期推进一个任务,周期长度等于所有阶段耗时的最大值。
    Tpipeline=max⁡i(ti)T_{\text{pipeline}} = \max_i(t_i)
  3. 吞吐率单位时间内完成的产物数量是周期的倒数,周期越短,单位时间走出来成品越多。
    1Tpipeline\frac{1}{T_{\text{pipeline}}}

优化策略

厨房运营的终极命题是:在现有灶台和人手下,如何让出菜速度逼近理论上限?答案藏在一个朴素的道理里——让每个人都忙起来,让每道菜都少等。AI 并行系统的优化,同样围绕“消灭空闲”和“压缩等待”展开。

落到工程上,有三大优化方向。负载均衡:让各设备的工作量尽可能均等,避免“旱的旱死、涝的涝死”;关键路径优化:找到耗时最长的依赖链,给链上的任务分配更多资源或进一步拆分;调度开销控制:减少任务调度本身的频率和成本,例如批量派单、合并小任务、复用工线程池。

负载是否均衡,可以量化衡量。设各设备工作量中的最大者为 $W_{\max}$,平均值为 $W_{\text{avg}}$,则负载不均衡度定义为:

该值越接近 0,各设备负载越均衡,空闲等待越少。

回到智慧餐厅:主厨调整排班表、控制传菜窗口的开合节奏、把午市高峰备餐分成几拨——每一次优化都是在与“等待”作斗争。并行执行没有魔法,不过是把每一份等待都转化成计算,让厨房的每一秒都物有所值。

Imbalance=Wmax⁡−WavgWavg\displaystyle \text{Imbalance} = \frac{W_{\max} - W_{\text{avg}}}{W_{\text{avg}}}
负载不均衡度是衡量“有没有人闲着”的数字指标。它源自最大负载与平均负载的差距,数值越小,说明任务分配越均匀。
  1. 最大负载$W_{\max}$ 是所有设备中最繁忙那台的工作量,它决定了整体完成时间。
    Wmax⁡W_{\max}
  2. 平均负载$W_{\text{avg}} = \frac{1}{N}\sum_i W_i$ 是总工作量被 $N$ 台设备平分的理想值,是均衡状态的基准。
    Wavg=1N∑iWiW_{\text{avg}} = \frac{1}{N}\sum_i W_i
  3. 不均衡度最大负载与平均负载的差值除以平均负载,得到偏离理想均衡状态的相对程度,值越小越好。
    Wmax⁡−WavgWavg\frac{W_{\max} - W_{\text{avg}}}{W_{\text{avg}}}

AI 应用场景

大模型分布式训练

阶段:训练阶段的数据并行与流水线调度

解决的问题:千亿参数模型单卡放不下,单机训练耗时数月。AI Runtime 将训练数据分片、模型参数按层切分,调度器把计算图并行分配到上千张 GPU。相比传统单机训练,训练时间从数月压缩到数天。

具体案例:某团队训练 70B 参数大模型,使用 512 张 A100,同时采用数据并行(每卡相同模型、不同数据)加流水线并行(按层切分成 16 个 stage),调度器动态调整微批次大小,最终训练吞吐提升约 40 倍。期间还通过工作窃取思想平衡各 stage 的负载差异。

推理服务集群的动态批处理

阶段:推理阶段的请求聚合与多设备调度

解决的问题:线上推理请求到达不均匀,传统排队方式会让 GPU 在空闲期浪费算力。Runtime 系统把请求聚合为动态 batch,由调度器按设备实时负载分发,空闲设备自动“偷”取待处理请求,实现资源和延迟的双赢。

具体案例:某云厂商的 LLM 推理服务在业务高峰时请求量突增 10 倍。调度器将文本预处理交给 CPU、模型推理交给多张 GPU、部分算子卸载到 NPU 加速,P99 延迟稳定在 200ms 以内,GPU 利用率从 35% 提升到 78%。

端侧异构芯片协同

阶段:端侧部署阶段的 CPU/GPU/NPU 协同

解决的问题:手机、汽车等端侧设备功耗和算力受限,必须按算子类型分配到最合适的处理器。调度器根据功耗模型与算子特性,把卷积类算子放到 NPU、控制类算子放到 CPU、图像处理算子放到 GPU,让整个模型并行跑起来。

具体案例:某旗舰手机运行实时人像分割模型,Runtime 将模型划分为 120 个子算子,调度到 CPU/GPU/NPU 上并行执行,相比纯 CPU 运行,帧率从 8fps 提升到 45fps,功耗下降 60%。

Python 演示

python48 行
"""异构并行调度模拟: 串行 vs 多设备并行执行对比"""import heapq # 任务列表: (工作量, 偏好设备)TASKS = [    (10, 'CPU'), (20, 'GPU'), (15, 'NPU'),    (8, 'CPU'), (12, 'GPU'), (25, 'NPU'),    (18, 'CPU'), (6, 'GPU'),] # 设备及其速度(单位工作量/时间)DEVICES = {'CPU': 10, 'GPU': 20, 'NPU': 15}  def serial_time(tasks):    """串行: 所有任务在单个CPU上依次执行"""    return sum(work / DEVICES['CPU'] for work, _ in tasks)  def parallel_time(tasks, devices):    """并行: 调度器每次把任务派给最早空闲的设备"""    heap = [(0.0, name) for name in devices]  # (空闲时刻, 设备名)    heapq.heapify(heap)    for work, pref in tasks:        free_at, dev = heapq.heappop(heap)        # 若偏好设备更早空闲, 则改派给它        cand = [(t, n) for t, n in heap if n == pref]        if cand and cand[0][0] < free_at:            heapq.heappush(heap, (free_at, dev))            free_at, dev = cand[0]            heap.remove(cand[0])            heapq.heapify(heap)        heapq.heappush(heap, (free_at + work / devices[dev], dev))    return max(heap)[0]  def main():    serial = serial_time(TASKS)    parallel = parallel_time(TASKS, DEVICES)    print(f"串行总耗时: {serial:.2f} 时间单位")    print(f"并行总耗时: {parallel:.2f} 时间单位")    print(f"加速比: {serial / parallel:.2f}x")  if __name__ == '__main__':    main()
目的:用最小堆模拟一个异构调度器,对比同一批任务在单设备串行与多设备并行(静态优先 + 动态改派)两种模式下的总耗时,直观展示并行执行的加速效果。
  1. heap = [(0.0, name) for name in devices]初始化每个设备的“空闲时刻”,0 表示最开始全部空闲。
  2. free_at, dev = heapq.heappop(heap)弹出当前最早空闲的设备来接收下一个任务。
  3. cand = [(t, n) for t, n in heap if n == pref]在其余设备中查找是否存在偏好设备,尊重任务与设备的亲和性。
  4. heapq.heappush(heap, (free_at + work / devices[dev], dev))计算任务在该设备上的完成时刻,并放回堆中等待下次调度。
  5. return max(heap)[0]所有任务完成后,堆中最大的空闲时刻就是并行执行的总耗时。

常见误区

误区我觉得并行执行很简单啊,Python 里 `ThreadPoolExecutor(max_workers=100)` 一开,一百个线程一起跑,速度不就上去了?

正解多线程只是并行的形式之一,真正的并行需要硬件有多核或多设备支撑。Python 由于 GIL 的存在,多线程在 CPU 密集任务上甚至比单线程更慢;而且线程一多,调度与同步开销迅速膨胀,负载不均时大量线程在空转等待。这就像餐厅招来 100 个厨师挤在一个小厨房,转个身都难,出菜速度反而下降。

为何常见:人们容易把“并发”(看起来同时发生)与“并行”(真正同时执行)混为一谈,误以为线程数就等于并行能力。

误区我把一个大矩阵乘切成 1000 份,每份一个线程,这样并行度最高,一定最快。

正解任务粒度与并行效率呈倒 U 形关系。粒度太细时,线程创建、任务调度、数据拆分合并的开销会超过计算本身省下的时间。第 3 节的公式 $T = N \times O_s + W/N$ 说明存在最优粒度,工程上通常以“计算时间与通信时间之比”来粗调,而不是越细越好。

为何常见:“拆分”是并行最直观的动作,人们容易忽视拆分这个动作本身也要付出代价,尤其是调度与通信成本。

误区Amdahl 定律?我们集群有 1024 张卡,随便加,总能把训练时间压到接近零。

正解Amdahl 定律指出,只要存在一点必须串行的部分(比如梯度汇总、最终结果合并),加速比就有严格上限 $1/(1-p)$。假设 5% 的串行比例,极限加速比只有 20 倍。这也是为什么 8 卡能近乎线性加速,但 1024 卡时强扩展(Strong Scaling)收益极小的原因。

为何常见:人们习惯用“线性外推”的直觉看待资源扩容,忽视了并行系统中天然存在的串行瓶颈,以及通信与同步的边际成本。

误区既然有 CPU、GPU、NPU,那我把任务随便分配,反正都是做计算嘛。

正解不同设备有完全不同的擅长的计算领域和独立的内存空间。矩阵乘法在 GPU 上可能快 10 倍,但把算子搬到 GPU 前,需要先把数据从 CPU 内存拷贝到显存,一次拷贝可能耗费数百微秒;对于小算子,拷贝开销反而让整体更慢。调度器必须综合考虑设备亲和性、数据搬运成本和算子形状,才能做出合理决策。

为何常见:只盯着“设备算力峰值”一个指标,忽略了数据传输延迟与设备特性差异这两个分量,导致“看起来能跑”但“实际更慢”。

面试题

Q1. 并行执行的基本思路是什么?

单选题 · easy

  • A.将大任务拆成小任务同时完成← 正确答案
  • B.将大任务压缩后再依次执行
  • C.让多个任务排队逐个依次执行
  • D.给任务分配更高的运行优先级

答案:A。并行执行的核心是把一个大任务拆解成多个可独立执行的小任务,并让它们在不同处理单元上同时运行,就像多个厨师同时烹饪不同菜品。压缩、排队、提优先级都仍是串行逻辑,不能真正缩短总耗时。

Q2. 异构计算中的“异构”指的是什么?

单选题 · easy

  • A.使用不同类型处理器协同工作← 正确答案
  • B.使用多个相同型号的 CPU
  • C.将程序放到不同机房去运行
  • D.使用不同的操作系统和版本

答案:A。异构指多种不同类型处理器(如 CPU、GPU、NPU)协同计算,各自发挥所长,如同厨房里炒锅、蒸箱、汤煲各司其职。多颗同型号 CPU 是“同构”;不同机房和 OS 属于部署环境差异,与硬件异构无关。

Q3. Amdahl 定律指出程序加速比上限主要取决于?

单选题 · mid

  • A.程序中可以并行执行的比例← 正确答案
  • B.处理器主频能够达到的高度
  • C.内存容量和带宽的大小
  • D.磁盘读写速度的快慢程度

答案:A。Amdahl 定律 $S=1/((1-p)+p/N)$ 表明,串行部分占比 $1-p$ 决定了加速比天花板。即使加再多处理器,串行部分也无法被并行消除,因此可并行比例 $p$ 是决定性因素,主频、内存、磁盘都不是该定律的直接变量。

Q4. 工作窃取调度的核心思想是什么?

单选题 · mid

  • A.空闲线程偷取忙碌线程的任务← 正确答案
  • B.主线程预先平均分配好任务
  • C.所有任务在所有线程间轮转
  • D.把全部任务交给最快的线程

答案:A。工作窃取让每个线程维护本地任务队列,当某线程空闲时,从其他线程的队列尾部偷取任务执行,从而使负载自动均衡。预先平均分配是静态调度的思路,轮转和“全部给最快线程”都不符合动态自均衡的思想。

Q5. 并行任务粒度为何不宜划分过细?

单选题 · mid

  • A.调度与通信开销会显著增加← 正确答案
  • B.过细的任务难以被系统识别
  • C.细粒度任务占更多磁盘空间
  • D.细粒度任务会导致编译变慢

答案:A。任务粒度越细,调度、分发、数据传递的相对开销占比越大,$T=N\times O_s+W/N$ 中的第一项会吞噬并行收益。细粒度任务并不会“难以识别”,占磁盘或导致编译变慢也不是本质原因。

Q6. 流水线并行中吞吐率主要由什么决定?

单选题 · mid

  • A.耗时最长的一个流水阶段← 正确答案
  • B.所有流水阶段的耗时总和
  • C.流水线包含的阶段总数
  • D.任务进入流水线的先后顺序

答案:A。流水线周期 $T=\max_i(t_i)$,整条流水线每完成一个产物所需的时间由最慢阶段钉死,吞吐率是周期的倒数。总耗时之和、阶段数量、进入顺序都不直接决定吞吐率,短板效应才是关键。

Q7. 某程序只有 10% 可并行化,理论加速比上限约为?

单选题 · extreme

  • A.约 1.1 倍← 正确答案
  • B.约 10 倍
  • C.约 100 倍
  • D.约 50 倍

答案:A。Amdahl 定律中 $p=0.1$,加速比 $S=1/((1-0.1)+0.1/N)$,当 $N\to\infty$ 时 $S\to 1/0.9\approx1.11$。许多人误以为“10% 可并行”对应“10 倍加速”,实际恰恰相反——只有极少部分能并行,加速比上限就被压得很低。

Q8. 以下哪些属于常见的并行执行模式?

多选题 · mid

  • A.数据并行:数据拆分到多设备← 正确答案
  • B.模型并行:模型切分到多设备← 正确答案
  • C.流水线并行:按阶段分工合作← 正确答案
  • D.文件压缩后并行解压的方式

答案:ABC。数据并行、模型并行、流水线并行是 AI 系统中的三大经典并行模式,分别对应“样本分片”“参数分片”“按层分阶段”。并行解压虽然涉及并行,但并非文中讨论的 AI 并行执行模式,属于干扰项。

Q9. 并行执行中需要重点处理的开销有哪些?

多选题 · hard

  • A.设备间的数据拷贝通信开销← 正确答案
  • B.任务调度的系统运行时开销← 正确答案
  • C.同步等待造成的设备空闲← 正确答案
  • D.代码注释带来的编译额外开销

答案:ABC。数据拷贝、调度决策、同步等待是并行执行三大开销源,分别对应通信成本、调度成本和空闲成本。代码注释在编译期会被忽略,不产生运行时开销,因此不属于并行系统需要处理的性能开销。

Q10. 以下措施中哪些能提升异构并行效率?

多选题 · hard

  • A.按设备擅长领域分配任务← 正确答案
  • B.平衡各设备之间的负载← 正确答案
  • C.尽量缩短关键路径长度← 正确答案
  • D.增加任务间的同步屏障数

答案:ABC。按设备特性分配提升亲和性,负载均衡减少设备空转,缩短关键路径直接压低总时长,这三者都能提升并行效率。增加同步屏障会让设备更多等待,反而降低效率,是错误选项。