|
Download docs/DESIGN.md from HelloSun/minicpm47woek_EPQ: direct link, hf CLI and curl.
- Browser
- Download file 6.12 kB
-
https://huggingface.co/HelloSun/minicpm47woek_EPQ/resolve/main/docs/DESIGN.md
- Command line
-
hf download hf://HelloSun/minicpm47woek_EPQ/docs/DESIGN.md
-
curl -L -o DESIGN.md https://huggingface.co/HelloSun/minicpm47woek_EPQ/resolve/main/docs/DESIGN.md
6.12 kB
| # 設計:記憶體帳目、SSD→RAM→CPU 資料流 | |
| 這個模型在 8 GiB 裡能跑,不是因為它小,而是因為**19.71 GiB 的權重裡有 18.16 GiB | |
| 從來不需要同時在 RAM 裡**。 | |
| ## 1. 權重帳目 | |
| `MiniCPM-V-4_7-35B-A3B-Q4_K_M.gguf`,21,166,255,008 B = **19.71 GiB**, | |
| 架構 `qwen35moe`,40 個 block。 | |
| | 項目 | 大小 | | |
| | --- | ---: | | |
| | 路由專家權重(40 層 × 256 個 × 3 個投影) | **18.16 GiB** | | |
| | 非專家常駐權重(embedding / lm_head / 注意力 / SSM / shared expert) | 1.54 GiB | | |
| | 單一 token 的專家工作集(40 層 × top-8) | 14.53 MiB 平均 / 15.56 MiB 最大 | | |
| 單一 token 在解碼時需要 320 個 layer-expert,但因為槽位分兩種尺寸,正確的說法是 | |
| **160 個大槽位 + 160 個小槽位**。8 GiB 的 arena 塞得下 3714 個槽位,其中約四成是 | |
| 大槽位(20/40 的層用大槽位),所以命中率可以到 70% 以上;2 GiB 的 arena 只有 | |
| 332 個槽位湊不出這個組合,命中率崩到 22.7%(見 README 的 budget 曲線)。 | |
| ### 兩種 expert 大小 | |
| 這個模型的 `ffn_down_exps` 量化型別**逐層不同**: | |
| | 層 | `ffn_down_exps` | 每個 expert 位元組 | | |
| | --- | --- | ---: | | |
| | 20 層 | Q6_K | **2,039,808 B** | | |
| | 20 層 | Q4_K | **1,769,472 B** | | |
| `ffn_gate_exps` 與 `ffn_up_exps` 全部是 Q4_K,每個 589,824 B。 | |
| 平均下來每個 layer-expert 是 1.95 MiB,因此 arena 的槽位分成兩種尺寸: | |
| **小 1.69 MiB × 2420 + 大 1.95 MiB × 806**(8 GiB 預算下)。 | |
| 這一點是這個模型相對於參考實作最重要的結構差異:參考的 Qwen3.6-35B-A3B 是 | |
| 37 層 1,900,544 B + 3 層 2,039,808 B,槽位比例完全不同。**槽位尺寸必須從 | |
| 模型 metadata 推導,不能寫死。** | |
| ### 混合架構 | |
| `qwen35moe.block_count = 40`,但只有 10 層是全注意力 | |
| (`full_attention_interval = 4`),另外 30 層是 SSM / Mamba 層 | |
| (`ssm.*` 張量,`recurrent_layers` 40 個元素全為 1)。 | |
| 這影響兩件事: | |
| 1. **正確性**:模型 metadata 裡因此多了一個 BOOL 陣列,參考實作的 GGUF 標頭 | |
| 解析器會在這裡錯位(見 `CORRECTNESS.md` §3.2)。 | |
| 2. **效能**:SSM 層的計算可以平行,不像注意力那樣受限於序列維度,所以 | |
| **執行緒數的結論與參考實作相反**(見 `NEGATIVE_RESULTS.md`)。 | |
| ## 2. 資料流 | |
| ``` | |
| GGUF 檔(19.71 GiB) | |
| │ | |
| │ 非專家權重:mmap,乾淨的 file-backed 頁 | |
| │ 路由專家權重:自訂 buffer type,只回報 4 KB, | |
| │ 真正的內容留在檔案裡(pager 用 pread 管理) | |
| ▼ | |
| SSD → RAM:pager 在需求觸發時把選中的 expert 讀進 arena 槽位 | |
| │ | |
| ▼ | |
| RAM → CPU:ggml 自己的量化點積(ggml_vec_dot_q4_K_q8_K / q6_K_q8_K) | |
| ``` | |
| 路由本身(logits / softmax / top-k / 權重正規化)仍然由 ggml 算完, | |
| `build_moe_ffn` 只在「把被選到的 8 個 expert 算出來並加權求和」這一段 | |
| 交給 `sdq_build_moe`。 | |
| ## 3. 實作的關鍵決定 | |
| ### 三段並行讀取 | |
| 一個 expert 的 gate / up / down 在檔案上互不相鄰。循序讀是 3 × 延遲的鏈, | |
| 改成三段同時讀之後,一層 8 個全 miss 的 expert 明顯變快。 | |
| ### 需求觸發的 LRU 淘汰 + pin | |
| `acquire()` 是「先讀完再 publish」。`reserve_slot()` 會在資料還沒讀進來之前 | |
| 就把 (layer, expert) 記進 page 表,一旦 pread 失敗,中斷槽就會留在表裡卻是 | |
| 舊資料,之後的 `probe()` 會把它當成命中 → **靜默算錯**。成本相同,但不能這麼做。 | |
| ### arena sizing 扣的是「匿名 RSS」 | |
| `statm` 的 resident 把 mmap 進來的模型權重頁也算成已用,但那些是乾淨的 | |
| file-backed 頁,記憶體吃緊時核心會直接回收,既不佔用寶貴的實體 RAM 也不會產生 | |
| swap I/O。把它算進「已用」會讓 arena 被無謂地縮水。 | |
| ``` | |
| arena = 預算 − 匿名 RSS − SDQ_RESERVE_MB | |
| ``` | |
| `RLIMIT_DATA` 只當**上界**,不當預算來源:它算的是匿名**虛擬**記憶體,而 | |
| llama.cpp 會配置大量「映射了但永遠不會 touch」的圖與 compute buffer。 | |
| ### 8 GiB 下的 reserve 校正 | |
| `SDQ_RESERVE_MB` 必須**逐模型、逐 ctx/ubatch 校正**。本模型在 | |
| ctx=4096 / ubatch=128 下量到的: | |
| | reserve | arena | 槽位 | 峰值匿名 RSS | 8 GiB 內? | | |
| | ---: | ---: | ---: | ---: | --- | | |
| | 128 MiB | — | — | 4042.6 MiB | 是(餘 53 MiB,太緊) | | |
| | 256 MiB | — | — | 3918.1 MiB | 是(餘 178 MiB) | | |
| | 384 MiB | — | — | 3795.7 MiB | 是(餘 300 MiB) | | |
| | **512 MiB** | **6.35 GiB** | **3714** | **7799 MiB** | **是(餘 446 MiB)** | | |
| | 640 MiB | 6.23 GiB | 3644 | 7623.0 MiB | 是(餘 292 MiB) | | |
| | 768 MiB | 6.11 GiB | 3573 | 7498.5 MiB | 是 | | |
| | 1024 MiB | 5.87 GiB | 3432 | 7251.6 MiB | 是 | | |
| 峰值匿名 RSS 幾乎與 reserve 成 1:1 反比(每減 128 MiB,RSS 多約 123 MiB), | |
| 因為省下來的預留全部變成 arena。**512 MiB 是這個模型在 8 GiB 下的選擇。** | |
| 換 RAM 預算時 reserve 也要跟著換。本 repo 的量測腳本用一條比例規則: | |
| ``` | |
| reserve = clamp(RAM 預算 / 16, 128 MiB, 1024 MiB) | |
| ``` | |
| 在 8 GiB 重現 512 MiB,在 4 GiB 給出 256 MiB(餘 178 MiB)。 | |
| **用固定的 640 MiB 跑 4 GiB 會直接超出上限。** | |
| ## 4. 輸出不直接寫 dst | |
| 同一個 token 的 8 個 expert 會撞在一起,所以每個執行緒一份累加緩衝, | |
| 最後一道 barrier 之後歸約。同時每個 expert 算完立刻 `release()` 它的槽位 —— | |
| 不放的話 prefill 一層會把整層用到的 expert 全部釘住(最多 256 個), | |
| 大槽位不夠時會中止。 | |
| ## 5. 這個設計沒有做的事 | |
| * **不碰路由。** 路由必須完整算完才知道要哪些 expert,而路由本身很小。 | |
| * **不用 swap。** swap 的延遲不可預測,而且本專案的 no-swap 是核心保證。 | |
| * **不做壓縮。** 權重已經是 Q4_K_M,再壓就是品質換速度,不在本專案範圍。 | |
| * **不處理視覺。** 這個 GGUF 的 vision projector 在另一個檔案 | |
| (`mmproj-model-f16.gguf`),本路徑只做文字推論。 |