---
# System prepended metadata

title: 2025q1 Linux Term Project (BitNet)

---

---
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 %}
![image](https://hackmd.io/_uploads/SkKoUt2Gge.png)

| 排名 | 函式名稱                                     | 所在模組                 | % 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） |

- 火焰圖
![image](https://hackmd.io/_uploads/rkb4LY2Mee.png)
- 簡化流程圖
```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  |






![截圖 2025-04-28 晚上9.05.03](https://hackmd.io/_uploads/SybKEB0Jgl.png)
- 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`
![image](https://hackmd.io/_uploads/S1_vytsble.png)
解法：用 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`
![image](https://hackmd.io/_uploads/B1an1M2bgx.png)


| 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`
![image](https://hackmd.io/_uploads/r1ycEfnWgl.png)

### 另一次實驗
```
 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)
- ![image](https://hackmd.io/_uploads/SyvEolvQxx.png)
- 跟矩陣乘法有關的（紫色部分）：![image](https://hackmd.io/_uploads/SkjF2WvXeg.png)

- [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`
![image](https://hackmd.io/_uploads/B1v3oV6fxl.png)
![image](https://hackmd.io/_uploads/Bk9J2V6Ggl.png)
- 關鍵指標說明

| 函數 / 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 的量化計算邏輯**       |



- 火焰圖
![image](https://hackmd.io/_uploads/B1jO2E6Mxl.png)
- 這表示絕大多數計算時間都在 `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`
![image](https://hackmd.io/_uploads/SJI3vTafge.png)

- `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
```
