十天前,我有一個基準測試:根據測量到的共激活(co-activation)重新排列 MoE 模型檔案中的專家權重,使得每個 token 的磁碟讀取次數下降了 2.23 倍。這項結果真實、可重現,而且完全是在我自己的機器上測量出來的——也就是說,它目前幾乎還沒有任何價值。

今天,與這項工作連結最強勁的數字,並非我自己測出來的:在 48 GB MacBook 上執行 235B 參數模型時,解碼吞吐量提升 +32.3%,首 token 時間減少 −26.3%,這是由一位我從未謀面的人,使用我從未運作過的推論引擎,完整套用我的腳本,並以交換 arms 作為頁面快取控制,測量出來的。在這個過程中,我原本的三個論點有兩個被數據推翻,我預先登錄的其中一個擴展預測未能達到門檻,而我對關鍵參數的兩次預測也分別朝相反方向失誤。

那些推翻,恰恰是這件事最有收穫的部分。這篇文章講述的是讓這些推翻變得「廉價」的過程:五個引擎、半打陌生人、一條 issue 討論串,以及其中逐漸成形的紀律——因為我認為這個過程具有普適性,而我平常看到的文章卻很少提及。

設定

這個想法(專案:mbolt)是將 BOLT/PGO 應用在模型二進位檔上。檢查點的張量順序是訓練流程的副產品;推論時的 MoE 路由遠非隨機——專家會以團體形式被激活。如果引擎從 SSD 串流專家權重,檔案佈局就決定了一個 token 的 miss 是少數幾次長的循序讀取,還是數千次的零散讀取。因此:追蹤路由、將共激活分群、重寫檔案,並保持權重位元完全一致。

獨自開發一週後,我在 80B 模型上測得 2.23× 讀取減少、對抗 patched llama.cpp 得到 1.55× 端到端解碼增益、正確性閘道(權重位元完全相同、路由映射 100.000% 通過置換、輸出差異低於同一引擎 CPU↔Metal 後端的 5 倍),以及一場 500 提示的盲測 A/B 結果為 48 比 46、26 場平手——對佈局變更來說,這正是目標。

我還測得一個自己發現的 null:在原版 llama.cpp 上,這一切都無關緊要。mmap page-fault 路徑對佈局無感;我測得等同於原版,也如實報告。增益需要一個會發出明確讀取的引擎。

這個 null 決定了發佈策略,雖然我當時並沒有把它想成策略。會發出明確讀取的專家串流器,其維護者屈指可數。我沒有發 Show HN,而是把數據直接丟到他們的 issue 追蹤器:ml-explore/mlx-lm#1438,@mabaeyens 正在為 MLX 開發專家卸載串流器,以及 JustVugg/colibri#119,一個從 NVMe 串流 744B 模型的 C 引擎。開場白是:分享數據是因為你的引擎是佈局 pass 的自然歸宿——而且我的發現之一指出,這個受眾正是你。

初次接觸:兩項論點在一則回覆中死去

mabaeyens 沒有對我的框架提出異議。他直接測量了它。

他的回覆把我的三個論點——把最熱門的專家固定在 RAM、根據共激活預取下一層的專家、以及重新排序檔案——前兩項用他引擎的計數器擊殺。把常駐集的熱門專家固定,輸給普通的每層 LRU,即使在相同主題、相同 session 的流量下也是如此:LRU 已經捕捉了重用,而每個被凍結的 slot 都是工作負載漂移無法使用的 slot。跨層預取則沒有可運行的訊號:相鄰層之間的專家重疊測得 0.017,對照隨機打亂的 0.016。獨立的每層路由器加上負載平衡損失,使各層去相關化。沒有東西可以預測。

我當天就在討論串中撤回這兩項。

我想強調的是整件事的關鍵:討論串把這些推翻視為範圍界定,而不是失敗。主張沒有死亡,只是搬移了。如果保留率讓解碼增益消失,佈局槓桿就會移到仍有讀取發生的地方——冷預填充,以及(正如他接下來的測量所示)一旦專家表遠大於 RAM,miss 會永遠保持冷時的解碼。他的碎片化數據讓論點比我原本的說法更尖銳:他的讀取器每個解碼 token 發出約 471 次檔案開啟、每個冷專家有九個位元組範圍。槓桿並未死亡,只是被貼錯標籤。

討論串凝聚出的規則

沒有人宣佈方法論。它是一次一次燒傷後逐漸累積出來的,最後看起來像這樣:

在執行實驗前先鎖定實驗。 當我們設計決定性測試——單純合併(無需分析檔、確定性)對比合併加上共激活重排(需要追蹤)——時,arms、指標與決策規則都在任何人執行前就已在討論串中達成共識,因為答案會決定他是否要打破引擎「直接從 shards 讀取」的限制。

預先登錄數字,並設定會傷到自己的門檻。 我公開預測:在更深的卸載下,合併增益可達約 +20%,而「如果低於 +15%,誠實的文章就該說這個槓桿頂多是『好用但非必要』」。結果是 +9.7%。我完全認輸——沒有訴諸設定差異,因為預先登錄已經放棄了那些藉口。

以提交的速度認輸。 我的分析模型高估了一個 36 µs 的 open() 成本,而他的讀取器早已消除這個成本——他分享 fd 的修正,大約在我引用該數字的四分鐘後就送出了。當他的測量結果低於我的模型時,模型公開落敗,就在同一天。記分備註:我在討論串中對冷讀取比例的預測 0-for-2(兩次都朝相反方向失誤),社群的規則因此硬化成每個引擎都要自己測量,永遠不要跨引擎套用

要有對照組,否則不算發生。 討論串的 Canary 是我最喜歡的產物。一個「2.23× 加速」其實是 macOS 拒絕逐出常駐頁面(F_NOCACHE 並非我們三人原先假設的那樣;蛛絲馬跡是一個控制組出現不可能的 0.18×)。一個預取 A/B 「確認」了預測組,直到作者發現兩個 cell 跑的是同一段程式碼——位元完全相同的命中率就是線索——重新正確執行後,預測組輸了 2.9%,讀取了 6.9 個位元組才省下 1 個位元組。一個跨架構的重疊主張,對照分析均勻基線顯示 1.17× 訊號,但對照隨機打亂控制組只有 0.98×——分析基線製造了訊號。交換 arms 來控制頁面快取狀態,成了討論串中每一個 A/B 的標準做法。

指名基線,否則數字無法轉移。 同一次重寫,對每個專家發出九次零散讀取的讀取器是 +32%,對深度卸載時每個投影發出三次讀取的是 +31%,而對已經把每個專家連續存放的容器則接近零。不指明基線讀取模式就引用佈局增益,是數字無法重現的原因。

維護一份活的文物。 進行到一半時,我把整合摘要發成一則評論,並持續版本控管——目前從 v1.0 到 v1.5.1——透過 API 在原地編輯,附有變更紀錄,並明確允許參與者畫紅線。人們真的在使用它。其中一位參與者的紅線純粹是歸屬問題(引擎名稱——我接下來兩天都拼錯;已在 v1.4.1 修正,而且是的,這也寫在變更紀錄裡)。摘要,而不是單一結果,才是討論串產出的東西:範圍界定的發現、測量到的點,以及陷阱區段。

成為收穫的證偽

+9.7% 的失敗,是這個專案最棒的事。

背後的機制——合併是 IOPS 槓桿,而 4 MB 的 slice 已經受頻寬限制——被歸納成討論串現在稱之為「slice-size 定律」:gain ≈ 1 + saved_ops · t_op / (slice_bytes / BW),在此硬體等級下 t_op ≈ 100 µs,BW ≈ 6 GB/s。端到端來看,它與冷讀取比例 s 組合,得到 speedup = 1/((1−s) + s/g)。這是工程上的近似,而非物理定律——但它現在有來自五個引擎的六個測量點,而且它做了一件好模型該做的事:事先告訴你,你的設定是否值得花力氣。

定律的檢驗來自兩位後續加入的參與者。@lBroth 把我的轉換腳本(未經修改)套用到 119 GB 的 Qwen3-235B 建置上,並把輸出接到他自己的引擎:+32.3% 解碼——不是因為每次讀取的增益很大(其實不大;大 slice,g ≈ 1.34),而是因為在 2.6× 超 DRAM 的情況下,冷讀取比例約 0.96,定律的另一項主導了結果。接著他在引擎內建置了更深的變體,測得 +31.2%,而定律對照他指名的基線預測值是 +31.3%。@pierre427——執行生產環境的 GLM-5.2 分頁器——預先登錄我們推導出的每次讀取增益區間(1.2,區間 1.15–1.3),並測得 1.195。這是定律第一個「先登錄、後測量」的點。

另一端也被定價了,這同樣重要:@philipjohnbasile 測量他的 744B 引擎容器——已經把每個專家存成一次連續的約 19 MB 讀取——得到殘餘 +1.3%,低於他自己的推廣門檻。他發文表示不採用,我同意,而我之前提出的約 +20% GLM 等級數字也因此退役。合併階梯現在已經完整定價:九次零散讀取 → 視比例而定 +14–32%;每個投影 → 深度卸載時 +31%;每個專家連續存放的容器 → 接近零,達到裝置頻寬上限。一個已測量並公開其不適用領域的最佳化,比只有成功案例的更有價值。

Null 也是產品

截至今日,討論串的墳場有:預測性預取(三個獨立的 null——結構性的、正確控制的 A/B、跨架構隨機打亂測試)、熱門固定對 LRU、更聰明的 LRU 逐出,以及我原本的跨層框架(已撤回)。每個 null 都有範圍界定——batch-1、解碼、這些架構——而每一個都是某人現在不必再開發的 roadmap 項目。

接著結局自然寫成。討論串後期,PhilipJohnBasile 開源了他的引擎——而他的 README 竟然包含了幾乎整個設計理論的獨立複製,在我們還沒交談過之前,就已經在第五個引擎上測量過:router-lookahead 預取以 −6.96% 撤回、更聰明的逐出以 −26% 死亡且命中率零變化、靜態固定死亡、熱集訓練死亡。他的總結是:「快取策略的前線已關閉:容量 vs 重用距離就是那道牆。」我們從四個不同的程式碼庫收斂到同一道牆。

(那次開源也解決了討論串中一個小而有趣的引用謎團:大家一直歸因於我們測量的吞吐量數字,其實是他的引擎所 fork 的母專案第 6141 行的義大利語註解——在七月初於 Linux 上測量,而他的 fork 複製時遺漏了平台限定詞。我們兩人都「驗證」了錯誤的出處,因為 GitHub 的程式碼搜尋會默默不索引那麼大的檔案。這也寫在陷阱區段裡。)

我的收穫

  • 把主張拿給有機器能擊垮它的人。 四位引擎作者在幾天內就證偽、界定範圍,或擴展了每一項主要主張。一般大眾做不到這件事。小眾討論串不是發佈的謙卑替代方案,而是更高頻寬的管道。
  • 預先登錄讓「錯」變得便宜。 我事先承諾的每一個數字,要嘛存活下來(現在帶有分量),要嘛乾淨地死亡(並產生一條定律)。我兩次猶豫的預測,正是需要後續多輪才能解開的。
  • 公開認錯就是貨幣。 討論串的作者在結尾說得比我好:「我從錯的地方學到的,比對的地方還多。好串。」
  • 保留一份可版本控管、可畫紅線的摘要。 它勝過捲動紀錄和任何論文:它即時、可編輯,而且每位參與者都有權修正。
  • 把你的 null 和勝利一起發佈。 一開始就貼出的原版 llama.cpp 等同結果,正是正確的人參與的原因。

討論串仍然開著。共激活感知排序現在是 mlx-lm PR 上的命名後續項目;colibri 社群正在用這項工作的共激活格式建立專家地圖;有一個已共同簽名、預先宣告的跨引擎實驗正在等待兩份路由追蹤。整合摘要在這裡(討論串接近尾聲處置頂),可重現的管線已公開,repo 在 github.com/doramirdor/mbolt

如果你有一個你相信的基準測試,找到那五位引擎能證偽它的人,並貼到他們出沒的地方。最壞的情況是你失去一項主張。最好的情況是你失去兩項,獲得一條定律。


致謝(依測量順序):@mabaeyens(mlx-lm 專家卸載,以及大部分的證偽)、@lBroth(規模驗證、引擎內建置)、@philipjohnbasile(744B 端點、iliria)、@pierre427(生產分頁器、已登錄區間的點),以及 colibri 團隊——@JustVugg、@ZacharyZcR、@bokiko、@mohamedmastouri2000-boop——他們正把共激活工作帶到我們誰都沒預料到的地方。

更多資訊與其他專案請至 https://getnadir.com