注意力加 MLP,重複
兩個子層
一個 transformer 區塊包含正好兩個子層,每個子層操作形狀為 [batch, seq_len, d_model] 的 token 序列:
1. 多頭注意力子層。 標記彼此查看。活動 68 詳細涵蓋了這一點。輸出形狀與輸入形狀匹配。
2. 前饋 MLP 子層。 每個標記獨立通過兩層感知器轉換。無跨標記資訊流。輸出形狀與輸入形狀匹配。
兩個子層都保留 [batch, seq_len, d_model] 形狀。這種保留讓區塊能夠堆疊:層 N 的輸出無需形狀變換即可饋送到層 N+1 的輸入。
每個子層的貢獻
注意力將資訊跨位置移動:位置 17 的標記可以從位置 1 到 16 拉取資訊。MLP 在每個位置內轉換資訊:標記的表示通過學習的非線性函數重塑,但永遠不會看到其鄰居。
堆疊區塊會交替進行這兩個操作。Layer 1 attention 混合位置。Layer 1 MLP 重新塑形每個位置。Layer 2 attention 再次混合,現在是針對重新塑形的表示。這種交替隨著深度增加而提升表達能力。
ANDREA 的堆疊
| Variant | n_layer | n_head | d_model | mlp_dim |
|---|---|---|---|---|
| ANDREA-12M | 6 | 12 | 384 | 1536 |
| ANDREA-120M | 12 | 12 | 768 | 3072 |
| ANDREA-480M | 16 | 24 | 1536 | 6144 |
注意整個系列中 mlp_dim = 4 × d_model。這個比例在幾乎所有現代 transformer 中都成立。第 3 節將詳細解釋原因。
命名子層
為什麼跳躍連接很重要
殘差模式
每個子層將其計算包裝在殘差連接中。輸出會加回輸入:
x = x + Attention(LayerNorm(x)) # attention sublayer
x = x + MLP(LayerNorm(x)) # MLP 子層
在每個子層內,函數 Attention(...) 或 MLP(...) 產生一個 delta。區塊不會取代輸入;它會加入一個學習到的修正。
為什麼這很重要
殘差連接主宰深度架構的三個原因:
1. 梯度流。 在反向傳播期間,殘差連接為梯度從輸出回傳到輸入提供了直接路徑,繞過子層。一個沒有殘差的 12 層堆疊會在到達嵌入層之前很久就丟失梯度訊號;有了殘差,梯度幅度在數百層中保持可用。
2. 身份初始化。 在初始化時,子層權重產生小的輸出。殘差連接意味著第 N 層最初幾乎不變地通過。訓練從一個有效的起點逐步學習增量。
3. 組合學習。 每個區塊學習一個細化,而不是替換。層 1 可能添加位置資訊。層 2 可能添加句法結構。層 7 可能添加語義關係。殘差流累積。
層歸一化
在每個子層之前,LayerNorm 將每個 token 的表示重新縮放到零均值與單位變異數,然後應用學習到的每個特徵增益與偏差:
y = gamma * (x - mean(x)) / sqrt(var(x) + epsilon) + beta
平均值與變異數是針對 d_model 維度計算的,對每個 token 分別獨立計算。兩個學習向量(gamma、beta,每個形狀為 [d_model])恢復表現力尺度。正規化將激活值保持在數值穩定的範圍內;若無正規化,小型訓練不穩定性會像雪球般滾大,導致 NaN 梯度。
Pre-Norm 與 Post-Norm
一個細微但關鍵的選擇
將層歸一化(layer norm)連接到殘差子層(residual sublayer)的兩種方式:
後歸一化(post-norm,原 2017 年論文):
x = LayerNorm(x + Attention(x))
層歸一化位於殘差加法之後。殘差流本身在每一層都會被歸一化。
Pre-norm(現代標準,用於 ANDREA):
x = x + Attention(LayerNorm(x))
層歸一化位於子層之前,在殘差分支內部。殘差流保持未歸一化;只有子層的輸入會被重新縮放。
為什麼 Pre-Norm 勝出
Post-norm 在沒有 LR 預熱及小心超參數調整的情況下訓練效果差。梯度在早期層中爆炸,因為每個層歸一化會打亂殘差流的累積狀態。原始 2017 年論文使用 post-norm 並進行廣泛調整;後續工作(GPT-2、LLaMA、ANDREA)標準化採用 pre-norm。
Pre-norm 訓練穩定。殘差流在所有層中乾淨累積;僅子層輸入進行正規化以確保數值穩定性。現代 transformer 預設使用 pre-norm,& ANDREA 繼承了這一選擇。
最終區塊方程式
結合殘差、pre-norm 位置的層正規化,& 兩個子層,即 ANDREA 的完整區塊:
def block_forward(x):
x = x + Attention(LayerNorm(x)) # attention 子層
x = x + MLP(LayerNorm(x)) # MLP 子層
return x
兩個子層,兩個殘差加法,兩個層歸一化(注意:每個子層有自己的層歸一化;ANDREA-120M 在 12 個區塊中有 24 個層歸一化,加上輸出處的最後一個,總共 25 個)。重複 12 次。這就是 ANDREA-120M 的主幹。
為什麼 Pre-Norm 能穩定訓練
兩個線性層,一個激活函數
三個操作
MLP 子層是一個兩層感知器,層與層之間有一個非線性激活函數:
def mlp_forward(x):
h = x · W_1 + b_1 # 擴展:d_model → mlp_dim
h = GELU(h) # 非線性激活
y = h · W_2 + b_2 # 收縮:mlp_dim → d_model
return y
三個操作。兩個線性,一個非線性。第一個線性擴展寬度;第二個收縮回來。
4× 擴展比率
現代 transformer 設定 mlp_dim = 4 × d_model。ANDREA-120M:
d_model = 768
mlp_dim = 4 × 768 = 3072
W_1 形狀 = [768, 3072] # ~2.36M 參數
W_2 形狀 = [3072, 768] # ~2.36M 參數
每個區塊的 MLP 參數 = 4.72M(忽略偏差)
兩個 MLP 位於每對注意力子層之間(每個區塊一個)。十二個區塊 × 4.72M ≈ 56.6M MLP 參數總計在 ANDREA-120M 中,約佔所有參數的一半。
為什麼是 4×
4× 比率是透過經驗得出的。較小的比率會降低模型容量。較大的比率在每參數花費上產生邊際效益遞減。在數十年的架構搜尋中,4× 一直屹立不搖;它出現在 GPT、BERT、T5、LLaMA 及 ANDREA 中。
近期研究 (PaLM、Chinchilla) 發現,閘控機制 (SwiGLU) 可以使用 8/3× 擴展,以較低的成本達到相當的容量;ANDREA 為了簡單,堅持經典的 GELU + 4×。
GELU:平滑激活函數
GELU 計算什麼
GELU (Gaussian Error Linear Unit) 是現代 Transformer 中 MLP 層之間的標準激活函數。其公式:
GELU(x) = x · Φ(x)
Φ(x) 是標準常態分佈的累積分佈函數:標準高斯隨機變數落在 x 或以下的機率。以數值方式計算:
Φ(x) ≈ 0.5 × (1 + tanh(sqrt(2/π) × (x + 0.044715 × x³)))
按區域的行為
- 對於大的正 x:Φ(x) ≈ 1,因此 GELU(x) ≈ x。類似 ReLU。
- 對於大的負 x:Φ(x) ≈ 0,因此 GELU(x) ≈ 0。類似 ReLU。
- 接近 x = 0:Φ(x) ≈ 0.5,因此 GELU(0) = 0 完全準確。通過原點的平滑過渡。
與 ReLU 不同,GELU 允許某些負輸入通過,由 Φ(x) 加權。一個小的負輸入仍貢獻一個小的負輸出,只是比完整輸入少。
為什麼 GELU 優於 ReLU
經驗上,使用 GELU 訓練的 transformer 在相同參數量下,能達到比使用 ReLU 訓練的 transformer 更低的損失。零點附近的平滑性很重要:ReLU 在零點的硬截斷會產生梯度不連續;GELU 的平滑曲線為反向傳播提供更乾淨的梯度。
ANDREA 的訓練引擎 microgpt_cuda.cu 內建手寫的 GELU CUDA 核心。該核心使用上述 tanh 近似;現代 GPU 將 tanh 作為單指令運算提供。
計算 MLP 參數
十二個區塊組成 ANDREA-120M
從區塊到模型
ANDREA-120M 的完整前向傳遞:
def model_forward(token_ids):
x = token_embed(token_ids) + position_embed(positions)
for block_idx in range(n_layer): # 12 個區塊
x = block_forward(x) # 注意力機制 + MLP 搭配殘差連接
x = LayerNorm(x) # 最終正規化
logits = x · token_embed.T # 輸出投影使用綁定權重
return logits
六行。主要部分位於 block_forward 內,被呼叫十二次。嵌入開始管道;綁定反嵌入(用於輸入查詢的相同矩陣,轉置用於輸出投影)結束它。
深度作為組合
每個區塊讀取殘差流,計算一個 delta,並加回它。經過十二次通過後,流包含每個區塊的累積貢獻。內部,層傾向於專門化:
- 早期層 (1-3):語法模式,位置結構
- 中間層 (4-8):詞語關係,片語邊界
- 晚期層 (9-12):語義內容,事實回憶
這種專門化來自訓練壓力,而非架構選擇。相同的均勻區塊設計在語言訓練時會產生專門化的層。
總區塊參數
| 組件 | 每個區塊 | 橫跨 12 個區塊 |
|---|---|---|
| 注意力投影 (4×W) | 2.36M | 28.3M |
| MLP 權重 (W_1 + W_2) | 4.72M | 56.6M |
| 層歸一化 (gamma, beta) | ~3K (可忽略) | ~37K |
| 每個區塊總計 | ~7.1M | ~85M |
主幹中有 85M 參數。加上 ~13M 的 token 嵌入 (8449 詞彙 × 768 d_model × 2 用於綁定輸入/輸出) 以及最終層歸一化,ANDREA-120M 的參數總數約為 120M。區塊設計佔三分之二;嵌入佔其餘部分。