whisper.cppには、Apple Silicon向けのオプションのCore MLエンコーダー経路があります。Macの音声入力アプリなら当然オンにするはずの機能です。Voice Typeはこれを強制的に無効化し、代わりにMetalバックエンドを使っています。
理由はシェイプの不一致であり、ライブ音声入力がオーディオをバッチ化する方法に由来します。
TL;DR
- コンパイル済みのCore MLエンコーダーバンドルは、固定の入力シェイプを前提としています。
- ライブ音声入力は、長さの異なるバッチを生成します。バッチ化処理が、フレーズを途中で切ることを拒否するためです。
- そのためVoice Typeは、コンテキスト作成時にCore ML経路を無効化し、可変長のオーディオを受け付けるMetalで動作します。
- この経路を削除したことで、モデルごとに163 MB〜1.17 GBあったダウンロードもなくなりました。
バッチの長さが一定でない理由
Voice Typeは、固定のタイマー間隔で区切ったオーディオを認識器に送るわけではありません。ストリーミング式の音声区間検出(VAD)が発話区間を判定し、バッチ化処理は発話区間をまるごと蓄積します。タイマーが作動したからといって、1つの区間を分割することはありません。
バッチをモデルに送るタイミングを決めるルールは、すべて時計ではなく発話に基づく条件です。
- ライブバッチの目標は29秒で、15〜29秒の範囲に制限されます。
- 5秒未満のバッチは、ポーズ(間)によってフラッシュされることはありません。待機し、次のフレーズがそこに加わります。
- ポーズによるフラッシュには、閉じた発話区間のあとに少なくとも400 msの無音が必要です。
- 次の区間をまるごと追加すると目標を超える場合は、まず現在のバッチをフラッシュし、その区間をまるごと次のバッチの先頭にします。
- ホットキーを離したときは、残っているものが長さにかかわらずフラッシュされます。
その結果、バッチの長さは定数ではなく分布になります。バッチは5秒のこともあれば31秒のこともあります。音声入力としては、これが正しい挙動です。そうしなければ、ウィンドウの境界で単語を真っ二つにしてしまうからです。
Core MLだと何がコストになるか
Core MLモデルは、入力の次元があらかじめ組み込まれた形で、.mlmodelcバンドルとして事前にコンパイルされます。コンパイル時と異なる長さのオーディオを渡すと、固定シェイプに合わせてパディングする(無音に対してもエンコーダーの処理コストを払うことになる)か、コンパイル済みのバリアントを複数用意しておく(ディスク上のバンドルが複数になる)か、のどちらかになります。
Voice Typeはどちらも選びません。Core MLエンコーダー経路は、whisperコンテキストを作成する箇所で強制的に無効化されており、コメントにはまさにこの理由、つまり固定シェイプの入力という制約が明記されています。そのうえでバックエンドのポリシーは、デフォルトでMetalを選択し、CPUにフォールバックするのは、シミュレーター上、ユーザーデフォルトでGPUが無効にされている場合、またはクラッシュ後にセーフモードがラッチされた場合に限ります。
Metalは、バッチ化処理が生成するどんな長さでも受け付けます。
削除によって解放されたもの
Core MLのサポートは、単に無効化されただけでなく、削除されました。以前のビルドでは、各モデルの重みの隣にエンコーダーバンドルggml-<name>-encoder.mlmodelcをインストールしており、モデルによって約163 MBから1.17 GBに及びました。これらのバンドルは、モデルごとにダウンロード、解凍、配置されていました。
もはやそれらを削除するコードパスは存在しないため、アプリにはワンタイムのマイグレーションが組み込まれており、アップグレード時に、孤立したエンコーダーバンドルをモデルディレクトリから一掃します。その際、隣にある.binの重みには決して触れないよう注意しています。
つまり、Core MLをオフにしたことでユーザーに見える結果は、ダウンロードサイズとモデルディレクトリが小さくなったことです。そのうえ、認識器は文の長さを気にしません。
破線の分岐は、アプリが意図的に通らない経路です。出典:whisper.cpp.swift/LibWhisper.swift。
Metalが実際に実行していること
MetalはAppleの低レベルGPU APIで、このアプリでは2つの別々の役割を担っています。
whisperコンテキストはMetalバックエンドで動作するため、エンコーダーとデコーダーはGPU上で実行されます。これとは別に、バッチごとのオーディオ調整が、モデルが何かを受け取る前の独立した信号処理ステージとして実行されます。DCオフセットの除去、フィルターとラウドネス制御、そしてモデルが想定する16 kHzへのリサンプリングです。
どちらもユニファイドメモリから読み込むため、キャプチャ経路とモデルのあいだで、オーディオバッファがバスを越えてコピーされることはありません。
主張していないこと
ここでは、確定処理(ファイナライズ)にかかる時間は公表していません。その数値と一緒にお渡しできるベンチマークスクリプトが今のところなく、再現する手段のないレイテンシの数字はマーケティングにすぎないからです。
ベンチマークがなくても構造的に言えて、真実であるのは、ホットキーを離した時点で残っている作業量は、あなたが話した長さではなく、バッチの目標によって上限が決まるということです。2分の音声入力と30分の音声入力は、仕上げに残る作業量が同じ状態で終わります。
ご自分のマシンで計測するには、同じ文を10回音声入力し、ホットキーを離してからテキストが入力されるまでの間隔を数えてください。重要なのはその数値であり、それはあなた自身のものです。
トレードオフ
モデルサイズと精度のトレードオフがあります。小さいモデルほどファイナライズは速くなりますが、聞き逃す単語は増えます。Voice Typeは27 MBから550 MBまで複数のサイズを提供しているので、このトレードオフはご自身で決められます。
大きいモデルは、ユニファイドメモリに余裕のあるApple Silicon Macに向いています。小さいモデルは8 GBのマシンに向いています。8 GBのマシンでは、大きいモデルはデコードよりもページングに時間を費やしてしまうからです。
