AI-LECTURER 速報課|2026-10-14|約 25 分鐘

記憶體不夠也能跑大模型:Janas 把「專家」放在 SSD 上現抓

取材:Janas: Run LLMs larger than RAM by streaming experts from SSD(Hacker News(AI 高人氣))
📍 真實場景
阿凱,32 歲,接案的後端工程師,筆電只有 32GB 記憶體

客戶要求程式碼不能上傳雲端,他想在自己的筆電上跑一個 80B 等級的本機程式碼助手

😖 卡住的地方:模型檔 48GB 比記憶體還大,用 llama.cpp 硬跑,每秒只吐 2.8 個 token,寫一段函式要等快兩分鐘,根本沒法用
💡 這課讓你看懂 Janas 為什麼能在同一台筆電上把速度拉到 24~37 tok/s,以及這個做法的代價在哪

🧭 這到底是什麼(白話版)

Janas 是一個推論引擎負責「載入模型並讓它一個字一個字回答」的程式,模型本身只是一大包數字,要靠它才會動。它的賣點很直接:模型檔比記憶體RAM,電腦做事時放資料的工作桌面,很快但容量有限還大,也能跑。原文實測:48.4GB 的 Qwen3-Next-80B,在 32GB 記憶體的筆電上,寫字速度 24 tok/s(開啟草稿加速後 37 tok/s),同一台機器上的 llama.cpp 只有 2.8 tok/s。

訣竅在於這類模型是 MoE(混合專家)一個模型裡藏了很多組「專家」小網路,每處理一個字只會叫其中幾個出來工作,其餘睡覺架構。檔名 A3B 的意思就是每次只啟用約 30 億參數。既然大部分專家此刻在睡覺,就沒必要全部塞進記憶體。

所以 Janas 把專家的權重模型學到的數字,專家的「知識」就存在這些數字裡留在 SSD固態硬碟,比記憶體慢很多但便宜又大 上,需要哪個才讀進來。這就像圖書館只把今天要用的書搬到桌上,其他留在書庫。

要注意:這是原文作者在自己那台機器(Intel Core Ultra 9 185H、NVMe SSD、Linux)上的測量,原文自己也說「換一台機器數字會不同」,並附了 janas-bench 讓你自己量。

🎯 為什麼值得你花時間

大模型不再被記憶體容量擋在門外原文中 48.4GB 的兩個模型都超過 32GB 記憶體,Janas 讀 prompt 62~66 tok/s、寫字 22~24 tok/s,llama.cpp 分別是 14~16 與 2.2~2.8。對筆電使用者,是「能不能用」的差別。
連放得進記憶體的模型也有加速表中 14 個模型 Janas 的寫字速度全部高於 llama.cpp,例如 Qwen3-30B-A3B 32 對 15、Qwen3-0.6B 130 對 45。不過讀 prompt 的差距小很多(135 對 122),優勢主要在寫字階段。
本機執行的隱私與成本資料不出自己的電腦,也不用付 API 費用。這對客戶有保密要求的接案、內部程式碼助手特別實際。

⚙️ 它是怎麼運作的

1
路由器挑專家每處理一個 token,MoE 模型的「路由器」會選出少數幾個專家(例如 64 個選 4 個),其他專家這一步不用算。
▼
2
檢查專家在不在記憶體引擎先看這幾個專家的權重是否已在快取把最近用過的資料留在快的地方,下次要用就不必重新去慢的地方拿裡。在,就直接用。
▼
3
不在就從 SSD 串流讀入沒命中的專家才從 SSD 讀進來,並擠掉最久沒用的專家,讓記憶體永遠只放「目前可能用到」的那一小部分。
▼
4
用 GPU 算,並盡量一次送完原文提到把整段 prompt 一次整批交給 GPU,Qwen3.8-27B 讀 prompt 因此從 26.4 提到 30.6 tok/s。每種量化把權重用更少位元存,檔案變小、速度變快、品質略降的壓縮手法格式(如 Q3_K、IQ4_XS)都需要各自的 GPU 運算核心,沒做到的格式就只能走慢路徑。
Janas 與 llama.cpp 在同一台筆電上的部分實測(原文數據,單位 tok/s)
模型讀 prompt:Janas讀 prompt:llama.cpp寫字:Janas寫字:llama.cpp
Qwen3-Next-80B-A3B(48.4GB,超過記憶體)6216242.8
Qwen3-30B-A3B1351223215
Qwen3.8-27B(dense,17.6GB)30.625.64.42.1
Qwen3-30B-A3B 3-bit(unsloth UD-Q3_K_XL)1001123611

🔍 程式碼漫遊(點有 ● 的行看白話講解)

這是下載專案裡 ExpertCache 的核心:用 LRU(最久沒用先淘汰)管理「哪些專家留在記憶體」。這是概念模擬,不是 Janas 的真實程式碼。

●def get(self, idx):
💬 有人要第 idx 號專家的權重,由快取決定從哪拿。
● if idx in self.cache:
💬 命中:專家本來就在記憶體裡。
● self.cache.move_to_end(idx)
💬 標記為「剛用過」,放到最新端,之後最不容易被淘汰。
● self.hits += 1
💬 命中次數+1,最後用來算命中率。
● return self.cache[idx]
💬 直接回傳,完全不碰磁碟,這就是快的原因。
● self.misses += 1
💬 沒命中:這一步必須付出讀 SSD 的代價。
● with open(self.path(idx), 'rb') as f:
💬 從磁碟檔案把整個專家讀進來,對應 Janas 從 SSD 串流專家。
● data = f.read()
💬 讀取的位元組數就是 I/O 成本,後面統計靠它。
● self.cache[idx] = data
💬 放進快取,下次再被選到就是命中。
● if len(self.cache) > self.slots:
💬 記憶體槽位滿了,這裡就是「記憶體不夠」的縮影。
● self.cache.popitem(last=False)
💬 把最久沒用的專家踢掉,為新專家騰位。
● return data
💬 回傳剛讀到的權重。

🛠️ 動手做:自己模擬一次「專家放 SSD、需要才讀」:看快取槽位數如何決定磁碟讀取量

這是一個真實小專案:下載(或複製)檔案,照步驟在你電腦上跑起來。

📄 moe_sim.py ⬇ 下載
import os
import random
import tempfile
from collections import OrderedDict

NUM_EXPERTS = 64
EXPERT_BYTES = 256 * 1024
TOP_K = 4
TOKENS = 400
HOT = list(range(12))


def make_store(folder):
    for i in range(NUM_EXPERTS):
        path = os.path.join(folder, 'expert_%d.bin' % i)
        with open(path, 'wb') as f:
            f.write(os.urandom(EXPERT_BYTES))


class ExpertCache:
    def __init__(self, folder, slots):
        self.folder = folder
        self.slots = slots
        self.cache = OrderedDict()
        self.hits = 0
        self.misses = 0

    def path(self, idx):
        return os.path.join(self.folder, 'expert_%d.bin' % idx)

    def get(self, idx):
        if idx in self.cache:
            self.cache.move_to_end(idx)
            self.hits += 1
            return self.cache[idx]
        self.misses += 1
        with open(self.path(idx), 'rb') as f:
            data = f.read()
        self.cache[idx] = data
        if len(self.cache) > self.slots:
            self.cache.popitem(last=False)
        return data


def pick_experts(rng, hot_ratio):
    chosen = set()
    while len(chosen) < TOP_K:
        if rng.random() < hot_ratio:
            chosen.add(rng.choice(HOT))
        else:
            chosen.add(rng.randrange(NUM_EXPERTS))
    return chosen


def run(folder, slots, hot_ratio):
    rng = random.Random(42)
    cache = ExpertCache(folder, slots)
    for _ in range(TOKENS):
        for idx in pick_experts(rng, hot_ratio):
            cache.get(idx)
    total = cache.hits + cache.misses
    mb = cache.misses * EXPERT_BYTES / 1024 / 1024
    print('快取槽 %2d 個 | 命中率 %5.1f%% | 從磁碟讀了 %7.1f MB' % (slots, 100.0 * cache.hits / total, mb))


def main():
    with tempfile.TemporaryDirectory() as folder:
        make_store(folder)
        total_mb = NUM_EXPERTS * EXPERT_BYTES / 1024 / 1024
        print('模型共 %d 個專家,合計 %.0f MB;每個 token 啟用 %d 個\n' % (NUM_EXPERTS, total_mb, TOP_K))
        for hot_ratio in (0.7, 0.0):
            label = '專家選擇集中(70%% 落在 12 個熱門專家)' if hot_ratio else '專家選擇完全平均'
            print('情境:' + label)
            for slots in (4, 16, 64):
                run(folder, slots, hot_ratio)
            print()


if __name__ == '__main__':
    main()
  1. 在任一資料夾(例如 D:\janas-demo)建立 moe_sim.py,把上面的內容存成 UTF-8 檔案。
  2. 開啟 PowerShell,輸入 chcp 65001 讓中文輸出不亂碼。
  3. 輸入 cd D:\janas-demo 進入該資料夾。
  4. 輸入 python moe_sim.py 執行(沒有 python 時請用專案的 .venv 內的 python.exe)。
  5. 觀察兩組結果:槽位 4、16、64 的命中率與讀取量如何變化;再比較「專家選擇集中」與「完全平均」兩種情境的差異。
  6. 進階:把 HOT 縮成 range(4) 或把 TOP_K 改成 8,預測結果後再執行驗證。
  7. (選做,需要 Linux x86-64)想測真正的 Janas,依原文:curl -LO https://github.com/prabanta-dev/janas/releases/latest/download/janas-linux-x86_64.tar.gz,解壓後執行 ./janas-chat。原文提供的是 Linux 版,Windows 上請使用 Linux 機器或虛擬機。

🧠 工程思維透鏡(資深工程師看到的是什麼)

🔭 為什麼這招幾乎只對 MoE 模型有效,對 dense 模型效果差很多?
MoE 每個 token 只用一小部分權重,所以「用到的資料量」遠小於「模型總大小」,用 SSD 補記憶體才划算。dense 模型每個 token 都要碰到全部權重,把它放 SSD 等於每個字都全量讀盤。原文數據也印證:Qwen3.8-27B(dense)寫字只有 4.4 vs 2.1 tok/s,絕對速度仍低;而 80B 的 MoE 卻有 24 tok/s。結構上的稀疏性,才是用 I/O 換記憶體的前提。
🔭 拿 SSD 當記憶體延伸,真正的瓶頸是什麼?工程上怎麼對付?
瓶頸是頻寬與延遲:SSD 比記憶體慢一到兩個數量級,每次快取沒命中就要付出一次讀取。對策是三層疊加:一、快取(LRU 之類)讓熱門專家常駐;二、串流與預取,讓讀取和計算重疊;三、讓每次讀取更小,例如更低位元的量化。注意這是在「速度、品質、容量」之間取捨:3-bit 量化讀 prompt 反而比 llama.cpp 慢(100 vs 112),因為 Q3_K 還沒接上 GPU 的快路徑,寫字卻快三倍。同一個格式在兩個階段的表現可以相反。
🔭 看這份 benchmark,資深工程師會對哪些地方保持警覺?
第一,是單一機器、單一作業系統、作者自己跑的結果,原文也坦言換機器數字會不同。第二,Janas 與 llama.cpp 的測量日期與參數不全一致(例如 Qwen3.8-27B 是不同天、不同指令量的),而且有帶 drafts 與不帶 drafts 的版本,比較時要看清楚是哪個數字。第三,tok/s 沒有同時告訴你輸出品質是否一樣。要判斷是否適合自己,應該用 janas-bench 在自己的硬體和模型上重量一次。

📝 隨堂考(點選答案,立即回饋)

Q1. 為什麼把專家權重放在 SSD、需要時才讀入,對 MoE 模型特別可行?
✅ MoE 每次只啟用少數專家,需要的資料量遠小於模型總大小,所以只讓熱門專家常駐記憶體、其餘在 SSD 上現讀是可行的。SSD 並不比記憶體快。
Q2. 原文中 Qwen3-Next-80B-A3B(48.4GB)在 32GB 記憶體筆電上,不含草稿加速時寫字速度 Janas 與 llama.cpp 分別約是?
✅ 寫字是 24 對 2.8(開草稿後 Janas 為 37);62 對 16 是「讀 1,024 個 token 的 prompt」的速度。
Q3. 3-bit 的 Qwen3-30B 為什麼在 Janas 上「讀 prompt 比 llama.cpp 慢」?
✅ 原文明說該格式的 Q3_K 權重尚未支援 GPU 的快速 prompt 處理,因此讀 prompt 100 對 112 略慢,但寫字 36 對 11 快三倍。
Q4. 下列哪個做法最能降低模擬程式裡「從磁碟讀取的 MB 數」?
✅ 槽位足以容納熱門專家時命中率最高、讀盤最少。調小槽位或清空快取只會讓 miss 更多。

🃏 翻牌記憶卡(先想答案,再點開對答)

MoE(混合專家)架構的核心特性點我翻面
模型內有很多專家小網路,每個 token 只啟用少數幾個,所以「實際計算量與用到的權重」遠小於模型總大小。
Janas 把大模型塞進小記憶體的核心手法點我翻面
專家權重留在 SSD,需要時串流讀入並用快取保留熱門專家,記憶體只放目前用得到的部分。
為什麼 dense 模型不太適合這招?點我翻面
dense 模型每個 token 都用到全部權重,放 SSD 等於每個字都要全量讀盤,I/O 壓力大幅增加。
讀 prompt 與寫字的速度為何常不一樣?點我翻面
讀 prompt 可以一次整批交給 GPU 平行處理,寫字則必須一個 token 一個 token 依序產生,兩者瓶頸不同,優化的路徑也不同。
看到單機 benchmark 該注意什麼?點我翻面
硬體、系統、測量日期、參數是否一致,有無開額外加速,以及品質有沒有一併比較;最好用工具在自己的機器上重測。

✅ 離開前自測(全勾=這課真的學會了)

0%