iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0

https://ithelp.ithome.com.tw/upload/images/20260822/20141816cmuI2ilrUt.png

實測開場,不免俗地要準備一下 SOP

從今天起除了推論引擎實測外,還有安裝相關軟體與工具的教學,在這些測試工作開跑前,先把測試方法論定下來,這樣每一篇都遵守相同的 SOP,資料之間才能較客觀地去比較。

測量什麼
LLM 推論的效能拆成兩段去測試,也就是大家常看到的 prefill(處理輸入提示詞)與 decode(逐 token 生成)。Day 2 文章我有提過這兩段的瓶頸完全不同,prefill 吃算力,decode 吃記憶體頻寬,混在一起報一個平均值是行銷術語,我比較在意的是工程測試數字。

怎麼量
以固定模型(gpt-oss-120b,昨天上線的量尺)、固定量化(MXFP4)、固定提示詞長度(512、2048、8192 token 三段,由 llama-bench 合成固定長度的 token 序列,因此最僅幾篇的各引擎測試都會對齊同一組長度)、固定生成長度(128 token)、每組跑三次取中位數與標準差。
機器狀態固定為無其他負載、模型已完成載入、且 GPU 已經暖機的熱狀態。

誠實原則
所有測試資料都是這台機器、這個環境、這個軟體版本的結果。如果你的環境,測試時跑出不同數字是正常的,方法論可複現比數字本身重要。

今天的主角是 llama.cpp,單用戶場景的基準線。

環境與版本

項目
llama.cpp 0.1.2-dev,commit 07822bd(2026-08-20),無 tag
建置方式 原始碼編譯,CUDA 後端,GGML_NATIVE 自動解析架構為 sm_121a
編譯器 GCC 13.3.0 / nvcc 13.0.88,Linux aarch64
模型 gpt-oss-120b-MXFP4.gguf,59.02 GiB 單一檔案,116.83 B 參數,36 層 128 experts
模型來源 /mnt/nas/AIModels/gguf/gpt-oss-120b-mxfp4/,NFSv4.1 掛載的 NAS(來源與 sha256 見 Day 7)
硬體 DGX Spark(GB10, sm_121),128 GiB 統一記憶體
驅動與 CUDA 580.173.02 / CUDA 13.0(見 Day 4 環境整備)

先講兩個可能會碰到的情況。

第一,模型是單一檔案,不是分片。 ggml-org 發布的 gpt-oss-120b GGUF 只有一個 59.02 GiB 的檔案,沒有 00001-of-0000N。網路上很多指令範例寫成分片是抄別的模型來的。

第二,官方 README 那行 cmake 指令在這台機器上會直接失敗。

cmake -B build -DGGML_CUDA=ON

失敗訊息長這樣:

/usr/include/aarch64-linux-gnu/bits/math-vector.h(96): error: identifier "__Float32x4_t" is undefined
5 errors detected in the compilation of "CMakeCUDACompilerId.cu".
-- Configuring incomplete, errors occurred!

看起來像是 glibc 或 ARM 的 SVE 型別出問題,其實不是,答案其實是:

Compiler: /usr/bin/nvcc

這台機器上有兩個 nvcc
一個是 DGX OS 內建的 CUDA 13.0(/usr/local/cuda/bin/nvcc),另一個是 apt 的 nvidia-cuda-toolkit 套件裝的 CUDA 12.0(/usr/bin/nvcc)。即使 PATH/usr/local/cuda/bin 排在前面、which nvcc 也回答 13.0,CMake 的 enable_language(CUDA) 還是挑了 /usr/bin/nvcc。而 CUDA 12.0 的 nvcc 前端看不懂 Ubuntu 24.04 glibc 標頭裡的 ARM SVE 向量型別,於是連 CMake 用來認編譯器的那個一行 .cu 測試檔都編不過。

指定正確的 nvcc 就好:

cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc
cmake --build build --config Release -j

架構旗標不必給。 llama.cpp 在 GGML_NATIVE=ON(預設)時會把 CMAKE_CUDA_ARCHITECTURES 設成 native,在這台機器上自動解析成 sm_121acuobjdump 可以驗證。

整包編完 2 分 49 秒(20 核心,CPU 使用率 1537%),零錯誤。編譯完成後 llama-benchllama-serverllama-cli 都在 build/bin 底下。

https://ithelp.ithome.com.tw/upload/images/20260822/201418163gRg6TGIty.png

基準量測:llama-bench

llama.cpp 內建的 llama-bench 就是為這件事而生的,直接量不同提示詞長度下的 prefill 與 decode:

./build/bin/llama-bench \
  -m /mnt/nas/AIModels/gguf/gpt-oss-120b-mxfp4/gpt-oss-120b-MXFP4.gguf \
  -p 8192,512,2048,8192 \
  -n 128 \
  -ngl 999 \
  -r 3

參數對應方法論,-p 三檔提示詞長度、-n 生成 128 token、-r 每組三次。

眼尖的會發現 -p 開頭多了一個 8192。那是暖機用的,先來看測試結果:

測項 提示詞長度 結果(tokens/s)
pp8192(暖機,丟棄) 8192 1873.35 ± 7.81
prefill(pp512) 512 1860.77 ± 48.78
prefill(pp2048) 2048 1906.63 ± 11.70
prefill(pp8192) 8192 1851.84 ± 36.53
decode(tg128) 短上下文 60.59 ± 0.22

https://ithelp.ithome.com.tw/upload/images/20260822/20141816pVYf27EB7y.png

三檔提示詞長度的 prefill 幾乎是同一個數字,從 512 到 8192 差距只有 2.9%,而且沒有隨長度單調下降,代表在這個長度區間,瓶頸還不是注意力,而是那 36 層 MoE 的矩陣乘法,而矩陣乘法的效率跟一次餵幾個 token 有關,512 個 token 其實還沒把 GPU 餵飽。

decode 的成績則不錯,測試跑到 60.59 tok/s,標準差 0.22,三次跑出 60.4、60.6、60.8。這種穩定度 decode 可能是被快取影響了,這件事跟 Day 9 那邊的 prefix cache 是同一類問題,容後述。

理論天花板看 decode

測試之後除了看成績外,也與理論值做一下比較。在系統中,decode 階段每生成一個 token,就要把該 token 用到的權重從記憶體完整讀一遍。gpt-oss-120b 是 MoE 架構,128 個專家每個 token 只啟用 4 個,所以實際搬動的是啟用參數的權重量:

理論 decode 上限 ≈ 記憶體頻寬 ÷ 每 token 需讀取的權重位元組數

分子是 GB10 的 273 GB/s,而分母才是麻煩的地方,不少人在社群討論時,測試在這裡會算錯。

常見的算法是「約 5B 啟用參數 × 4 bit = 2.5 GB」。這個數字的問題在於,MXFP4 每個區塊還帶一個縮放係數,不是乾淨的 4 bit,而且 MoE 的每一次前向傳播,不管路由選了誰,所有非專家層都要整份讀過,那些層在這顆模型裡是 Q8_0 和 F32,不是 MXFP4。

所以把 GGUF 的張量表檢視一遍,用程式可以量不同層是專家層、是什麼量化型別、實際占幾個位元組這樣。

角色 位元組
dense(注意力、norm、router、lm_head) 1,685,668,608 1.570 GiB,每個 token 全讀
expert(ffn_*_exps 61,073,326,080 56.879 GiB,每個 token 只讀 4/128
token_embd(查表,不計) 615,329,280 0.573 GiB
每 token 讀取量 = 1.570 GiB + 56.879 GiB × (4/128) = 3.347 GiB = 3.594 GB

啟用參數量算出來是 5.13 B,也就是我們在社群看到會有人說講的「約 5B」。但有效位元寬是 5.60 bit,不是 4 bit,所以真正的分母比常見估算大了 40%。

理論上限 = 273 GB/s ÷ 3.594 GB = 75.96 tok/s
實測     = 60.59 tok/s
達成率   = 79.8%

換句話說,decode 實際跑出了 217.8 GB/s 的有效頻寬

79.8% 這個數字是有意義的,原本預期落在五到七成,那是過往這類測試結果常見的區間。而本次測試之所以偏高,是因為分母算對了,如果照一般「5B × 4 bit」的粗估,理論上限會被算成 106.4 tok/s,達成率就變成 56.9%,剛好落進那個常見區間,然後你會以為自己的推論引擎還有四成空間可以壓榨,但實際上沒空間。剩下的 20% 是排程、KV cache 讀取、kernel 啟動與那 273 GB/s 本身就達不到的部分,llama.cpp 在單請求 decode 上已經到了極限,很難再擠出更多空間來。

哪些設定值是有感的呢?

基準線出來後,逐一測試常見調校設定值的實際效果,每次只動一個變因。

Flash Attention(-fa

fa pp2048 pp8192 tg128
off 1342.67 ± 8.55 860.71 ± 2.42 57.52 ± 0.30
on 1915.90 ± 5.97 1893.91 ± 7.62 60.79 ± 0.21
差距 +42.7% +120.0% +5.7%

這是四組實驗裡差距最大的一組,它解釋了前面那個prefill 不隨長度衰減的現象。decode 也快了 5.7%。decode 每步只處理一個 token,注意力的計算量很小,但它仍然要把 KV cache 讀一遍,Flash Attention 少搬幾趟就是省。

批次大小(-b-ub

-b 是邏輯批次,-ub 是實際送進 GPU 的微批次。-ub 決定一切,-b 沒有作用。

這個設定值得代價是計算緩衝區變大,吃掉一些本來可以給 KV cache 的記憶體。在 128 GiB 統一記憶體、模型已經占掉 59 GiB 的機器上,這個取捨要自己拿捏。但如果你的場景是長提示詞(RAG、整份文件摘要),-ub 2048 大概是這台機器上最值得改的一個設定

明天預告

Day 9 換上 vLLM,它是個多人使用下效能較佳的推論引擎,有 NVIDIA 為 Spark 維護的官方容器版本,也有社群版本等可以用,重點會是聚焦在單請求效能與 llama.cpp 的差距,以及並發請求下的總吞吐變化。今天的測試成績就是明天的對照組。

我們 Day 9 見囉。

Day 1|為什麼 2026 年是地端 AI 部署元年:系列規劃與硬體總覽
Day 2|DGX Spark GB10 深度解析:128GB 統一記憶體到底解決了什麼問題
Day 3|網路與儲存規劃:10GbE 骨幹、雙 Spark 直連與 NFS 集中模型庫
Day 4|Day 4|開箱之後:DGX OS 初始環境建置與 CUDA、Docker 生態確認
Day 5|NFS 模型庫實戰:下載工具、權限設計與版本管理
Day 6|llama.cpp、vLLM、TensorRT-LLM 、Ollama、DS4 與 Unsloth 的定位與取捨
Day 7|第一個模型上線:gpt-oss-120b 從模型庫到 API 的完整流程
Day 8|llama.cpp 實測:先定量尺與 SOP
Day 9|vLLM 實測:官方容器、並發吞吐曲線,與剛出爐的新模型 Ornith 1.5


上一篇
Day 7|第一個模型上線:gpt-oss-120b 從模型庫到 API 的完整流程
下一篇
Day 9|vLLM 實測:官方容器、並發吞吐曲線,與剛出爐的新模型 Ornith 1.5
系列文
128GB 統一記憶體的三十天:DGX Spark 地端 LLM 與生成式 AI 部署實戰9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言