随着模型参数的增长,推理GPU资源需求从1/N卡、单卡、多卡、多节点,再到超节点,规模不断增加,之前在训练里面常用的DP/TP/SP(CP)/EP/PP/Zero等并行方法也在推理中逐步使用起来。
推理的并行还有着自己的一些特点,比如PD分离场景下P和D的部署方式存在差异、PP场景下Attention与FFN数量可以不等。在展开推理并行优化前,对一些基础知识进行了解是必要的。
1 矩阵运算
并行底层的运算涉及二维矩阵拆分运算、多维矩阵切分与合并运算。在神经网络中线性运算基本是矩阵乘法:C = A x B。矩阵运算的输入、输出可以是多维矩阵(>2维),但维度之间有两个关键约束:
- 若A尺寸为[M, N],B的第一维度必须为N。B为**[N, K]时,计算得到的C尺寸为[M, K]**。
- 多维矩阵的乘法都会变为二维矩阵的乘法,倒数第1和第2维必须满足第一条的MNK关系。
- 多维乘法认为是批量的二维矩阵乘法。
对第二点有个补充:多维矩阵乘除了最后两维,其它维度必须相等(或者进行广播),举个例子:A size: [10 , 3, 2]、B size: [2, 4] ,计算A x B
- B广播变为[10, 2, 4],
- 对应Ai [i, :, :] 与Bi [i, :, :]进行相乘,
- 得到的C维度为[10, 3, 4]。
知道尺寸的约束关系后,接下来分析矩阵的分块运算。先看二维矩阵的运算切分场景。下面的举例中都进行的矩阵二分,矩阵的多次切分可以看成二分的扩展。
1.1
场景一:B矩阵列切(column split)
B矩阵二分(列切)后,子矩阵B1、B2的尺寸为**[N, K/2],切分计算不改变局部元素值,但需要进行一次列维度**的拼接才能获得完整矩阵C。
特点:计算量减半,多了一次allgather集群通信;显存空间变化不一定,若是非原地操作,则变化为 (- K/2 * N + M * K/2) * element_memory。
应用:在并行运算中,通常用两个GPU来完成这一个拆分运算,比如当A矩阵为输入激活值,B矩阵为权重,最后结果通过allgather进行拼接。
矩阵切分运算可用numpy做个辅助验证,这里提供一个示例:
import numpy as np # 1. 定义整数输入矩阵 (M, N) 和 (N, K) M, N, K = 3, 4, 6 A = np.random.randint(0, 10, size=(M, N)) # 随机整数矩阵 [0, 10) B = np.random.randint(0, 10, size=(N, K)) print("A:\n", A, "\nshape:", A.shape) print("\nB:\n", B, "\nshape:", B.shape) # 2. 对 B 按列切分(均分) num_splits = 3 # 切分块数 B_splits = np.split(B, num_splits, axis=1) # 沿列切分 print("\nB 分块结果:") for i, B_i in enumerate(B_splits): print(f"B_{i}:\n", B_i, "\nshape:", B_i.shape) # 3. 模拟并行计算:每个进程计算 A @ B_i local_results = [A @ B_i for B_i in B_splits] print("\n局部乘积结果:") for i, C_i in enumerate(local_results): print(f"C_{i} (A @ B_{i}):\n", C_i, "\nshape:", C_i.shape) # 4. 模拟 allgather:拼接所有局部结果 C_final = np.concatenate(local_results, axis=1) print("\n合并后的 C_final:\n", C_final, "\nshape:", C_final.shape) # 5. 验证结果与直接乘法的等价性 C_ground_truth = A @ B print("\n标准乘法结果 (A @ B):\n", C_ground_truth) print("\n验证一致性:", np.array_equal(C_final, C_ground_truth))1.2
场景二:A矩阵行切(row split)
A矩阵二分(行切)后,子矩阵A1、A2的尺寸为**[M/2, N],切分不改变局部元素值,但需要进行一次行维度**的拼接才能获得完整矩阵C。
特点:计算量减半,多了一次集群通信(allgather);若是非原地操作,显存变化: (K-N) * M/2 * element_memory。
应用:这类切分比较常见,如A矩阵是激活值,B矩阵为权重,如果后面的还是线性运算或者元素行运算,allgather步骤可以放到最后且只需进行一次。
如: A[M/2, N] x B x E x F … -> allgather 与原计算得到的结果一致。这就是常见的激活值进行序列切分的场景:[sequence/sp, hidden_dim] 。
Note:这里有个值得注意的点,仅切分一个矩阵的方式,适合任何线性运算,但不一定能够带来性能/存储空间的收益。
1.3
场景三:A矩阵列切+B矩阵行切
A、B矩阵都进行二分后,输出矩阵C的大小不变,所有C矩阵进行元素求和,才能让结果与之前的计算结果相等。
特点:计算量减半,多了一次集群通信(allreduce);显存变化:输入、权重减半,输出值取决于是否为原地操作。
这种切分方式的使用场景并不多,更多的是下面这种方式:
1.4
场景四:B矩阵列切+C矩阵行切
在A x B x C矩阵乘法场景,B矩阵列切, C矩阵行切。
特点:在场景三基础上,中间值的存储大小减半。
在模型并行运算的过程中,大部分都是这些应用场景的组合与变形,需要根据模型层的特点设计合适的矩阵切分策略。
2 线性层的并行计算
线性层的并行运算,包括层内切分、层间切分以及参数冗余消除,并行方式包括了数据并行(DP)、序列并行(SP)、张量并行(TP)、层并行(PP)、参数冗余消除(Zero)。
2.1
层内并行
可根据输入激活值的切分维度对应不同并行策略,一般切batch为DP、切序列为SP、切隐藏层尺寸为TP。
关键特点:
- DP可以看成批量矩阵乘法,不会改变二维矩阵的乘法形态,所以对应的权重不需要变化。
- SP切分不会影响最后一维的计算,如:LayerNorm,softmax
- TP切分激活值后,权重必须做对应的切分。
DP与SP/TP可以解耦独立使用,SP与TP一般可以配合使用(在当前推理应用中一般设计成:DP*TP=world_size,SP= TP)。
又因为:
- allreduce是可以被拆分为ReduceScatter和AllGather两个步骤;
- SP不影响元素操作的运算,如LayerNorm,softmax。
所以在上面矩阵运算场景四的基础上,对于最后的集群通信操作有如下改进,将allreduce运算进行拆分,变为两个步骤。并且将allgather操作移动到softmax计算后:
这样能够降低元素运算相关计算的数据大小、降低过程中激活值的大小(示例中GPU上的LayerNorm输入减半),即能节省显存、也能提升速度。在megatron论文里面介绍了这种场景的应用,MLP层中用SP-TP-SP的组合方式:
2.2
层间并行
模型的各个层分散放在不同GPU上面,激活值按照流水的方式在层间传递,这种方式称为流水线并行PP(Pipeline Parallelism),具体可参看megatron2论文。
在推理中,由于特殊计算场景,比如FFN为稀疏的MoE结构时,往往attention与FFN对算力的需求不同,层间并行可以变为异构模式, 如AFD,attention层与MoE分离部署到不同设备上:
2.3
冗余参数消除
冗余参数消除的方式,如deepseed的Zero,将参数分散存储,当参数需要使用时通过集群通信将参数召回。根据Zero的思想,这里介绍两种消除某一层权重冗余存储的方式。
一般而言,大模型由多个blocks构成,blocks中有重复的层结构。当有多个设备时,可以把这些blocks的权重均分到各个设备中。如下图所示,以blocks中线性层B为例,每个GPU存储模型的两层权重,当GPU需要使用到某一层权重时,先对该层的权重进行广播。
由于GPU支持多流,所以可以做计算、广播通信的掩盖,比如计算到x层时,提前把x+1层的权重先广播出去(权重预取)。
这种方式适合于模型层权重偏小的场景,模型层权重比较大时,或者说更常使用的是权重切片(shard)的场景。即将每一层的权重进行n等分,每个GPU设备上面存一份,当需计算时将其allgather回来。
3 Attention层的并行计算
Attention层并行计算与FFN(或模型中其他线性层)计算的主要区别在于:
- 在计算attention之前,会多一个head的维度,这个head维度是在线性映射(投影)运算中产生的。[bs, seq_len,heads*head_dim] -> [bs,heads,seq_len,head_dim]
- 序列(seq_len维度)并行需要修正运算。
在并行维度上面,将Head维度的切分也算到TP里面,而序列维度并行上称之为CP(context parallel)或SP。
Attention的层间并行、冗余参数消除方式与线性层的方式一致,层内并行的主要差异是TP和SP,所以重点关注到这两类上面。
3.1
序列并行
在经过线性映射计算后,模型输入变为多头的Q、K、V值,其尺寸表达为:[bs, heads, seq_len, head_dim],序列并行是切分seq_len维度。有个前提问题:QKV的序列并行跟线性层的差异在哪,是否能直接拆分运算后合并?
Scaled Dot-Product Attention
这里我们分三个子问题来分析:
- 如果只切Q 的序列,KV保持不变,结果拼接后是否相等?
- 如果只切K 的序列,QV保持不变,结果拼接后是否相等?
- 如果只切V的序列, QK保持不变,结果拼接后是否相等?
先看问题1,Q的切分后的尺寸为[bs, heads, seq_len/SP, head_dim],按照attention计算:
- step1:求解score,Q x K 相当于左矩阵行切(场景二),score尺寸:[bs, heads, seq_len/SP, seq_len]
- step2:softmax求解的是最后一个维度,计算元素值相同,得到attention_weights,
- step3:attention_weights与V进行矩阵乘,还是左矩阵行切运算,元素值相同,计算得到O的分块结果
- step4:将计算的O进行allgather,结果相等。
所以:单独切Q的序列值后进行结果拼接,与原计算相等。我们可以通过如下例子来验证:
import numpy as np def softmax(x): e_x = np.exp(x - np.max(x, axis=-1, keepdims=True)) return e_x / np.sum(e_x, axis=-1, keepdims=True) def attention(Q, K, V): scores = np.matmul(Q, K.transpose(0, 1, 3, 2)) / np.sqrt(K.shape[-1]) weights = softmax(scores) return np.matmul(weights, V) # 设置随机种子(确保可复现) np.random.seed(42) # 定义形状:batch_size=1, num_heads=1, seq_len=4, hidden_dim=3 bs, heads, seq_len, hidden_dim = 1, 1, 4, 3 # 随机生成 Q, K, V Q = np.random.randn(bs, heads, seq_len, hidden_dim) K = np.random.randn(bs, heads, seq_len, hidden_dim) V = np.random.randn(bs, heads, seq_len, hidden_dim) # 全局注意力计算 global_out = attention(Q, K, V) print("全局注意力输出:\n", global_out) # 将 Q 按 seq_len 切分为两部分 Q1 = Q[:, :, :2, :] # 前两行 Q2 = Q[:, :, 2:, :] # 后两行 # 分片计算注意力(K/V 保持完整) local_out1 = attention(Q1, K, V) local_out2 = attention(Q2, K, V) # 合并分片结果(模拟 all_gather) local_out = np.concatenate([local_out1, local_out2], axis=2) print("分片注意力输出:\n", local_out) # 验证一致性 print("结果是否一致:", np.allclose(global_out, local_out, atol=1e-6))问题2:单独切K的序列。
第一步,QK乘法得到的score尺寸为:[bs, heads, seq_len, seq_len/SP]
进一步计算softmax,由于最后一个维度的数据只有之前的一半长度,而softmax的计算跟整个序列相关,直接拼接会导致结果不相等。所以,单独切K序列后拼接,结果不等。
问题3: 单独切V的序列。softmax之前的计算保持不变,得到attention_weights尺寸完整,计算attention_weights xV,因为V矩阵被行切,所以attention_weights 需要列切,根据前面的场景三,最后allreduce能够获得完整结果。
综上看到attention序列切分的关键:如果需要进行Q、K、V在序列维度的同时切分,要解决softmax的分块计算。
当前比较常用的解决方式是:QKV按照相同方式切分,然后对计算结果进行修正,这个方法在BlockwiseMulti-HeadAttention、ring attention、tree attention、merge attention中都有应用,softmax修正:
关键点:
- 每个切分Qi要与所有的Ki、Vi进行一次计算,得到Oi;
- 最后的分块Oi结果需要进行修正;
有了softmax的修正方式,结合推理的计算场景实现序列并行:
prefill:训练forward/推理的prefill阶段,Q的序列长度与KV保持一致,开启SP后,GPU之间需要交换KV值与Q进行运算(ring-attention方式),计算过程的CP/SP并行可以参看:[并行训练]Context Parallelism的原理与代码浅析。至于是用KV传递还是Q传递,可以参看这篇分析(https://arxiv.org/pdf/2411.01783)
decode:与prefill阶段不同的是,在decode阶段Q的长度为1,若对应的KV值分散在各个GPU设备中,可以将Q复制N份与切片的KV值进行attention计算后,最后将结果gather到一个设备上再计算修正结果。
这个方式意味着GPU0需要完成最大值、修正运算、求和运算,GPU0可能成为瓶颈。一种Tree attention算法对过程做了改进,提升了通信效率。就是把gather换成多次allreduce,这种方式在跨节点场景中优势明显。
Tree attention
prefix cache:在prefill阶段,还有个特殊场景prefix cache,若开启了prefix cache功能,表示有一段KV cache可以复用。把序列seq_len拆分成两部分讨论,prefix_cache_seq_len、tail_seq_len。 Q/K/V的激活值尺寸:
[bs,heads,prefix_cache_seq_len+tail_seq_len,head_dim]
其中KV值的prefix_cache_seq_len已被缓存,prefix_cache_seq_len KV的计算量更少。Q序列只需计算tail_seq_len的结果,加上前面的分析(Q序列单独切分,不会改变元素计算结果)可知,计算分为两类场景:
- Qtail_seq_len 与 prefix_cache_seq_lenKV计算attention;
- Qtail_seq_len 与tail_seq_lenKV计算attention;
在有prefix cache的序列切分时要考虑的:
- 问题1:两类计算如何分配,保证负载均衡?
- 问题2:已有的prefix cache如何从存储位置传递到计算位置(prefix cache可能不在GPU上)。
这里举例一个简单的实现方式,将prefix_cache_seq_len和tail_seq_len进行均分,每个GPU拿取一份,同时进行Q分片的轮转,如下图所示:
with Q pass ring attention
优势:当切分比例保持不变且KV值每次计算后都在本地显存中,则无须进行KV分片的搬运,(解决了问题2)。当prefix cache在其它存储位置时,比如内存中,依然要考虑重新切分和cache的传输问题。
该方式在计算的时候可能需要进一步拆分,因为按照SP切分后的KV序列与Q序列不一定相等:prefix_cache_seq_len/SP != tail_seq_len/SP。若相等,理想情况下,一次迭代计算中只需要两次attention运算。
3.2
张量并行
Attention的张量并行分为两个部分:QKV运算、线性投影运算。
QKV运算的TP切分一般针对heads维度,相当于矩阵的并行的批量运算。attention计算切heads,每个rank(GPU)拿到不同的head,最后进行heads还原(allgather);attention前后的线性运算按照hidden_dim/heads方式切分。
Attention的张量并行与其它层的并行可结合使用降低通信/计算量,这里给出megatron中TP-SP结合的例子:
megatron并行示例
目前的deepseek中用的attention模块MLA与MHA的计算在线性层的处理中有些不同,导致其线性层的TP的并行策略设计也要做对应的调整。我们针对这一差异展开详细讨论: 首先看一下MLA的计算公式:
MLA计算公式
MLA相较于MHA的主要特点是:线性运算多了一个步骤:在QKV生成之前,先要做降维down_project线性运算,然后进行升维up_project线性运算。分头运算是在up_project阶段。
根据公式绘制运算流图,非矩阵吸收的MLA(KV up_project运算在压缩计算后)如下所示,这种形态常用在prefill阶段(Q的输入为完整序列时)。
MLA一般形态
主要的计算步骤:
1、Q、K、V的下采样线性计算
2、Q上采样运算
3、KV上采样运算
4、attention计算
5、O线性运算
分别来看一下这些计算步骤的TP策略:
- 下采样阶段还没有heads,所以一般而言Q、K、V的下采样不进行TP切分。考虑到独立切分适应任何场景,也可参考前面提的矩阵切分场景来分割下采样。
- 如果按照heads进行切分的话,步骤2、3的W矩阵需要进行列切(column split),步骤5的W矩阵行切(row split);
- 步骤4的attention则进行heads维度切分(head split)。
MLA还有第二种形态,就是将KV的up_project运算移动到Q上面,即K、V输入没有了up_project计算(多heads通过广播实现)。MLA吸收矩阵形态的逻辑如下所示:
MLA (吸收矩阵)常用在decode中
这种形态的TP切分策略:
- 步骤1不采用TP切分;
- 步骤2的W矩阵需要进行列切(column split);
- 步骤5的W矩阵行切(row split);
- 步骤4的attention则进行heads维度切分(head split);
- 步骤3的两次线性运算均进行heads维度切分(head split);
注:MLA的KV矩阵吸收运算(W_UK -> W_UQ W_UV -> W_O)是重新建立一个线性映射矩阵(即,两次的W_UK不相等),MLA的矩阵吸收的证明见参考文献7。
附1:
常见的集群通信方式
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线科技企业深耕十二载,见证过太多因技术卡位而跃迁的案例。那些率先拥抱 AI 的同事,早已在效率与薪资上形成代际优势,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在大模型的学习中的很多困惑。我们整理出这套AI 大模型突围资料包:
- ✅ 从零到一的 AI 学习路径图
- ✅ 大模型调优实战手册(附医疗/金融等大厂真实案例)
- ✅ 百度/阿里专家闭门录播课
- ✅ 大模型当下最新行业报告
- ✅ 真实大厂面试真题
- ✅ 2025 最新岗位需求图谱
所有资料 ⚡️ ,朋友们如果有需要《AI大模型入门+进阶学习资源包》,下方扫码获取~
① 全套AI大模型应用开发视频教程
(包含提示工程、RAG、LangChain、Agent、模型微调与部署、DeepSeek等技术点)
② 大模型系统化学习路线
作为学习AI大模型技术的新手,方向至关重要。 正确的学习路线可以为你节省时间,少走弯路;方向不对,努力白费。这里我给大家准备了一份最科学最系统的学习成长路线图和学习规划,带你从零基础入门到精通!
③ 大模型学习书籍&文档
学习AI大模型离不开书籍文档,我精选了一系列大模型技术的书籍和学习文档(电子版),它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础。
④ AI大模型最新行业报告
2025最新行业报告,针对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。
⑤ 大模型项目实战&配套源码
学以致用,在项目实战中检验和巩固你所学到的知识,同时为你找工作就业和职业发展打下坚实的基础。
⑥ 大模型大厂面试真题
面试不仅是技术的较量,更需要充分的准备。在你已经掌握了大模型技术之后,就需要开始准备面试,我精心整理了一份大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。
以上资料如何领取?
为什么大家都在学大模型?
最近科技巨头英特尔宣布裁员2万人,传统岗位不断缩减,但AI相关技术岗疯狂扩招,有3-5年经验,大厂薪资就能给到50K*20薪!
不出1年,“有AI项目经验”将成为投递简历的门槛。
风口之下,与其像“温水煮青蛙”一样坐等被行业淘汰,不如先人一步,掌握AI大模型原理+应用技术+项目实操经验,“顺风”翻盘!
这些资料真的有用吗?
这份资料由我和鲁为民博士(北京清华大学学士和美国加州理工学院博士)共同整理,现任上海殷泊信息科技CEO,其创立的MoPaaS云平台获Forrester全球’强劲表现者’认证,服务航天科工、国家电网等1000+企业,以第一作者在IEEE Transactions发表论文50+篇,获NASA JPL火星探测系统强化学习专利等35项中美专利。本套AI大模型课程由清华大学-加州理工双料博士、吴文俊人工智能奖得主鲁为民教授领衔研发。
资料内容涵盖了从入门到进阶的各类视频教程和实战项目,无论你是小白还是有些技术基础的技术人员,这份资料都绝对能帮助你提升薪资待遇,转行大模型岗位。