一座城市要办一场千人宴,总厨拿到一本厚厚的菜谱——每一页都写着完整做法的菜式。一个人根本做不完,于是总厨把菜谱按“菜系”拆成几册:川菜一册、粤菜一册、淮扬菜一册,分给几位分厨。每一位分厨手里都收到一模一样的核心食材,但只负责做自己那一册菜谱上的菜。等到开席时间,传菜员把各位分厨做好的菜统统端上同一张大桌,一桌完整的千人宴才算上齐。 这就是今天要讲的“列并行”——一种让多张 GPU 协作完成同一份计算任务的聪明分工方式。它把一份大计算按“输出列”切开分给多张卡,最后把各部分结果拼接成完整答案。它是大模型训练中最基础的并行招式之一,也是 Megatron-LM 这类明星框架的基石。接下来,我们把这场宴席的每一道工序拆开看:怎么切菜谱、怎么算菜、怎么上菜、怎么和激活函数配合,以及整套分工在千卡集群里如何运转。
掌握列并行,你就掌握了张量并行的半壁江山——这是你理解 Megatron-LM、万亿参数训练与推理加速的一把钥匙,也是大模型分布式训练面试中最高频的考点之一。
目录
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
切分原理:一册菜谱的分工之道
千人宴的后厨里,总厨不再一个人面对几百页菜谱。他把菜谱按菜系拆成三册——川菜一册、粤菜一册、淮扬菜一册,分别交给三位分厨。注意,这里切的是“菜的种类”,不是“食材的分量”:每一册菜谱都是完整的做法,只是包含的菜式变少了。这个朴素的直觉,正是列并行切分权重的核心思想。
在矩阵世界里,“菜谱”就是权重矩阵 W,它的每一列恰好对应输出向量的一个分量。列并行的第一步,是把 W 按列切成 p 块:第 i 块 W^{(i)} 的形状是 d × (h/p)。与此同时,输入 X 原封不动地复制给每一张 GPU——如同同样的食材同时送到每一位分厨手中。每张卡只拿一部分列,但食材是完整的,于是它能够独立算完自己负责的那几列输出。
切分的关键约束是输出维度 h 必须能被 GPU 数量 p 整除,否则某人拿到“半道菜谱”,谁也没法做出一道完整的菜。实践中,工程师会在切分前检查矩阵形状,确保每个分厨手里的菜谱都完整可用。
三位分厨各自翻开自己那册菜谱,手里的食材一模一样,做出来的菜却各不相同——这正是列并行的精髓:输入共享,输出分家。至于他们如何把菜“算”出来,请看下一节。
- 权重矩阵$W$ 是 $d \times h$ 的权重矩阵,$d$ 是输入特征数(食材种类),$h$ 是输出特征数(菜式总数)。
- 列拼接符号$\Vert$ 表示把多个矩阵按列方向拼接,就像把散开的三册菜谱按顺序放回书架。
- 切出的每一块$W^{(i)}$ 的维度是 $d \times \tfrac{h}{p}$,只含原来 $h$ 列中的 $h/p$ 列。
- 整除约束$h$ 必须能被 $p$ 整除;否则各块的列数不等,计算负载就会失衡。
数学基础:矩阵乘法的列视野
三位分厨开始做菜:相同的食材 X 加上自己那册菜谱 W^{(i)},产出几道特色菜 XW^{(i)}。为什么把 W 竖着切开,各路乘法就能各算各的?答案藏在矩阵乘法的“分配律”里。
若把 W 按列分成两块 [W_a ∥ W_b],那么 X[W_a ∥ W_b] = [XW_a ∥ XW_b]——乘法可以“穿透”列切分线:先各算各的,再把结果并列拼起来。这个性质对任意 p 块都成立,于是列并行有了坚实的数学保证。
具体来说,设 X 的形状是 b × d(b 是批量大小,相当于一次宴席的桌数),W 是 d × h。完整计算 Y = XW 得到 b × h。列并行把 W 切成 p 块后,第 i 张卡只做一次 b × d 与 d × (h/p) 的乘法,得到 b × (h/p) 的部分结果。把 p 个部分结果沿列拼接,就得到与单卡完全一致的完整 Y。每张卡的计算量从 O(bdh) 降到 O(bdh/p)——典型的人多力量大。
分厨们用完全相同的食材,按各自的菜谱做出一盘盘特色菜。数学向总厨保证:只要各按各的菜谱做,最后拼桌时,这桌宴席的味道和总厨一个人掌勺时一模一样——不多一分,不少一毫。
- 完整视角$Y = XW$,输入 $X$ 是 $b \times d$,权重 $W$ 是 $d \times h$,输出 $Y$ 是 $b \times h$。
- 分配律穿透矩阵乘法对列拼接满足分配律,$X$ 可以分别乘进每一块再并列。
- 局部计算第 $i$ 张卡只做 $XW^{(i)}$,得到 $b \times \tfrac{h}{p}$ 的部分输出。
- 拼接合并把所有 $Y^{(i)}$ 沿列方向拼起来,恢复成 $b \times h$ 的完整输出。
通信机制:传菜员的 All-Gather
三道菜摆在三个厨房里,可客人要的是“一桌席”。这时传菜员上场——他从每个厨房端走一道菜,把菜拼到同一张大桌上。这个动作在分布式训练里叫 All-Gather(全收集)。
All-Gather 的语义很直白:p 张卡各自持有大小为 b × (h/p) 的部分输出(自己的菜),执行一次 All-Gather 后,每张卡都能拿到完整的 b × h 输出(整桌菜)。通信层面,每张卡要把它那份数据发给其余 p-1 张卡,同时收回 p-1 份别人的数据。
从通信量的角度,每张卡发收的总元素数约为 (p-1)/p × b·h。有意思的是,行并行末尾的那次 All-Reduce,通信量也是同一量级——从通信成本看,两者是“对称”的。真正的差异在于通信原语的语义:All-Gather 保留全部信息(列拼起来),All-Reduce 则把信息规约成一个结果。这个差异在 MLP 里会变成巨大的工程优势(见下一节)。
传菜员跑这一趟,满桌的菜才算真正成席。但传菜的通道只有那么宽——p 越大,传菜的花费越不可忽视。聪明的后厨会让传菜和做菜同时进行,这正是训练实践里“通信与计算重叠”技巧的由来。
- 每卡份额每个 GPU 持有 $\tfrac{b h}{p}$ 个元素的部分输出,$b$ 是批量大小,$h$ 是输出维度。
- 发送自己那份每卡把自己这份发给其余 $p-1$ 张卡,共发出 $p-1$ 份。
- 接收他人份额同时每卡也收到其余 $p-1$ 份数据,收发对称。
- 与行并行对比行并行一次 All-Reduce 的通信量也在 $b h$ 量级,两者成本相当。
MLP 应用:GeLU 的天然兼容与 f/g 融合
宴席里每道菜上桌前都要“试味”——咸淡是否合适。对列并行来说,试味可以在每个厨房里独立完成:分厨只尝自己做的菜,根本不需要等另外两盘菜端过来。这正是列并行与“逐元素”激活函数天然兼容的直觉来源。
GeLU 是 Transformer 里最常用的激活函数,它对输出向量的每个元素独立计算:g(z_j) 只依赖 z_j 自己,与其余元素无关。因此,把输出按列切成 p 块、对每块分别做 GeLU,和先做完整 GeLU 再切分,结果完全一致——数学上叫“逐元素操作的切分不变性”。
这个性质成就了 Megatron-LM 的经典 MLP 设计。MLP 的计算是 GeLU(X·W1)·W2,第一层权重 W1 用列并行,第二层权重 W2 用行并行。妙处在于“f/g 融合”:第一层列并行本来要 All-Gather 拼出完整中间结果,但第二层行并行恰好需要把中间结果按行分发——一拼一拆正好抵消,中间那次 All-Gather 被省掉了,整个 MLP 前向只需在最后做一次 All-Reduce,通信量几乎减半。
分厨在自己厨房里试完味,直接把菜递给下一道工序的摆盘师傅,中间的传菜员完全绕开了——直到整桌宴席上齐,才统一对一次账(All-Reduce)。这套“边做边传”的流程,正是 Megatron-LM 在千亿参数模型上保持高效的秘密武器。
- GeLU 定义$\mathrm{GeLU}(z) = z \cdot \Phi(z)$,其中 $\Phi$ 是标准正态分布的累积分布函数,逐元素计算。
- 分块不变性对第 $i$ 块部分输出做 GeLU,结果等于整体 GeLU 后取对应列。
- 本地无等待每卡算完 $XW^{(i)}$ 立即做激活,不需要等 All-Gather 完成。
- 融合省通信$Z_i$ 直接喂给第二层行并行权重 $B^{(i)}$,中间 All-Gather 被吸收,仅在末尾 All-Reduce。
优缺点分析:分工的得与失
把菜谱分给十位厨师,一方面每位厨师的案板轻松了,另一方面后厨的传菜通道变得拥挤。任何工程方案都是取舍,列并行也不例外。
先说得。第一,显存大幅下降:权重 W 的 d·h 个参数平均摊到 p 张卡上,每卡只占 1/p;Adam 优化器的动量与方差也随参数同步减少——训练 175B 参数模型时,这一点是生死攸关的。第二,计算负载天然均衡:只要 h 能被 p 整除,每卡做的乘法量完全相等,不需要担心“有人忙死有人闲死”。
再谈失。第一,通信不可避免:每步前向都要一次 All-Gather,p 越大通信占比越高。第二,维度约束:h 必须能被 p 整除,这限制了 p 的上限。第三,收益递减:计算时间随 p 线性下降,通信时间却随 p 上升,两者交叉处就是最优并行度——所以实践中张量并行度通常取 2~8,更大的扩展交给数据并行维度。
十位厨师各守一块案板摊子,确实轻松;但传菜员的走廊只有那么宽。聪明的后厨会算一笔账:与其让一百个厨师挤在一个厨房里,不如把宴席拆成多场(数据并行),让每场只要三五位厨师配合(张量并行度 2~8)——这正是大模型训练里“混合并行”的基本哲学。
- 总参数量$W$ 含 $d \times h$ 个浮点参数,决定了单卡显存压力的量级。
- 按卡均摊列切分后每卡只存 $\tfrac{d h}{p}$ 个参数。
- 优化器同缩Adam 为每个参数额外存两份状态(一阶、二阶矩),同样除以 $p$。
- 权衡关系计算量随 $p$ 等比例下降,通信量却随 $p$ 逼近 $b h$,一降一升,存在最优 $p$。
训练实践:千卡集群的厨师班底
真正的千人宴不可能只靠一个后厨。现实中,训练一个千亿参数大模型需要上千张 GPU,它们被组织成“多厨房多班组”:数据并行负责复制多个配置相同的后厨,列并行/行并行负责让每个后厨内部的几位厨师分工协作。
Megatron-LM 的实践有一条经验法则:张量并行(含列并行)通常限制在单节点内部,8 卡以内。原因是节点内的 NVLink 带宽高达 900 GB/s,All-Gather 几乎“免费”;跨节点的慢速网络则交给数据并行和流水线并行处理。列并行作为张量并行的左半身,负责把矩阵乘法的“宽”切薄,让每个 Transformer 层都能在多卡上协同计算。
实际训练中,列并行有三个实践要点。第一,通信重叠:把 All-Gather 拆成细粒度小块,与下一层计算流水线重叠,让通信“藏在”计算背后。第二,负载均衡:保证 h 被 p 整除、批量 b 尽量大,摊薄通信占比。第三,混合并行:数据并行 × 张量并行 × 流水线并行构成三维网格。以 1024 卡训练 GPT-3 为例,常取数据并行 128 × 张量并行 8,模型层再切 1 段流水线——列并行只负责单副本内部的 8 卡分工。
千人宴的后厨调度,本质上就是在这三个维度上寻找平衡:后厨要复制多少份(数据并行)、每份内部几位厨师合作(张量并行)、菜肴按几道工序传递(流水线并行)。列并行作为“单副本内分工”的基石,决定了每一份后厨能容纳多少厨师、能做出多大的宴席——这也是它在大模型基础设施中不可替代的地位。
- 总卡数分解$N_{\mathrm{total}}$ 张卡被组织成三维并行网格,每个维度各司其职。
- 数据并行维$D$ 个副本分别处理不同批次的数据,副本间互不干扰。
- 张量并行维$T$ 张卡在一个副本内部分工,列并行和行并行都在这一维。
- 流水线并行维$P$ 按层切分,把不同 Transformer 层放到不同阶段。
AI 应用场景
场景一:Megatron-LM 千亿参数预训练
阶段:模型并行训练(分布式训练)
解决的问题:175B 参数的 GPT-3 单卡显存完全放不下(权重加优化器状态需要数 TB)。传统方式只能缩小模型或增大 batch,二者都不可接受;列并行把每个 Transformer 层的权重按列切开,让 8 张卡合算一层,显存压力瞬间摊薄,训练才能继续推进。
具体案例:用 1024 张 A100 训练 175B 模型时,每个节点内 8 张卡做张量并行:MLP 第一层用列并行、第二层用行并行,注意力层的 Q/K/V 与输出投影同样按列/行切分;跨节点再用数据并行与流水线并行。列并行让单层矩阵乘法从“一个人算不完”变成“八个人各算各的再拼起来”。
场景二:大模型推理服务(张量并行推理)
阶段:模型部署与推理加速
解决的问题:70B 模型即使只加载权重也要 140 GB 显存,单张 A100(80 GB)放不下;推理服务还要求低延迟。列并行把权重分到多卡,一次前向由多卡协同完成,延迟可控且单卡显存压力骤减。
具体案例:vLLM、TensorRT-LLM 等推理框架部署 LLaMA-70B 时,用 TP=8 的张量并行(列并行 + 行并行组合),8 张 A100 协同计算一次前向,同时配合 KV cache 管理,把首 token 延迟压到秒级以内。对用户来说,请求被“悄悄”分给八位厨师,上菜依然是一整桌。
Python 演示
import numpy as np def column_parallel_forward(X, W_list): """simulate column parallel: compute partial outputs then all-gather""" partials = [X @ W_local for W_local in W_list] Y = np.concatenate(partials, axis=1) return Y, partials def main(): np.random.seed(42) batch, d_in, d_out, p = 4, 8, 9, 3 X = np.random.randn(batch, d_in) W = np.random.randn(d_in, d_out) # single GPU reference Y_ref = X @ W # split W by columns into p chunks (simulate p GPUs) W_list = np.split(W, p, axis=1) Y, partials = column_parallel_forward(X, W_list) print("partial shapes:", [part.shape for part in partials]) print("gathered shape:", Y.shape) print("match single GPU:", np.allclose(Y, Y_ref)) if __name__ == "__main__": main()
W = np.random.randn(d_in, d_out)随机生成一个 d 行 h 列的权重矩阵,相当于总厨手里那本完整菜谱。Y_ref = X @ W单卡基准:总厨一个人做完整道菜,作为后续对比的“标准味道”。W_list = np.split(W, p, axis=1)把权重按列切成 p 份,模拟 p 个分厨各拿一册菜谱。partials = [X @ W_local for W_local in W_list]每张卡只做自己的局部乘法,产出的部分输出互不依赖。Y = np.concatenate(partials, axis=1)模拟 All-Gather:把三个部分输出沿列方向拼成完整输出。np.allclose(Y, Y_ref)验证拼接结果与单卡完全一致,这就是列并行正确性的数学保证。
常见误区
误区“并行就是把数据也切开分了呀,列并行应该同时把输入和权重都切成几份,一人拿一份吧?”
正解列并行中只有权重 W 按列切分,输入 X 是完整广播给每张卡的。原因很直接:第 i 张卡要计算 XW^{(i)},它手里的 X 必须完整,否则乘法都无法进行。不妨用宴席比喻:分厨们拿到的是同一批完整食材,只是各自负责不同的菜式。真正切输入样本的是数据并行,初学者常常把两者搞混。
为何常见:直觉上“并行 = 数据切分”,混淆了张量并行(切权重)和数据并行(切样本)两种完全不同的并行方式。
误区“每张卡算完自己那部分就已经是结果了,为什么还要通信?直接输出不就好了?”
正解每张卡算出的 Y^{(i)} 只是完整输出的一部分列,必须执行一次 All-Gather 把所有部分拼起来才能得到最终 Y。行并行同理需要 All-Reduce。可以这样想:每位分厨只做了自己那几道菜,客人要的是一整桌席——传菜员必须跑一趟,把菜拼上桌。计算确实无依赖,但“拼接结果”这一步通信省不掉。
为何常见:把“无依赖计算”误解为“无需通信”,忽略了输出合并本身就是一种通信。
误区“反正都是把矩阵切开分给多卡,列并行和行并行效果差不多,选哪个都行吧?”
正解两者通信原语不同(All-Gather 拼接列 vs All-Reduce 求和),与后续算子的衔接也不同。列并行输出是“完整列块”,可以直接接逐元素激活函数;行并行输出是“部分和”,必须先 All-Reduce 才能继续。在 Megatron 的 MLP 里,第一层必须用列并行、第二层必须用行并行,才能实现 f/g 融合省掉中间通信——交换位置会破坏整个融合结构。
为何常见:只盯着单层矩阵乘法觉得“对称”,没看到跨层衔接时通信模式的关键差异。
误区“GPU 加到 100 张,每张只用原来的 1/100,速度应该提升 100 倍吧?”
正解p 受限于输出维度 h(必须整除,且 p 不能超过 h);通信量随 p 增长,p 超过一定值后计算收益被通信吞掉。实践中张量并行度通常取 2~8,更多的卡通过数据并行和流水线并行扩展。千卡集群的扩展靠“三维并行”组合拳,不是单靠列并行一维加到底。
为何常见:忽略了通信带宽是瓶颈,也混淆了张量并行(切模型)与数据并行(切数据)的扩展方式。
误区“反正都是卡与卡之间通信,All-Gather 和 All-Reduce 不就是名字不同吗?”
正解语义差异巨大。All-Reduce 输出是“规约后的单一结果”,每张卡拿到的都相同(例如部分和相加);All-Gather 输出是“拼接后的完整张量”,保留全部信息。行并行需要 All-Reduce(部分和必须相加),列并行需要 All-Gather(不同列必须拼接)。MLP 融合之所以成立,正是靠这两种原语语义上的互补。
为何常见:集合通信概念繁多,初学者容易只记名字不记语义,导致张量并行两种切分的原理也记混。
面试题
Q1. 列并行中,权重矩阵 W 是如何划分给各 GPU 的?
- A.按行切开,每卡拿到若干完整行
- B.按列切开,每卡拿到若干完整列← 正确答案
- C.按二维块状网格切分
- D.整块复制,不做任何切分
答案:B。列并行按列切分权重,即沿输出特征维度把 W 竖切成 p 块,每张卡持有 d × (h/p) 的局部权重。A 是行并行的做法,C 是二维切分(实践中少见),D 根本不是并行。
Q2. 在列并行中,输入 X 是如何发送到各 GPU 的?
- A.按样本批次拆分发给各卡
- B.按特征维度拆分发给各卡
- C.完整复制广播给每一张卡← 正确答案
- D.随机洗牌后再平均分卡
答案:C。列并行中 X 必须完整广播给每张卡,因为每张卡要计算 XW^{(i)},输入矩阵不完整就无法做局部乘法。A 是数据并行的思路,B 是行并行需要讨论的切法,D 无意义。
Q3. 列并行计算完成后,各 GPU 的部分输出如何得到最终结果?
- A.用 All-Reduce 把所有部分求和
- B.用 All-Gather 沿列方向拼接← 正确答案
- C.用 Reduce-Scatter 求全局平均
- D.直接输出任意一张卡的结果
答案:B。每张卡算出的部分输出是完整 Y 的不同列块,需要用 All-Gather 沿列拼接。A 是行并行的做法(部分和相加),C 是另一种规约型通信,D 会丢数据。传菜员是把不同菜拼上桌,而不是把菜倒进同一个锅里煮。
Q4. 为什么列并行可以直接对部分输出应用 GeLU 激活函数?
- A.GeLU 逐元素操作,切块后结果不变← 正确答案
- B.GeLU 是线性操作,满足分配律
- C.GeLU 只在输出拼接后执行一次
- D.每张卡都持有完整的输出矩阵
答案:A。GeLU 对每个元素独立计算,输出某一列的值只依赖输入同一列的元素,所以分块后逐块做 GeLU 与整体做再切分完全等价。B 错在 GeLU 是非线性的;C、D 都违背列并行“先局部后拼接”的基本流程。就像试味只需尝自己那盘菜,不必等整桌菜上齐。
Q5. 关于列并行的通信开销 All-Gather,下列说法正确的是?
- A.每卡需向其余各卡发送自身输出← 正确答案
- B.每卡通信量约为 (p-1)/p 乘以 bh← 正确答案
- C.列并行完全不需要通信开销
- D.通信量与行并行一次 All-Reduce 相当← 正确答案
答案:ABD。All-Gather 中每张卡把自己那份 b×(h/p) 输出发给其余 p-1 张卡,同时收回它们的数据,单卡通信量约为 (p-1)/p × bh。行并行一次 All-Reduce 也是同一量级,所以两者通信成本相当。C 完全错误,任何形式的张量切分都需要集合通信来合并结果。
Q6. Megatron-LM 的 MLP 中,两层权重 W1 和 W2 分别怎么切?
- A.第一层列并行,第二层行并行← 正确答案
- B.第一层行并行,第二层列并行
- C.两层都用列并行切分
- D.两层都用行并行切分
答案:A。MLP 计算为 GeLU(XW1)W2。第一层用列并行,使每卡产出完整列块并直接本地做 GeLU;第二层用行并行,使每卡算部分和后 All-Reduce。两者组合才能实现 f/g 融合,省掉中间一次 All-Gather。顺序一旦颠倒,GeLU 就无法在部分输出上本地计算。
Q7. 使用 p 张卡做列并行,每张卡的权重显存约为原来的多少?
- A.和单卡一样,保持完全不变
- B.缩减为原来的 p 分之一← 正确答案
- C.增加为原来的 p 倍
- D.缩减为原来的二分之一
答案:B。权重按列切成 p 块后,每张卡只存 d×(h/p) 个参数,显存降为 1/p;Adam 优化器状态也按同比例缩减。这就是列并行让千亿参数模型“装得下”的根本原因。D 只是固定减半,忽略了 p 的效果。
Q8. 关于列并行与行并行的对比,下列表述正确的是?
- A.列并行输出是完整列块,用 All-Gather← 正确答案
- B.行并行输出是部分和,用 All-Reduce← 正确答案
- C.两者通信量处于同一数量级← 正确答案
- D.列并行完全不触发任何通信
答案:ABC。列并行把输出列分给不同卡,最终用 All-Gather 拼接;行并行把输出行分给不同卡,部分和用 All-Reduce 累加;两者单卡通信量都在 bh 量级,成本相当。D 错,列并行每步前向都需要一次 All-Gather。两者的差异不在通信量,而在通信原语与后续算子的衔接方式。
Q9. 在千卡训练集群中,限制列并行扩展规模的最主要因素是?
- A.输入维度必须能被 GPU 数整除
- B.输出维度需整除且通信占比上升← 正确答案
- C.激活函数无法在部分输出上计算
- D.千卡集群不支持 All-Gather 通信
答案:B。列并行要求输出维度 h 能被 p 整除,且 p 增大时通信占比上升、加卡收益递减,所以张量并行度通常取 2~8。A 错在输入是广播的,不参与切分;C 错在 GeLU 逐元素特性反而让列并行更兼容;D 错在千卡集群完全支持 All-Gather,只是跨节点代价高。
Q10. 关于 MLP 中列并行与行并行的 f/g 融合,下列说法正确的是?
- A.中间一次 All-Gather 被第二层吸收← 正确答案
- B.融合后 MLP 前向只需一次 All-Reduce← 正确答案
- C.融合之后完全不再需要任何通信
- D.融合依赖 GeLU 逐元素的切分不变性← 正确答案
答案:ABD。第一层列并行本要 All-Gather,但第二层行并行恰好需要切分后的中间结果,两者抵消,只剩末尾一次 All-Reduce,通信几乎减半。D 正确:GeLU 的逐元素特性是“先激活再传给第二层”而非“先拼完整再激活”的前提。C 错误,MLP 末尾的 All-Reduce 以及反向传播中的通信依然存在,只是中间少了一次。