返回文章1. 兆位元組問題2. 為何 bf16 強化學習權重幾乎總是稀疏的3. HF Buckets 與架構3.1 什麼是 Bucket?3.2 三個核心元件4. 協定4.1 Safetensors 作為傳輸格式4.2 訓練器端:來自優化器掛鉤的布林遮罩4.3 vLLM 端:一個 30 行的擴充功能5. 在 Spaces 上實際部署6. 這項技術究竟帶來了什麼?
7. 未來展望8. 立即體驗TL;DR(太長不看),因為您有模型要訓練,我們理解:非同步強化學習(Async RL)有一個不為人知的問題:在每個步驟中,訓練器(trainer)都必須將整個模型傳輸到推論引擎(inference engine)。
對於一個 7B 參數的 bf16 模型來說,這意味著 14 GB 的資料量。而對於一個前沿的 1 兆參數模型檢查點,每次步驟的資料量可達兆位元組(terabyte)等級。然而,事實證明您不必如此。在兩個連續的 RL 優化器步驟之間,大約 99% 的 bf16 權重在位元層面是相同的(在最壞情況下也從未低於 98%)。
實際的差異(delta)非常微小。我們在 TRL 中實現了一個 PR,它將僅變更的元素編碼為稀疏的 safetensors 檔案,上傳到 Hugging Face Bucket,並通知 vLLM 進行擷取。在 Qwen3-0.6B 模型上,每個步驟的傳輸負載從 1.2 GB 降至 20 到 35 MB。
更棒的是:我們進行了一次完全分散式(disaggregated)的訓練,其中訓練器位於一個機器上,vLLM 運行在一個 Hugging Face Space 中,Wordle 環境則位於另一個 Space,而權重則透過單一的 Hub bucket 流動。
無需共享叢集、無需 RDMA、也無需 VPN。非同步 RL 變得更加經濟實惠。請繼續閱讀。兩種傳輸相同權重的方式。紅色部分表示沒有生成 token 的實際時間。1. 兆位元組問題如果您閱讀過我們之前關於非同步 RL 訓練現況的文章,您已經知道重點了。
每個非同步 RL 函式庫,無論其如何拼寫「actor model」或其 NCCL 後端是何種顏色,最終都會遇到相同的根本問題:權重同步。推論引擎執行步驟 N 的策略。訓練器剛完成步驟 N+1。新的權重必須在推論引擎開始嚴重偏離策略之前,從一端傳輸到另一端。
無論您運行同步或非同步,這都位於關鍵路徑上:阻塞式傳輸會浪費 GPU 的閒置運算時間,使其無法生成 token。透過稀疏增量(sparse delta)路徑,您可以將該閒置時間縮短至數秒,並且訓練器甚至不必等待推論引擎準備就緒:它只需在優化器步驟完成時發布「權重已就緒」並將權重上傳到共享 bucket,而推論引擎則在自己的時間擷取。
Fireworks 在其文章《Frontier RL Is Cheaper Than You Think》中提出了一個令人難忘的數字:對於一個 fp8 格式(他們的設定)的前沿 1 兆參數檢查點,完整快照為 1024 GiB,而傳統觀念認為每次更新策略部署群時都必須傳輸這麼多。
正是這種數字讓大家開始繪製包含巨型叢集、RDMA 網路和專用跨區域連結的圖表。他們測量到相鄰檢查點之間的平均增量為 20.3 GiB,僅佔完整模型的 1.98%,並且「連續檢查點之間,超過 98% 的 bf16 格式權重保持位元等效」。Cursor 的 Composer 2 報告也講述了類似的故事。
他們在不同區域運行訓練和推論,並透過一個共享的 S3 bucket(他們的原文)將它們連接起來,訓練器在每個訓練步驟中將壓縮的權重差異上傳到該 bucket。每個叢集獨立地從共享的增量鏈下載並重建,這「不需要與訓練叢集直接連接」。雙方從不直接就參數進行溝通。
bucket 就是傳輸媒介。這兩篇論文都同意三件事,我們想慢慢重複它們,因為這篇文章的其餘部分本質上就是一個忠實的開源翻譯:在兩個相鄰的 RL 步驟之間,大部分權重實際上並未改變。如果您只傳輸改變的部分,您的頻寬費用將會大幅減少約兩個數量級。
如果您透過共享物件儲存來傳輸這些微小的差異,您就不再需要訓練器和推論叢集位於同一個資料中心。唯一缺少的是一個您可以透過 pip 安裝的版本。所以我們寫了一個。2. 為何 bf16 強化學習權重幾乎總是稀疏的在我們進行任何連接之前,值得了解為何這場遊戲甚至可以獲勝。
「98% 的權重沒有改變」的說法聽起來很可疑,像是那種在演示中有效,但在實際應用中卻會崩潰的數字。但事實並非如此。這源於 bf16 算術在 RL 所使用的學習率下是如何運作的。一個 bf16 數字有 7 個尾數位元(mantissa bits)。
在兩個連續的 2 的冪之間,精確地有 2^7 = 128 個可表示的值,因此在 |w| 附近的相鄰 bf16 數字之間的間距大約是 |w| * 2^-7。當更新量小於該間距的一半時,即當 |Δw| < |w|/256 時,更新會被 bf16 轉換所吸收。
這就是 PULSE 在其圖 3 中繪製的「bf16 可見性閾值」。現在看看 Adam 優化器做了什麼。在 RL 的學習率(例如 3×10^-6)下,單一權重的更新量為:Δw = -η * m̂ / (√v̂ + ε)標準化步驟 m̂/(√v̂+ε) 大約是數量級為一,因此 |Δw| ≈ η ≈ 3×10^-6。
對於大多數權重,|w| 大約在 10^-2 到 10^-1 之間(PULSE 報告代表性 LLM 權重的中位數為 0.019)。在該數量級下的閾值 |w|/256 大約是 4×10^-5 到 4×10^-4,這比更新量還要大。換句話說:優化器在低語,而 bf16 聽不見。
更新被四捨五入所吸收,w 的位元組表示沒有改變,從推論引擎的角度來看,這個權重沒有移動。將其乘以數億個參數,您就可以免費獲得超過 99% 的稀疏度,且無需任何近似。這正是 PULSE 論文(Mihai & Belilovsky, 2026)中正式提出的論點。
他們定義了兩個閾值。吸收界限 10η 是 Adam 更新的保守最壞情況,而有效界限 η 則是實際運作的範圍。bf16 可見性閾值是 |w|/256。每當更新量低於可見性閾值時,它就會被吸收,bf16 位元組不會改變。他們的圖 3 繪製了這兩個界限與代表性 LLM 權重雲的關係,結論是明確的:在 η=3×10^-6 時,吸收界限本身就已經低於模型中幾乎所有權重的可見性閾值。
他們在 Qwen2.5 (0.5B/1.5B/7B)、Llama-3.2-3B 和 Gemma-3-4B 上進行了實證測量,並持續發現平均每個步驟的稀疏度約為 99%,在 400 個訓練步驟中標準差為 0.2% 到 0.4%。最壞情況的步驟也保持在 98% 以上。
因此,<1% 的變更並非僥倖測量;這是算術所保證的。我們不必分析性地預測這一點(事實上,我們曾嘗試從 Adam 的 m 和 v 統計數據中預測變更遮罩,但召回率僅為可悲的 30%,稍後會詳細說明)。我們只需要觀察哪些位元組翻轉了。這是一個每個參數微小的布林張量,在優化器步驟前後計算。
將學習率降低到 RL 範圍,並觀察「轉換回 bf16」的標記如何回到原始刻度。左下角的 256 元素網格是微型模型上的總體效果。3. HF Buckets 與架構這就是故事的第二部分,也是這篇文章不再是 Fireworks/Cursor 的翻譯,而開始成為 Hugging Face 自己的東西的地方。
3.1 什麼是 Bucket?Bucket 是 Hub 上的一種儲存庫類型,專為高頻率物件儲存而設計。沒有提交儀式、沒有 PR 工作流程、也沒有 LFS 的怪癖。您可以新增檔案、列出檔案、下載檔案。Python 介面只有兩個函式:from huggingface_hub import batch_bucket_files, download_bucket_files# Trainer sidebatch_bucket_files("my-org/wordle-deltas", add=[(buffer, "deltas/step_000042.safetensors")])# Inference sidedownload_bucket_files("my-org/wordle-deltas", files=[("deltas/step_000042.safetensors", local_path)])就這樣。
兩個函式呼叫,您的權重就開始傳輸了。在底層,buckets 由 Xet 提供支援,Xet 是 Hub 的內容定義分塊(content-defined chunking)儲存層。Xet 會檢查您上傳的每個檔案,根據其實際內容(而非固定偏移量)將其切片成塊,並與 bucket 中已有的所有內容進行去重複。
實際結果(在此情境下令人欣喜)是,即使我們懶得編寫稀疏編碼,而只是在每個步驟上傳完整的錨點(anchors),Xet 仍然只會傳輸已更改的塊。稀疏編碼 + Xet 堆疊:我們為移動的內容付費,而且只付費一次。這相當於 Fireworks 和 Cursor 都採用的「共享 S3 bucket」的開源版本,只不過儲存層已經了解內容雜湊(content hashing),您現有的 HF token 已經擁有權限,並且它能與堆疊的其餘部分(Spaces、datasets、models)原生整合。
3.2 三個核心元件完整的架構精確地包含三個核心元件和一個共享基礎:訓練器(Trainer)。您想在哪裡都可以。一個 GPU、八個 GPU、一台連接 USB H100 的筆記型電腦,我們都不會評判。它擁有模型權重,運行優化器,並發出稀疏增量。
HF Bucket。一個單一的儲存庫,兩個前綴:anchors/ 用於偶爾的完整快照,deltas/ 用於其間的稀疏補丁。這是雙方唯一達成共識的部分。vLLM 策略部署伺服器(rollout server)。您想在哪裡都可以,而且關鍵是它不一定需要與訓練器位於同一位置。
它從 bucket 中拉取資料,應用增量,並提供策略部署服務。環境(Environment)。以通常的方式(HTTP、函式呼叫,無論您的環境支援什麼)連接到策略部署伺服器。需要內化的特性,也是 Cursor 論文極力推銷且在此完全適用的特性是:訓練器和策略部署伺服器從不直接就權重進行溝通。
它們只交換一個包含 {"repo_id": ..., "filename": ...} 的微小 POST 請求,這就是整個控制平面。實際的位元組傳輸發生在每一方與 bucket 之間,並行進行,沒有共享的網路架構。這在實務上為何重要:策略部署伺服器可以位於另一個區域、另一個雲端,或在 Hugging Face Space 內部透過 NAT 運行。
這都沒關係。N 個推論副本可以從同一個 bucket 拉取相同的增量,而 Xet 會對所有這些副本的位元組進行去重複。訓練器永遠不必知道有多少推論副本存在、它們在哪裡,或者其中一個是否剛崩潰。訓練器寫入。副本讀取。Hub 負責連接。4. 協定現在我們可以揭開其內部運作。
該協定包含四個部分:傳輸格式、bucket 配置、一個 30 行的 vLLM 擴充功能,以及一個訓練器端的變更偵測器。老實說,程式碼比聽起來要少。4.1 Safetensors 作為傳輸格式我們選擇 safetensors 作為磁碟上和傳輸中的格式。
它已經是 Hub 上規範的檢查點格式,每個合理的框架都可以讀取它,並且標頭攜帶任意字串後設資料(metadata)。這個後設資料欄位就是我們隱藏協定的地方。bucket 中有兩種檔案。錨點(Anchors)看起來像一個正常的檢查點:每個參數一個張量,完整的 bf16 權重,每 N 次同步寫入一次(我們預設 N=10)。
anchors/step_000010.safetensors├,model.layers.0.self_attn.q_proj.weight (bf16, full)├,model.layers.0.self_attn.k_proj.weight (bf16, full)└...metadata:sparse=False, model_version=10, sparsity=0.0增量(Deltas)是其中有趣的部分。
對於每個實際改變的參數,我們儲存兩個條目:一個扁平的 int32 張量,包含元素索引;以及一個 bf16 張量,包含這些索引處的值。deltas/step_000011.safetensors├,model.layers.0.self_attn.q_proj.weight.indices (int32, [num_changed])├,mode



