他想訓練一個比現有模型更聰明的語言模型,老師建議改用「混合專家」架構,因為聽說參數多、卻不用每次全部計算
Ai2 這次釋出的 Olmo-core 3,是一套用來訓練大型語言模型的開源框架,也就是一套 訓練框架把『怎麼把一個超大模型切開、分給很多張顯示卡一起學習』這件事寫好的工具箱。這一版的重點,是重新設計了訓練 混合專家模型簡稱 MoE,模型裡有很多個各有專長的小腦袋,每次只叫出其中幾個來工作 的系統。
MoE 的好處是:模型可以放很多 參數模型學到的知識所存放的數字,越多通常代表能記住越多東西,但每處理一個 token語言模型一次處理的最小文字單位,大約是一個字或半個單字,只會用到其中一小部分。素材裡的實測是:把專家從 8 個增加到 128 個,每個 token 仍然只挑 4 個專家,平均啟用的參數大約固定在 3.2B,總參數卻從 4.6B 長到 47B,訓練速度(throughput)只掉不到 5%。同一套基礎架構也已經測過超過一兆(1T)總參數。
但 MoE 有隱藏成本:整個模型仍然要放在 GPU 記憶體顯示卡上的專用記憶體,模型、資料都得先放進去才能算 裡,而且要把每個 token 送到正確的專家那裡,在叢集中會產生大量通訊與協調成本。專家越多,這些成本越可能吃掉「只用一部分」所省下的運算。Olmo-core 3 就是為了縮小這個落差而設計的。
具體做法是換掉舊的資料切分方式:舊版用 FSDP完全分片資料平行,把模型權重切碎存在各張卡上,要用時再臨時湊起來,用完再拆開,而且是每一小批訓練資料就湊一次、拆一次權重;新版改成以 DDP分散式資料平行,每張卡各自拿一份模型、各吃不同資料,彼此同步結果 為基礎的系統,讓專家常駐在 GPU 上,改成把『資料』送去找專家,避免反覆搬權重。在 8 張 NVIDIA B300 的初步測試中,47B 的 MoE 每張卡每秒處理 52,000 個 token,舊實作是 19,400 個,約 2.7 倍。
| 系列 | 架構 | 備註 |
|---|---|---|
| OlmoE | MoE,64 個路由專家 | Ai2 稀疏模型的起點 |
| Olmo 3 | 密集(dense)架構,幾乎整個模型每個 token 都啟用 | 訓練系統是圍繞密集設計打造的 |
| Olmo-core 3 | 為更大的 MoE 重新設計:DDP 為基礎、專家常駐 GPU | 已測到超過 1T 總參數;47B MoE 比舊 FSDP 版約快 2.7 倍 |
素材給了兩組總參數量(8 專家=4.6B、128 專家=47B)與每個 token 啟用約 3.2B。這段程式用這兩個數字反推『每位專家多大、共用部分多大』,再驗證啟用參數是否真的約 3.2B。注意:這是我們依素材數字做的簡化估算(假設總參數=共用部分+專家數×單一專家大小),不是 Ai2 公布的內部結構。
TOTAL_8, TOTAL_128 = 4.6, 47.0TOP_K = 4expert_size = (TOTAL_128 - TOTAL_8) / (128 - 8)shared = TOTAL_8 - 8 * expert_sizeactive = shared + TOP_K * expert_sizedef route(num_tokens, num_experts, top_k, seed=0): for e in rng.sample(range(num_experts), top_k): load[e] += 1這是一個真實小專案:下載(或複製)檔案,照步驟在你電腦上跑起來。
import random
# 素材公布的兩組總參數量(單位:十億 B)與每個 token 挑選的專家數
TOTAL_8, TOTAL_128 = 4.6, 47.0
TOP_K = 4
# 假設 總參數 = 共用部分 + 專家數 x 單一專家大小,用兩組數字反推
expert_size = (TOTAL_128 - TOTAL_8) / (128 - 8)
shared = TOTAL_8 - 8 * expert_size
print('單一專家約 %.3fB,共用部分約 %.2fB' % (expert_size, shared))
for n in (8, 32, 128):
total = shared + n * expert_size
active = shared + TOP_K * expert_size
print('專家數 %3d | 總參數 %5.1fB | 每個 token 啟用 %.2fB' % (n, total, active))
def route(num_tokens, num_experts, top_k, seed=0):
# 教學簡化:隨機挑專家。真實路由器是學出來的,不會是隨機
rng = random.Random(seed)
load = [0] * num_experts
for _ in range(num_tokens):
for e in rng.sample(range(num_experts), top_k):
load[e] += 1
return load
print()
for n in (8, 128):
load = route(10000, n, TOP_K)
avg = sum(load) / n
print('專家數 %3d | 平均每位專家 %.0f 個 token | 最忙 %d | 最閒 %d' % (n, avg, max(load), min(load)))
# 素材中的實測吞吐量(8 張 B300、47B MoE)
print()
print('新版/舊版吞吐量倍數:%.2f' % (52000 / 19400))