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. 重現

# 受管治的單次執行
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