單張 4GB 顯卡如何推論 700 億參數模型:AirLLM 的分層卸載技術
GitHub / Hacker News · 2026-08-04
AirLLM 是一個開源 Python 函式庫,讓開發者能在僅有 4GB 顯示記憶體的消費級 GPU 上執行 Llama 3.1 70B、甚至規模更大模型的推論。專案在 2026 年 6 月釋出 v3.0,加入 FP8 支援與 DeepSeek-V3、Qwen3 相容性,是這波 Hacker News 討論的直接起因。核心手法不是把模型壓縮到更小,而是把模型「拆成一層一層來跑」。
核心技術
AirLLM 採用的做法是逐層串流權重(layer-by-layer weight streaming):推論時一次只把一個 transformer layer 的權重載入 GPU,算完立刻換下一層,GPU 上永遠只需要容納單一層的參數,而不是整個模型。這代表顯存需求只跟「單層大小」有關,而非模型總參數量。官方 README 給出的對照數字如下表。支援的模型清單涵蓋 Llama(2/3/3.1/3.3/4)、Qwen(1/2/2.5/3)、DeepSeek(V2/V3/R1)、Mistral、Mixtral、Phi、Gemma、ChatGLM、Baichuan、InternLM 等,透過標準的 AutoModel.from_pretrained() 介面呼叫。
| 模型 | 參數量 | 所需顯存 |
|---|---|---|
| Llama 3.1 405B | 405B | 約 8 GB |
| DeepSeek-V3 | 671B | 約 12 GB |
| Qwen3-235B | 235B | 約 3 GB |
| Kimi K3 | 2.8T | 低於 4 GB |
硬體與量化
AirLLM 支援的最低硬體門檻是 1-2GB VRAM 的消費級顯卡,同時也支援 macOS Apple Silicon 與純 CPU 推論。專案另外提供 4-bit / 8-bit 模型壓縮選項,官方數據宣稱量化可為推論延遲帶來 3x 的提升。也就是說,分層卸載與量化是兩個可疊加的獨立手段——卸載解決「放得下」的問題,量化解決「跑多快」的問題。
實際限制
README 沒有提供具體的 tokens/sec 或延遲數字,只強調「瓶頸主要在硬碟讀取(disk loading)」。這句話等於承認了逐層卸載的代價:每換一層都要即時從硬碟把權重讀進 GPU,這段 I/O 等待時間取代了原本一次性把整個模型塞進顯存的耗時。換句話說,AirLLM 是用時間換取空間——犧牲吞吐量與延遲,換取讓超大模型能在小顯存裝置上「跑得起來」而非完全跑不動。專案在 2026 年 6 月的 v3.0 更新加入 FP8 精度與新模型支援,顯示這個專案目前仍在持續維護。
Rust 提案新增 Move/Forget trait:把「不可移動」與「保證解構」寫進型別系統
Rust Project Goals / Hacker News · 2026-08-04
Rust 語言團隊在 rust-project-goals 儲存庫的 2026 目標文件中,提出為 2026-2027 週期新增兩個 auto trait——Move 與 Forget——目標是讓編譯器能表達「這個型別不能被移動」與「這個型別的解構子一定要執行」。這份目標由 @lcnr(型別系統)與 @jackh726(語言組)共同擔任 champion,目前狀態為 Accepted for 2026-2027。
問題所在
Rust 現行的假設是:所有型別都可以被移動,也可以被 mem::forget 安全地丟棄而不執行解構子。這個假設造成兩個具體麻煩。第一是自我參照(self-referential)型別,一旦這類型別的記憶體位置被移動,內部指標就會失效,但 Rust 型別系統目前沒辦法把「不可移動」表示成型別本身的屬性,只能靠 Pin 把不可移動性掛在「記憶體位置」而非「型別」上,增加了大量使用複雜度。第二是保證解構的需求,例如交易(transaction)控制代碼這類必須執行 commit 或 rollback 的型別,因為 mem::forget 在安全 Rust 中永遠合法,沒辦法保證解構子一定會被呼叫,這也連帶擋住了 async 情境下「安全的 scoped spawn」這類模式。
提案機制
文件提出的兩個新 auto trait,延續了 Sized 系列 trait 的做法——用「選擇退出(opt-out)」取代「全域假設」。Move trait 的語意是:實作 !Move 的型別不能被移動,且必須在整個生命週期中保持穩定位址。對應的 Forget trait 則規定:實作 !Forget 的型別必須讓其解構子執行。文件給出的 lang-item 定義如下:
#[lang = "move"]
unsafe auto trait Move {}這種做法把「能不能移動」從 Pin 那種放置(place)層級的技巧,提升成型別本身可以宣告的屬性,理論上能簡化目前圍繞 Pin 打轉的 self-referential 型別與 async 生態系複雜度。追蹤進度可見 rust-project-goals 的 issue #635,以及 rust-lang/rust 主倉庫的 issue #149607。
拿掉一個 if,Rust 的 filter 效能提升近 4 倍
Lobsters · 2026-08-03
greyblake.com 部落格作者在 2026-08-03 發布的技術文章中,拆解了一個常見場景——依照閾值過濾一組 f64 陣列——並示範拿掉其中的 if 分支後,最差情況下效能提升了將近 4 倍。文章的重點不是什麼神奇函式庫,而是分支預測失敗(branch misprediction)在資料無法預測時的實際代價。
原本的實作
最直覺的寫法是用 iterator 搭配 filter:
pub fn filter_iter(input: &[f64], threshold: f64) -> Vec {
input.iter().copied().filter(|&x| x > threshold).collect()
} 這段程式碼編譯後會產生一個條件分支——每比對一個元素,CPU 都要「猜」這個元素會不會通過 x > threshold。當篩選比例接近 50%(也就是資料完全不可預測)時,CPU 的分支預測器幾乎跟丟硬幣一樣沒把握,大量的分支預測失敗會直接拖慢管線(pipeline)執行。
去分支後的作法
作者改寫成一個「永遠寫入、只是不一定前進指標」的版本:
pub fn filter_branchless(input: &[f64], threshold: f64) -> Vec {
let mut out = vec![0.0; input.len()];
let mut n = 0;
for &x in input {
out[n] = x;
n += (x > threshold) as usize;
}
out.truncate(n);
out
} 關鍵在 n += (x > threshold) as usize 這一行——把布林比較結果直接轉成 0 或 1,用算術加法取代 if 判斷式來決定要不要前進寫入位置。不管 x 有沒有通過門檻,out[n] = x 都會被執行,只是沒通過的值之後會被下一次寫入蓋掉。這樣 CPU 完全不需要猜測,也就沒有分支預測失敗的代價。
效能數據
作者針對「50% 選擇率」這個最差情況做基準測試,結果如下:
| 版本 | 50% 選擇率耗時 |
|---|---|
filter_iter(含分支) | 3.94 ms |
filter_branchless(去分支) | 1.03 ms |
去分支版本在最差情況下速度提升了將近 4 倍,文章也特別指出,去分支後的效能「變得與資料本身無關(independent of the data)」——不論篩選比例是多少,耗時都差不多,不會再被分支預測命中率牽著走。