---
hackmd:
url: https://hackmd.io/kztDWXnISIWNPOic64h5TA
title: linux final
lastSync: 2025-04-26T05:04:38.718Z
---
# 2025q1 Linux Term Project (BitNet)
## 優化方向
- memory
- NUMA
- mmap (llama.cpp 原本使用)
- Huge Page
- THP
- I/O
- `io_uring`
- scheduler
- FIFO
- CFS
- cpu bound / io bound
## 信件
- [信件](https://mail.google.com/mail/u/0/#inbox/QgrcJHsTmbFLcjVnzSxbVNxsTFFmpJPbdbv) [facebook post](https://www.facebook.com/groups/system.software2025/posts/973447758325359/)
- BitNet
- [在 GNU/Linux 系統運作 BitNet b1.58 2B4T 並重現論文實驗](#%E5%9C%A8-GNULinux-%E7%B3%BB%E7%B5%B1%E9%81%8B%E4%BD%9C-BitNet-b158-2B4T-%E4%B8%A6%E9%87%8D%E7%8F%BE%E8%AB%96%E6%96%87%E5%AF%A6%E9%A9%97)
* [以 perf 在內的工具,測量推理過程中運算資源佔比前 20 大的函式,並探討其作用](#%E4%BB%A5-perf-%E5%9C%A8%E5%85%A7%E7%9A%84%E5%B7%A5%E5%85%B7%EF%BC%8C%E6%B8%AC%E9%87%8F%E6%8E%A8%E7%90%86%E9%81%8E%E7%A8%8B%E4%B8%AD%E9%81%8B%E7%AE%97%E8%B3%87%E6%BA%90%E4%BD%94%E6%AF%94%E5%89%8D-20-%E5%A4%A7%E7%9A%84%E5%87%BD%E5%BC%8F%EF%BC%8C%E4%B8%A6%E6%8E%A2%E8%A8%8E%E5%85%B6%E4%BD%9C%E7%94%A8)
* [分析記憶體使用量,特別是過程中的 page fault, TLB miss 等統計](#%E5%88%86%E6%9E%90%E8%A8%98%E6%86%B6%E9%AB%94%E4%BD%BF%E7%94%A8%E9%87%8F%EF%BC%8C%E7%89%B9%E5%88%A5%E6%98%AF%E9%81%8E%E7%A8%8B%E4%B8%AD%E7%9A%84-page-fault-TLB-miss-%E7%AD%89%E7%B5%B1%E8%A8%88)。在 XMRig [2] 一類的挖礦程式中,善用 huge page (或 THP),可達到加速效果
* [評估 T-MAC[3] [5],特別是其搭配 BitNet 的查表效益,紀錄過程中的 perf 事件統計](#%E8%A9%95%E4%BC%B0-T-MAC%EF%BC%8C%E7%89%B9%E5%88%A5%E6%98%AF%E5%85%B6%E6%90%AD%E9%85%8D-BitNet-%E7%9A%84%E6%9F%A5%E8%A1%A8%E6%95%88%E7%9B%8A%EF%BC%8C%E7%B4%80%E9%8C%84%E9%81%8E%E7%A8%8B%E4%B8%AD%E7%9A%84-perf-%E4%BA%8B%E4%BB%B6%E7%B5%B1%E8%A8%88)
* 觀察載入模型的機制,能否用 splice [4] 一類的機制予以加速
- 如何藉由 Linux 核心的機制來加速 BitNet
[1] [https://github.com/microsoft/BitNet](https://github.com/microsoft/BitNet)
[2] [https://xmrig.com/docs/miner/hugepages](https://xmrig.com/docs/miner/hugepages)
[3] [https://github.com/microsoft/T-MAC](https://github.com/microsoft/T-MAC)
[4] [https://hackmd.io/@sysprog/linux-zerocopy](https://hackmd.io/@sysprog/linux-zerocopy)
[5] BitNet 有 LUT: [https://github.com/microsoft/BitNet/tree/main/src](https://github.com/microsoft/BitNet/tree/main/src)
## TODO (start)
- [名詞解釋](https://hackmd.io/@alanhc/B1ZkopHmlx)
- [profiling](/JqdN3X6hQSm5A4-NsKEMFQ)
- [ ] ik_llama.cpp [筆記](https://hackmd.io/@alanhc/BkyjoTHmll)
- [ ] [評估 ik_llama.cpp 效能表現](#%E8%A9%95%E4%BC%B0-ik_llamacpp-%E6%95%88%E8%83%BD%E8%A1%A8%E7%8F%BE)
- [ ] BitNet [筆記](https://hackmd.io/@alanhc/rkJJoPnzel)
- [x] [在 GNU/Linux 系統運作 BitNet b1.58 2B4T 並重現論文實驗](#%E5%9C%A8-GNULinux-%E7%B3%BB%E7%B5%B1%E9%81%8B%E4%BD%9C-BitNet-b158-2B4T-%E4%B8%A6%E9%87%8D%E7%8F%BE%E8%AB%96%E6%96%87%E5%AF%A6%E9%A9%97)
- [x] [以 perf 在內的工具,測量推理過程中運算資源佔比前 20 大的函式,並探討其作用](#%E4%BB%A5-perf-%E5%9C%A8%E5%85%A7%E7%9A%84%E5%B7%A5%E5%85%B7%EF%BC%8C%E6%B8%AC%E9%87%8F%E6%8E%A8%E7%90%86%E9%81%8E%E7%A8%8B%E4%B8%AD%E9%81%8B%E7%AE%97%E8%B3%87%E6%BA%90%E4%BD%94%E6%AF%94%E5%89%8D-20-%E5%A4%A7%E7%9A%84%E5%87%BD%E5%BC%8F%EF%BC%8C%E4%B8%A6%E6%8E%A2%E8%A8%8E%E5%85%B6%E4%BD%9C%E7%94%A8)
- [ ] T-MAC [筆記](https://hackmd.io/@alanhc/t-mac)
- [ ] [T-MAC 載入模型的機制](#T-MAC-%E8%BC%89%E5%85%A5%E6%A8%A1%E5%9E%8B%E7%9A%84%E6%A9%9F%E5%88%B6)
- [ ] [分析記憶體使用量,特別是過程中的 page fault, TLB miss 等統計](#%E5%88%86%E6%9E%90%E8%A8%98%E6%86%B6%E9%AB%94%E4%BD%BF%E7%94%A8%E9%87%8F%EF%BC%8C%E7%89%B9%E5%88%A5%E6%98%AF%E9%81%8E%E7%A8%8B%E4%B8%AD%E7%9A%84-page-fault-TLB-miss-%E7%AD%89%E7%B5%B1%E8%A8%88)
- [ ] [使用 perf 分析 T-MAC](#%E4%BD%BF%E7%94%A8-perf-%E5%88%86%E6%9E%90-T-MAC)
- [ ] [使用 uftrace 定位內積運算的效能成本](#%E4%BD%BF%E7%94%A8-uftrace-%E5%AE%9A%E4%BD%8D%E5%85%A7%E7%A9%8D%E9%81%8B%E7%AE%97%E7%9A%84%E6%95%88%E8%83%BD%E6%88%90%E6%9C%AC)
- [ ] scheduler
- [ ] [關閉 GGML_USE_OPENMP,由 llama.cpp 自行設計的多執行緒機制進行矩陣運算,避免 GCC/LLVM 提供的 OpenMP 帶來的不確定性。](#%E9%97%9C%E9%96%89-GGML_USE_OPENMP%EF%BC%8C%E7%94%B1-llamacpp-%E8%87%AA%E8%A1%8C%E8%A8%AD%E8%A8%88%E7%9A%84%E5%A4%9A%E5%9F%B7%E8%A1%8C%E7%B7%92%E6%A9%9F%E5%88%B6%E9%80%B2%E8%A1%8C%E7%9F%A9%E9%99%A3%E9%81%8B%E7%AE%97%EF%BC%8C%E9%81%BF%E5%85%8D-GCCLLVM-%E6%8F%90%E4%BE%9B%E7%9A%84-OpenMP-%E5%B8%B6%E4%BE%86%E7%9A%84%E4%B8%8D%E7%A2%BA%E5%AE%9A%E6%80%A7%E3%80%82)
- [ ] [嘗試改用 FIFO 排程策略](#%E5%98%97%E8%A9%A6%E6%94%B9%E7%94%A8-FIFO-%E6%8E%92%E7%A8%8B%E7%AD%96%E7%95%A5)
- [ ] [改進 llama.cpp 的內部排程機制,見 ggml: Implement yield barrier using futex for improved thread scheduling efficiency](https://hackmd.io/kztDWXnISIWNPOic64h5TA#%E6%94%B9%E9%80%B2-llamacpp-%E7%9A%84%E5%85%A7%E9%83%A8%E6%8E%92%E7%A8%8B%E6%A9%9F%E5%88%B6%EF%BC%8C%E8%A6%8B-ggml-Implement-yield-barrier-using-futex-for-improved-thread-scheduling-efficiency)
- [ ] [關閉 `GGML_USE_OPENMP`,由 `llama.cpp` 自行設計的多執行緒機制進行矩陣運算,避免 GCC/LLVM 提供的 OpenMP 帶來的不確定性。](#%E9%97%9C%E9%96%89-GGML_USE_OPENMP%EF%BC%8C%E7%94%B1-llamacpp-%E8%87%AA%E8%A1%8C%E8%A8%AD%E8%A8%88%E7%9A%84%E5%A4%9A%E5%9F%B7%E8%A1%8C%E7%B7%92%E6%A9%9F%E5%88%B6%E9%80%B2%E8%A1%8C%E7%9F%A9%E9%99%A3%E9%81%8B%E7%AE%97%EF%BC%8C%E9%81%BF%E5%85%8D-GCCLLVM-%E6%8F%90%E4%BE%9B%E7%9A%84-OpenMP-%E5%B8%B6%E4%BE%86%E7%9A%84%E4%B8%8D%E7%A2%BA%E5%AE%9A%E6%80%A7%E3%80%82)
- [ ] [改進 llama.cpp 的內部排程機制,見 ggml: Implement yield barrier using futex for improved thread scheduling efficiency](#%E6%94%B9%E9%80%B2-llamacpp-%E7%9A%84%E5%85%A7%E9%83%A8%E6%8E%92%E7%A8%8B%E6%A9%9F%E5%88%B6%EF%BC%8C%E8%A6%8B-ggml-Implement-yield-barrier-using-futex-for-improved-thread-scheduling-efficiency)
- [ ] memory
- [ ] [現行 llama.cpp 的 mmap 載入機制](#%E7%8F%BE%E8%A1%8C-llamacpp-%E7%9A%84-mmap-%E8%BC%89%E5%85%A5%E6%A9%9F%E5%88%B6)
- [ ] [TODO: 使用 Huge Page 加速](#TODO-%E4%BD%BF%E7%94%A8-Huge-Page-%E5%8A%A0%E9%80%9F)
- [ ] [評估 Huge Page 效益 io_uring](#%E8%A9%95%E4%BC%B0-Huge-Page-%E6%95%88%E7%9B%8A)
- [ ] [實驗開啟 THP 與否對於 BitNet 推論的 perf stat 結果](#%E5%AF%A6%E9%A9%97%E9%96%8B%E5%95%9F-THP-%E8%88%87%E5%90%A6%E5%B0%8D%E6%96%BC-BitNet-%E6%8E%A8%E8%AB%96%E7%9A%84-perf-stat-%E7%B5%90%E6%9E%9C)
- [ ] [評估 T-MAC,特別是其搭配 BitNet 的查表效益,紀錄過程中的 perf 事件統計](#%E8%A9%95%E4%BC%B0-T-MAC%EF%BC%8C%E7%89%B9%E5%88%A5%E6%98%AF%E5%85%B6%E6%90%AD%E9%85%8D-BitNet-%E7%9A%84%E6%9F%A5%E8%A1%A8%E6%95%88%E7%9B%8A%EF%BC%8C%E7%B4%80%E9%8C%84%E9%81%8E%E7%A8%8B%E4%B8%AD%E7%9A%84-perf-%E4%BA%8B%E4%BB%B6%E7%B5%B1%E8%A8%88)
- [ ] 對照閱讀 Introduce New Lookup-Table(LUT)-Based Matrix Multiplication Method 討論
## 本篇實驗環境
- ASUS gl552vw 6700hq 16g ram hdd
- Intel nuc7i7dnhe i7 8650U 16g ram ssd
- Intel 14700 (desktop) 64g ram ssd
- perf 設定
- `sudo sh -c 'echo -1 > /proc/sys/kernel/perf_event_paranoid'`
## Backgrounds
- [huge page](https://hackmd.io/5LvqUZQ4RtmkQAoz3Dw6Bw#Huge-Page)
- [llama.cpp](/fdgpX6KkTSmBiLJzpakb-A)
- [profiling](/JqdN3X6hQSm5A4-NsKEMFQ)
- [bitnet](/K_oSz8V5RcibFD04lj-Emg)
- BitNet 有潛在的弱點:[TriLM vs FloatLM:
Ternary LLMs are more Performant than Quantized FP16 LLMs](https://openreview.net/pdf?id=gvaDL9omKU)
- [t-mac](/6U2Ac9Z7QB2GfFBYtNcxPw)
### 發展歷史
- 25 Apr 2025 BitNet b1.58 - v2
- 16 Apr 2025 BitNet b1.58 - v1
- 25 Mar 2025 TMAC - v2
- 25 Jun 2024 TMAC - V1
- 17 Oct 2023 BitNet
[A Survey of Low-bit Large Language Models: Basics, Systems, and Algorithm](https://arxiv.org/abs/2409.16694)
## T-MAC 載入模型的機制
- [T-MAC 載入模型的機制](https://hackmd.io/6U2Ac9Z7QB2GfFBYtNcxPw#T-MAC-%E8%BC%89%E5%85%A5%E6%A8%A1%E5%9E%8B%E7%9A%84%E6%A9%9F%E5%88%B6)
## 以 perf 在內的工具,測量推理過程中運算資源佔比前 20 大的函式,並探討其作用
- cmake:呼叫 CMake 指令。
- `-B build`:指定 build 目錄(等同於 mkdir build && cd build)。
- `-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS=...`:設定建置類型與 C++ 編譯旗標。
- `RelWithDebInfo`:代表 最佳化(Release)等級,同時保留 debug symbol(可用於除錯)。
- `-g`:加上除錯符號。
- `-fno-omit-frame-pointer`:保留 call stack,用於 perf, gdb, uftrace 等工具追蹤堆疊。
- `BitNet/setup_env.py` 加入
```python
def compile():
...
run_command(["cmake", "-B", "build", "-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS=-g -fno-omit-frame-pointer"], log_step="generate_build_files")
run_command(["cmake", "--build", "build", "--config", "RelWithDebInfo"], log_step="compile")
```
- 使用 perf 分析
- `perf record -g python run_inference.py -m ~/models/BitNet-b1.58-2B-4T/ggml-model-i2_s.gguf -p "hi, how are you" -n 10 --temp 0`
- `perf report` 結果
https://gist.github.com/alanhc/a03265dde546bf9b8cfaedc0643e2a81
{%gist a03265dde546bf9b8cfaedc0643e2a81 %}

| 排名 | 函式名稱 | 所在模組 | % cycles | 說明 |
| -- | ---------------------------------------- | -------------------- | -------- | ------------------------------------------------ |
| 1 | `ggml_graph_compute.omp_outlined` | `libggml.so` | \~90% | OpenMP 包裹的主計算 kernel,負責所有 graph node 計算分派。 |
| 2 | `__kmp_invoke_microtask` | `libomp.so.5` | \~90% | OpenMP runtime 啟動的子任務(thread 工作),會呼叫 ggml 的並行算子。 |
| 3 | `ggml_compute_forward_thread` | `libggml.so` | \~90% | 每條 thread 執行個別 graph node 計算的實體函式。 |
| 4 | `ggml_compute_forward` | `libggml.so` | \~89% | ggml 對一個 node 做 forward 計算(包含矩陣乘、activation 等)。 |
| 5 | `ggml_compute_forward_mul_mat` | `libggml.so` | \~88% | 主要的矩陣乘法 (MatMul) 運算入口。 |
| 6 | `ggml_compute_forward_mul_mat_one_chunk` | `libggml.so` | \~87% | 被 `mul_mat` 呼叫,實際將大矩陣切成 chunk 執行。 |
| 7 | `ggml_vec_dot_i2_i8_s` | `libggml.so` | \~52% | **i2/i8 壓縮格式的向量點積運算**(表示使用了低位元量化模型)。這是核心運算。 |
| 8 | `llama_decode` | `libllama.so` | \~46% | 解碼流程主函式,呼叫 ggml graph 執行 token 推論。 |
| 9 | `llama_decode_internal` | `libllama.so` | \~46% | 實際實作:將 prompt context -> logits 的一輪計算。 |
| 10 | `llama_graph_compute` | `libllama.so` | \~46% | 封裝 graph 執行的介面,用於解耦 backend 與 compute。 |
| 11 | `ggml_backend_sched_graph_compute_async` | `libggml.so` | \~46% | 啟動非同步推論(threadpool 排程器)。 |
| 12 | `ggml_backend_sched_compute_splits` | `libggml.so` | \~46% | 將 ggml graph 拆分並分派給各 thread。 |
| 13 | `ggml_backend_graph_compute_async` | `libggml.so` | \~46% | 非同步 graph 計算主入口。 |
| 14 | `ggml_backend_cpu_graph_compute` | `libggml.so` | \~46% | 用 CPU backend 執行 ggml graph(vs GPU/NPU)。 |
| 15 | `ggml_graph_compute` | `libggml.so` | \~46% | graph 計算主入口,呼叫 backend。 |
| 16 | `__kmpc_fork_call` | `libomp.so.5` | \~45% | OpenMP fork 任務的函式,設立 thread team。 |
| 17 | `main` → `__libc_start_main` | `llama-cli` / `libc` | \~55% | 啟動點。佔比其實只是因為包含整段 call stack。 |
| 18 | `_start` | `llama-cli` | \~55% | binary entry point,非實際計算熱點。 |
| 19 | `libc.so.6` 相關 entry | `libc.so.6` | \~55% | 應屬呼叫栈初始 setup,無實質運算負擔。 |
| 20 | `0x…` 匿名符號 | `libomp.so.5` | \~89% | OpenMP runtime 中的匿名指標,通常與 microtask 相關。 |
| 熱點函數 | 說明 |
| ------------------------------ | ---------------------------------- |
| `ggml_vec_dot_i2_i8_s` | INT2 的 bitwise 內積運算,為 BitNet 專屬 |
| `ggml_compute_forward_mul_mat` | ggml 中最主要的 matmul 入口 |
| `libomp.so.5` 多次出現在 call stack | 表示大量計算平行化透過 OpenMP 進行 |
| `ggml_vec_dot_f16` | 有部分非量化張量還在使用 F16(推論時可能是 embedding) |
- 火焰圖

- 簡化流程圖
```mermaid
graph TD
llama_cli_main --> llama_decode
llama_decode --> llama_graph_compute
llama_graph_compute --> ggml_graph_compute
ggml_graph_compute --> ggml_compute_forward
ggml_compute_forward --> ggml_compute_forward_mul_mat
ggml_compute_forward_mul_mat --> ggml_vec_dot_i2_i8_s
```
### TODO: 使用 Huge Page 加速
> 在 XMRig 一類的挖礦程式中,善用 huge page (或 THP),可達到加速效果
- [XMRig](https://deepwiki.com/xmrig/xmrig)
- [https://xmrig.com/docs/miner/hugepages](https://xmrig.com/docs/miner/hugepages)
- [Huge page 開啟方法](https://hackmd.io/5LvqUZQ4RtmkQAoz3Dw6Bw#Huge-Page)
- [舊實驗](https://gist.github.com/alanhc/474128a1e581a6f36e32a2a032d9d4e1)
## 在 GNU/Linux 系統運作 BitNet b1.58 2B4T 並重現論文實驗
- [重現論文實驗](https://hackmd.io/K_oSz8V5RcibFD04lj-Emg#%E9%87%8D%E7%8F%BE%E8%AB%96%E6%96%87%E5%AF%A6%E9%A9%97)
## 現行 llama.cpp 的 mmap 載入機制
- [現行 llama.cpp 的 mmap 載入機制](https://hackmd.io/fdgpX6KkTSmBiLJzpakb-A#%E7%8F%BE%E8%A1%8C-llamacpp-%E7%9A%84-mmap-%E8%BC%89%E5%85%A5%E6%A9%9F%E5%88%B6)
## 分析記憶體使用量,特別是過程中的 page fault, TLB miss 等統計
- 在 BitNet 裡面
> 在 [XMRig](https://deepwiki.com/xmrig/xmrig) 一類的挖礦程式中,善用 huge page (或 THP),可達到加速效果
```
sudo perf stat -e cycles,instructions,minor-faults,major-faults,dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses,page-faults,cache-references,cache-misses \
./build/bin/llama-cli \
-m ./models/BitNet-b1.58-2B-4T/ggml-model-i2_s.gguf \
-p "Hi, how are you?" \
-n 10 \
--temp 0.0 \
--top_k 1 \
--ignore-eos \
```
- result
- https://gist.github.com/alanhc/701f510a45fddfe399f60451fa93f3e2
- IPC
| 指標 | 數值 | 評估 |
| -------------- | ---- | ---------------------- |
| `cpu_core` IPC | 2.82 | ✅ 很高,代表 pipeline 執行效率佳 |
| `cpu_atom` IPC | 1.73 | ✅ 合理,Atom 核心通常效率較低 |
- Cache
| 項目 | 數值 | miss rate |
| --------------------------- | ---- | ---------- |
| `cpu_core/cache-references` | 295M | - |
| `cpu_core/cache-misses` | 127M | **43.13%** |
| `cpu_atom/cache-misses` | 34M | **46.28%** |
- cache miss rate 超過 40% 代表模型存取 memory 的方式缺乏 spatial / temporal locality。可能包括:
- 推論過程使用的 int2 模型壓縮方式導致權重分佈不連續
- 缺乏 cache blocking / tiled matrix multiplication
- 輸出 token 數太少,導致 init + graph 建立的成本無法 amortize
- 記憶體系統:TLB
| 項目 | 數值 | 說明 |
| --------------------- | ----- | --------------------------- |
| `cpu_core/dTLB-loads` | 44.6B | 記憶體存取多 |
| `dTLB-load-misses` | 4.3M | **miss rate 僅 \~0.009%**,很低 |
| `iTLB-load-misses` | 56K | 正常範圍,表示指令 cache 命中良好 |
- Page Faults
| 指標 | 數值 |
| ------------ | ---- |
| minor-faults | 101K |
| major-faults | 0 |
- 時間
| 指標 | 數值 |
| ----------------- | ------ |
| `real time` | 1.80 秒 |
| `user + sys time` | 7.59 秒 |
| 平均執行緒數估計 | ≈ 4.2 |

- iTLB-load-misses 有點高
- 指令翻譯錯誤率極高
- 大量小 page 導致 instruction fetch 時反覆 page table walk
- cache-misses
- cache miss 非常高
- memory access 沒有局部性,跨 page 很嚴重
- **iTLB miss** 這麼高 → **指令區(程式本身)破碎、翻譯負擔重**。
- **cache miss** 這麼高 → **資料區(模型權重、activation)散亂**。
## 實驗開啟 THP 與否對於 BitNet 推論的 perf stat 結果
- 沒開
- https://gist.github.com/alanhc/14355d2075dad3d4c6e185f496b6f8b2
- 開啟
https://gist.github.com/alanhc/4e1a45d4b4143125296567c7d0e13da8
### 數據對比
| 指標 | **THP 關閉** | **THP 開啟** | 差異 (%) |
| ---------------------- | ---------- | ----------- | --------------- |
| ⏱️ Time Elapsed | 1.817 sec | 1.823 sec | 🔄 +0.33%(幾乎無差) |
| 🧠 Instructions (Core) | 108.73B | 105.32B | 🔽 -3.1% |
| 🔄 Cycles (Core) | 38.76B | 36.89B | 🔽 -4.8% |
| ⚙ IPC (Core) | 2.80 | **2.86** | 🔼 +2.1% |
| 🧭 Cache References | 294.48M | 289.08M | 🔽 -1.8% |
| ❗ Cache Misses | 125.97M | **126.14M** | 🔼 +0.1% |
| 🧮 Cache Miss Ratio | 42.78% | **43.63%** | 🔼 +0.85% |
| 🔄 dTLB Misses (Core) | 4.42M | **3.12M** | 🔽 -29.5% ✅ |
| 🔄 iTLB Misses (Core) | 49.17K | 46.74K | 🔽 -4.9% ✅ |
| ⚠ Minor Page Faults | 101K | **21K** | 🔽 -79% ✅ |
- 分析結果
- 優化
- dTLB Misses 大幅減少(-29.5%)
- THP 的最大效益就是減少 TLB misses,因為它把原本 4 KiB 的頁面變成 2 MiB,整體頁表壓縮 512 倍。
- Minor Page Fault 減少(-79%)
- 大頁面減少了觸發頁面分配的次數。
- Core IPC 增加(2.80 → 2.86)
- 處理器每個 cycle 執行更多指令,表示 memory bottleneck 稍微改善。
- 持平
- Cache Miss 數量基本持平
- 因為 THP 改變的是 虛擬頁面對應與 TLB 命中率,對 L1/L2 cache miss 的幫助有限。
- Total time elapsed 幾乎沒變
- 表示整體推論邏輯仍以 CPU compute-bound 為主,THP 改變的是記憶體存取行為,而不是 compute-intensive 結構。
## 評估 T-MAC,特別是其搭配 BitNet 的查表效益,紀錄過程中的 perf 事件統計
:::success
$\to$ TODO: 對照閱讀 [Introduce New Lookup-Table(LUT)-Based Matrix Multiplication Method](https://github.com/ggml-org/llama.cpp/pull/10181) 討論
:::
::: info
這看起來是 T-MAC 的維護者 QingtaoLi1 要整合進 ggml-org/llama.cpp slaren 表示可能會有不好整合,這可能是一個貢獻點?
- T-MAC 會需要消除所有第三方依賴,因為 llama.cpp 的設計原則是不依賴外部函式庫
- 這裡面好像也由提到一些 NPU 或不同硬體規格下的效能
- 他在 12700 及 M2 Ultra 做測試,我有一台 m1 pro 可能可以測試
:::
- TODO
- 整合進 llama.cpp
- `~/models/BitNet-b1.58-2B-4T/ggml-model-i2_s.gguf`
sudo perf stat -e cycles,instructions,minor-faults,major-faults,dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses,page-faults,cache-references,cache-misses ./3rdparty/llama.cpp/build/bin/llama-cli -m ~/models/BitNet-b1.58-2B-4T/ggml-model-i2_s.gguf -p "Hi, how are you?" --temp 0.0
```
perf stat -e cycles,instructions,minor-faults,major-faults,dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses,page-faults,cache-references,cache-misses ./3rdparty/llama.cpp/build/bin/llama-bench -m ~/model/model.INT_N.gguf -n 128 -t 28 -p 256 -ngl 0
```
```
perf stat -e cycles,instructions,minor-faults,major-faults,dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses,page-faults,cache-references,cache-misses ./3rdparty/llama.cpp/build/bin/llama-bench -m ~/model/model.INT_N.gguf -n 128 -t 4 -p 256 -ngl 0
```
- result
- intel 14700
- https://gist.github.com/alanhc/a422987be400751cbfb984fc1dfffea8
[https://github.com/microsoft/T-MAC](https://github.com/microsoft/T-MAC)
https://deepwiki.com/microsoft/T-MAC
- iTLB-loads / misses (6.4M / 3.1M → 48.49% miss rate)
- 指令 TLB 有接近一半 miss,可能表示程式碼分散或使用了大量函式呼叫。可考慮使用 hugepages 或降低動態程式碼載入。
- cache-misses (15.98G → 39.85% miss rate)
- 相當高的 cache miss,可能是模型參數或中介資料沒有 fit 到 L3 cache。需考慮:
- 調整 batch size
- 使用 NUMA-aware 執行或 memory layout 優化
BitNet 有 LUT: [https://github.com/microsoft/BitNet/tree/main/src](https://github.com/microsoft/BitNet/tree/main/src)
## 使用 uftrace 定位內積運算的效能成本
- https://github.com/namhyung/uftrace
```shell
cd ~/workspace/T-MAC/3rdparty/llama.cpp
rm -rf build && mkdir build && cd build
```
- `sudo apt install libc++-dev libc++abi-dev`
```
delete
export INSTR_FLAGS="-finstrument-functions -fno-omit-frame-pointer -stdlib=libc++"
```
```shell
export CC=clang
export CXX=clang++
export INSTR_FLAGS="-fno-omit-frame-pointer -stdlib=libc++"
cmake .. \
-DGGML_TMAC=ON \
-DCMAKE_PREFIX_PATH=/home/alanhc/workspace/T-MAC/install/lib/cmake/t-mac \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_C_FLAGS="$INSTR_FLAGS" \
-DCMAKE_CXX_FLAGS="$INSTR_FLAGS"
cmake --build . --target llama-cli -j$(nproc)
```
- 使用 uftrace
```
LD_LIBRARY_PATH=/usr/lib/llvm-18/lib:$LD_LIBRARY_PATH \
uftrace record -a ./bin/llama-cli \
-m ~/model/model.INT_N.gguf -n 5 -t 28 -p "hello" --temp 0
```
:::info
上面設定目前會有 WARN: child terminated by signal: 7: Bus error 的錯誤,正在排查中,且RAM會不斷增長
應該跟 /dev/shm 有關
可以把這清空避免記憶體炸掉: `sudo find /dev/shm -mindepth 1 -delete`

解法:用 filter 不要一次觀察太多 function
:::
- 看報告
- `uftrace report`
https://gist.github.com/alanhc/5b2f479c0bbf2a7540d2e3b95471fa23
- `14.994 s 14.994 s 38 linux:schedule`
- 意義:花 15 秒在等待 thread 被排入 CPU 執行(context switch)
- 猜測:thread 被 OS 搶走了 → idle 或阻塞等待 → 無法持續算下去。
## 使用 perf 分析 T-MAC
- cmd: `sudo perf record -g ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 2 -p "hello" --temp 0`
- https://gist.github.com/alanhc/98aabd4b843536159316bb123f55dd87
- CPU 資源幾乎完全耗在 OpenMP 多執行緒矩陣運算上(libomp.so.5 和 libggml.so)
- 核心 bottleneck 在於:
- ggml_compute_forward_mul_mat(矩陣乘法 forward)
- tbl_g4_int8_int32_update_impl<16, 2>(可能是 INT8 量化矩陣乘法)
- TMACGeMMWrapper::llama_cpp_compute(...)(QGEMM kernel 包裝)
```
# 這個是 -99
llama_perf_sampler_print: sampling time = 0.13 ms / 12 runs ( 0.01 ms per token, 90909.09 tokens per second)
llama_perf_context_print: load time = 2227.85 ms
llama_perf_context_print: prompt eval time = 258.68 ms / 2 tokens ( 129.34 ms per token, 7.73 tokens per second)
llama_perf_context_print: eval time = 1326.53 ms / 9 runs ( 147.39 ms per token, 6.78 tokens per second)
llama_perf_context_print: total time = 1588.04 ms / 11 tokens
Performance counter stats for './3rdparty/llama.cpp/build/bin/llama-cli -m /home/alanhc/model/model.INT_N.gguf -n 10 -t 28 -p hi --temp 0':
183,218,574,897 cpu_atom/cycles/ (94.41%)
292,689,953,915 cpu_core/cycles/ (95.34%)
105,868,429,772 cpu_atom/instructions/ # 0.58 insn per cycle (94.41%)
130,288,033,071 cpu_core/instructions/ # 0.45 insn per cycle (95.34%)
170,662 minor-faults
0 major-faults
38,306,828,734 cpu_atom/dTLB-loads/ (94.41%)
43,447,572,227 cpu_core/dTLB-loads/ (95.34%)
595,512 cpu_atom/dTLB-load-misses/ # 0.00% of all dTLB cache accesses (94.41%)
1,054,263 cpu_core/dTLB-load-misses/ # 0.00% of all dTLB cache accesses (95.34%)
<not supported> cpu_atom/iTLB-loads/
<not supported> cpu_core/iTLB-loads/
1,740,032 cpu_atom/iTLB-load-misses/ (94.41%)
27,339 cpu_core/iTLB-load-misses/ (95.34%)
170,662 page-faults
148,428,442 cpu_atom/cache-references/ (94.41%)
169,337,301 cpu_core/cache-references/ (95.34%)
77,247,617 cpu_atom/cache-misses/ # 52.04% of all cache refs (94.41%)
78,828,727 cpu_core/cache-misses/ # 46.55% of all cache refs (95.34%)
3.930637455 seconds time elapsed
96.185629000 seconds user
0.214916000 seconds sys
```
- cache miss rate: 52% (atom), 46% (core) 非常高,代表 cache 行為差,L1/L2 失效率高
- IPC (atom/core) 0.58 / 0.45 很低 ➜ CPU 被 memory 或 cache 限制
- iTLB miss atom: 1.7M instruction fetch 還好,但稍高
- 整體來看是 memory-bound(尤其 cache-bound)而非 CPU-bound。
- `sudo perf report`

| Symbol (函式) | Total % | Self % | 說明 |
| ------------------------------------- | ------- | ------ | ----------------------------------------------- |
| `ggml_graph_compute_thread` | 91.59% | 43.35% | 多數計算集中在這個主執行緒(佔總時間近一半) |
| `ggml_compute_forward` | 47.54% | 0.00% | forward 運算主流程(呼叫各種 layer/block) |
| `ggml_compute_forward_mul_mat` | 47.25% | 7.17% | 矩陣乘法核心入口 |
| `ggml_tmac_mul_mat_task_compute` | 36.46% | 0.00% | 這是 TMAC 矩陣乘法封裝任務 |
| `TMACGeMMWrapper::llama_cpp_compute` | 36.46% | 0.00% | QInt kernel 的 wrapper |
| `tbl_g4_int8_int32_update_impl<16,2>` | 34.39% | 19.26% | 🚨**主熱點!** 你的矩陣乘法核(可能是 table-based INT8 kernel) |
| `qgemm_lut_t1_int8_*` | 12\~17% | -- | 多種不同參數的 LUT-based GEMM kernel |
`./stackcollapse-perf.pl out.perf > out.folded`
`./flamegraph.pl out.folded > flamegraph.svg`

### 另一次實驗
```
export OMP_PROC_BIND=true
export OMP_PLACES=cores
```
```
export CC=clang
export CXX=clang++
export INSTR_FLAGS="-fno-omit-frame-pointer -stdlib=libc++"
cmake .. \
-DGGML_TMAC=ON \
-DCMAKE_PREFIX_PATH=/home/alanhc/workspace/T-MAC/install/lib/cmake/t-mac \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_C_FLAGS="$INSTR_FLAGS" \
-DCMAKE_CXX_FLAGS="$INSTR_FLAGS"
cmake --build . --target llama-cli -j$(nproc)
```
` sudo perf record -g ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 1 -p "hello" --temp 0`
`sudo perf report`
```
Samples: 5K of event 'cpu_core/cycles/P', Event count (approx.): 7699093253
Children Self Command Shared Object Symbol
+ 99.97% 0.00% llama-cli llama-cli [.] _start
+ 99.97% 0.00% llama-cli libc.so.6 [.] __libc_start_main@@GLIBC_2.34
+ 99.97% 0.00% llama-cli libc.so.6 [.] __libc_start_call_main
+ 99.95% 0.00% llama-cli llama-cli [.] main
+ 82.22% 0.00% llama-cli libllama.so [.] llama_decode
+ 82.09% 0.00% llama-cli libllama.so [.] llama_graph_compute(llama_context&, ggml_cgraph*, int, ggml_threadpool*)
+ 82.09% 0.00% llama-cli libggml.so [.] ggml_backend_sched_graph_compute_async
+ 82.09% 0.00% llama-cli libggml.so [.] ggml_backend_cpu_graph_compute(ggml_backend*, ggml_cgraph*)
+ 81.94% 0.00% llama-cli libggml.so [.] ggml_graph_compute
+ 81.87% 0.07% llama-cli libggml.so [.] ggml_graph_compute_thread
+ 69.14% 0.31% llama-cli libggml.so [.] ggml_compute_forward_mul_mat
+ 67.97% 0.02% llama-cli libggml.so [.] ggml_tmac_mul_mat_task_compute
+ 67.95% 0.02% llama-cli libggml.so [.] TMAC::TMACGeMMWrapper<float, 4>::llama_cpp_compute(void*, void*, void*, void*, void*, vo
+ 67.50% 67.43% llama-cli libggml.so [.] int tbl_g4_int8_int32_update_impl<16, 2>(int, int*, signed char*, unsigned char*)
+ 30.18% 0.10% llama-cli libggml.so [.] qgemm_lut_t1_int8_m640_k3200_n1_b2
+ 24.09% 0.00% llama-cli llama-cli [.] llama_init_from_gpt_params(gpt_params&)
+ 22.52% 0.19% llama-cli libggml.so [.] qgemm_lut_t1_int8_m128_k3200_n1_b2
+ 15.16% 0.12% llama-cli libggml.so [.] qgemm_lut_t1_int8_m256_k8640_n1_b2
+ 11.46% 3.57% llama-cli libc.so.6 [.] __memset_avx2_unaligned_erms
+ 10.68% 10.65% llama-cli libggml.so [.] ggml_vec_dot_f16
+ 10.08% 0.09% llama-cli [kernel.kallsyms] [k] asm_exc_page_fault
+ 7.71% 0.00% llama-cli [kernel.kallsyms] [k] handle_mm_fault
+ 7.58% 0.14% llama-cli [kernel.kallsyms] [k] __handle_mm_fault
+ 7.40% 0.02% llama-cli [kernel.kallsyms] [k] handle_pte_fault
+ 6.62% 0.02% llama-cli [kernel.kallsyms] [k] exc_page_fault
+ 6.48% 0.09% llama-cli [kernel.kallsyms] [k] do_user_addr_fault
Cannot load tips.txt file, please install perf!
```
🔍 重點觀察分析
1. 絕大多數 CPU 時間在 llama_decode → ggml_graph_compute
llama_decode:82.22%
ggml_graph_compute_thread → ggml_compute_forward_mul_mat:主導整體執行
重點內核:
tbl_g4_int8_int32_update_impl<16, 2>:67.50% Self Time
ggml_vec_dot_f16:10.65% Self Time
__memset_avx2_unaligned_erms:3.57% Self Time
👉 表示目前主要瓶頸是 低階矩陣乘法與資料初始化/搬移過程。
2. 記憶體相關錯誤處理開銷
asm_exc_page_fault, handle_mm_fault, __handle_mm_fault 合計近 10%
暗示:
有可能是過度記憶體分配(大量 mmap 或懶加載 tensor)
cache miss 或 TLB miss 頻繁
```
export CC=clang
export CXX=clang++
# 加入 uftrace 所需的插裝與除錯資訊
export INSTR_FLAGS="-fno-omit-frame-pointer -finstrument-functions -g -O2 -stdlib=libc++"
cmake .. \
-DGGML_TMAC=ON \
-DCMAKE_PREFIX_PATH=/home/alanhc/workspace/T-MAC/install/lib/cmake/t-mac \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_C_FLAGS="$INSTR_FLAGS" \
-DCMAKE_CXX_FLAGS="$INSTR_FLAGS"
cmake --build . --target llama-cli -j$(nproc)
```
` uftrace record -F 'ggml_compute_forward_mul_mat*' --no-libcall --depth 2 ./bin/lla
ma-cli -m ~/model/model.INT_N.gguf -n 2 -p "hello" -t 1 --temp 0`
- `uftrace report`
```
Total time Self time Calls Function
========== ========== ========== ====================
1.020 m 4.359 ms 705 ggml_compute_forward_mul_mat
1.015 m 1.015 m 36112 ggml_tmac_mul_mat_task_compute
4.122 s 13.274 ms 2387 ggml_compute_forward_mul_mat_one_chunk
4.108 s 4.107 s 312326 ggml_vec_dot_f16
371.800 ms 371.774 ms 904 ggml_tmac_mul_mat_task_init
263.510 ms 263.503 ms 2372 llamafile_sgemm
21.626 ms 21.626 ms 614 linux:schedule (pre-empted)
2.541 ms 2.541 ms 4995 ggml_fp32_to_fp16_row
492.011 us 492.011 us 3092 ggml_is_contiguous
189.770 us 189.770 us 2601 ggml_row_size
167.919 us 167.919 us 8526 ggml_type_size
91.019 us 91.019 us 653 ggml_tmac_can_mul_mat
62.760 us 62.760 us 3438 ggml_n_dims
40.840 us 40.840 us 2372 ggml_blck_size
25.202 us 25.202 us 546 ggml_tmac_get_type_bits
23.095 us 23.095 us 653 ggml_barrier
11.440 us 11.440 us 107 ggml_is_numa
```
| Function | 說明 | Time (Total) | Calls | 重點 |
| ---------------------------------------- | ----------------------------------------------- | ------------- | ------- | ---------------------------- |
| `ggml_compute_forward_mul_mat` | 高層張量乘法呼叫 | **1.020 min** | 705 | 是入口層的封裝呼叫 |
| `ggml_tmac_mul_mat_task_compute` | 真正做矩陣乘法的核心任務 | **1.015 min** | 36,112 | 幾乎佔了全部運算時間 |
| `ggml_compute_forward_mul_mat_one_chunk` | 分塊矩陣乘法 | 4.1 秒 | 2,387 | 可考慮平行化或 chunk 拆分優化 |
| `ggml_vec_dot_f16` | 向量內積(float16) | 4.1 秒 | 312,326 | 單一點乘運算,量非常大 |
| `llamafile_sgemm` | fallback 的 float32 sgemm | 263 ms | 2,372 | 可能是 fallback 情況(非 fastpath) |
| `linux:schedule` | context switch 次數 | 21.6 ms | 614 | 有一定 preempt 發生,可能 OpenMP 被搶佔 |
| 其他 | 如 `fp16 轉換`, `barrier`, `row_size`, `type_size` | 微小,影響不大 | | |
## 嘗試改用 FIFO 排程策略
### 如何知道使用什麼排程策略?
- 找 pid : `ps aux | grep llama`
```
alanhc 39697 593 2.5 2500804 1693468 pts/4 Rl+ 16:08 0:19 ./bin/llama-cli -m /home/alanhc/model/model.INT_N.gguf -n 10 -t 8 -p hello, how are you --temp 0
```
```
$ chrt -p 39958
pid 39958's current scheduling policy: SCHED_OTHER
pid 39958's current scheduling priority: 0
```
### 使用 CFS
- cmd: `./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0`
```
llama_perf_sampler_print: sampling time = 0.10 ms / 16 runs ( 0.01 ms per token, 156862.75 tokens per second)
llama_perf_context_print: load time = 1027.12 ms
llama_perf_context_print: prompt eval time = 1980.98 ms / 6 tokens ( 330.16 ms per token, 3.03 tokens per second)
llama_perf_context_print: eval time = 3325.68 ms / 9 runs ( 369.52 ms per token, 2.71 tokens per second)
llama_perf_context_print: total time = 5308.03 ms / 15 tokens
```
### 使用 FIFO
#### 設定
- 先查看當前最大值,通常是99
`ulimit -r`
- 若為0需要進行以下步驟
1. `sudo nano /etc/security/limits.conf`
加上以下,以帳號 alanhc 為例
```
alanhc - rtprio 99
alanhc - nice -20
```
2. 編輯 `sudo nano /etc/pam.d/common-session`
確保有
```
session required pam_limits.so
```
3. 重新開機
#### FIFO 優先權 5
- `sudo chrt -f 5 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0`
```
llama_perf_sampler_print: sampling time = 0.19 ms / 16 runs ( 0.01 ms per token, 84656.08 tokens per second)
llama_perf_context_print: load time = 1641.62 ms
llama_perf_context_print: prompt eval time = 2330.08 ms / 6 tokens ( 388.35 ms per token, 2.58 tokens per second)
llama_perf_context_print: eval time = 3814.60 ms / 9 runs ( 423.84 ms per token, 2.36 tokens per second)
llama_perf_context_print: total time = 6155.38 ms / 15 tokens
```
#### FIFO 優先權 20
- `sudo chrt -f 20 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0`
```
llama_perf_sampler_print: sampling time = 0.19 ms / 16 runs ( 0.01 ms per token, 85561.50 tokens per second)
llama_perf_context_print: load time = 1394.06 ms
llama_perf_context_print: prompt eval time = 2340.22 ms / 6 tokens ( 390.04 ms per token, 2.56 tokens per second)
llama_perf_context_print: eval time = 3830.23 ms / 9 runs ( 425.58 ms per token, 2.35 tokens per second)
llama_perf_context_print: total time = 6173.55 ms / 15 tokens
```
#### FIFO 優先權 80
- `sudo chrt -f 80 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0`
```
llama_perf_sampler_print: sampling time = 0.19 ms / 16 runs ( 0.01 ms per token, 84210.53 tokens per second)
llama_perf_context_print: load time = 1388.09 ms
llama_perf_context_print: prompt eval time = 2329.73 ms / 6 tokens ( 388.29 ms per token, 2.58 tokens per second)
llama_perf_context_print: eval time = 3712.91 ms / 9 runs ( 412.55 ms per token, 2.42 tokens per second)
llama_perf_context_print: total time = 6045.73 ms / 15 tokens
```
#### FIFO 優先權 99
- `sudo chrt -f 99 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0`
```
llama_perf_sampler_print: sampling time = 0.19 ms / 16 runs ( 0.01 ms per token, 84656.08 tokens per second)
llama_perf_context_print: load time = 1385.15 ms
llama_perf_context_print: prompt eval time = 2295.40 ms / 6 tokens ( 382.57 ms per token, 2.61 tokens per second)
llama_perf_context_print: eval time = 3825.41 ms / 9 runs ( 425.05 ms per token, 2.35 tokens per second)
llama_perf_context_print: total time = 6123.93 ms / 15 tokens
```
`./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0`
```
llama_perf_sampler_print: sampling time = 0.11 ms / 12 runs ( 0.01 ms per token, 107142.86 tokens per second)
llama_perf_context_print: load time = 1648.90 ms
llama_perf_context_print: prompt eval time = 1202.29 ms / 2 tokens ( 601.14 ms per token, 1.66 tokens per second)
llama_perf_context_print: eval time = 5723.40 ms / 9 runs ( 635.93 ms per token, 1.57 tokens per second)
llama_perf_context_print: total time = 6926.95 ms / 11 tokens
```
```
llama_perf_sampler_print: sampling time = 0.05 ms / 7 runs ( 0.01 ms per token, 132075.47 tokens per second)
llama_perf_context_print: load time = 1151.84 ms
llama_perf_context_print: prompt eval time = 678.12 ms / 2 tokens ( 339.06 ms per token, 2.95 tokens per second)
llama_perf_context_print: eval time = 1480.29 ms / 4 runs ( 370.07 ms per token, 2.70 tokens per second)
llama_perf_context_print: total time = 2159.31 ms / 6 tokens
real 0m3.410s
user 0m18.802s
sys 0m0.309s
```
```
llama_perf_sampler_print: sampling time = 0.09 ms / 7 runs ( 0.01 ms per token, 74468.09 tokens per second)
llama_perf_context_print: load time = 1410.44 ms
llama_perf_context_print: prompt eval time = 760.87 ms / 2 tokens ( 380.43 ms per token, 2.63 tokens per second)
llama_perf_context_print: eval time = 1690.04 ms / 4 runs ( 422.51 ms per token, 2.37 tokens per second)
llama_perf_context_print: total time = 2453.07 ms / 6 tokens
22.15user 0.40system 0:04.01elapsed 562%CPU (0avgtext+0avgdata 1675264maxresident)k
0inputs+0outputs (0major+185871minor)pagefaults 0swaps
```
#### 比較 CFS 與 FIFO
- 核心指標
| 策略 | Load time (ms) | Prompt eval time / tok | Eval time / tok | Total time (ms) |
| ----------- | -------------- | ---------------------- | --------------- | --------------- |
| **CFS** | 1027.12 | 330.16 ms / tok | 369.52 ms / tok | 5308.03 |
| **FIFO 5** | 1641.62 | 388.35 ms / tok | 423.84 ms / tok | 6155.38 |
| **FIFO 20** | 1394.06 | 390.04 ms / tok | 425.58 ms / tok | 6173.55 |
| **FIFO 80** | 1388.09 | 388.29 ms / tok | 412.55 ms / tok | 6045.73 |
| **FIFO 99** | 1385.15 | 382.57 ms / tok | 425.05 ms / tok | 6123.93 |
- 結論
- CFS 反而最快
- CFS 在本機執行環境下給出的 load time、eval time、total time 都是最短的。
- FIFO 效能差異與 priority 數值關係不大
- 推測
- llama-cli 預設用 OpenMP 多執行緒,而 FIFO 是 per-thread 排程,可能造成部分 thread 被 FIFO 鎖死排程。
- FIFO 無時間片設計,容易造成 thread starvation 或不均衡的 thread 運作。
- CFS 有動態調整 fairness,反而讓 OpenMP 更平均地跑在各核心上。
## 關閉 `GGML_USE_OPENMP`,由 `llama.cpp` 自行設計的多執行緒機制進行矩陣運算,避免 GCC/LLVM 提供的 OpenMP 帶來的不確定性。
:::success
$\to$ TODO: 關閉 `GGML_USE_OPENMP`,由 `llama.cpp` 自行設計的多執行緒機制進行矩陣運算,避免 GCC/LLVM 提供的 OpenMP 帶來的不確定性。
> 抑制 OpenMP,使用 GGML 的 [thread pool](https://github.com/ggml-org/llama.cpp/pull/8672),這樣才可進行後續的改進
:::
- 先設定 `export GGML_N_THREADS=8`
### 停用 OpenMP 實驗,並使用 8 threads 進行實驗
```
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
ZSTD_SO=$(ldconfig -p | grep libzstd.so | head -n1 | awk '{print $4}')
```
```
cmake -G Ninja .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_FLAGS="-DGGML_USE_OPENMP=0" \
-DCMAKE_CXX_FLAGS="-DGGML_USE_OPENMP=0" \
-DUSE_LLVM=llvm-config \
-DUSE_GRAPH_EXECUTOR=ON \
-DUSE_AUTO_SCHEDULER=ON \
-DZSTD_LIBRARY="$ZSTD_SO" \
-DZSTD_INCLUDE_DIR=/usr/include
```
#### cfs
- cmd: `./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0`
- result
```
llama_perf_sampler_print: sampling time = 0.10 ms / 16 runs ( 0.01 ms per token, 152380.95 tokens per second)
llama_perf_context_print: load time = 291.67 ms
llama_perf_context_print: prompt eval time = 95.98 ms / 6 tokens ( 16.00 ms per token, 62.51 tokens per second)
llama_perf_context_print: eval time = 261.51 ms / 9 runs ( 29.06 ms per token, 34.42 tokens per second)
llama_perf_context_print: total time = 358.19 ms / 15 tokens
```
#### fifo
- `sudo chrt -f 99 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0`
- result
```
llama_perf_sampler_print: sampling time = 0.18 ms / 16 runs ( 0.01 ms per token, 87912.09 tokens per second)
llama_perf_context_print: load time = 460.26 ms
llama_perf_context_print: prompt eval time = 249.23 ms / 6 tokens ( 41.54 ms per token, 24.07 tokens per second)
llama_perf_context_print: eval time = 567.86 ms / 9 runs ( 63.10 ms per token, 15.85 tokens per second)
llama_perf_context_print: total time = 818.15 ms / 15 tokens
```
#### 分析
- result
| 指標 | CFS | FIFO |
| ---------------------------- | ----------- | ----------- |
| **tokens/sec(eval)** | `34.42` | `15.85` |
| **eval time(per token)** | `29.06 ms` | `63.10 ms` |
| **prompt eval time / token** | `16.00 ms` | `41.54 ms` |
| **總耗時** | `358.19 ms` | `818.15 ms` |
- CFS 表現較佳
- GGML 的 thread pool 是 cooperative thread pool(使用 pthread + semaphore),它依賴 CFS scheduler 幫忙做 thread yield 與公平排程,適合多執行緒非即時並行的 workload。
- FIFO 即時排程會讓主執行緒(或某些 thread)長時間霸佔 CPU,不容易 yield → 導致其他 thread 無法即時排程被調度 → 降低並行效率。
- 所以 Linux 無法靠 kernel-level 的 scheduling 優化分配。FIFO 會破壞這種細膩協調。
## 改進 `llama.cpp` 的內部排程機制,見 [ggml: Implement yield barrier using futex for improved thread scheduling efficiency](https://github.com/ggml-org/llama.cpp/pull/13079)
:::success
$\to$ TODO: 改進 `llama.cpp` 的內部排程機制,見 [ggml: Implement yield barrier using futex for improved thread scheduling efficiency](https://github.com/ggml-org/llama.cpp/pull/13079)
> 背景知識: [實作輕量級的 Mutex Lock](https://hackmd.io/@sysprog/concurrency/%2F%40sysprog%2Fconcurrency-mutex) 及 [建立相容於 POSIX Thread 的實作](https://hackmd.io/@sysprog/concurrency/%2F%40sysprog%2Fconcurrency-thread-package)
:::
- [ggml: Implement yield barrier using futex for improved thread scheduling efficiency](https://github.com/ggml-org/llama.cpp/pull/13079)
- SongXiaoXi: futex-based yield barriers versus traditional spin barriers
- yielding 可以提高系統 scalability 和 efficiency ,但會在較輕量的 workload 增加 context-switching 成本
- slaren:目前推理(generation)階段效能損失太大
- SongXiaoXi
- do_spin() implementation
- Counter and throttled Counter 和 throttled
- Tuning for hybrid cores
- 目的: 在多核心 CPU 上進行推理時,ggml 使用多執行緒來加速計算,而這些執行緒之間需要同步。
- 傳統(spin barrie):每個執行緒會「忙等(busy wait)」直到所有人到齊再繼續,但這會浪費 CPU。
- 改進做法(yield barrier): 果發現還有其他執行緒沒來,它會呼叫 futex() 把自己暫時 block 起來,等別人來叫醒我(wake up),減少 CPU 使用。
- do_spin(): 是一段 fallback 行為:執行緒會先 spin 一下(短暫忙等),如果還沒等到其他執行緒,就 futex_wait()。
- Counter and throttled Counter
- Counter:用來追蹤目前有多少執行緒已經抵達同步點。
- Throttled Counter:可能用來避免太早進入 sleep 狀態,例如只當前面已經很多人到了才開始 futex wait。
### 單一threads
#### cfs
`./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 1 -p "hello, how are you" --temp 0`
- 結果
```
llama_perf_sampler_print: sampling time = 0.10 ms / 16 runs ( 0.01 ms per token, 156862.75 tokens per second)
llama_perf_context_print: load time = 4711.76 ms
llama_perf_context_print: prompt eval time = 12384.73 ms / 6 tokens ( 2064.12 ms per token, 0.48 tokens per second)
llama_perf_context_print: eval time = 20465.06 ms / 9 runs ( 2273.90 ms per token, 0.44 tokens per second)
llama_perf_context_print: total time = 32851.03 ms / 15 tokens
```
#### fifo
`sudo chrt -f 99 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 1 -p "hello, how are you" --temp 0`
- 結果
```
llama_perf_sampler_print: sampling time = 0.19 ms / 16 runs ( 0.01 ms per token, 84210.53 tokens per second)
llama_perf_context_print: load time = 6111.02 ms
llama_perf_context_print: prompt eval time = 16312.76 ms / 6 tokens ( 2718.79 ms per token, 0.37 tokens per second)
llama_perf_context_print: eval time = 26601.43 ms / 9 runs ( 2955.71 ms per token, 0.34 tokens per second)
llama_perf_context_print: total time = 42917.32 ms / 15 tokens
```
#### fifo + tasklet
`sudo taskset -c 2 chrt -f 99 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 1 -p "hello" --temp 0`
```
llama_perf_sampler_print: sampling time = 0.10 ms / 12 runs ( 0.01 ms per token, 117647.06 tokens per second)
llama_perf_context_print: load time = 5024.82 ms
llama_perf_context_print: prompt eval time = 4679.66 ms / 2 tokens ( 2339.83 ms per token, 0.43 tokens per second)
llama_perf_context_print: eval time = 22225.47 ms / 9 runs ( 2469.50 ms per token, 0.40 tokens per second)
llama_perf_context_print: total time = 26906.25 ms / 11 tokens
```
### 結論
| 指標 | FIFO 單執行緒(taskset) | CFS 單執行緒 |
| -------------------- | ----------------------- | ---------------- |
| **total time** | \~26.9 秒 | \~32.9 秒 |
| **token/sec** | 0.41 | 0.44 |
| **CPU utilization** | 高、穩定(幾乎100%) | 高但有 scheduler 介入 |
| **context switches** | 幾乎 0(FIFO 無 preemption) | 較多,因為有時間片 |
| **能效推測** | ✅ 穩定且長時間佔用 CPU | ⚠ 有更多切換與空轉成本 |
- FIFO 的優勢
- taskset 確保 cache locality,無核心移動。
- 沒有 CFS 中的時間片 preemption → 更少 scheduler overhead。
- CFS 的劣勢
- 有更多 context switch(多次 kernel/user 切換)
- 有可能發生 CPU idle-wake→重新進入 active state,造成 power spike
## 評估 [ik_llama.cpp](https://github.com/ikawrakow/ik_llama.cpp) 效能表現
:::success
TODO: 評估 [ik_llama.cpp](https://github.com/ikawrakow/ik_llama.cpp) 效能表現,見 [Updated BitNet arch bitnet-b1.58](https://github.com/ikawrakow/ik_llama.cpp/issues/365)
---
看起來這個 llama-server 有效能議題 [Research: performance divergence #476](https://github.com/ikawrakow/ik_llama.cpp/issues/476)
CPU Processing speed 在大的 context 可能有效能議題 [Feature Request: Improve CPU processing speed for large contexts #26](https://github.com/ikawrakow/ik_llama.cpp/issues/26)
[CPU prompt processing speed for large contexts #25](https://github.com/ikawrakow/ik_llama.cpp/discussions/25)
:::
- [筆記](https://hackmd.io/@alanhc/BkyjoTHmll)
- 
- 跟矩陣乘法有關的(紫色部分):
- [reddit 討論 ik_llama.cpp](https://www.reddit.com/r/LocalLLaMA/comments/1keoint/llama_gotta_go_fast_both_ik_and_mainline_llamacpp/)
- `./build/bin/llama-quantize --allow-requantize ~/model/ggml-model-i2_s.gguf ggml-model-i2_s_bn.gguf iq2_bn`
- ` ./build/bin/llama-cli -m ggml-model-i2_s_bn.gguf --prompt "Once upon a time" -n 32 --temp 0`
`sudo perf record -g ./build/bin/llama-cli -m ggml-model-i2_s_bn.gguf --prompt "Once upon a time" -n 32 --temp 0`


- 關鍵指標說明
| 函數 / Symbol | CPU 佔比 | 說明 |
| ------------------------------ | ------ | ------------------------------------------------------------ |
| `ggml_compute_forward_mul_mat` | 89.23% | LLM 主推論流程中,最底層的「forward 矩陣乘法」核心函數。幾乎全部 CPU 時間花在這 |
| `iqk_mul_mat_4d` | 43.44% | 這是 GGML 中處理 4D 張量矩陣乘法的量化實作邏輯 |
| `mul_mat_iq2bn_q8_K64<1>()` | 8.14% | 真正做 bit-packed int4 \* int8 矩陣乘法的內核,這裡通常做 SIMD 或 blocking 計算 |
| `__cyg_profile_func_enter` | 12.63% | profiling 開銷,非計算熱點,但也消耗非小資源,可能需要移除 |
| `libggml.so` | 88%+ | 幾乎所有熱點都來自這個共享物件,代表你的 bottleneck 完全集中於 **GGML 的量化計算邏輯** |
- 火焰圖

- 這表示絕大多數計算時間都在 `ggml_compute_forward_mul_mat()` 函式中,也就是 矩陣乘法的 forward pass。這是 LLM 推論的核心瓶頸。
- `sudo perf report`:
{%gist 5b730dcc52018bb59d53e49376c38b12 %}
```
sudo perf stat -e cycles,instructions,minor-faults,major-faults,dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses,page-faults,cache-references,cache-misses ./build/bin/llama-cli -m ggml-model-i2_s_bn.gguf --prompt "Once upon a time" -n 32 --temp 0
```
{%gist 9d2270a1e72dacd7300350a61f9f71e1 %}
- Cycles 與 Instructions
- IPC > 1 表示有良好的指令並行度。
- core 比 atom 效率略高,符合預期(Atom 是低功耗核心)。
- dTLB(data Translation Lookaside Buffer)存取與 miss
- miss rate 非常低(< 0.01%),代表記憶體區塊大致集中、TLB 命中率佳,沒有明顯分頁效能問題。
- iTLB(instruction TLB)miss
- Atom 的 miss 高出 core 很多,表示 atom 核心讀取指令的 locality 較差,或該 core 使用了更多小函式與 page 邊界。
- Page Faults
- minor-faults: 記憶體尚未對應,但系統已有頁面可用(如匿名 mmap)。
- major-faults = 0: 沒有造成磁碟 I/O,表示模型已 preload,不須從磁碟載入。
- Cache access
- miss ratio 偏高,表示:
- 推論過程 memory footprint 過大(不完全 fit in cache)。
- 模型權重或資料佈局沒有 cache-friendly(e.g., matrix 排列不佳)。
- 建議後續可優化 ggml_compute_forward_*() 計算路徑的 memory layout。
| 指標 | 狀況 | 建議 |
| ---------- | -------- | --------------------------- |
| IPC | 高(1.67+) | 執行效率佳,CPU 資源利用不錯 |
| TLB Miss | 低 | 記憶體分頁良好 |
| Cache Miss | 偏高 | 考慮調整張量排布或 cache blocking 優化 |
| Page Fault | 無 major | 模型已載入記憶體 |
| CPU 使用率 | 很高 | 表示 threading 有效,但也代表競爭存在 |
- `sudo uftrace record -F ggml_compute_forward_mul_mat ./build/bin/llama-cli -m ggml-model-i2_s_bn.gguf --prompt "Once upon a time" -n 32 --temp 0`
https://gist.github.com/alanhc/b4a389ce56d16e0e5e9f29d63d08b5f8
{%gist b4a389ce56d16e0e5e9f29d63d08b5f8 %}
- 熱點
| Function Name | Total Time | Self Time | Calls | 說明 |
| ------------------------------ | ----------- | ----------- | ------- | ------------------------------------ |
| `ggml_compute_forward_mul_mat` | **4.865 s** | 179 ms | 71,544 | 矩陣乘法的主進入點(總熱點)🔥 |
| `iqk_mul_mat_4d` | 4.271 s | 13 ms | 127,248 | 對 4D 張量的 IQ 量化矩陣乘法 |
| `iqk_mul_mat` | 4.257 s | 20 ms | 190,608 | 展開後的量化乘法計算 |
| `mul_mat_NxM` | 4.211 s | 10 ms | 134,904 | 模板化乘法邏輯(包裝器) |
| `mul_mat_iq2bn_q8_K64` | **2.786 s** | **2.784 s** | 55,440 | 核心內核!真正的 int4 × int8 乘法🔥 |
| `linux:schedule` | 339 ms | 339 ms | 91,701 | OS thread 排程(代表有大量 context switch)⚠️ |
| `mul_mat_qY_K_q8_K_T` | 1.332 s | 1.332 s | 264 | 類似內核,可能用於不同層級或配置 |
- annotate `mul_mat_iq2bn_q8_K64`

- `linux:schedule`
- 程式有明顯的 thread context switching
https://gist.github.com/alanhc/3fcf8cf1bc61de173cf43bc52d5ccbcc
{%gist 3fcf8cf1bc61de173cf43bc52d5ccbcc %}
- 看熱點怎麼執行:uftrace replay -F mul_mat_iq2bn_q8_K64
- 產生火焰圖 `uftrace dump -F -S > folded-stacks.txt`
- `flamegraph.pl folded-stacks.txt > llama-flamegraph.svg`
## 評估 Huge Page 效益
`llama.cpp` 專案中 (注意: BitNet 使用 [llama.cpp 的分支](https://github.com/Eddie-Wang1120/llama.cpp/tree/merge-dev),與 huge page 相關的討論:
* [Huge Page Support](https://github.com/ggml-org/llama.cpp/issues/2251)
* [Llama.cpp patch for using static hugepages](https://www.reddit.com/r/VFIO/comments/1fop300/llamacpp_patch_for_using_static_hugepages/)
* [allow mmap to take advantage of hugepage feature](https://github.com/ggml-org/llama.cpp/issues/12444)
* [support hugepage feature of pagesize 2M or 1G](https://github.com/ggml-org/llama.cpp/pull/12552)
:::warning
看起來 T-MAC 應該是用這: https://github.com/kaleid-liner/llama.cpp/tree/eb07ecf0172230d58fff5d23a3fd6feebda35065?
:::
:::success
TODO: 評估上述修改的效益
現行的 `llama.cpp` 使用 mmap 系統呼叫,儘管是通用的縮減模型載入時間的方法,但考慮到 SSD 一類的儲存媒介,尚有改進空間。`llama.cpp` 如何使用 `mmap`:
1. 檔案→位址空間: 啟動時,`llama.cpp` 透過 `mmap()` 將整個模型檔案映射到自己的虛擬位址空間
2. 按需分頁錯誤: 當程式第一次存取某個頁面(例如讀取某層權重)時,核心會觸發分頁錯誤,將那 4 KiB 的頁面從磁碟載入到作業系統分頁快取及記憶體中
3. 分頁快取緩衝: 所有頁面都儲存在 Linux 的分頁快取裡,後續存取就直接在記憶體(若未被換出)和 CPU 快取中命中
4. 鎖定到記憶體: 如果加了 `-mlock`,在映射完畢(或在第一次分頁錯誤後,或使用 `MAP_POPULATE` 事先預取)會呼叫 `mlock()`。這會將這些頁面鎖在記憶體中,避免被換出
為何考慮用 `io_uring` 取代 `mmap`?考量因素並非 SSD 寫入壽命,也不是要繞過記憶體 → CPU 快取。真正的優勢在於模型載入吞吐量:
1. 系統呼叫開銷更低: 使用 `io_uring`,可在一個 SQE 批次裡提交成百上千個讀取請求,而不是讓每次分頁錯誤都走一次 `read()` 路徑。
* 批次 I/O 可減少上下文切換和中斷次數
2. 非同步、平行 I/O: 同時向高速 NVMe 發出多個讀取請求,並非同步地處理完成事件
* 相較於序列化的分頁錯誤或 `MAP_POPULATE`,更有機會飽和儲存裝置效能
3. 選擇繞過分頁快取: 若確定載入時只會讀取一次全部資料,可開啟 `O_DIRECT`(或在 `io_uring` 用 `IORING_OP_READ` + `IOSQE_BUFFER_SELECT`),將資料直接串流到應用程式緩衝區,避免分頁快取的雙重緩衝
- [I/O 模型演化: Linux 的 io_uring](https://hackmd.io/@sysprog/iouring)
`io_uring` 的考量:
* 適合時機
- 在非常高效能的 SSD 上做大規模連續載入,批次、全非同步 I/O 有機會達到最高吞吐量
- 需要控制記憶體使用時:繞過分頁快取以保留更多 RAM 給其他工作。
* 不適合時機
- 推論階段的小範圍隨機存取:`mmap` 的按需分頁已經對隨機讀取做了高度調整
* 已鎖定工作集:一旦用 `mlock` 把頁面鎖在記憶體,`mmap` + 快取的零複製存取就足夠高效
延伸閱讀:
* [Load LLaMA Models Instantly](https://news.ycombinator.com/item?id=35199418)
* [OS-Level Challenges in LLM Inference and Optimizations](https://eunomia.dev/blog/2025/02/18/os-level-challenges-in-llm-inference-and-optimizations/)
:::
:::info
wip
:::
```mermaid
flowchart TD
subgraph Kernel Space
K1[🔧 mmap 註冊 VMA]
K2[📍 page fault handler]
K3[📦 page cache buffer]
K4[🔁 io_uring SQ/CQ 管理]
end
subgraph RAM
R1["🧠 page cache (OS managed)"]
R2["📤 User buffer (mapped)"]
R3["🔒 Locked page (mlock)"]
end
subgraph User Space
U1[📁 llama.cpp 啟動]
U2[📖 access weights → vaddr]
U3["🔄 使用 io_uring 提交多筆 read()"]
U4["📬 用 buffer[] 接收 read 資料"]
end
%% mmap 流程
U1 --> K1 --> U2 --> K2 --> K3 --> R1 --> R2
R2 --> U2
%% io_uring 流程
U3 --> K4 --> R1
K4 --> R2 --> U4
%% mlock
R2 --> R3
%% 樣式
style K1 fill:#e6f7ff,stroke:#3399cc
style K2 fill:#ffe6e6,stroke:#cc3333
style K3 fill:#fff2cc,stroke:#cc9900
style K4 fill:#ddeeff,stroke:#3399cc
style R1 fill:#f9ffe6,stroke:#ccff33
style R2 fill:#ccffee,stroke:#33cc99
style R3 fill:#ccffcc,stroke:#66cc66
style U1 fill:#f0f0f0,stroke:#888
style U2 fill:#f0f0f0,stroke:#888
style U3 fill:#f0f0f0,stroke:#888
style U4 fill:#f0f0f0,stroke:#888
```
## 視覺化理解
:::info
wip
:::
```mermaid
flowchart TD
subgraph "🧠 NUMA Memory (User Space RAM)"
B[📂 mmap-ed Model Memory]
end
subgraph "🧍♂️ User Space"
G["🧩 ggml_load_model_from_file()"]
H["📊 ggml_compute_forward()"]
I[⚙️ SIMD / matmul / attention kernel]
end
subgraph "🧷 Kernel Space"
D["🧠 mmap() → VMA 建立"]
E["📬 io_uring_submit()"]
F[📤 Completion Ring Polling]
end
subgraph "💽 I/O Subsystem (Disk, SSD/NVMe)"
J[📄 GGUF Model File]
end
subgraph "🧠 NUMA Node 0"
A[CPU Core + Local Memory]
end
subgraph "🧠 NUMA Node 1"
A1[Other CPU Core + Remote Memory]
end
%% Data Flow
J -->|io_uring 讀取模型資料| F
F -->|寫入頁框| B
A -->|allocate| B
A1 -->|fallback| B
B --> D
D --> E
E --> F
B --> G
G --> H
H --> I
%% Style
style A fill:#d1f4ff,stroke:#00a3cc
style A1 fill:#f0f8ff,stroke:#0088cc
style B fill:#fff6e6,stroke:#ffcc00
style D fill:#e6f7ff,stroke:#3399ff
style E fill:#e6f7ff,stroke:#3399ff
style F fill:#e6f7ff,stroke:#3399ff
style G fill:#e6ffe6,stroke:#00cc66
style H fill:#e6ffe6,stroke:#00cc66
style I fill:#f0fff0,stroke:#00b300
style J fill:#fff0f0,stroke:#ff6666
```