【CPU上の実行に対応】OpenAI Whisperの高性能推論ver Whisper.cppを試してみた

## まえがき
OpenAI Whisperの高性能推論verが話題となっていたので試してみました。
C/C++で実装され、CPUの実行に最適化されたものとなっているようです。
特徴としては以下が挙げられていました。
- 依存性のないプレーンなC/C++による実装
- x86 アーキテクチャの AVX 組み込み関数のサポート
- POWER アーキテクチャの VSX 組み込み関数のサポート
- F16 / F32 の混合精度
- 低メモリ使用量
- CPU上で実行
- Windows / Linux / Mac OS / iOS / Android / WebAssembly / Raspberry Piのプラットフォームに対応

## 導入

- ソースコードをローカルへクローンする
```bash
$ git clone https://github.com/ggerganov/whisper.cpp.git
```

- 使用するモデルをダウンロードする
```bash
$ bash ./models/download-ggml-model.sh <Model>
Downloading ggml model small ...
ggml-small.bin                                             
 100%[====================================================================>] 465.01M  2.84MB/s 時間 1m 58s   
Done! Model 'small' saved in 'models/ggml-small.bin'
You can now use it like this:

   ./main -m models/ggml-small.bin -f samples/jfk.wav
```

- 対象モデルは以下となります。

| Model     | Disk   | Mem     | 
| ---       | ---    | ---     | 
| tiny      |  75 MB | ~390 MB | 
| tiny.en   |  75 MB | ~390 MB | 
| base      | 142 MB | ~500 MB | 
| base.en   | 142 MB | ~500 MB | 
| small     | 466 MB | ~1.0 GB | 
| small.en  | 466 MB | ~1.0 GB | 
| medium    | 1.5 GB | ~2.6 GB | 
| medium.en | 1.5 GB | ~2.6 GB | 
| large     | 2.9 GB | ~4.7 GB | 

OpenAIのWhisperでは、VRAMの使用量について言及されていましたが、Whisper.cppではVRAMの記述はありません。

- makeコマンドを実行するためインストールされていない場合は、以下をインストールする


```bash
$ yum install make -y
$ yum install gcc -y
$ yum install gcc-c++ -y
```

- Whisper.cppをクローンしたディレクトリでmakeコマンド実行
```bash
$ make
g++ -I. -I./examples -O3 -std=c++11 -pthread -c whisper.cpp -o whisper.o
g++ -I. -I./examples -O3 -std=c++11 -pthread examples/main/main.cpp whisper.o ggml.o -o main 
./main -h

usage: ./main [options] file0.wav file1.wav ...

options:
  -h,       --help           show this help message and exit
  -s SEED,  --seed SEED      RNG seed (default: -1)
  -t N,     --threads N      number of threads to use during computation (default: 4)
  -p N,     --processors N   number of processors to use during computation (default: 1)
  -ot N,    --offset-t N     time offset in milliseconds (default: 0)
  -on N,    --offset-n N     segment index offset (default: 0)
  -d  N,    --duration N     duration of audio to process in milliseconds (default: 0)
  -mc N,    --max-context N  maximum number of text context tokens to store (default: max)
  -ml N,    --max-len N      maximum segment length in characters (default: 0)
  -wt N,    --word-thold N   word timestamp probability threshold (default: 0.010000)
  -v,       --verbose        verbose output
            --translate      translate from source language to english
  -otxt,    --output-txt     output result in a text file
  -ovtt,    --output-vtt     output result in a vtt file
  -osrt,    --output-srt     output result in a srt file
  -owts,    --output-words   output script for generating karaoke video
  -ps,      --print_special  print special tokens
  -pc,      --print_colors   print colors
  -nt,      --no_timestamps  do not print timestamps
  -l LANG,  --language LANG  spoken language (default: en)
  -m FNAME, --model FNAME    model path (default: models/ggml-base.en.bin)
  -f FNAME, --file FNAME     input WAV file path
```

- その後、以下コマンドを実行し、Whisper.cppを動かそうとすると以下エラーが表示される。

OpenAIのWhisperはm4aなど他のファイルにも対応していたが、Whisper.cppは16kHzのWAVファイルにのみ対応しているとのこと。  
そのため、ffmpegを利用して音声データをWAVファイルに変換する。
```bash
$ ./main -m models/ggml-small.bin -f output.wav -l ja -ovtt
./main: WAV file 'audio.wav' must be 16 kHz
```

- 以下コマンドを実施しffmpegをインストールする。（CentOS8例）
```bash
dnf install https://download.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
dnf localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm
dnf install https://rpmfind.net/linux/epel/7/x86_64/Packages/s/SDL2-2.0.14-2.el7.x86_64.rpm
dnf -y install ffmpeg
```

- ffmpegにより16kHzのWAVファイルに変換する。

```bash
ffmpeg -i audio.m4a -ar 16000 -ac 1 -c:a pcm_s16le output.wav
ffmpeg version 4.2.8 Copyright (c) 2000-2022 the FFmpeg developers
  built with gcc 8 (GCC)
  configuration: --prefix=/usr --bindir=/usr/bin --datadir=/usr/share/ffmpeg --docdir=/usr/share/doc/ffmpeg --incdir=/usr/include/ffmpeg --libdir=/usr/lib64 --mandir=/usr/share/man --arch=x86_64 --optflags='-O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' --extra-ldflags='-Wl,-z,relro -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld ' --extra-cflags=' ' --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libvo-amrwbenc --enable-version3 --enable-bzlib --disable-crystalhd --enable-fontconfig --enable-frei0r --enable-gcrypt --enable-gnutls --enable-ladspa --enable-libaom --enable-libdav1d --enable-libass --enable-libbluray --enable-libcdio --enable-libdrm --enable-libjack --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libmp3lame --enable-nvenc --enable-openal --enable-opencl --enable-opengl --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-librsvg --enable-libsrt --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libvorbis --enable-libv4l2 --enable-libvidstab --enable-libvmaf --enable-version3 --enable-vapoursynth --enable-libvpx --enable-libx264 --enable-libx265 --enable-libxvid --enable-libzimg --enable-libzvbi --enable-avfilter --enable-avresample --enable-libmodplug --enable-postproc --enable-pthreads --disable-static --enable-shared --enable-gpl --disable-debug --disable-stripping --shlibdir=/usr/lib64 --enable-libmfx --enable-runtime-cpudetect
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libavresample   4.  0.  0 /  4.  0.  0
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'audio.m4a':
  Metadata:
    major_brand     : M4A 
    minor_version   : 0
    compatible_brands: M4A mp42isom
  Duration: 00:00:25.22, start: 0.000000, bitrate: 127 kb/s
    Stream #0:0(und): Audio: aac (LC) (mp4a / 0x6134706D), 32000 Hz, mono, fltp, 126 kb/s (default)
Stream mapping:
  Stream #0:0 -> #0:0 (aac (native) -> pcm_s16le (native))
Press [q] to stop, [?] for help
Output #0, wav, to 'output.wav':
  Metadata:
    major_brand     : M4A 
    minor_version   : 0
    compatible_brands: M4A mp42isom
    ISFT            : Lavf58.29.100
    Stream #0:0(und): Audio: pcm_s16le ([1][0][0][0] / 0x0001), 16000 Hz, mono, s16, 256 kb/s (default)
    Metadata:
      encoder         : Lavc58.54.100 pcm_s16le
size=     788kB time=00:00:25.21 bitrate= 256.0kbits/s speed=1.22e+03x    
video:0kB audio:788kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.009666%
```

## 実行


| オプション  | 内容   |
| ---    | ---    | 
| -m   |  利用モデル | 
| -f   | インプットとなるWAVファイルのパス | 
| -l  | 言語を指定する |
| -ovtt | 結果をvttファイルで出力する | 


```bash
./main -m models/ggml-small.bin -f output.wav -l ja -ovtt
whisper_model_load: loading model from 'models/ggml-small.bin'
whisper_model_load: n_vocab       = 51865
whisper_model_load: n_audio_ctx   = 1500
whisper_model_load: n_audio_state = 768
whisper_model_load: n_audio_head  = 12
whisper_model_load: n_audio_layer = 12
whisper_model_load: n_text_ctx    = 448
whisper_model_load: n_text_state  = 768
whisper_model_load: n_text_head   = 12
whisper_model_load: n_text_layer  = 12
whisper_model_load: n_mels        = 80
whisper_model_load: f16           = 1
whisper_model_load: type          = 3
whisper_model_load: mem_required  = 1044.00 MB
whisper_model_load: adding 1608 extra tokens
whisper_model_load: ggml ctx size = 464.56 MB
whisper_model_load: memory size =    68.48 MB
whisper_model_load: model size  =   464.44 MB

system_info: n_threads = 4 / 4 | AVX2 = 1 | AVX512 = 0 | NEON = 0 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 0 | 

main: processing 'output.wav' (403456 samples, 25.2 sec), 4 threads, 1 processors, lang = ja, task = transcribe, timestamps = 1 ...
```

## 実行結果
CPU、メモリを増設して検証してみました。(1時間分の音声データ）

### 増設前
- Intel(R) Xeon(R) CPU E5-2667 4コア
- RAM 8GB

この状態で実行したとき、処理完了まで**33分**かかりました。

### 増設後
- Intel(R) Xeon(R) CPU E5-2667 8コア
- RAM 16GB

この状態で実行したとき、処理完了まで**26分**かかりました。

### 総評
スケールアップをすることで、1時間当たりの音声データでVRAM非搭載サーバ上からの実行により**7分の削減**ができ、CPUの性能にwhisper.cppの動作が依存していると言えます。
また、OpenAI純正のWhisperで同環境で実行した際は、GPUを搭載していないため動作にかなりの時間がかかりましたので、**GPU非搭載サーバ上で動作させる場合は、Whisper.cppの利用を推奨します。**


## 参考
https://github.com/ggerganov/whisper.cpp