🎤

whisper.cppのCore ML版をM1 MacBook Proで動かす

2023/05/03に公開

OpenAIの音声認識モデルであるWhisperの高速推論版であるwhisper.cppが、いつのまにか [1] Core ML対応していた。

Core ML対応したということは、macOS/iOSデバイス(Mac, iPhone, etc...)に搭載されているNeural Engine、GPUを利用して推論処理を行うようになった、ということを意味する。[2]

さっそくREADMEの手順をなぞりつつ手元のMBPで動かしてみたメモ。

なお、実行環境は以下の通り:

  • MacBook Pro M1 Max 64GB
  • macOS Ventura 13.3.1

Core MLモデルの生成手順

依存パッケージのインストール

whisper.cppのCore MLモデルの作成に必要なパッケージをインストールする。

pip install ane_transformers
pip install openai-whisper
pip install coremltools

Core MLモデルの生成

models フォルダ配下に generate-coreml-model.sh にCore MLモデル生成スクリプトが用意されているので、以下のように実行する:

./models/generate-coreml-model.sh base

base はモデルの種類。サイズ・精度違いで以下の種類が指定でき、英語専用版は base.en のように .en をつけて指定する。

Model Disk Mem
tiny 75 MB ~125 MB
base 142 MB ~210 MB
small 466 MB ~600 MB
medium 1.5 GB ~1.7 GB
large 2.9 GB ~3.3 GB

スクリプトを実行すると、models 配下に、 coreml-encoder-base.mlmodelcbase モデルを指定した場合)という名前のファイルが生成される。

Core MLサポート版whisper.cppをビルドする

トップフォルダ(whisper.cpp )配下で以下を実行する。

make clean
WHISPER_COREML=1 make -j

make を実行すると、usageが出力されてきて一瞬失敗したのかと思ったが、

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

options:
  ...(以下略)

よく読むと親切にmakeにより生成される main コマンドの使い方を書いてくれているだけだった。(つまり成功している)

実行

次のようにして実行できる。

./main -m models/ggml-base.bin -f file1.wav

日本語の指定

試しに日本語音声base モデルで認識させてみたところ、全然ダメだった。

言語を指定しない場合の認識結果
[00:00:00.000 --> 00:00:18.980]   Hello, I'm BAN. This is GESTO. I've been to the Ongo-Yakutip Hiroshi. I'm Ongo-Yakutip Hiroshi. I'm the software director. I'm a software director.
[00:00:18.980 --> 00:00:30.460]   I was born from ramen in the US military, so I worked with an American organisation, and it was very interesting and interesting.
[00:00:30.460 --> 00:00:37.180]   I think I had to go through the theme, "Determined."
[00:00:37.180 --> 00:00:45.920]   We are not sure if I know that this is going to be a good response in Youtube, but if I don't know how it will end, I think.
[00:00:45.920 --> 00:01:01.340]   The current screen back there, I have always been doing this on-sale on-sale, but I wanted to film some videos to improve my videos.
[00:01:01.340 --> 00:01:14.240]   I think it's great to see the brand new pin mic on Red 1. Thank you very much.
[00:01:14.240 --> 00:01:18.380]   Kiroshi-san said that we had a problem with nature, playing a practice,
[00:01:18.380 --> 00:01:25.500]   so I asked him to tell us how we could adapt to nature and how we could learn.
[00:01:25.500 --> 00:01:26.520]   What's your training?
[00:01:26.520 --> 00:01:27.200]   I'd love to.
[00:01:27.200 --> 00:01:33.880]   I asked him to tell us how we could adapt to nature, so I asked him to tell us how we could adapt to nature and how we could adapt to nature.
[00:01:33.880 --> 00:01:42.900]   You know, when you were teaching a little bit of history, we started learning the art.
[00:01:42.900 --> 00:01:44.060]   What did you do?
[00:01:44.060 --> 00:01:45.780]   I was busy studying.
[00:01:45.780 --> 00:01:46.880]   What did you do then?
[00:01:46.880 --> 00:01:48.880]   It was always like this.
[00:01:48.880 --> 00:01:53.380]   I started learning about art during my development.
[00:01:53.380 --> 00:01:55.880]   I didn't go out all the time.
[00:01:55.880 --> 00:01:59.380]   I also started to study at the company's 7th standard.
[00:01:59.380 --> 00:02:03.380]   We started to work at around 100 students.
[00:02:03.380 --> 00:02:06.380]   We started to work at the company's 2nd standard.
[00:02:06.380 --> 00:02:09.380]   We started to work at the company's 2nd standard.
[00:02:09.380 --> 00:02:12.380]   We started to work at the company's 3rd standard.
[00:02:12.380 --> 00:02:14.380]   We started to work at the company's 3rd standard.
[00:02:14.380 --> 00:02:16.380]   We started to work at the company's 3rd standard.
[00:02:16.380 --> 00:02:19.380]   We started to work at the company's 3rd standard.
[00:02:19.380 --> 00:02:22.380]   We started to work at the company's 3rd standard.
[00:02:22.380 --> 00:02:25.380]   We started to work at the company's 3rd standard.
[00:02:25.380 --> 00:02:28.380]   We started to work at the company's 3rd standard.
[00:02:28.380 --> 00:02:31.380]   We started to work at the company's 3rd standard.
[00:02:31.380 --> 00:02:34.380]   We started to work at the company's 3rd standard.
[00:02:34.380 --> 00:02:37.380]   We started to work at the company's 3rd standard.
[00:02:37.380 --> 00:02:40.380]   We started to work at the company's 3rd standard.
[00:02:40.380 --> 00:02:43.380]   We started to work at the company's 3rd standard.
[00:02:43.380 --> 00:02:45.380]   We started to work at the company's 3rd standard.
[00:02:45.380 --> 00:02:48.380]   We started to work at the company's 3rd standard.
[00:02:48.380 --> 00:02:51.380]   We started to work at the company's 3rd standard.
[00:02:51.380 --> 00:02:54.380]   We started to work at the company's 3rd standard.
[00:02:54.380 --> 00:02:56.380]   We started to work at the company's 3rd standard.
[00:02:56.380 --> 00:02:58.380]   We started to work at the company's 3rd standard.
[00:02:58.380 --> 00:03:00.380]   We started to work at the company's 3rd standard.
[00:03:00.380 --> 00:03:03.380]   We started to work at the company's 3rd standard.
(以下略)

序盤はちゃんと日本語が認識されているようだが英語として出力されてしまうし、途中からはまったく認識しなくなった。

言語を指定するには、-l オプションを使用する。

-l LANG, --language LANG [en ] spoken language ('auto' for auto-detect)

(デフォルトは auto だと思っていたが、 en だったようだ)

次のように日本語を指定すると、

% ./main -m models/ggml-base.bin -l ja -f test.wav

ちゃんとした認識結果が出力されるようになった。

日本語を指定した場合の認識結果
[00:00:00.000 --> 00:00:05.000]  こんにちは、エンジニット人生動画バーンです。
[00:00:05.000 --> 00:00:11.000]  今日はゲストにオブジェクティブ広Cさんに入っていただきました。
[00:00:11.000 --> 00:00:13.000]  オブジェクティブ広Cです。
[00:00:13.000 --> 00:00:18.000]  モトラーミア天調プログラマで活動しています。
[00:00:18.000 --> 00:00:30.000]  ラメン屋さんから愛媛先生になられて、愛媛先生とコメントに向かって、メキメキと確保を表しています。
[00:00:30.000 --> 00:00:37.000]  エンジニット人生というテーマが良いと思います。
[00:00:37.000 --> 00:00:43.000]  今日は、これたぶん YouTubeでは白くなっていると思います。
[00:00:43.000 --> 00:00:49.000]  白くなっているがどうなっているか分からないですが、これ今後ろにグリーンバックの回転があります。
[00:00:49.000 --> 00:00:53.000]  僕が、ずっとこのバンチャンネル音声でやってたんですけど、
[00:00:53.000 --> 00:01:01.000]  それを動画を撮りたいなって、何と言ってたら、動画撮影に協力してくれるということで、
[00:01:01.000 --> 00:01:07.000]  レンドワンさんにスタジオをおかしいいただいて、今ものすごいピンマイクとか、
[00:01:07.000 --> 00:01:13.000]  すごいワッカーのツイート、照明とか、すごいカメラとかで撮影していただいています。
[00:01:13.000 --> 00:01:15.000]  ありがとうございます。
[00:01:15.000 --> 00:01:20.000]  白小さんは、ラメン屋さんから愛媛先生になっているので、
[00:01:20.000 --> 00:01:23.000]  モトとかに、ラメン屋さんからインターバーを見ると、
[00:01:23.000 --> 00:01:27.000]  見ると、どんな勉強したことかかれています。
[00:01:27.000 --> 00:01:31.000]  だから、どんな勉強したかとか、そこを見ていただいて、
[00:01:31.000 --> 00:01:37.000]  ラメン屋さんの時代に変調をされてたの?
[00:01:37.000 --> 00:01:43.000]  ちょっとだけ歴史で話すると、大学で出てからラメン屋さんだったんですよ。
[00:01:43.000 --> 00:01:47.000]  何でも歴史の感想が出たんですか?
[00:01:47.000 --> 00:01:51.000]  ちょっと、もう一回。
[00:01:51.000 --> 00:01:55.000]  僕が大学生の時にリマショックだったんですよね。
[00:01:55.000 --> 00:02:01.000]  全然収索してたくて、その時デザインのアメッチボードだったんですけど、
[00:02:01.000 --> 00:02:07.000]  100人くらい会場に集まってて、そのうからの二人とかで無理じゃんってなって、
[00:02:07.000 --> 00:02:13.000]  地元に乗って、フード系のミズネスの弁者に行ったら、
[00:02:13.000 --> 00:02:15.000]  ラメン屋さんだったんですよね。
[00:02:15.000 --> 00:02:17.000]  大切なラメン屋さんでした。
[00:02:17.000 --> 00:02:23.000]  もともとデザインの足持ってことは、もともと、こっちの業界に興味はあって、
[00:02:23.000 --> 00:02:25.000]  昔から何か作るの好きでしたね。
[00:02:25.000 --> 00:02:27.000]  いい買い方に何か買いたいのか。
[00:02:27.000 --> 00:02:33.000]  で、収索が難しくて、フード系の大きい会社に入ったら、
[00:02:33.000 --> 00:02:35.000]  ラメン屋さんの誕生日だったのか。
[00:02:35.000 --> 00:02:37.000]  それは、アベンチャーのスコーン?
[00:02:37.000 --> 00:02:39.000]  アベンチャーのスコーン?
[00:02:39.000 --> 00:02:41.000]  ラメン屋さんの店長はフォーインだったんですよ。
[00:02:41.000 --> 00:02:43.000]  フォージョン?
[00:02:43.000 --> 00:02:45.000]  フォージョンをやっちバッとなって感じですか?
[00:02:45.000 --> 00:02:47.000]  本当ですか。
[00:02:47.000 --> 00:02:49.000]  これは一緒にやるのだと。
[00:02:49.000 --> 00:02:53.000]  でも、僕らから見ると、
[00:02:53.000 --> 00:02:55.000]  なかなかできない経験なんです。
[00:02:55.000 --> 00:02:57.000]  興味あるんですけど、
[00:02:57.000 --> 00:02:59.000]  テンポオケーとか、
[00:02:59.000 --> 00:03:01.000]  フランチャーズ展開とか、
[00:03:01.000 --> 00:03:03.000]  ちょっとかかわっているんです。
[00:03:03.000 --> 00:03:07.000]  僕は完全にやと割天長だったので、
(以下略)

"error: failed to open 'xxxx.m4a' as WAV file" エラー

上述の方法でビルドできる main コマンドは16ビットのWAVフォーマットのみサポートしている。

Note that the main example currently runs only with 16-bit WAV files

なので、WAVファイル以外の音声ファイル(たとえば.m4a)を渡すと、次のようなエラーになる。

error: failed to open 'xxxx.m4a' as WAV file
error: failed to read WAV file 'xxxx.m4a'

対応するフォーマットに変換するには ffmpeg を用いて以下のようにする:

ffmpeg -i input.mp3 -ar 16000 -ac 1 -c:a pcm_s16le output.wav

気になる実行速度

mainコマンドは処理完了後に実行時間も出力してくれる。

以下は約15分の音声ファイルを認識させたときの実行時間:

whisper_print_timings:     load time =    89.64 ms
whisper_print_timings:     fallbacks =   1 p /   0 h
whisper_print_timings:      mel time =  1097.88 ms
whisper_print_timings:   sample time =  2312.26 ms /  5772 runs (    0.40 ms per run)
whisper_print_timings:   encode time =  1973.52 ms /    33 runs (   59.80 ms per run)
whisper_print_timings:   decode time = 16290.62 ms /  5773 runs (    2.82 ms per run)
whisper_print_timings:    total time = 21953.23 ms

なんと、たった22秒で処理が完了している。

実時間の1/40 〜 1/50の処理時間、本家Whisperのモデル(CPU実行)と比べて約70倍の速さ [3] という結果になった。 [4]

base でこれぐらい速いのであれば、さらに高精度な small, mediumlarge の利用さえも現実的に選択肢に入ってくる。

medium / largeモデルを使用する際のトラブルシューティング

本記事を公開後、Twitter等で「mediumlarge モデルのCore ML変換がうまくいかない」というような反応が散見された。

実際に手元で試してみたところ、たしかにいろいろつまづきポイントがあるが、最終的にはいずれのモデルもM1 MBPで動いたので、問題点と解決法を以下に書いておく。

Core MLモデル生成時の問題

medium モデルや large モデルをCore MLモデルに変換しようとしてみたところ、このissueコメントと同じ場所で止まる。

・・・が、ひたすら待っていれば完了した。所要時間はmediumモデルで3時間36分だった。(M1 Pro Max 64GB利用)

この感じだとlargeは一晩か、下手したらもっとかかりそうな気がしたので試していない。しかしとにかく待てば完了するのではと。

で、実は変換済みのCore MLモデルを公開してくれている人がいて [5]、以下のissueコメントにあるURLからダウンロードできる。

https://github.com/ggerganov/whisper.cpp/pull/566#issuecomment-1531319899

makeの問題

make を走らせることで、各モデル(.mlmodelc の方ではなく、ggml-xxxx.bin の方)をダウンロードできる。

make tiny.en
make tiny
make base.en
make base
make small.en
make small
make medium.en
make medium
make large-v1
make large

が、最後に次のようなエラーが出て終わることがある。

whisper_init_no_state: failed to load model
error: failed to initialize whisper context

というエラーが出て終わる。

ログ詳細
(前略)
===============================================
Running medium on all samples in ./samples ...
===============================================

----------------------------------------------
[+] Running medium on samples/jfk.wav ... (run 'ffplay samples/jfk.wav' to listen)
----------------------------------------------

whisper_init_from_file_no_state: loading model from 'models/ggml-medium.bin'
whisper_model_load: loading model
whisper_model_load: n_vocab       = 51865
whisper_model_load: n_audio_ctx   = 1500
whisper_model_load: n_audio_state = 1024
whisper_model_load: n_audio_head  = 16
whisper_model_load: n_audio_layer = 24
whisper_model_load: n_text_ctx    = 448
whisper_model_load: n_text_state  = 1024
whisper_model_load: n_text_head   = 16
whisper_model_load: n_text_layer  = 24
whisper_model_load: n_mels        = 80
whisper_model_load: ftype         = 1
whisper_model_load: type          = 4
whisper_model_load: mem required  = 1899.00 MB (+   43.00 MB per decoder)
whisper_model_load: adding 1608 extra tokens
whisper_model_load: model ctx     = 1462.35 MB
whisper_model_load: model size    =  157.00 MB
whisper_model_load: ERROR not all tensors loaded from model file - expected 947, got 32
whisper_init_no_state: failed to load model
error: failed to initialize whisper context

原因を調べてみたところ、ggmlモデルのダウンロードに失敗していたようだ。以下からモデルがダウンロードできるのだが、サイズが違っていた。

https://huggingface.co/datasets/ggerganov/whisper.cpp/tree/main

make 自体は medium, large 等、使用するモデルを変えるたびにやりなおす必要はないので、ggmlモデルのダウンロードだけが目的であれば上のURLからダウンロードした方が確実。

書き起こし実行時の問題

ggmlモデルのダウンロードに失敗している場合

ggml-xxxx.bin モデルのダウンロードが失敗していると、main 実行時に次のようなエラーになる

main実行時のログ詳細
% ./main -m models/ggml-medium.bin -l ja -f xxxx.wav 
whisper_init_from_file_no_state: loading model from 'models/ggml-medium.bin'
whisper_model_load: loading model
whisper_model_load: n_vocab       = 51865
whisper_model_load: n_audio_ctx   = 1500
whisper_model_load: n_audio_state = 1024
whisper_model_load: n_audio_head  = 16
whisper_model_load: n_audio_layer = 24
whisper_model_load: n_text_ctx    = 448
whisper_model_load: n_text_state  = 1024
whisper_model_load: n_text_head   = 16
whisper_model_load: n_text_layer  = 24
whisper_model_load: n_mels        = 80
whisper_model_load: ftype         = 1
whisper_model_load: type          = 4
whisper_model_load: mem required  = 1899.00 MB (+   43.00 MB per decoder)
whisper_model_load: adding 1608 extra tokens
whisper_model_load: model ctx     = 1462.35 MB
whisper_model_load: model size    =  157.00 MB
whisper_model_load: ERROR not all tensors loaded from model file - expected 947, got 32
whisper_init_no_state: failed to load model
error: failed to initialize whisper context

これは上述したように、huggingfaceからダウンロードしてくればOK。

"first run on a device may take a while ..." から進まない問題

baseでは気になるほどではなかったので気づかなかったが、ログに

"first run on a device may take a while ..."

(初回起動に時間がかかる場合があります)

と出る部分で、mediumやlargeだと本当にめちゃくちゃ時間がかかる。mediumモデルでは3時間21分、largeモデルではなんと11時間16分もかかった。(M1 Pro Max 64GB使用)

"first run on a device may take a while ..."から進まないときのログ
whisper_init_from_file_no_state: loading model from 'models/ggml-medium.bin'
whisper_model_load: loading model
whisper_model_load: n_vocab       = 51865
whisper_model_load: n_audio_ctx   = 1500
whisper_model_load: n_audio_state = 1024
whisper_model_load: n_audio_head  = 16
whisper_model_load: n_audio_layer = 24
whisper_model_load: n_text_ctx    = 448
whisper_model_load: n_text_state  = 1024
whisper_model_load: n_text_head   = 16
whisper_model_load: n_text_layer  = 24
whisper_model_load: n_mels        = 80
whisper_model_load: ftype         = 1
whisper_model_load: type          = 4
whisper_model_load: mem required  = 1899.00 MB (+   43.00 MB per decoder)
whisper_model_load: adding 1608 extra tokens
whisper_model_load: model ctx     = 1462.35 MB
whisper_model_load: model size    = 1462.12 MB
whisper_init_state: kv self size  =   42.00 MB
whisper_init_state: kv cross size =  140.62 MB
whisper_init_state: loading Core ML model from 'models/ggml-medium-encoder.mlmodelc'
whisper_init_state: first run on a device may take a while ...

ちなみに「初回起動時は」とある通り、2度目以降は速い。2度目の場合、mediumモデル、初回実行時と同じ4分弱の音声ファイルの書き起こしで、約38秒で処理が完了した。つまりこの差分の3時間20分が、mediumモデルの初回起動時の処理にかかるオーバーヘッドということになる。

なおこの件に関してはもう一つ付随する問題があり、時間がかかるからといって ctrl+c 中断して、モデルを変えるなりしてもう一度main を実行しようとしても動かない。たとえば以前はサクッと動いたはずのbaseモデルまで動かなくなっている。

原因は、コマンドを中断しても、 ANECompilerService というプロセスが生き続けていること。Activity Monitor等で当該プロセスをForce Quitすることで、また別のタスクを実行できるようになる。

以上のトラブルシューティングにより、M1 MBPでもmedium, largeモデルを快適に動作させられるようになった。

medium, largeの実行速度

前述の通り、medium / largeの初回実行は非常に時間がかかる。二度目以降の書き起こしはそれなりに高速で動くが、実際にどのぐらいなのか測定してみた。

上でbaseで試したの同じ約15分の音声の書き起こし。baseでは22秒だったのが、

  • medium: 2分13秒
  • large: 4分52秒

という結果になった。最高精度のlargeでも実時間の3分の1で処理できているのは上出来 [6] ではと。

またmediumはlargeの2倍程度速く、baseは13倍程度だった。Whisperの公式ドキュメント に載っているRelative speedとも「まぁまぁ」合致している。

Size Parameters English-only model Multilingual model Required VRAM Relative speed
tiny 39 M tiny.en tiny ~1 GB ~32x
base 74 M base.en base ~1 GB ~16x
small 244 M small.en small ~2 GB ~6x
medium 769 M medium.en medium ~5 GB ~2x
large 1550 M N/A large ~10 GB 1x

Next

Core MLモデルはiOSデバイス(iPhone, iPad)でも動作する。whisper.cppがこれぐらい速いのであれば、音声認識の処理速度の限界により過去実現を諦めたアプリもつくれそうだ。

というわけで、次のステップとしてはiOS/macOSアプリにこのCore MLモデルを組み込んでみたい。

なお、whisper.cppリポジトリの examples配下に whisper.objcwhisper.swiftui という2つのiOSサンプルがあるが、これらはCore ML版を利用するものではない。

関連記事:
https://note.com/shu223/n/n6bcef511d00c

脚注
  1. READMEのコミット履歴をたどると、2023年4月15日あたりに Core MLの説明が追記 されている。 ↩︎

  2. Core MLの詳しい解説は、こちらのスライド、および動画 を参照。 ↩︎

  3. 本家Whisperモデルで15分の音声を認識させるとあまりに時間がかかるので、4分弱の音声データを使用して比較した。whisper.cppのCore ML版だと4.5秒で処理完了したところが、本家Whisperモデルだと319.7秒もかかった。 ↩︎

  4. 前述の通り実行環境はMBP M1 Max 64GB。比較に使用した本家Whisperのモデルは同精度の base↩︎

  5. いつまでもここに置いてくれてるかはわからないが、誰が変換しても同じものができるわけなので、これがあるべき運用だとは思う。 ↩︎

  6. 前述の通り、iOS/macOS標準のSpeechフレームワークでは実時間かかる。 ↩︎

Discussion