minicpm47woek_EPQ / docs /STRICT_8GIB.md
HelloSun's picture
EPM: expert paging CPU inference for MiniCPM-V-4.7-35B-A3B Q4_K_M
12eb3a5 verified
|
Raw History Blame Contribute Delete
5.91 kB
# 8 GiB / no-swap 的方法、證據與限制
本專案的核心主張是「21.17 GB 的模型在 8 GiB RAM、沒有 swap 的條件下解碼」。
**每一次執行都由 `tools/strict8g.py` 在行程執行期間管治**,不是事後推論。
`strict_ok=False` 的樣本一律不採用。
## 1. 兩條腿
### no-swap 由核心保證,不是讀一次數值
行程所有**匿名**映射都被 `tools/epq_lock.so` 以 `mlock()` 鎖住。
被鎖定的頁永遠不會被寫到 swap 裝置,所以 no-swap 是核心在整段執行期間強制
的,而不是執行完之後從某一次讀取推論出來的。
標配執行下 `VmLck` ≈ 7.6 GiB,這個數字可以直接驗證。
### 實體常駐量有硬上限
取樣器(預設每 5 ms 讀一次 `/proc/<pid>/status`)一旦發現匿名 RSS 超過上限
立刻 `SIGKILL`;另外用 `RLIMIT_DATA` 當第二道防失控的閘門。
每次執行都記錄 `VmHWM`、`RssAnon`、`VmSwap`、`VmLck`。
## 2. 標配結果
8 GiB 上限、ctx=4096、ubatch=128、8 threads、greedy、48 requested tokens、
提示詞中英交替、**N=9 配對交錯**:
| 指標 | 值 |
| --- | ---: |
| **Decode tok/s(中位數)** | **5.83** |
| 單輪全距 | 5.19 – 6.63(抖動 25%) |
| Expert cache hit rate | 68.68% |
| SSD 讀取 / decode token | 214.1 MB |
| Arena | 6.35 GiB(3714 slots) |
| **峰值匿名 RSS** | **7799 MiB** / 8192 MiB 上限 |
| **峰值 VmSwap** | **0.000 MiB** |
| strict_ok | 9/9 |
條件:2× Intel Xeon Platinum 8559C(AVX-512 / AMX)。
> 這是**共享主機**,同一組設定的單輪抖動可達 25%。數字足以支撐「能不能跑」
> 與「大概多快」,不足以支撐 5% 等級的宣稱。
## 3. 同一個 8 GiB 上限下的傳統 llama.cpp
未修改的 llama.cpp(commit `9d7d5049`,獨立 worktree 編譯的 `llama-completion`)
在**同一個 8192 MiB 上限**下:
```
[strict8g] FAIL strict: rc=1 wall=27.3s | anon=928.8 file=20199.0 total=21127.2
ggml_aligned_malloc: insufficient memory (attempted to allocate 14400.00 MB)
ggml_backend_buft_alloc_buffer_n_default: failed to allocate CPU_REPACK buffer of size 15099494400
llama_model_load: error loading model: unable to allocate CPU_REPACK buffer
```
**模型都載不進去**,在載入階段就失敗。
| 路徑 | 結果 |
| --- | --- |
| 傳統 llama.cpp(未套 patch) | **無法載入模型** |
| EPM `epq-chat` | **5.83 tok/s**,峰值匿名 RSS 7799 MiB,VmSwap 0 |
對照必須在**同一個上限**下建立:先量「兩邊都沒限制」再比較,是把 RAM 這個
變數漏掉了。
## 3b. RAM 擴張曲線怎麼讀
完整表格在 README。方法學上兩件事必須先講清楚:
1. 這條曲線**不用 mlock**(理由見上),no-swap 的保證只保留給 8 GiB 這個部署目標。
2. **arena 不預先碰滿**。全部路由專家權重只有 18.16 GiB,所以 64 GiB 預算下
預先碰觸的上限就是 18.17 GiB;峰值 RSS 停在約 24.0 GiB 才是真實的記憶體佔用。
結論(每個點 N=9,配對交錯,Welch t 檢定,36/36 輪 strict_ok):
* **4 GiB → 8 GiB:+16.6%,t = −5.44,p = 0.0001(顯著)。**
* **8 GiB → 16 GiB:−0.8%,t = −0.30,p = 0.76(無法分辨)。**
* **8 GiB → 64 GiB:−0.2%,t = −0.05,p = 0.96(無法分辨)。**
* 命中率在 16 GiB 飽和於 77.54%,SSD/token 飽和於 ~158 MB。
* 記憶體需求飽和於約 24.0 GiB 峰值。
## 4. 儲存裝置會改變結論
本機是本地 NVMe / overlayfs,不是網路檔案系統。`tools/io_probe.py` 量到的儲存
特性決定了瓶頸在哪一邊:
* **慢速儲存上** I/O 排程是瓶頸,預取與並行讀取才有意義。
* **快速本機 NVMe 上** 命中率(arena 大小)才是瓶頸,I/O 最佳化的邊際收益很低。
本 repo 的 tok/s **不能直接套用到慢速儲存的機器上**。先跑 `tools/io_probe.py`
再決定要最佳化哪一邊。
## 5. 這個量測**沒有**證明的事
1. **不是 cgroup 驗證。** 這個環境的 cgroup2 掛載唯讀、沒有 `CAP_SYS_ADMIN`,
所以做不到 `memory.max=8G` + `memory.swap.max=0`。目前的保證是
「kernel `RLIMIT_DATA` + 取樣殺行程」與「mlock 匿名頁」兩條腿。
要做到與 cgroup 完全等價,需要在有 `CAP_SYS_ADMIN` 的主機上跑
(`docker run --memory=8g --memory-swap=8g`,進容器後確認
`memory.max=8589934592`、`memory.swap.max=0`)。
2. **主機 swap 是開啟的。** no-swap 由 `mlock` 讓它不可能發生,不是把主機的
swap 關掉。主機層級的 swap 行為沒有被測到。
3. **`brk()` 主堆疊未被鎖**(glibc 內部直接下 syscall)。大型配置都走 `mmap`。
4. **共享主機**,單輪抖動可達 25%。
5. **只驗到 4K context**;模型原生支援 262 K。
6. **只做文字推論。** vision projector 是另一個檔案,沒有測。
7. **儲存裝置比歷史量測的機器快一到兩個數量級。**
完整方法與原始資料見 `bench/results/`。
## 6. 重現
```bash
# 受管治的單次執行
python3 tools/strict8g.py --limit-mb 8192 --record /tmp/one.json -- \
epq-chat -m model.gguf -p "..." -n 48 -c 4096 -t 8 -b 128 \
--temp 0 --no-chat --no-interactive --ram-budget-mb 8192
# 標配基準(N=9,配對交錯)
python3 -m sdq.strict_bench --model model.gguf \
--bins base=./build/bin/epq-chat --rounds 9 --strict-mb 8192 --label headline
# 曲線與負結果
python3 bench/run_budget_curve.py --model model.gguf --bin ./build/bin/epq-chat --rounds 5
python3 bench/run_ramsweep.py --model model.gguf --bin ./build/bin/epq-chat --rounds 3
python3 bench/run_n9.py --model model.gguf --bin ./build/bin/epq-chat --rounds 9
python3 bench/run_negative.py --which prefetch --model model.gguf --bin ./build/bin/epq-chat
python3 bench/run_negative.py --which threads --model model.gguf --bin ./build/bin/epq-chat
```