File size: 12,130 Bytes
f53c54f bc3209b 111541a bc3209b 111541a f53c54f bc3209b f65ef17 bc3209b f53c54f bc3209b f53c54f 722130e f65ef17 26151cf 111541a f53c54f f65ef17 f53c54f bc3209b f53c54f 111541a f53c54f 26151cf f53c54f 111541a 26151cf f53c54f 111541a 26151cf f53c54f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | ---
license: mit
library_name: stable-diffusion.cpp
tags:
- stable-diffusion
- z-image
- z-image-turbo
- gguf
- cpu
- benchmark
- amx
- avx512
- operator-fusion
---
# sdcpp_zimage_opt
stable-diffusion.cpp 在 **純 CPU** 上跑 Z-Image-Turbo 的優化實驗。這個 repo 放的是建置腳本、測速腳本、patch 與結果,**不含模型權重**(腳本會從上游下載)。
上游 sd.cpp 版本固定為 commit `77b72a2efed78f55de26892c7e909cee001e8b83`。
## 結論
- 同一提示詞、同一 seed、512x512、**8 steps**。sd.cpp 最快的組合是 **Q4_K-ffn13 + AMX 權重佈局**(54.2 s),相對 stock(59.7 s)約快 1.10x。
- **OpenVINO INT4**(同樣 8 steps)總生成 **37.5 s**,是本批次最快的一列(約 1.59x stock),峰值 RSS 12.6 GiB。但它是不同的權重與實作(optimum-intel + OpenVINO),像素與 sd.cpp 不可直接比較品質。
- 單獨啟用 AMX 權重佈局(`SD_CPU_EXTRA_BUFT=1`,Q4_K)相對 `amx_lto` 約快 15–20%,是 sd.cpp 內最主要的收益來源。
- AVX512 相對 AVX2 約快 2 倍(118.5 s → 58.1 s)。
- FFN gate/up 融合(w1 + w3 → w13)輸出與原版逐位元相同,但在這台機器上沒有可量測的加速。
- **Refiner 慢 2 倍的根因**:參考 Q4_K GGUF 把 2 個 noise-refiner 與 2 個 context-refiner 存成 **Q8_0**(主層是 Q4_K)。在記憶體受限的 CPU GEMM 上,同形狀 w13 每次 89.4 ms(Q8_0)→ 42.9 ms(Q4_K)。用 `tools/requant_refiner.py` 只把這 16 個 refiner 的 attention/FFN 權重改成 Q4_K,每 8 步約省 **1.5 s**(`-rq` 模型,屬 opt-in,像素差 mean 6.4)。
- **flash-attn tiling**:tile 由 64x64 改為 128x128 並把 V 打包提升為每頭一次,`FLASH_ATTN_EXT` 4663 → 4451 ms(-4.5%),輸出逐像素相同。
- Q4_K 是 sd.cpp 在此 CPU 上最快的量化格式。Q8_0 即使開 AMX 也比 Q4_K 慢。
- 峰值 RSS:sd.cpp 7.7–11.1 GiB,OpenVINO 12.6 GiB,皆遠低於記憶體守衛上限。
**雜訊說明**:同一 stock 設定在不同批次的總生成時間為 56.8 s(前次)與 59.7 s(本次)。排名相近的列(例如 Q4_K-ffn13+AMXw 與 Q4_K+AMXw)差距在 ±5% 內,不宜過度解讀。
**負載說明**:主機是共用環境,co-tenant 負載會讓**所有**絕對時間一起上下移動(本 session 觀察到 OpenVINO 從 37.5 s 到 25 s、sd.cpp 從 46 s 到 36 s)。因此跨批次只能比**比值**或在同一批交錯量測;表中的絕對秒數是記錄當時負載下的結果,不是跨批次可比的定值。
## 比較表(中位數,n = 2)
| # | 版本 (variant) | 模型 | 額外環境變數 | 採樣 (s) | 總生成 (s) | 相對 stock | 峰值 RSS (GiB) | 與 stock 像素差 (mean/max) |
|---|---|---|---|---|---|---|---|---|
| 1 | stock | Q4_K | - | 49.2 | 59.7 | 1.00x | 7.86 | ref |
| 2 | avx2 | Q4_K | - | 106.7 | 118.5 | 0.50x | 7.86 | 3.18 / 241 |
| 3 | avx512 | Q4_K | - | 47.6 | 58.1 | 1.03x | 7.86 | 3.17 / 239 |
| 4 | amx_lto | Q4_K | - | 52.4 | 63.7 | 0.94x | 7.86 | 0.00 / 0 |
| 5 | amx_lto | Q4_K | SD_CPU_EXTRA_BUFT=1 | 42.0 | 52.7 | 1.13x | 8.16 | 1.28 / 227 |
| 6 | amx_lto | Q4_K-ffn13 | - | 46.5 | 57.1 | 1.05x | 7.86 | 0.00 / 0 |
| 7 | amx_lto | Q4_K-ffn13 | SD_CPU_EXTRA_BUFT=1 | 43.1 | 54.2 | 1.10x | 8.14 | 1.28 / 227 |
| 8 | amx_lto | Q4_0 | - | 172.5 | 183.5 | 0.33x | 7.69 | 22.84 / 252 |
| 9 | amx_lto | Q8_0 | - | 161.4 | 172.0 | 0.35x | 10.38 | 12.51 / 249 |
| 10 | amx_lto | Q8_0 | SD_CPU_EXTRA_BUFT=1 | 55.9 | 66.4 | 0.90x | 11.11 | 12.33 / 249 |
| 11 | openvino | OV-INT4 | - | 36.8 | 37.5 | 1.59x | 12.59 | 27.71 / 254 |
| 12 | llamafile | Q4_K-ffn13 | SD_CPU_EXTRA_BUFT=1 | 39.1 | 46.0 | 1.30x | 8.14 | 1.89 / 227 |
| 13 | llamafile | Q4_K-ffn13 | SD_CPU_EXTRA_BUFT=1 + conv-direct | 40.5 | 45.9 | 1.30x | 7.23 | 1.89 / 227 |
n = 2 run(s) per row (median). Speedup = stock-Q4_K generate time / row generate time.
- 「相對 stock」= stock 的總生成時間 / 該列的總生成時間,大於 1 表示更快。
- OpenVINO 列為 **8 steps**(與 sd.cpp 相同步數);它的「像素差」是與 stock 的差異,反映的是不同實作,不是品質差距。
- 「像素差」只對**權重相同**的列有意義。第 6 列與第 1 列完全相同,表示 FFN 融合沒有改變輸出。第 5、7 列是 AMX 的數值差異(平均差 1.28),視覺上與 stock 相同。Q4_0、Q8_0 使用不同權重,差異大是預期的,不代表品質問題。
- AVX2 與 AVX512 的輸出與 stock 不同,原因是浮點運算順序不同。
## Pipeline 階段 P1:flash-attn K 打包(已完成,優先執行)
先量測而非猜測:矩陣乘在 pipeline 內與微基準一致,沒有額外開銷;pipeline 的額外開銷是非矩陣乘部分(約 1.4 s/步),主要是 flash-attn。
把 K 的轉置打包改為每個頭只做一次,FLASH_ATTN 從 1607 ms 降到 1226 ms(-24%),輸出與修改前逐像素相同。
詳見 `stages/stage_p1_pipe/README.md`。
## 第一階段:VAE 解碼(已完成)
OpenVINO 的 VAE 解碼約 0.55 s,sd.cpp 約 10 s。第一階段嘗試了 conv-direct、VAE tiling、F16 VAE 權重與 llamafile sgemm。
llamafile 版本把 VAE 解碼從 10.7 s 降到 6.6 s(-38%),加上 conv-direct 後為 5.0 s(-53%)。
詳細數據與重現步驟見 `stages/stage1_vae/README.md`。
## VAE 階段 3:conv2d im2col 向量化(已完成)
用 `GGML_CONV_PROFILE=1` 拆開 direct conv 的三個階段後發現 im2col 與 GEMM 各佔一半。改寫 im2col:
把 `sy/sx` 與邊界檢查提出迴圈、對齊的內部區塊直接整段複製(`[ic][ky][kx]` 佈局 + 通道最後的來源),
im2col 由 1716 ms 降到 875 ms,VAE 解碼 ~4.7 → ~3.9 s,輸出逐像素相同。
剩下的 GEMM(~2.2 s,F32 llamafile sgemm)才是下一關,需要量化/bf16 AMX 卷積。詳見 `stages/stage3_vae_conv/README.md`。
## Pipeline 階段 P2:refiner 重新量化 + flash-attn tile(已完成)
參考 GGUF 的 refiner 區塊是 Q8_0,是 P1「refiner w13 慢 1.7x」未解之謎的根因。`tools/requant_refiner.py`
只改 16 個 refiner attention/FFN 權重為 Q4_K(adaLN 保持 Q8_0),同形狀每次 89.4 → 42.9 ms,每 8 步省約 1.5 s。
flash-attn 的 V 打包提升為每頭一次、tile 改 128x128,`FLASH_ATTN_EXT` 再降 4.5%,且輸出逐像素相同。
詳見 `stages/stage_p2_refiner/README.md`。
## 各版本的意義
| variant | CMake 設定 | 說明 |
|---|---|---|
| `stock` | `-DGGML_NATIVE=ON` | 上游預設(`-march=native`) |
| `avx2` | `GGML_NATIVE=OFF`,只開 AVX2,`GGML_AVX512=OFF` | 量測 AVX512 的貢獻 |
| `llamafile` | `amx_lto` + `GGML_LLAMAFILE=ON`(F32 sgemm,第一階段) | VAE 與 F32 矩陣乘的 CPU 內核 |
| `avx512` | `GGML_NATIVE=OFF`,明確開 AVX512/VNNI/BF16/AMX,LTO | 與 native 版本效能接近(60.0 s vs 56.8 s,差距在雜訊附近) |
| `amx_lto` | `GGML_NATIVE=ON`,AMX_TILE/INT8/BF16,LTO | 目前最佳的編譯選項 |
| `openvino` | HelloSun/Z-Image-Turbo-OpenVINO-INT4,`tools/ov_bench.py`(optimum-intel 2.2.0 / OpenVINO 2026.4.0) | 不同推論框架與 INT4 權重,只作為對照,步數與 sd.cpp 相同(8) |
執行期的選項:
- `SD_CPU_EXTRA_BUFT=1`:擴散模型的 2 維權重改用 CPU 的 AMX 佈局(實驗性質,預設關閉)。
- `z_image_turbo-Q4_K-ffn13.gguf`:由 `tools/fuse_ffn.py` 把 FFN 的 w1、w3 串接成 w13,模型會自動偵測並使用融合路徑。
- `z_image_turbo-Q4_K-ffn13-rq.gguf`:再由 `tools/requant_refiner.py` 把 refiner 的 16 個 attention/FFN 權重從 Q8_0 改成 Q4_K(速度優先、opt-in,輸出與 base 略有差異)。
- `GGML_CPU_PROFILE=1`:逐算子計時,輸出到 stderr(僅供分析,預設關閉)。
- `GGML_CONV_PROFILE=1`:VAE direct-conv 的 im2col/gemm/permute 階段計時(僅供分析,預設關閉)。
## Profile(Q4_K,優化前)
| 算子 | 佔比 | 備註 |
|---|---|---|
| MUL_MAT | 74% | 主要瓶頸,AMX 佈局主要影響這一項 |
| FLASH_ATTN_EXT | 11% | 已融合 |
| IM2COL | 7% | VAE 卷積 |
| FUSED_RMS_NORM_MUL | 2% | ggml 既有融合 |
| 其他逐元素算子 | 約 5% | 融合的理論上限很低 |
## 硬體配置
- CPU:Intel Xeon Platinum 8559C,2 sockets × 48 cores,SMT 2,2 個 NUMA node
- 有效 CPU 能力:容器 cgroup 配額 16 CPU(`cpu.max = 1600000 100000`),測試綁定 CPU `0-15`(NUMA node 0),`-t 16`
- 相關指令集:AVX2、AVX512F、AVX512_VNNI、AVX512_BF16、AMX_INT8、AMX_BF16
- 記憶體:1999 GiB(主機共用,測試時 load average 約 45,結果有 ±3 s 左右的雜訊)
- GPU:無可用(NVML 初始化失敗),純 CPU 推論
- OS:Debian GNU/Linux 13,kernel 6.18
- 編譯器:gcc 14.2.0,cmake 3.31.6
完整資訊見 `results/hardware.txt`。
## 重現
```bash
# 1. 安裝套件、拉釘選版本的 sd.cpp、套用 patch、建置四個變體、下載模型、融合 FFN
./build.sh --all # 或只建置指定變體:./build.sh amx_lto avx512
./build.sh --list # 查看變體與對應的 CMake 參數
# 2. 用同一組提示詞測速,輸出 results/results.csv、results/results.md
REPEAT=2 ./SmartTest.sh # 每列跑 2 次取中位數
```
- `build.sh` 會建立 `./work/`(原始碼、binary、模型、Python venv)。模型下載約 14 GB。
- 模型來源:
- `leejet/Z-Image-Turbo-GGUF`:`z_image_turbo-Q4_K.gguf`、`z_image_turbo-Q8_0.gguf`、`z_image_turbo-Q4_0.gguf`
- `unsloth/Qwen3-4B-Instruct-2507-GGUF`:`Qwen3-4B-Instruct-2507-Q4_K_M.gguf`(文字編碼器)
- `ffxvs/vae-flux`:`ae.safetensors`(FLUX.1 VAE 的鏡像;官方 `black-forest-labs/FLUX.1-schnell` 為 gated)
- `SmartTest.sh` 的矩陣定義在腳本內的 `MATRIX` 陣列,可自行增減。
## 內容
- `build.sh`:一鍵建置。釘選 sd.cpp commit,套用 patch,依參數建置指定變體。
- `SmartTest.sh`:測速與報告。記錄採樣時間、總生成時間、峰值 RSS 與像素差。
- `patches/`
- `sdcpp-0001-zimage-ffn-w13-and-cpu-extra-buft.patch`:`z_image.hpp`(FFN w13 融合)、`model_manager.cpp`(AMX 權重佈局)
- `ggml-0001-cpu-op-profile.patch`:`ggml-cpu.c`(逐算子計時)
- `ggml-0002-flash-attn-kpack.patch`:`ops.cpp`(flash-attn K/V 打包提升為每頭一次)
- `ggml-0003-flash-attn-tiles.patch`:`common.h`(flash-attn tile 128x128)
- `ggml-0004-conv2d-im2col.patch`:`ops.cpp`(VAE direct-conv im2col 向量化 + `GGML_CONV_PROFILE`/`GGML_CONV_PATCH_MB` 診斷)
- `tools/fuse_ffn.py`:把 GGUF 的 FFN w1、w3 串接成 w13(Q4_K/Q8_0 的位元組串接是合法的)。
- `tools/requant_refiner.py` + `tools/q4k.c`:把 refiner 區塊的 Q8_0 權重重新量化成 Q4_K。
- `stages/stage_p2_refiner/`:refiner 根因分析、requant 與 flash-attn tile 的數據。
- `stages/stage3_vae_conv/`:VAE direct-conv 的階段拆解與 im2col 向量化數據。
- `tools/measure_rss.py`:執行指令並回報行程樹的峰值 RSS 與 wall time。
- `results/`:`results.csv`、`results.md`、`hardware.txt`、`images/`(每次執行的 512x512 輸出)、`logs/`。
## 注意事項與限制
- **實驗性質**:`SD_CPU_EXTRA_BUFT=1` 會改變數值(平均像素差 1.28),預設關閉。
- **融合效益有限**:FFN 融合的 MUL_MAT 呼叫數減少 272 次(1948 → 1676),但時間沒有改善,在這台機器上屬於「正確但沒有實益」。
- **Q8_0 與 Q4_0**:在此 CPU 上比 Q4_K 慢,即使 AMX 也一樣。不建議用於速度優先的情境。
- **雜訊**:主機為共用環境,每列跑 2 次取中位數。個別執行的差異約 ±3 s。
- **歷史數據**:早期未綁核、預設 96 執行緒的 baseline 為 137.7 s,不在上表中,因為執行條件不同。
- **像素差的意義**:只有權重相同的列可以直接比較輸出是否改變。
- **模型不包含在此 repo**,請依上游授權使用。Z-Image-Turbo 量化檔的授權為 Apache-2.0;FLUX.1 VAE 依 FLUX.1-schnell 為 Apache-2.0;sd.cpp 為 MIT。
|