More than code

More Than Code
The efficiency of your iteration of reading, practicing and thinking decides your understanding of the world.
  1. 首页
  2. 未分类
  3. 正文

MegatronLM TensorParallel

2026年1月10日 42点热度 0人点赞 0条评论

这篇文章来介绍一下MegatronLM中,TensorParallel相关的实现,主要是面向源码。
相关论文:Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism,推荐先看一下

MegatronLM实现的TensorParallel需要对模型结构有改动,用支持并行计算的层来替换掉原始模型中的那些层,并不是类似Torch FSDP这种对模型结构无感知的实现方法。
所以在阅读代码的过程中,主要需要看两个点:

  • 并行Layer的实现
    • 代码主要在megatron/core/tensor_parallel目录下
  • 如何组合这些并行Layer,组成更大的模型
    • 代码主要在megatron/core/models中,这里支持了若干常见的模型,这次我们只针对GPT这个模型来看

Layers

Linear

ColumnParallelLinear

  • 对应论文中,将Weight按照列来拆分。
    • A = [A1, A2]

    • [Y1, Y2] = [XA1, XA2]

  • 从计算图的角度来看,这里相当于X有两个下游节点。所以在反向传播的时候,需要汇聚来自Y1和Y2的梯度
    • 同时因为X在两个rank上都有一个副本,所以可以理解成是用all-reduce来做“汇聚”+“复制”的操作
  • 实现上,有一个小的优化点,每个rank上计算了D_Y / D_X之后,会发起异步的all-reduce,同时计算D_Y / D_A。
    • overlap了all reduce的通信和对Weight的梯度计算。大概示意图如下


RowParallelLinear

  • 对应论文中,将Weight按照行来切分。因为Y = XA,如果A按照行来切分了,需要让X按照列来切分
    • Y = Y1 + Y2 = X1A1 + X2A2
  • forward/backward和上面的Column版本的基本相同。因为每个Rank上仍然是 Y = XA的形式,只不过含义不同,以及有一些额外的通信操作。

  • 说一嘴题外话,图画到这里感觉可以根据计算图来做一些切分,来做自动的tensor parallel?可能一些静态图的框架就是这样做的

  • ColumnParallel/RowParallel一般是成对出现的。具体的组合方式可以看上面的图。
    • 同样标注了dimension
  • 这块延伸讲,还有另一种设计思路。可以在每一个ColumnLinear内,做完计算后就执行一个All Gather。
    • 如果hidden dim都一样的情况下(h0 = h1 = h2),通信量:两次All Gather和一次All-Reduce是一样的。计算量:差了两倍

    • 同时因为这种先上投影+activation+下投影是一个比较常见的操作,在中间上投影后的层进行All Gather和最后下投影再做,通信量是不同的。

Other

除去Linear层可以并行之外,还有起始的输入,最后的lm_head,这两个在论文中也提到一般是共享权重的。以及因为lm_head输出的logits也是分片后的,所以MegatronLM还支持了使用这个分片后的输出计算cross_entropy_loss(当然也在论文中有提到)
对应代码中的cross_entropy.py文件,以及VocabParallelEmbedding

VocabParallelEmbedding

  • 这里有两个选择,按照embedding_dim切,或者是按照词表切。
    • 如果按照embedding_dim切,后续要做一下all reduce。或者是进入到QKV projection的时候,用RowParallel算QKV,然后再做all reduce。(不过QKV之后再all reduce的话,因为head * head_hidden一般大于d_model,不太划得来)

    • 同时如果按照embedding_dim切的话,最后计算loss的时候还需要做all-reduce + cross-entropy

  • 综上,这里选择的是根据词表切。

    • 每一个rank有部分的词表。如果输入的token不在自己负责的词表中,就输出一个0。

    • 计算完自己负责部分的Embedding后,做All-Reduce,就可以得到整个序列的Embedding了

CrossEntropy
这里计算CrossEntropy的时候,也是Fuse了Softmax和CrossEntropy:

并行的设计上,每一个rank有部分的z_j。这里MegatronLM的做法是:

  • all_reduce,计算全局的max

  • 用本地的logits,减去全局max,然后计算本地的sum_exp

  • all_reduce,计算全局的sum_exp

  • all_reduce,同步全局的predict_logits

  • 按照上面的公式计算即可

这种方法在论文中也提到,可以把通信量减少sequence倍,因为现在只需要通信统计值,不需要通信原始的logits了

Model

有了上面的layer,现在来尝试组装一下模型。

首先是MLP和Attention,这两个在论文中也有图了,这里就简单讲讲实现点

MLP

  • MLP的实现就是简单的两个Linear,对应上面的一个ColumnParallel和一个RowParallel

  • 按照hidden_dim来切分,每个rank上持有部分的hidden_dim

  • 同时支持GLU,实现上就是up_projection之后,把得到的hidden_dim对半分(因为要做element_wise的相乘)。计算activate_func(x_glu) * x_linear,其中 [x_glu, x_linear] = XA_r

Attention

  • qkv的矩阵是放到一起的,作为一个ColumnParallelLinear出现。其中

    • query_projection_size = kv_channel * num_attention_head

    • kv_projection_size = kv_channel * num_query_groups,用来支持GQA

    • ColumnParallelLinear的output_size就是query_projection_size + 2 * kv_projection_size

  • 每一个rank按照head做切分,过完Linear之后,再根据上面计算的projection size切分成QKV三组

    • 按照head切分后,后面的Attention就可以独立计算了
  • 最后还有一个o的矩阵,是一个RowParallelLinear,正好对应了每个rank上不同的head,也就是不同部分的hidden_dim。最后再做All-Reduce,计算就完成了。

实现上,可能还有一个小问题是模型切分是在哪里做的呢?

  • MegatronLM有一个全局的ParallelState,可以得知各种并行策略对应的world_size/rank

  • 在每个module内,判断如果启动了TP,需要调整对应Linear层的size,除以world_size

总结一下每一个TransformerBlock:

  • MLP按照d_model切分,up_projection是Column切分,down_projection是Row切分

  • Attention按照head切分,qkv projection是Column切分,output projection是Row切分

  • forward阶段一共两次all-reduce

通过TransformerBlock构建GPTModel就与单机无异了。

标签: 暂无
最后更新:2026年1月10日

sheep

think again

点赞
< 上一篇
下一篇 >

文章评论

取消回复

COPYRIGHT © 2021 heavensheep.xyz. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS