io_uring
GGML_USE_OPENMP
,由 llama.cpp
自行設計的多執行緒機制進行矩陣運算,避免 GCC/LLVM 提供的 OpenMP 帶來的不確定性。sudo sh -c 'echo -1 > /proc/sys/kernel/perf_event_paranoid'
-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
加入使用 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
排名 | 函式名稱 | 所在模組 | % 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) |
在 XMRig 一類的挖礦程式中,善用 huge page (或 THP),可達到加速效果
在 XMRig 一類的挖礦程式中,善用 huge page (或 THP),可達到加速效果
指標 | 數值 | 評估 |
---|---|---|
cpu_core IPC |
2.82 | ✅ 很高,代表 pipeline 執行效率佳 |
cpu_atom IPC |
1.73 | ✅ 合理,Atom 核心通常效率較低 |
項目 | 數值 | miss rate |
---|---|---|
cpu_core/cache-references |
295M | - |
cpu_core/cache-misses |
127M | 43.13% |
cpu_atom/cache-misses |
34M | 46.28% |
項目 | 數值 | 說明 |
---|---|---|
cpu_core/dTLB-loads |
44.6B | 記憶體存取多 |
dTLB-load-misses |
4.3M | miss rate 僅 ~0.009%,很低 |
iTLB-load-misses |
56K | 正常範圍,表示指令 cache 命中良好 |
指標 | 數值 |
---|---|
minor-faults | 101K |
major-faults | 0 |
指標 | 數值 |
---|---|
real time |
1.80 秒 |
user + sys time |
7.59 秒 |
平均執行緒數估計 | ≈ 4.2 |
沒開
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% ✅ |
這看起來是 T-MAC 的維護者 QingtaoLi1 要整合進 ggml-org/llama.cpp slaren 表示可能會有不好整合,這可能是一個貢獻點?
TODO
~/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
https://github.com/microsoft/T-MAC
https://deepwiki.com/microsoft/T-MAC
BitNet 有 LUT: https://github.com/microsoft/BitNet/tree/main/src
sudo apt install libc++-dev libc++abi-dev
上面設定目前會有 WARN: child terminated by signal: 7: Bus error 的錯誤,正在排查中,且RAM會不斷增長
應該跟 /dev/shm 有關
可以把這清空避免記憶體炸掉: sudo find /dev/shm -mindepth 1 -delete
解法:用 filter 不要一次觀察太多 function
看報告
14.994 s 14.994 s 38 linux:schedule
sudo perf record -g ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 2 -p "hello" --temp 0
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
sudo perf record -g ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 1 -p "hello" --temp 0
sudo perf report
🔍 重點觀察分析
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
👉 表示目前主要瓶頸是 低階矩陣乘法與資料初始化/搬移過程。
暗示:
有可能是過度記憶體分配(大量 mmap 或懶加載 tensor)
cache miss 或 TLB miss 頻繁
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
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 |
微小,影響不大 |
ps aux | grep llama
./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0
ulimit -r
sudo nano /etc/security/limits.conf
sudo nano /etc/pam.d/common-session
sudo chrt -f 5 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0
sudo chrt -f 20 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0
sudo chrt -f 80 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0
sudo chrt -f 99 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0
./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0
策略 | 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 |
GGML_USE_OPENMP
,由 llama.cpp
自行設計的多執行緒機制進行矩陣運算,避免 GCC/LLVM 提供的 OpenMP 帶來的不確定性。 TODO: 關閉 GGML_USE_OPENMP
,由 llama.cpp
自行設計的多執行緒機制進行矩陣運算,避免 GCC/LLVM 提供的 OpenMP 帶來的不確定性。
抑制 OpenMP,使用 GGML 的 thread pool,這樣才可進行後續的改進
export GGML_N_THREADS=8
./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0
sudo chrt -f 99 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 8 -p "hello, how are you" --temp 0
指標 | 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 |
llama.cpp
的內部排程機制,見 ggml: Implement yield barrier using futex for improved thread scheduling efficiency TODO: 改進 llama.cpp
的內部排程機制,見 ggml: Implement yield barrier using futex for improved thread scheduling efficiency
./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 1 -p "hello, how are you" --temp 0
sudo chrt -f 99 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 1 -p "hello, how are you" --temp 0
sudo taskset -c 2 chrt -f 99 ./bin/llama-cli -m ~/model/model.INT_N.gguf -n 10 -t 1 -p "hello" --temp 0
指標 | 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 | ⚠ 有更多切換與空轉成本 |
TODO: 評估 ik_llama.cpp 效能表現,見 Updated BitNet arch bitnet-b1.58
看起來這個 llama-server 有效能議題 Research: performance divergence #476
CPU Processing speed 在大的 context 可能有效能議題 Feature Request: Improve CPU processing speed for large contexts #26
CPU prompt processing speed for large contexts #25
跟矩陣乘法有關的(紫色部分):
./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
:
指標 | 狀況 | 建議 |
---|---|---|
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
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
https://gist.github.com/alanhc/3fcf8cf1bc61de173cf43bc52d5ccbcc
uftrace dump -F -S > folded-stacks.txt
flamegraph.pl folded-stacks.txt > llama-flamegraph.svg
llama.cpp
專案中 (注意: BitNet 使用 llama.cpp 的分支,與 huge page 相關的討論:
看起來 T-MAC 應該是用這: https://github.com/kaleid-liner/llama.cpp/tree/eb07ecf0172230d58fff5d23a3fd6feebda35065?
TODO: 評估上述修改的效益
現行的 llama.cpp
使用 mmap 系統呼叫,儘管是通用的縮減模型載入時間的方法,但考慮到 SSD 一類的儲存媒介,尚有改進空間。llama.cpp
如何使用 mmap
:
llama.cpp
透過 mmap()
將整個模型檔案映射到自己的虛擬位址空間-mlock
,在映射完畢(或在第一次分頁錯誤後,或使用 MAP_POPULATE
事先預取)會呼叫 mlock()
。這會將這些頁面鎖在記憶體中,避免被換出為何考慮用 io_uring
取代 mmap
?考量因素並非 SSD 寫入壽命,也不是要繞過記憶體 → CPU 快取。真正的優勢在於模型載入吞吐量:
io_uring
,可在一個 SQE 批次裡提交成百上千個讀取請求,而不是讓每次分頁錯誤都走一次 read()
路徑。
MAP_POPULATE
,更有機會飽和儲存裝置效能O_DIRECT
(或在 io_uring
用 IORING_OP_READ
+ IOSQE_BUFFER_SELECT
),將資料直接串流到應用程式緩衝區,避免分頁快取的雙重緩衝io_uring
的考量:
mmap
的按需分頁已經對隨機讀取做了高度調整mlock
把頁面鎖在記憶體,mmap
+ 快取的零複製存取就足夠高效延伸閱讀:
wip
wip