# <div align="center"> Tensor-Parallel-Training </div> [論文參考](https://arxiv.org/abs/1909.08053) ## 張量並行 **Megatron-LM** 這篇論文主要貢獻是模型並行化(Model Parallelism)技術,用於在 多個 GPU 之間拆分 Transformer 層,主要用於訓練超大語言模型 ![image](https://hackmd.io/_uploads/S1hYESCOJl.png) 張量並行主要透過切分 **FFN(MLP)層** 和 **多頭自注意力層**來實現張量並行 ## FFN(MLP)層 ![image](https://hackmd.io/_uploads/HJf8BHAu1l.png) - \\(Y=GeLU(XA)\\) - \\(Z=Dropout(YB)\\) - \\( X \\) 是輸入矩陣(batch size × hidden dimension) - \\( A \\) 是權重矩陣(hidden dimension × MLP hidden size) - \\(B\\) 是 第二個權重矩陣(MLP hidden size × hidden dimension) ### 前向傳播 #### 方法 : 將 \\(A\\) 沿著列方向切割 1. 將 \\( A \\) 沿著列方向劃分為兩個部分: \\[A = [A_1, A_2]\\] - 透過這樣切分,計算變成 \\[[Y_1, Y_2] = [\text{GeLU}(X A_1), \text{GeLU}(X A_2)]\\] - 可以在每個 GPU 上獨立應用 GeLU ,**避免了同步點的需求**,使得計算更為高效 2. 進行Dropout,我們經過剛剛那步已經得到\\[[Y_1, Y_2] = [\text{GeLU}(X A_1), \text{GeLU}(X A_2)]\\],代表第一個 GEMM 和 GeLU 的計算已經完成,接下來進行 第二個 GEMM 和 Dropout \\[Z=Dropout(YB)\\] - \\( B \\) 沿行方向分割: \\[B = \begin{bmatrix} B_1 \\ B_2 \end{bmatrix}\\] - 計算變為: \\[Z = \text{Dropout}(Y_1 B_1 + Y_2 B_2)\\] - 這種處理方式會需要同步點(透過 All Reduce),因為Dropout 不能作用在部分張量,必須先 All Reduce 相加兩張卡的數值,才能進行 Dropout ### 反向傳播 1. **對 \\(Z\\) 進行微分** \\[\delta_Z= \frac{\partial \mathcal{L}}{\partial Z}\\] - 前向傳播時,已經進行了 All-Reduce,每張GPU 都有完整的數值,所以梯度也是完整的 - 由於 Dropout 作用在整個 \\(YB\\) 之後,反向傳播時,應用 Dropout 反向操作 \\[\delta_{YB} = \delta_Z \odot M\\] - \\(M\\) 是 Dropout 的 mask 2. **對 \\(B\\) 和 \\(Y\\) 進行微分** - 對 \\(B\\) 求梯度,因為是沿行方向分割 \\[B = \begin{bmatrix} B_1 \\ B_2 \end{bmatrix}\\] - 在不同 GPU 上: \\[ \frac{\partial \mathcal{L}}{\partial B_1} = Y_1^T \delta_{YB}, \quad \frac{\partial \mathcal{L}}{\partial B_2} = Y_2^T \delta_{YB} \\] - 這些梯度可以在 各 GPU 上獨立計算,不需要 All-Reduce - 對 \\(Y\\) 求梯度 \\[Z = Y B\\] \\[\frac{\partial \mathcal{L}}{\partial Y} = \delta_{YB} B^T\\] \\[\delta_Y = \delta_{YB} B^T\\] - 因為 \\(B\\) 是沿行方向分割 \\[\delta_{Y_1} = \delta_{YB} B_1^T, \quad \delta_{Y_2} = \delta_{YB} B_2^T\\] - 因為 \\(A\\) 是沿著列方向分割的: - \\(Y_1\\) 和 \\(Y_2\\)沒有加總,所以反向傳播時梯度可以在各自的 GPU 上獨立計算,不需要 All-Reduce 3. **對 \\(A\\) 和 \\(X\\) 進行微分** - 對 \\(A\\) 求梯度 - 已知 \\[Y_1 = \text{GeLU}(X A_1), \quad Y_2 = \text{GeLU}(X A_2)\\] - 鏈式規則 \\[ \delta_{X A_1} = \delta_{Y_1} \cdot \text{GeLU}^{\prime}(X A_1), \quad \delta_{X A_2} = \delta_{Y_2} \cdot \text{GeLU}^{\prime}(X A_2) \\] - 對\\( A_1, A_2 \\)求梯度 \\[ \frac{\partial \mathcal{L}}{\partial A_1} = X^T \delta_{X A_1}, \quad \frac{\partial \mathcal{L}}{\partial A_2} = X^T \delta_{X A_2} \\] - 這些梯度只涉及當前 GPU 上的 \\( A_1, A_2 \\),不用 All-Reduce - 對 \\(X\\) 求梯度 \\[\delta_X = \delta_{X A_1} A_1^T + \delta_{X A_2} A_2^T\\] - \\(X\\) 是所有GPU 上是共享的 - 因為 \\(A\\) 是沿列進行分割,\\( \delta_{X A_1} \\) 和 \\( \delta_{X A_2} \\) 是各自計算的,所以需要 All-Reduce 確保所有 GPU 都獲得完整的梯度 \\( \delta_X \\) \\[ \delta_X = \text{AllReduce}(\delta_{X A_1} A_1^T + \delta_{X A_2} A_2^T) \\] ## 多頭自注意力層 ![image](https://hackmd.io/_uploads/ryC42PRuJg.png) ### 多頭注意力的張量並行 主要解決**自注意力層**中,查詢(\\(Q\\))、鍵(\\(K\\))跟值(\\(V\\))的大規模矩陣乘法,Megatron-LM 通過列方向(Column Parallel)分割來優化: - 將 \\(Q, K, V\\) 矩陣乘法沿列方向劃分 - 每張 GPU 只負責計算部分注意力頭 - 注意力頭與頭之間在部分計算是無關的,不需要立即進行 GPU 間的通訊 - GPU 間不用交換 \\(Q, K, V\\) 的部分結果,減少通訊開銷 ### 自注意力後的線性變換並行 算完自注意力後,最後有一個線性變換層,Megatron-LM 透過將輸出線性層的矩陣乘法沿行方向(Row Parallel)分割 - 輸出線性層的矩陣沿行方向劃分(Row Parallel),接收前一層平行化的注意力輸出 ### 範例 : GPU 數 : 2 - 輸入向量 \\(X\\),形狀為\\((B, S, D)\\) - 自注意力頭數為 8 - 每張 GPU 負責 4 個注意力頭計算(Megatron-LM 通過列方向分割來優化) #### 張量並行 1. **列方向分割** - 將 \\(Q, K, V\\) 沿列方向劃分 - GPU-0:計算前 4 個注意力頭的 \\(Q, K, V\\) - GPU-1:計算後 4 個注意力頭的 \\(Q, K, V\\) 每張 GPU,只需計算: \\[ Q_{\text{local}} = X W_Q^{\text{local}}, \quad K_{\text{local}} = X W_K^{\text{local}}, \quad V_{\text{local}} = X W_V^{\text{local}} \\] 2. **各自計算局部注意力(跟原本一致)** **GPU** 間不需要通信 \\[ \text{Attention}_{\text{local}} = \text{softmax} \left( \frac{Q_{\text{local}} K_{\text{local}}^T}{\sqrt{d_k}} \right) V_{\text{local}} \\] 3. **行方向分割輸出線性層(Row Parallelism)** **假設有輸出線性層的權重矩陣 \\(W_O\\)** 1. 前面假設 2 張 GPU,把 \\(W_O\\) 沿 行方向(Row Parallel) 劃分成 2 部分: \\[W_O = \begin{bmatrix} W_{O_1} \\ W_{O_2} \end{bmatrix}\\] 2. 每張 GPU 計算自己的部分輸出 - GPU-0 計算 \\[Z_1 = \text{Attention} W_{O_1}\\] - GPU-1 計算 \\[Z_2 = \text{Attention} W_{O_2}\\] 4. **合併結果** 每張 GPU 只計算了一部分 \\(Z\\),需要一個 All-Reduce 操作來合併 \\(Z_1\\) 和 \\(Z_2\\) - 讓 所有 GPU 共享 \\(Z_1\\) 和 \\(Z_2\\) - 每張 GPU 都可以獲得完整 \\(Z\\) ## Input embedding層 1. **並行化輸入嵌入** 將輸入的 Token(詞彙索引)轉換為向量表示 - 透過嵌入矩陣(Embedding Matrix),嵌入矩陣的形狀為: \\[W_E\in\mathbb{R}^{V \times H}\\] - **\\( O \in \mathbb{R}^{V \times B S} \\)** 是 one-hot 編碼的輸入 Token - 進行行方向分割 \\[ W_E = \begin{bmatrix} W_{E1} \\ W _{E2} \end{bmatrix}, \quad W_{Ei} \in \mathbb{R}^{\frac{V}{2} \times H} \\] - GPU 0 負責查找詞表範圍 \\(0\sim\frac{V}{2}\\) - GPU 1 負責查找詞表範圍 \\(\frac{V}{2}\sim V\\) - 嵌入主要是查找操作,可視為一個 矩陣乘法 \\[X = W_E^T \cdot O\\] - 在每張 GPU 上計算局部嵌入 \\[ X_i = W_{E_i}^T \cdot O_i, \quad X_i \in \mathbb{R}^{H \times B S} \\] - 因為每張 GPU 只負責一部份向量查找,所以需要透過 All Reduce 聚合結果 \\[X = \sum_{i=1}^{N} X_i, \quad X \in \mathbb{R}^{H \times B S}\\] ## Output embedding層 ### 一般並行做法 一般輸出輸出嵌入層(Output Embedding Layer) 的維度通常是 Hidden Size(\\(H\\)) × Vocabulary Size(\\(V\\)) \\[\text{Output Embedding}\in\mathbb{R}^{H \times V}\\] 在 Megatron-LM 中,對其進行並行化處理,再者,因為 Transformer語言模型中輸入嵌入與輸出嵌入共用權重,因此都會修改計算方式 - 原本Output embedding層的形狀(與Input embedding層共享權重) \\[W_O \in \mathbb{R}^{H \times V}\\] - 輸出層的矩陣運算為: \\[Y=XW_O\\] 其中: - **\\( X \in \mathbb{R}^{B \times S \times H} \\)**(Transformer 最後一層的 hidden state) - **\\( W_O \in \mathbb{R}^{H \times V} \\)** - **\\( Y \in \mathbb{R}^{B \times S \times V} \\)**(最終 logits,對應詞彙表中的每個詞的預測分數) - 將Output embedding層進行列方向切分 - 將 \\(W_O\\) 沿著詞表維度 \\(V\\)進行列方向切分 \\[W_O=[W_{O_1}, W_{O_2}], \quad W_{O_i}\in \mathbb{R}^{H \times V /2}\\] - 每張 GPU 只對自己負責的詞表進行 logits 計算 \\[ Y_i = X W_{O_i}, \quad Y_i \in \mathbb{R}^{B \times S \times V/2} \\] - **每張 GPU 的輸出大小 \\( (B, S, V/2) \\)**,比完整的 \\( (B, S, V) \\) 小 \\( 2 \\) 倍 - 最終的 logits \\(Y_i\\) 要完整的 \\(V\\) 才可以計算交叉熵損失,因此要 **All Gather** 來合併計算結果 \\[Y = \text{All-Gather}([Y_1, Y_2])\\] ### 進一步優化(減少通信開銷) - Megatron-LM : 交叉熵融合(Loss Fusion) **避免 All-Gather,讓每張 GPU 直接計算部分 logits 的交叉熵,只通信標量損失值,而不是完整 logits** 交叉熵計算公式: \\[L = -\sum_{j=1}^{V} t_j \log p_j\\] - **\\( t_j \\) 是目標 Token 的 one-hot 編碼**,如果正確的詞是 \\( j^* \\),則 \\( t_{j^*} = 1 \\),其餘為 0 - **\\( p_j \\) 是 softmax 後的機率分布** \\[ p_j = \frac{e^{Y_j}}{\sum_{k=1}^{V} e^{Y_k}} \\] #### 直接在每張 GPU 上計算部分 logits 的交叉熵 - 每張 GPU 只存 \\( W_O \\) 的一部分,並計算對應的 logits \\( Y_i \\) \\[Y_1=XW_{O_1}, \quad Y_2=XW_{O_2}, \quad Y_1, Y_2 \in \mathbb{R}^{B \times S \times V/2}\\] 1. 每張 GPU 先找出他自己負責部分的最大值 \\(Y_{max, i}\\) \\[Y_{\max, i} = \max Y_i, \quad Y_{\max, i} \in \mathbb{R}^{B \times S}\\] 2. All-Reduce 操作獲得全局最大值 \\[Y_{\max} = \text{All-Reduce}(\max(Y_1, Y_2))\\] 3. 計算局部 softmax 分子(穩定數值修正,避免數值溢出) \\[\tilde{Y}_i = Y_i - Y_{\max}\\] - 這邊還是 partial logits 4. 計算 softmax 分母 \\[Z_i = \sum_{j=1}^{V/2} e^{\tilde{Y}_{i,j}}\\] 5. All-Reduce,獲得完整的歸一化因子 \\[Z = \text{All-Reduce}(Z_1 + Z_2)\\] 6. 計算部分 softmax 機率 \\[p_i = \frac{e^{\tilde{Y}_i}}{Z}\\] 7. 計算交叉熵損失 \\[L_i = - \sum_{j=1}^{V/2} t_j \log p_j\\] 8. All-Reduce,獲得總損失 \\[L = \text{All-Reduce}(L_1 + L_2)\\] #### 總結 | **步驟** | **計算內容** | **All-Reduce?** | | --------------------- | ----------------------------------------- |:---------------- | | **計算局部 logits** | \\( Y_i = X W_{O_i} \\) | 否 | | **計算最大值** | \\( Y_{\max,i} = \max(Y_i) \\) | 是 | | **計算 softmax 分母** | \\( Z_i = \sum \exp(\tilde{Y}_i) \\) | 是 | | **計算 softmax 機率** | \\( P_i = \frac{\exp(\tilde{Y}_i)}{Z} \\) | 否 | | **計算局部交叉熵** | \\( L_i = -\log P(y^*) \\) | 否 | | **最終合併損失** | \\( L = \sum L_i \\) | 是 |