Instructions to use HelloSun/SmallThinker4b with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- llama-cpp-python
How to use HelloSun/SmallThinker4b with llama-cpp-python:
# !pip install llama-cpp-python from llama_cpp import Llama llm = Llama.from_pretrained( repo_id="HelloSun/SmallThinker4b", filename="{{GGUF_FILE}}", )output = llm( "Once upon a time,", max_tokens=512, echo=True ) print(output)
- Notebooks
- Google Colab
- Kaggle
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 |
⚠️ 必看的坑
-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 型別。- **分頁開著時一定要關掉
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 不含權重。
Model tree for HelloSun/SmallThinker4b
Base model
Tiiny/SmallThinker-4BA0.6B-Instruct