File size: 5,908 Bytes
12eb3a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# 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
```