Whisper CPP で C/C++ で音声認識を極めたいメモ(M1 では large で2倍速認識いける)

Whisper で音声認識しゅごい...

CPU でも動かしたい...

Whisper CPP がありました!

https://github.com/ggerganov/whisper.cpp

libtorch や ONNX など使わず, 自前の ggml で model の forward 計算と, 自前で C++ で FFT 処理(MEL spectrogram 計算) + Token decode していてしゅごい.

CPU 推論はいろいろ CPU 専用命令使ってもそれなりにはかかります.


~~だいたい GPU の 10 倍くらい時間かかる感じでしょうか(large で GPU だと等速で認識なのが 5~10 倍くらい時間かかる)~~

M1 (普通) だと二倍速, i9 1900K だと 1.45 倍速, Ryzen9 3950X だと二倍遅で認識できました :tada:

## M1 + Accelerate framework だと早い

なんか最近いろいろ最適化施されたようで, 2023/01 時点では, M1 mac mini(2020/11 発売のやつ) だとおよそ 2 倍速で認識できました :tada:

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ふぇぇ... M1 mac mini 最廉価モデル(mem 8 GB) で Whisper C++ large で 457 秒の「恋する女子のときめキュン あるある!」テストデータが 225 秒　で認識(およそ 2 倍)できたよぉ... 🥺✌️ すごいリアルタイム狙わなければもはや large を CPU 処理でいい気がしてきた❗️😳 <a href="https://t.co/RqbQqSgSrX">pic.twitter.com/RqbQqSgSrX</a></p>&mdash; syoyo.eth 🌸 レイトラ ® 🐯 4 周年 🎉 (@syoyo) <a href="https://twitter.com/syoyo/status/1620828358992097280?ref_src=twsrc%5Etfw">February 1, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

~~M1 ですと計算の一部は Neural core か GPU 使っているのかもしれませんね.
(ただ, 精度としては M1(Accelerate での実行)は以下のデスクトップ PC 実行より微妙に劣る感じでした. どこかで精度劣化があるのかもしれません)~~
Accelerate framework 有効のコードパスでは,  BLAS matmul を使っていました. BLAS 実装(Accelerate framework)が CPU でチューンされていたのかも.

iPhone でもメモリ 6GB(iPhone 12 Pro あたり)でギリ入るなら等倍速で認識してくれそうです!
(あとは頑張って int8 量子化 + オンザフライデコードでメモリ消費減らすのを頑張るか)

Android なら最近(2023/01 時点)ではハイエンドはメモリ 12 GB とかなので問題なく動いてくれそう
(速度は M1 ほどではないかもであるが)




また, M1 mac mini で Accelerate framework off だと 511 秒でした.

```
whisper_print_timings:     load time =  1829.53 ms
whisper_print_timings:      mel time =   689.58 ms
whisper_print_timings:   sample time =   205.00 ms
whisper_print_timings:   encode time = 371706.53 ms / 11615.83 ms per layer
whisper_print_timings:   decode time = 137201.16 ms / 4287.54 ms per layer
whisper_print_timings:    total time = 511646.03 ms
```

## Desktop CPU Ryzen 3950X. 4 thread がベスト

Ryzen9 3950X 16 threads では, openblas 使って thread 16 で 24 分くらいでした(1434 秒).

```
[00:07:34.000 --> 00:07:35.000]  有村香澄でした
[00:07:35.000 --> 00:07:37.000]  (字幕視聴ありがとうございました)


whisper_print_timings:     fallbacks =   3 p /  13 h
whisper_print_timings:     load time =  1225.23 ms
whisper_print_timings:      mel time =   622.55 ms
whisper_print_timings:   sample time =  3056.56 ms /  4377 runs (    0.70 ms per run)
whisper_print_timings:   encode time = 591237.06 ms /    18 runs (32846.50 ms per run)
whisper_print_timings:   decode time = 837819.12 ms /  4368 runs (  191.81 ms per run)
whisper_print_timings:    total time = 1434076.12 ms
24240.23user 13956.84system 23:54.20elapsed 2663%CPU (0avgtext+0avgdata 3851004maxresident)k
144inputs+0outputs (0major+1789436minor)pagefaults 0swaps
```

元データの三倍くらいでした.

thread 8 だと

```
[00:07:34.000 --> 00:07:35.000]  有村霞でした
[00:07:35.000 --> 00:07:37.000]  (字幕視聴ありがとうございました)


whisper_print_timings:     fallbacks =   3 p /  12 h
whisper_print_timings:     load time =  1212.94 ms
whisper_print_timings:      mel time =  1418.44 ms
whisper_print_timings:   sample time =  2929.99 ms /  4260 runs (    0.69 ms per run)
whisper_print_timings:   encode time = 296616.84 ms /    18 runs (16478.71 ms per run)
whisper_print_timings:   decode time = 521551.16 ms /  4249 runs (  122.75 ms per run)
whisper_print_timings:    total time = 823833.69 ms
```

thread 8 のほうが処理時間早くて元データの二倍でいけました.

thread 4 だともうちょっと早くなりました.

```
[00:07:34.000 --> 00:07:35.000]  有村霞でした
[00:07:35.000 --> 00:07:37.000]  (字幕視聴ありがとうございました)


whisper_print_timings:     fallbacks =   3 p /  12 h
whisper_print_timings:     load time =  1244.70 ms
whisper_print_timings:      mel time =  2767.07 ms
whisper_print_timings:   sample time =  2887.64 ms /  4260 runs (    0.68 ms per run)
whisper_print_timings:   encode time = 287248.72 ms /    18 runs (15958.26 ms per run)
whisper_print_timings:   decode time = 474788.66 ms /  4249 runs (  111.74 ms per run)
whisper_print_timings:    total time = 769043.69 ms
6034.62user 5248.40system 12:49.16elapsed 1466%CPU (
```

thread 2 だと 960 秒になり速度低下しました.
デフォルトの 4 がちょうどよいようです.

計算によっては (hardware_concurrent で?) 16 threads 使っているときもありましたので, `-t` オプションでのスレッドで必ずしも動いているというわけではありませんでした.


有村香澄 -> 有村霞の違いはありましたが, ほかはだいたい同じでした.
演算よりもメモリアクセス周りがパフォーマンスに影響与えるのですかね.

ggml_graph_compute で threadpool でロックを取っていたりするので, このあたりも影響しているかもしれません.

また, デスクトップならメモリに余裕があるので, fp32 で ggml モデルデータ作って処理でもいいかもです(fp16 だと一応 Ryzen であれば F16C 命令があるが, fp16 <-> fp32 変換していくらかパフォーマンスロスがあると予想)

Ryzen 7000(ZEN4)だと, AVX512 で BF16, INT8 が使えるので(FP16 は使えないが)

https://www.mersenneforum.org/showthread.php?p=614191

BF16, INT8 ベースでやる手もありそうです!


https://zenn.dev/syoyo/articles/0e470e33e35408

Ryzen 7900 あたりだと等倍で認識狙えるかもしれません.

### Desktop i9 12900K(DDR4). 6 thread がベスト.

6 thread で 526 秒でした.
(一番性能がよかった thread 数)

```
system_info: n_threads = 6 / 24 | AVX = 1 | AVX2 = 1 | AVX512 = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 0 | SSE3 = 1 | VSX = 0 | 

main: processing 'arimura.wav' (7321902 samples, 457.6 sec), 6 threads, 1 processors, lang = ja, task = transcribe, timestamps = 1 ...


whisper_print_timings:     fallbacks =   4 p /  15 h
whisper_print_timings:     load time =  1350.63 ms
whisper_print_timings:      mel time =   644.24 ms
whisper_print_timings:   sample time =  1336.44 ms /  3701 runs (    0.36 ms per run)
whisper_print_timings:   encode time = 223186.20 ms /    20 runs (11159.31 ms per run)
whisper_print_timings:   decode time = 300202.69 ms /  3686 runs (   81.44 ms per run)
whisper_print_timings:    total time = 526810.38 ms

```

15% 遅い程度で認識できました

OpenBLAS 有効ですと

```
system_info: n_threads = 6 / 24 | AVX = 1 | AVX2 = 1 | AVX512 = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | VSX = 0 | 

main: processing 'arimura.wav' (7321902 samples, 457.6 sec), 6 threads, 1 processors, lang = ja, task = transcribe, timestamps = 1 ...


whisper_print_timings:     fallbacks =   3 p /  12 h
whisper_print_timings:     load time =  1355.08 ms
whisper_print_timings:      mel time =   641.63 ms
whisper_print_timings:   sample time =  1765.78 ms /  4260 runs (    0.41 ms per run)
whisper_print_timings:   encode time = 251396.44 ms /    18 runs (13966.47 ms per run)
whisper_print_timings:   decode time = 437509.28 ms /  4249 runs (  102.97 ms per run)
whisper_print_timings:    total time = 692758.50 ms
```

と遅くなってしまいました. Alder Lake ですと ggml で AVX 等に任せるのがよい結果となりました.

DDR5 だともう少し早くなり, 等倍狙えると思われます. もしくは 13 世代のいいやつとか.

Alder Lake(12 世代) 以降では AVX-VNNI で INT8 使えますので(INT8 は AVX-VNNI では多少制約がある), Alder Lake で INT8 使ったら M1 くらいの性能でるかもです.

## WASM

wasm 化してブラウザ実行などもできるでしょう.
ただ通常の WASM ですと 32bit なので, 使えるモデルは small (~1 GB mem)が限界でしょうか.
whisper 1.2 でメモリ削減して medium 1.7 G mem 消費になったのでギリはいるかもですが...

64bit wasm(wasm64)のランタイムが求められますね.

## その他ベンチマーク

https://github.com/ggerganov/whisper.cpp/issues/89

にベンチマーク結果があります.

M1 の性能がええですね
(↑にあるように頑張れば x64 でも性能引き出せる余地はありそうではあるが)