這個 repo 是推論專案(patch + 量測工具 + 證據),模型權重本身在
unsloth/Qwen3.5-4B-GGUF,不重複發佈。
SDQwen35-4B
Qwen3.5-4B(Q4_K_M, 2.553 GiB)在 1 GiB 總 RAM、禁止 swap 的條件下推論。 權重與 KV cache 放在 SSD 檔案,RAM 只留執行必須的部分。
| 上下文 | 峰值總 RSS | swap | 推論 | |
|---|---|---|---|---|
| 已驗證 | 1,024 | 0.384 GiB | 0 | ✅ all_passed: true |
| 理論最高 | 262,144 | (尚未實測) | — | ⚠️ 未驗證 |
模型:unsloth/Qwen3.5-4B-GGUF /
Qwen3.5-4B-Q4_K_M.gguf(2.553 GiB)
引擎:llama.cpp 0504396 + 本專案兩個 patch(見下)
證據:validate/ 每一個數字都有對應檔案
⚠️ 誠實說明:262144 上下文還沒實測過。模型本身支援(
context_length = 262144, 架構上 KV 有 patch 0002 放 SSD),但「跑得起來」跟「在 1 GiB 預算內跑得起來」是兩件事, 本 repo 目前只對 1K 上下文負責。跑./installpi.sh --probe-maxctx才會產生那個證據。
這個專案在解決什麼
4B 模型只有 2.553 GiB,看起來「1 GiB RAM 跑 4B」很輕鬆。實際上不難,但做法決定差 20 倍以上:
Qwen3.5 是 hybrid 架構:32 層中只有 8 層是 full attention(每 interval 4 層), 其餘 24 層是 linear/SSM。這代表 KV cache 只跟 8 層有關,不是 32 層 —— 這是它比同級模型省 KV 的原因。
| 層 | 大小 | 位置 |
|---|---|---|
| 權重 | 2.553 GiB | SSD(mmap + 背景 sweeper 定期丟回) |
| KV cache @1024 | 32 MiB(f16) | SSD(檔案 mmap,本專案 patch 0002) |
| SSM 狀態 / compute buffer / 執行檔 | ~0.2 GiB | RAM |
★ 最重要的結論:權重留 page cache,比全丟 SSD 快 22 倍
這是本專案實測出來、與直覺相反的一件事。兩個配置都符合 1 GiB 約束:
| 配置 | 峰值總 RSS | swap | decode | prefill |
|---|---|---|---|---|
權重留 page cache(MEMWATCH_SWEEP=0) |
0.384 GiB | 0 | 2.67 tok/s | 7.72 tok/s |
權重頁全丟回 SSD(MEMWATCH_SWEEP=1) |
0.226 GiB | 0 | 0.12 tok/s | 0.94 tok/s |
為了一個模型大小零頭的記憶體差異(0.16 GiB),全丟 SSD 要賠上 22 倍的 decode 速度。 背景 sweeper(patch 0001)才是關鍵機制,不是「把權重丟掉」。
對照組:不釋放權重頁(LLAMA_MMAP_RELEASE=0)的兩個配置峰值都是 2.78 GiB
= 整份權重常駐,直接違規 —— 這證明 sweeper 是必要的,不是可有可無的調校。
三個量測陷阱(全部都踩過,也全部修好了)
這個專案最大的風險不是記憶體不夠,是量測看起來通過、實際沒通過:
mmap的權重頁會算進 RSS,而且真的佔用系統 RAM。 只算匿名記憶體會得到 「0.21 GiB ✓」這種假通過(總 RSS 其實是 2.78 GiB)。→ 一律用peak_rss_gb。bench.sh判定基準寫錯過。 原本硬寫--limit-gb 4,所以 2.78 GiB 的違規配置 也被標成within_limit: true。→ 已改用MEMORY_LIMIT_GB(預設 1)並輸出明確verdict。validate/bench.jsonl曾經被mkdir成目錄,導致每次結果 append 都失敗, 整個矩陣的結果靜默遺失 —— 記憶體佳的舊資料沒有留下來。 → 已修。另外posix_fadvisesweep 打錯檔案會低估 RSS(頁沒清掉卻不記錄)。
兩個 patch(都在 patches/,未改動任何上游原始碼)
| patch | 做什麼 | 環境變數 |
|---|---|---|
0001-ssd-mmap-release.patch |
權重頁 madvise(MADV_DONTNEED),由背景 sweeper 在計算進行中定期丟回 SSD |
LLAMA_MMAP_RELEASE=1, LLAMA_MMAP_RELEASE_INTERVAL=20 |
0002-kv-ssd-pager.patch |
KV buffer 改用檔案 mmap,可放 SSD |
LLAMA_KV_SSD=auto|0|1, LLAMA_KV_SSD_MIN_MIB, LLAMA_KV_SSD_PATH |
套用順序必須先 0001 再 0002(0002 的 hunk 是相對「已套 0001」的樹)。
依賴固定 commit 0504396140d1c882f5f6ee34466a42db7ae90114,master 前進後 patch 錨點會失效。
為什麼 sweeper 必須在計算進行中跑:單一 token 的 decode 會走完全部層,
峰值發生在任何「算完才釋放」之前 —— 實測放在 synchronize() 之後時峰值會衝到整個模型大小。
模型架構(實讀 GGUF metadata)
general.architecture = qwen35
block_count = 32
full_attention_interval = 4 -> 8 層 full attention, 24 層 linear/SSM
attention.head_count_kv = 4
attention.key_length / value_length = 256
embedding_length = 2560
context_length = 262144
general.file_type = 15 (Q4_K_M)
KV 每 token = 2 × 8 × 4 × 256 = 16384 elems:
| KV 型別 | 每 token | @1024 | @262144 |
|---|---|---|---|
| f16 | 32.0 KiB | 0.031 GiB | 8.000 GiB |
| q8_0 | 17.0 KiB | 0.017 GiB | 4.250 GiB |
| q4_0 | 9.0 KiB | 0.009 GiB | 2.250 GiB |
→ 262144 上下文連最省的 q4_0 都超出 1 GiB 預算,必須靠 SSD-backed KV。
使用方法
想直接用 prompt 讓人(或 AI agent)從零重建整個專案 →看 **
REPRODUCE.md**, 裡面有集中的變數表、驗收門檻與換模型指引。
一鍵安裝 + 驗證
git clone https://huggingface.co/HelloSun/sddqwen35-4b
cd sddqwen35-4b
# 編譯 + 下載模型(2.553 GiB)+ 啟動 + 驗證 1K 上下文
export HF_TOKEN=hf_... # 模型是公開的,但設了會走 xet 加速
./installpi.sh
編譯在 2 vCPU 上約 6 分鐘(-j2),模型下載約 10 秒(xet 加速)。
只做驗證約 8 分鐘(CPU 純推論)。
常用指令
./installpi.sh --validate-only # 只重跑 1K 驗證
./installpi.sh --probe-maxctx # 驗證 262144(⚠️ 尚未有證據,跑完請 sync)
./installpi.sh --ctx 8192 # 用任意上下文啟動
./bench.sh # 記憶體 / 速度取捨矩陣
./bench.sh "mycase:MEMWATCH_SWEEP=0 LLAMA_MMAP_RELEASE=1" # 自訂單一配置
./sync.sh "改了什麼" # 推回 HF(崩潰後的唯一保險)
當成 OpenAI 相容 API 用
installpi.sh 會在 127.0.0.1:8080 啟動 llama-server:
curl http://127.0.0.1:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"qwen3.5-4b-q4_k_m",
"messages":[{"role":"user","content":"用一句話介紹你自己"}],
"max_tokens":8}'
搭配 pi coding agent(installpi.sh 會自動寫 ~/.pi/agent/models.json):
pi-local -p "你好"
建議的生產設定
MEMWATCH_SWEEP=0 # 權重留 page cache -> 快 22 倍, 仍在 1 GiB 內
LLAMA_MMAP_RELEASE=1
LLAMA_MMAP_RELEASE_INTERVAL=20
LLAMA_KV_SSD=1 # KV 一律放 SSD
關鍵環境變數
| 變數 | 預設 | 說明 |
|---|---|---|
MEMORY_LIMIT_GB |
1 |
硬約束(總 RSS,含 page cache) |
LLAMA_MMAP_RELEASE |
1 |
權重背景釋放(核心機制,關掉就會違規) |
LLAMA_MMAP_RELEASE_INTERVAL |
20 |
sweeper 間隔 ms |
LLAMA_MMAP_PREFETCH |
0 |
不做 MAP_POPULATE,權重留在 SSD |
LLAMA_KV_SSD |
auto |
KV ≥ LLAMA_KV_SSD_MIN_MIB 才放 SSD |
LLAMA_KV_SSD_PATH |
$MODEL_DIR/kv-ssd.cache |
SSD KV 檔 |
LLAMA_REPACK |
0 |
--no-repack,省掉權重匿名複本 |
LLAMA_ENABLE_AMX |
0 |
編譯期關 AMX(會產生 13.9 GiB 權重複本) |
LLAMA_THREADS |
8 |
執行緒(越多 → 計算緩衝區越大) |
效能:誠實說明
| 配置 | 峰值總 RSS | decode | prefill | ≤1 GiB |
|---|---|---|---|---|
| 權重留 page cache(建議) | 0.384 GiB | 2.67 tok/s | 7.72 tok/s | ✅ |
| 權重全丟 SSD | 0.226 GiB | 0.12 tok/s | 0.94 tok/s | ✅ |
| 權重常駐 RAM + KV SSD(解除限制) | 2.77 GiB | 2.82 tok/s | 6.21 tok/s | ❌ |
解除記憶體限制幾乎沒有速度收益:把 2.4 GiB 權重灌進 RAM 只快 6%
(2.82 vs 2.67 tok/s),記憶體卻變 7.2 倍、直接超標 2.8 倍。
原因是 4B 的權重只有 2.4 GiB,SSD 讀取速度遠高於計算需求 ——
這台機器的瓶頸是 CPU 算力,不是 I/O。
(在 sddqwen38-27b 結論相反:15.33 GiB 權重才讓 I/O 成為瓶頸。)
另外兩個實測發現:KV 放 SSD 反而比放 RAM 快(2.82 vs 2.47 tok/s);
執行緒開越多越慢(2 threads 2.82 → 4 threads 1.74 → 8 threads 0.32),
因為本機只有 2 vCPU。LLAMA_THREADS 請設成 nproc 或更少,預設的 8 在小機器上是錯的。
tok/s 是「模型 × 機器 × 配置」的性質,不是模型的性質。
上表是 2 threads 的數字;舊機器 8 threads 量到 7.50 tok/s。換機器請重跑 ./bench.sh。
因為權重要從 SSD 讀,這台磁碟的讀取速度直接決定結果 —— 程式碼不需要改,只會變快。
證據
| 檔案 | 內容 |
|---|---|
validate/validation.json |
1K 驗證報告,all_passed: true |
validate/memwatch-ctx1024.json |
1K 峰值總 RSS / swap / 實際生效的強制手段 |
validate/chat-ctx1024.json |
1K 實際推論輸出與 timings |
validate/bench.jsonl |
記憶體 / 速度取捨矩陣(含 verdict 判定欄位) |
validate/bench-logs/ |
每個配置的完整 memwatch + 推論記錄 |
validate/bench-nolimit.jsonl |
解除記憶體限制的實測(權重常駐 RAM) |
validate/bench-logs-nolimit/ |
上面那批的原始 memwatch + 推論記錄 |
STATUS.md |
進度真相來源(含所有技術決定的理由與矩陣) |
AGENTS.md |
續作指引 + 已知的坑(接手前必讀) |
REPRODUCE.md |
可復現本專案的完整 prompt(自我足夠,含換模型指引) |
接手前請先讀
本機很不穩,agent 可能隨時崩潰,所以 AGENTS.md 存在的唯一理由是
「換一個 agent 也能接著做」。其中記了幾個「看起來對、實際不對」的坑,全部靠實測抓到。
約束只有兩條:行程總 RSS(含 page cache)≤ MEMORY_LIMIT_GB(預設 1 GiB),
以及 swap 使用量 = 0。驗證必須同時滿足這兩條,缺一不算通過。
下一步(見 STATUS.md):跑 --probe-maxctx 補上 262144 的證據,
並驗證建議的新生產設定在 262144 下是否仍合規。
參考資料(未修改)
- HelloSun/sddqwen38-27b — 前作專案,3 GiB / 27B
- ggml-org/llama.cpp — 上游,固定 commit
0504396 - unsloth/Qwen3.5-4B-GGUF — 模型權重