SmallThinker-4B-A0.6B — 熱參數在 RAM、冷參數在 SSD

在 llama.cpp 上跑 Tiiny/SmallThinker-4BA0.6B-Instruct-GGUF 的 SmallThinker-4B-A0.6B-Instruct.Q4_K.gguf(2,630,212,704 bytes = 2508 MiB), expert 級 SSD 分頁:權重永遠是 SSD 上的 file-backed mmap,RAM 只留熱的 expert 權重,用不到的 expert 會被明確丟回 SSD。

計算路徑完全沒動過 —— 仍然是 llama.cpp 自己的 mul_mat_id, 所以輸出與上游逐字元相同。這是本專案唯一不妥協的正確性條件。

./llama_server.sh                        # port 8080
./llama_server.sh --plan                 # 只印推導出來的參數
./llama_server.sh --verify               # 啟動 + 打一次 chat + 量記憶體
ST_RAM_BUDGET_MB=256 ./llama_server.sh   # 手動壓低 RAM 預算
ST_PAGER=0 ./llama_server.sh             # 關閉分頁,回到上游行為

實測結果

本機:16 vCPU、無 GPU、檔案系統 overlayfs、2026-10-07。 完整證據:validate/ab-test.json(all_passed: true)、 validate/last-run.json。

記憶體 vs 分頁預算

同一份 llama-cli、同一個 prompt、同一個 seed(--seed 42)、ctx 1024、8 threads:

設定 peak 總 RSS 匿名 檔案對映 swap SSD 重讀 命中率 淘汰次數
ST_PAGER=0(上游) 2.565 GiB 0.103 2.559 0 — — —
預算 1024 MiB 1.301 GiB 0.103 1.300 0 2317 MiB 57.4% 2,894
預算 512 MiB 0.833 GiB 0.103 0.836 0 2316 MiB 55.1% 4,572
預算 256 MiB 0.596 GiB 0.103 0.592 0 2320 MiB 53.7% 5,625
預算 128 MiB 0.661 GiB 0.103 0.657 0 2317 MiB 54.1% 5,298

總 RSS 從 2.565 GiB 降到 0.596 GiB(−76.8%),swap 全程 0。

量的是總 RSS(含 file-backed mmap 的權重頁),不是只有匿名記憶體 —— 權重頁會算進 RSS 且真的佔用系統 RAM。只看匿名記憶體會嚴重低估(本實測 0.103 GiB)。 峰值由模型載入決定,不是 prefill:實測 prompt 2 token 與 211 token 的峰值幾乎一樣。

預算 128 MiB 反而比 256 MiB 高一點:預算已逼近 hot window 內必須保留的下限 (ST_HOT_TOKENS=8 × 32 層 × 4 experts/層),再壓沒有意義,命中率也沒比較好。

速度代價

設定 prefill decode 耗時
ST_PAGER=0(上游,權重全在 RAM) 178.7 t/s 55.4 t/s 9.06 s
預算 1024 MiB 7.8 t/s 22.9 t/s 7.85 s
預算 512 MiB 7.9 t/s 21.6 t/s 7.67 s
預算 256 MiB 8.1 t/s 19.7 t/s 7.88 s
預算 128 MiB 7.9 t/s 20.9 t/s 8.13 s

用約 2.42.8× 的 decode 速度換 34.3× 的記憶體。 這是 demand paging 的必然代價: 被丟掉的 expert 下次要用時必須重新讀 SSD。

本機 SSD 讀取能力(validate/io-probe.json)

threads 1 2 4 8
循序讀取 337 MB/s 667 MB/s 1328 MB/s 2558 MB/s

4K 隨機讀 ≈ 1767 IOPS。換機器務必用 tools/io_probe.py 重測, decode 速度幾乎完全由它決定。

端到端(llama_server.sh --verify,ST_RAM_BUDGET_MB=512)

項目 值
peak 總 RSS 1.090 GiB
VmSwap 0
expert 常駐 / 預算 510.3 / 512.0 MiB ✅
分頁命中率 41.2%
read_bytes(整個行程) 2.44 GiB
chat 輸出 A **MoE (Model Parallelism) layer** enables a neural network to

1.090 GiB 的組成:非 expert 權重 410 MiB(常駐)+ expert 分頁 510 MiB

  • KV / compute buffer / 執行檔(llama-server 開 4 個 slot)。

模型事實(全部從 GGUF metadata 實測,不是抄設定檔)

項目 值
arch smallthinker
層數 32
hidden 1536
experts / 每 token 用 32 / 4
expert FFN 768
attention heads / KV heads 12 / 2
key_len / val_len 128 / 128
context_length(模型上限) 32768
量化 Q4_K_M
檔案 2,630,212,704 bytes(2508 MiB)

權重組成

位元組 佔檔案
expert 權重(1024 個) 2,194,145,280(2092.5 MiB) 83.4%
其餘(embedding / attention / norms / router) 430,065,024(410.2 MiB) 16.6%

expert 佔 83.4% —— 所以「熱在 RAM、冷在 SSD」這件事基本上就是 「expert 權重怎麼放」。

每個 expert 是三段連續位元組(blk.N.ffn_{gate,up,down}_exps.weight, expert 維度是最外層 ne[2],所以單一 expert 是一段連續區間):

段 shape layer 0 每個 expert
gate [1536, 768, 32] 663,552 B(Q4_K)
up [1536, 768, 32] 663,552 B(Q4_K)
down [768, 1536, 32] 967,680 B(Q6_K)
合計 2.19 MiB

⚠️ down_exps 的量化型別逐層不同:32 層裡有 16 層是 Q6_K、16 層是 Q4_K (層號 0,1,2,3,6,9,12,15,18,21,24,27,28,29,30,31 是 Q6_K)。 所以「一個 expert 多大」必須逐層算:Q6_K 層 70.03 MiB/層、Q4_K 層 60.75 MiB/層, 平均 2.04 MiB/個。拿 layer 0 當全模型常數會把總量高估 148 MiB(7%)。


機制

llama.cpp 正常載入(權重留在 SSD 的 file-backed mmap)
        │
        ├─ 每層 top-k 選完 expert 之後,插入一個 ggml custom op
        │     ├─ 讀出這一層這批 token 用到哪些 expert,標成「熱」
        │     └─ 若 resident 超過預算 → 用 LFU→LRU 順序淘汰最冷的
        │            a. madvise(MADV_DONTNEED)   拿掉本行程 PTE → RSS 下降
        │            b. posix_fadvise(DONTNEED)   丟掉 page cache → 下次真的讀 SSD
        │
        └─ 計算本身:完全不動,仍是上游 ggml_mul_mat_id

淘汰的兩步驟順序不能換: 只做 (a) → RSS 下降但 read_bytes 不變(頁還在 page cache); 只做 (b) → page cache 下降但行程 RSS 不變(VMA 還映射著)。 (核心的 invalidate_mapping_pages() 會跳過仍被 VMA 映射的頁,所以必須先 (a)。)

與 HelloSun/sddqwen35a3b_v01 的取捨

sddqwen35a3b_v01 本專案
權重怎麼進 RAM pread 進自己配置的 arena 留在 llama.cpp 自己的 mmap
MoE 計算 自訂 ggml op,自己重寫 Q4_K 點積 不動,用上游 mul_mat_id
輸出正確性 要另外驗證 kernel 寫對沒有 逐字等於上游,結構上不可能錯
SSD 讀取 預取可與計算重疊 demand paging(碰到缺頁才讀)
程式碼量 ~2800 行 ~550 行

放棄 I/O 預取,換取「計算路徑完全沒動過」的保證。


環境變數

變數 預設 說明
ST_PAGER 1 0 = 關閉分頁(上游行為,做 A/B 用)
ST_RAM_BUDGET_MB 自動偵測 expert 權重可以常駐 RAM 的上限
ST_RESERVE_MB 0 KV + compute 預留,從預算扣掉
ST_ARENA_MB 0 直接指定 expert 常駐量(有值優先)
ST_HOT_TOKENS 8 最近 N 個 tick 用過的 expert 不淘汰
ST_VERBOSE 0 印每次淘汰 + 實際 RSS
ST_STATS_FILE — 行程退出時把統計寫成 JSON(SIGUSR2 也會寫)
ST_EVICT_MODE both 診斷用:both/madvise/fadvise/none

⚠️ 必看的坑

  1. -DGGML_CPU_REPACK=OFF 是硬性要求(llama_server.sh 已自動加上)。 開著時 ggml 把 Q4_K 轉成 repack 格式放進匿名緩衝區,權重就離開 mmap。 這時 madvise(MADV_DONTNEED) 不是「讀 SSD」,而是把那塊記憶體歸零 → 輸出變成 papers bases abstract… 之類的亂碼,但行程看起來完全正常、 tok/s 只掉一點。patch 裡也有 in_map 檢查會擋下並印出 buffer 型別。
  2. **分頁開著時一定要關掉 MAP_POPULATE**,否則整個 2.45 GiB 會在 「載入模型」那一瞬間全部 fault 進 page cache,峰值立刻爆掉 —— 而且分頁統計看起來完全正常(sweep 有跑、帳面 resident 也對),光看統計抓不到。

完整清單(每條都有實測證據)在 STATUS.md 的「踩過的坑」表格。

檔案

檔案 用途
llama_server.sh 一鍵啟動(自動抓 llama.cpp / 套 patch / 編譯 / 下載權重)
patches/0001-st-expert-pager.patch llama.cpp 改動
tools/ab_test.py A/B 驗證(RSS / swap / SSD 讀取 / 輸出比對)
tools/verify_run.py 對 llama-server 打 chat 並量記憶體
tools/io_probe.py 實測本機 SSD 讀取能力(換機器必跑)
tools/drop_model_cache.py 量測前丟掉模型檔的 page cache
STATUS.md 進度真相來源
AGENTS.md 續作指引(假設 agent 崩潰後只看這兩個檔案)

重現

git clone https://huggingface.co/HelloSun/SmallThinker4b
cd SmallThinker4b

# 端到端驗證
ST_RAM_BUDGET_MB=512 ./llama_server.sh --verify

# A/B 驗證(baseline vs 各種預算)
python3 tools/ab_test.py --bin <path>/llama-cli --model <path>/model.gguf \
    --budget 1024 --budget 512 --budget 256 --out validate/ab-test.json

# 本機 SSD 讀取能力
python3 tools/io_probe.py --model <path>/model.gguf --out validate/io-probe.json

進度與續作

工作成果保存在這個 repo(./sync.sh "改了什麼" 即可上傳)。 假設 agent 崩潰:clone 這個 repo → 讀 STATUS.md → 讀 AGENTS.md → 接著做。 本機很不穩,每改完程式並編譯過就上傳,不要累積。

尚未做(見 STATUS.md 的「下一步」):I/O 預取(讓 SSD 讀取與計算重疊)、 KV cache 也放 SSD、限制 prefill 批次大小以壓低 prefill 階段峰值。

授權

本 repo 的程式碼(llama_server.sh / patches/ / tools/)沿用 llama.cpp 的 MIT。模型權重屬於 Tiiny/SmallThinker-4BA0.6B-Instruct-GGUF (Apache-2.0),本 repo 不含權重。

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for HelloSun/SmallThinker4b