跳到主要內容
AI 原理與協定

Dense、MoE、Hybrid:Transformer 的 FFN 有三種長法

DeepSeek-V3 寫著 671B 總參數、只啟動 37B;Snowflake Arctic 是 480B 總量、17B active。同樣叫 Transformer,差在哪?答案不在注意力,而在每一層的 FFN——本文拆解 Dense、MoE、與兩種 Hybrid 的結構差異。

整理模型卡資料時,我又看到那組很容易讓人停住的數字:DeepSeek-V3 是「671B 總參數、37B activated」,Snowflake Arctic 則是「480B total、17B active」。

參數不就是參數嗎?為什麼還要分「模型裡有多少」和「這次真的拿來算多少」?

這個落差正是 MoE(Mixture of Experts,混合專家)要處理的事。不過在進入 MoE 前,先抓住一個定位:這些 Transformer 的差異,大多不在 attention,而在每層裡的 FFN(Feed-Forward Network,前饋網路)。attention 負責讓 token 互相交換訊息;FFN 才是模型替各個 token 做非線性轉換、也是參數最集中的地方。

同一塊 FFN,大致有三種擺法:全部一起算的 Dense、每次只叫少數專家上工的 MoE,和把兩者混在一起的 Hybrid。看懂這三種,開頭那兩組數字就不再神祕。


先定位:FFN 在 Transformer 的哪裡

先把位置釘清楚。一個簡化的 Transformer layer,骨架就是兩塊:先過 attention,再過 FFN。

def layer(x):
    x = x + attention(x)   # 注意力:token 之間互相看
    x = x + ffn(x)         # FFN:每個 token 各自做一次非線性變換
    return x

真實實作通常還會有 normalization、dropout,FFN 也未必剛好是兩層;這裡先省略,免得把主題沖淡。重要的是:FFN 對每個 token 各自運算,且在多數 LLM 中佔了很大一部分參數。

因此 Dense、MoE、Hybrid 其實都在回答同一題:這塊 FFN,要讓每個 token 怎麼走?


Dense Transformer:每個 token 都走同一條路

最直覺的做法是,整層只有一個 FFN;不管 token 是程式碼、標點符號還是中文詞彙,全部送進同一組權重。

def dense_layer(x):
    x = x + attention(x)
    x = x + ffn(x)         # 唯一的 FFN,全部 token 共用
    return x

這種設計的優點很務實:沒有路由器、沒有 expert 調度,訓練和部署的心智負擔較低。LLaMA 2 / 3 這類模型走的就是這條路。

代價要到模型放大後才特別刺眼:每個 token 都得動用同一整套 FFN 權重。 70B 的 dense 模型,不能只挑其中幾個 FFN 來算。要增加容量,通常也得一起支付更多計算;參數量和每 token 的計算量綁得很緊。

MoE 想拆開的,就是這個綁定關係。


MoE Transformer:把 FFN 拆成一堆專家,每次只挑幾個

MoE 的想法是:與其只放一個大 FFN,不如放一排較小的 FFN,稱為 expert(專家);每個 token 到來時,只挑其中幾個計算。

def moe_layer(x):
    x = x + attention(x)
    scores = router(x)               # router 替每個 expert 算分數
    idx = top_k(scores, k=2)         # 只取分數最高的兩個
    out = sum(scores[i] * expert[i](x) for i in idx)  # 實作上通常會先正規化權重
    x = x + out                      # 其餘 expert 這一步完全沒動
    return x

關鍵在那個 router(也常稱 gate,門控網路)。它是個可學習的小網路,替每個 token 算出「該交給哪些 expert」,再只啟動分數最高的 top-k 個(常見的 k 是 1 或 2)。沒被選到的 expert,這一步不執行 FFN 計算。

這就是稀疏激活(sparse activation),也是開頭那個「總參數 vs 啟動參數」的來源:

  • 總參數:所有 expert 的權重加起來,決定模型的容量上限
  • 啟動參數:單個 token 實際走過的那 k 個 expert,決定每一步的計算量

兩個帳第一次被拆開了:總參數代表模型可用的容量,active parameters 則更接近單一 token 的實際計算負擔。以 DeepSeek-V3 的技術報告為例,它有 256 個 routed experts、每個 token 選 8 個,另有 1 個所有 token 都會經過的 shared expert;全模型為 671B,官方報告的每 token activated parameters 是 37B。

不同模型的 expert 數和 top-k 不能直接排成一條「越新越大」的演進線,因為每個 expert 的大小、FFN 寬度和其他模組都不同。不過它們確實展示了不同取捨:Switch Transformer 用 top-1 簡化路由;Mixtral 是 8 個較大的 expert、選 top-2;DeepSeek-V3 則用 256 個較細的 routed expert,加上一個常駐的 shared expert。先把常見例子擺在一起:

模型專家數每個 token 啟動總 / 啟動參數
Switch Transformer數十~數千top-1—
Mixtral 8x7B8top-2約 47B / 13B
Grok-18top-2314B / 約 79B
DeepSeek-V3256 routed + 1 sharedtop-8 routed671B / 37B

DeepSpeed-MoE 不是模型,而是工具。 它是用來訓練、推論 MoE 的基礎設施,不是一個可下載的模型權重。把它列進「代表模型」很容易讓人找 checkpoint 找到懷疑人生。

MoE 不是免費的午餐,它把成本搬到別的地方:

router 會偏心。 若沒有約束,router 可能長期把 token 丟給少數 expert,其他 expert 幾乎學不到東西,模型容量等於閒置。 Switch Transformer 在訓練目標加入 auxiliary load-balancing loss;DeepSeek-V3 則採用不靠 auxiliary loss 的負載平衡策略,以隨負載調整的 bias 來引導路由。做法可以不同,但「別讓 expert 閒著或塞車」始終是 MoE 的必修題。

省了計算,通常沒省權重記憶體。 每個 token 只算 k 個 expert,FFN 的 FLOPs 可以下降;但要低延遲服務時,多數或全部 expert 權重仍得能被快速存取。因此它比較像用更多權重儲存與系統複雜度,換取較低的每 token 計算。記憶體不足時,MoE 不會自動變成省錢方案。

分散式下的通訊不便宜。 expert 常分散在不同 GPU;token 被路由到哪,就要把 activations 送去、再收回來。這類 all-to-all 通訊會成為大規模訓練與推論的實際瓶頸。


Dense-MoE Hybrid:不是二選一,是縫在一起

知道兩端之後,中間地帶就好理解了:誰說一個模型只能全 Dense 或全 MoE?Hybrid 就是把兩者混用。但「混」有兩種完全不同的縫法,常被講成同一回事——拆開看才清楚。

縫法一:層與層之間交錯

第一種是某些 layer 用一般 Dense FFN,另一些 layer 用 MoE。常見安排是前幾層保持 Dense,後面才換 MoE。

def model(x):
    for i, layer in enumerate(layers):
        x = x + attention(x)
        if i < n_dense:
            x = x + dense_ffn(x)   # 前幾層:穩定的稠密 FFN
        else:
            x = x + moe_ffn(x)     # 後面層:稀疏的專家路由
    return x

DeepSeek-V3 是這種安排的明確例子:前 3 層保留 Dense FFN,其餘層才換成 MoE。至於「低層學通用特徵、高層才適合分流」是好理解的直覺,不該當成已被普遍證實的定律;至少從架構上看,這個選擇提供了一段不經路由的早期處理路徑。

縫法二:同一層裡並聯

第二種是 Dense 和 MoE 在同一層並聯,再把結果相加。 Snowflake Arctic 是很清楚的例子:它把約 10B 的 Dense Transformer,和一組 residual 的 128×3.66B MoE MLP 並聯,總量 480B、每個 token 啟動 17B(top-2)。

def arctic_layer(x):
    x = x + attention(x)
    x = x + dense_ffn(x) + moe_ffn(x)   # 稠密路徑與稀疏路徑並聯,結果相加
    return x

並聯不只是在堆容量。Arctic 的設計把 MoE 的 all-to-all 通訊和 Dense 路徑的本地計算安排為可重疊的工作,目標是減少通訊暴露在關鍵路徑上的時間。這是我覺得很值得記下來的一點:架構不只決定模型能力,也可以直接對準系統瓶頸。

其實還有第三種:層內的「常駐專家」

順帶一提,DeepSeek-V3 的「256 + 1」中的 shared expert(共享專家),在功能上也帶有 Dense-MoE 混合的味道,只是發生在 expert 這一層。它是所有 token 都會經過的常駐 FFN,不參與路由;另外 256 個 routed expert 才走 top-k 選擇。

可以把它理解為:共通的變換交給常駐路徑,較有條件的變換才交給稀疏路由。這是架構意圖的解讀,不代表每個 shared expert 都必然學到同一類知識;但 DeepSeek-V3 的確同時出現了層間的 Dense/MoE 交錯,以及層內的 shared/routed expert 分工。


三者怎麼選

把上面的取捨收進一張表。注意「推論效率」這欄別誤讀:MoE 是單位啟動參數的計算效率高,不代表它整體更省——它吃的記憶體更大。

DenseMoEHybrid
參數容量 / 每 token 計算高度綁定可部分解耦可部分解耦
FFN 的每 token 計算全部執行只執行 top-k expert取決於混法
權重記憶體壓力隨模型大小增加常較高(需可存取許多 expert)通常介於兩者之外、視設計而定
訓練穩定性高要顧負載平衡看縫法
工程複雜度低高(路由、通訊)最高
代表模型LLaMA 2/3Mixtral、Grok、DeepSeek-V3Arctic、DeepSeek-V3

幾條實務上的判斷線:

想要單純、可控、好部署,先選 Dense。 中小模型,或不想碰路由與 expert parallelism 的場景,Dense 是穩妥的起點。標準的 PyTorch / HuggingFace Transformer 元件就能處理大部分需求。

想用受限的每 token 計算換更大容量,再考慮 MoE。 當權重記憶體與多機通訊可以接受,但 FLOPs 或單位吞吐成本是瓶頸時,MoE 才有施力點。實務上也不建議從零手刻 router 和 expert dispatch;DeepSpeed-MoE、Megatron 等框架已經處理了不少負載平衡與通訊細節。

Hybrid 不是「我全都要」,而是對準特定瓶頸。 Arctic 的層內並聯,目標是讓通訊和計算重疊;DeepSeek-V3 的層間配置則保留了一段 Dense 前段。先說清楚要解的是記憶體、通訊、訓練穩定性還是延遲,再選混法;若瓶頸都還沒量到,Hybrid 多半只是替系統加一層複雜度。


收在模型卡上的一個閱讀習慣

Dense、MoE、Hybrid 聽起來像三種模型,實際上是在回答同一題:每層的 FFN,要不要讓所有 token 走同一條路?

Dense 的回答是全都走,得到的是單純和可預期的系統;MoE 只啟動少數路徑,讓容量和每 token 計算不再完全綁死,但把問題轉成權重記憶體、負載平衡與通訊;Hybrid 則只在能解釋清楚的地方把兩者拼起來。

所以下次看到「671B total、37B active」,我會先把它拆成兩本帳:前者是模型準備了多少容量,後者是單一 token 在這個架構下實際會經過多少參數。接著再追問:那些沒啟動的 expert 放在哪裡?router 怎麼平衡?通訊能不能藏在計算背後?

模型卡上少了一個名詞,工程上的問題通常才剛開始;但先從 FFN 的走法切入,至少不會看錯題目。