Speech AI と GPU Runtime:Kaldi・Whisper のデプロイと運用ノート
Speech AI と GPU runtime に対する私の考え方をまとめました。Kaldi と Whisper の特性とトレードオフ、NVIDIA driver / CUDA / container のレイヤー分け、デプロイと性能運用で確認する項目を扱います。
2022 年から、私は Kaldi や Whisper といった speech recognition 技術と GPU runtime を実際のシステムに組み込む仕事をしています。範囲は model inference、system deployment、そしてその後の performance maintenance です。
この記事は特定プロジェクトの詳細な事例紹介ではなく、社内のアーキテクチャ、データ、性能数値は含みません。まとめたいのは、この種の仕事への向き合い方です。Kaldi と Whisper の違いをどう捉えているか、なぜ GPU runtime を先に安定させるのか、デプロイ後に何を確認しているのかを書きます。技術的な説明は公式ドキュメントに基づき、トラブル対応の経験は blog.clarence.tw(中国語)で公開してきた記事から引いています。
プロダクトにおける Speech AI の位置づけ
私の仕事の進め方 でも書いたように、LLM や Speech AI の仕事は model API を呼ぶだけでは終わりません。batch processing、local model、GPU runtime、既存の backend workflow とつなげる必要があります。
音声認識は、多くの場合プロダクト全体の流れの一部です。一般的には、認識の前に音声の取得、形式変換、分割があり、認識の後にはテキストの後処理、保存、検索、他サービスへの受け渡しがあります。そのため Speech AI のコンポーネントを評価するときは、認識結果そのものだけでなく、次の点も一緒に見ます。
- 入力音声の形式と品質はそろっているか。前処理はどのレイヤーが担当するか。
- ストリーミング認識か batch 処理か。失敗時にどう retry するか。
- モデル出力をどう検証し、どの場合に人の確認が必要か。
- この runtime を別のマシンで再構築できるか。
同じツールでも内容によって結果が大きく変わることは、早い段階で実感しました。2019 年に Amazon Transcribe が簡体字中国語に対応したとき、試してみた感想を記事にしました。歌では結果が良くなかった一方、会話中心の動画ではかなり良くなりました。managed service でも自前でデプロイするモデルでも、実際の利用シーンに近い音声で判断する必要があります。
Kaldi と Whisper の特性とトレードオフ
私の仕事では Kaldi と Whisper の両方を使います。どちらかがもう一方を置き換えるものではなく、設計思想が大きく異なる二つのツールだと考えています。
Kaldi
Kaldi の公式説明 によると、Kaldi は C++ で書かれ、Apache License v2.0 で公開されている speech recognition toolkit で、speech recognition の研究者向けに作られています。主な特徴は次のとおりです。
- finite state transducer(FST)を中心に据え、OpenFst を利用しています。従来の decoding graph は HCLG で、HMM、context dependency、lexicon、grammar / language model を合成したものです。
- さまざまなデータセット向けの recipes があり、データ準備から学習、デコードまでの流れを参照できます。
- online decoding 用のライブラリがあり、
src/cudadecoderには batched CUDA decoding の実装もあります。
運用の観点では、コンポーネントが細かく分かれていて、lexicon、language model、acoustic model をそれぞれ調整できるのが強みです。その代わり、構築の手順が長く、音声処理の専門知識が多く必要で、コンパイルオプションや依存パッケージの違いで環境が壊れやすいという面があります。
Whisper
openai/whisper によると、Whisper は Transformer の sequence-to-sequence 構成による汎用の音声認識モデルで、大規模で多様な音声データで学習されています。多言語の文字起こし、英語への翻訳、言語識別に対応し、コードとモデルの重みは MIT License で公開されています。
デプロイ時に気をつけたい点は次のとおりです。
- モデルには tiny、base、small、medium、large、turbo などのサイズがあります。公式に示されている VRAM の目安はおよそ 1 GB から 10 GB で、速度も大きく異なるため、GPU リソースと合わせて選ぶ必要があります。
ffmpegが必要で、transcribe()は音声全体を 30 秒の sliding window で処理します。- turbo は large-v3 の最適化版で高速ですが、翻訳タスク向けには学習されていないため、翻訳には多言語モデルを使います。
- 公式の model card では、音声にない内容を出力する(hallucination)可能性、繰り返し出力の傾向、言語・アクセント・方言による性能差が指摘されており、高リスクな意思決定の場面での利用は推奨されていません。
選び方
| 観点 | Kaldi | Whisper |
|---|---|---|
| 設計 | 複数コンポーネントによる pipeline(acoustic model、lexicon、language model、FST decoding) | end-to-end の Transformer モデル |
| 導入の難しさ | 高い。recipe と構築手順の理解が必要 | 低い。パッケージを入れればすぐ文字起こしできる |
| 調整のしやすさ | 語彙や language model などを個別に調整できる | 主にモデルサイズ、デコード設定、前後処理で調整する |
| 多言語対応 | 学習データと recipe 次第 | 多言語の文字起こし、英語への翻訳、言語識別を標準で備える |
| 特に注意する点 | ビルド環境、依存パッケージ、モデルと graph のバージョン管理 | VRAM、長い音声、hallucination、繰り返し出力 |
私の原則はシンプルで、まず実際の利用シーンに近い音声で比較し、そのうえでデプロイと保守のコストを考えます。公開 benchmark だけでは決めません。
Kaldi への公開コントリビューション
Kaldi への私の公開コントリビューションは小さな修正です。kaldi-asr/kaldi#4657 で、2021 年 12 月にマージされました。当時 Gitpod で Kaldi を開くと .gitpod.dockerfile が見つからないというエラーが出ていました。原因は設定ファイル上のファイル名と、実際の .gitpod.Dockerfile の大文字・小文字の違いです。この PR ではファイル名を修正し、Dockerfile から存在しないユーザーと使われていないパスを削除し、Intel MKL のインストールバージョンを固定しました。
変更は小さいものですが、この種のツールで私がずっと大事にしていることの表れでもあります。開発環境と実行環境は、他の人が再現できるべきです。
GPU runtime を先に安定させる:driver、CUDA、container
Kaldi でも Whisper でも、GPU を使う以上、モデルの下には runtime のレイヤーが丸ごと存在します。私はそれを次のように分け、問題が起きたときもこの順番で調べます。
- ハードウェアとファームウェア設定:GPU が認識されているか、Secure Boot が有効か。
- Kernel と NVIDIA kernel module:module が現在の kernel 向けにビルドされているか、ロードが許可されているか。
- NVIDIA driver:
nvidia-smiが正常に応答するか。 - CUDA と関連ライブラリ:CUDA runtime、cuDNN などのバージョンが driver や framework と互換性があるか。
- Container runtime:container から GPU が見えるか。
- アプリケーションとモデル:PyTorch、Kaldi のビルド、モデルファイルそのもの。
Driver と CUDA のバージョン関係
NVIDIA の CUDA Compatibility によると、driver と CUDA toolkit は別々のタイミングで更新できます。CUDA 11 からは minor version compatibility があり、同じ major バージョン内でビルドしたアプリケーションは、driver が最低バージョン以上(例:CUDA 12.x なら 525 以上、CUDA 11.x なら 450 以上)であれば実行できます。ただし、新しい driver を必要とする一部の機能は制限され、device code を PTX にして JIT コンパイルするアプリケーションは対象外です。
実務的には、まず driver のバージョンを決め、その上で動かせる CUDA のバージョンを決めるということです。framework を更新するたびに driver を触る必要はありません。
Container で CUDA と framework を分離する
NVIDIA Container Toolkit は、host に NVIDIA driver がインストールされていることが前提です。Docker の場合、パッケージをインストールした後に次を実行します。
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart dockernvidia-ctk は host の /etc/docker/daemon.json を変更するため、この手順は構成管理に含めるべきです。続いて公式の sample workload で、container から GPU が見えることを確認します。
sudo docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smiこのように分けると、host は主に driver と container toolkit を担当し、CUDA、cuDNN、framework は image と一緒に管理できます。サービスごとに異なる CUDA バージョンを使え、アップグレードや rollback も host でパッケージを入れ直すのではなく、image の切り替えで済むようになります。
ブログに記録してきた問題
以下は、私がこれまで公開してきた GPU 環境の問題です(記事は中国語)。同じ環境で起きたとは限りませんが、runtime をレイヤーごとに管理すべき理由がよくわかります。
| 症状 | 原因と対応 | 学んだこと |
|---|---|---|
| nvidia-smi が driver と通信できない | kernel 更新後に driver module が再ビルドされていなかった。DKMS で新しい kernel 向けに再ビルド | kernel の更新も GPU 環境の変更である |
| Secure Boot 環境で driver がロードできない | DKMS でローカルビルドした module が未署名で拒否された。Ubuntu の署名済み linux-modules-nvidia-* パッケージに切り替え |
driver のために Secure Boot を無効にする必要はない |
| nvidia-smi が No devices were found を表示する | パッケージでインストールした driver が GPU を初期化できなかった。NVIDIA 公式 installer で入れ直し、kernel と GCC のバージョン不一致を解消 | インストールに失敗したら、まず kernel log と installer log を見る |
| CUDA パッケージの依存関係を解決できない | GUI が driver を使用中だった。multi-user.target に切り替えてから削除・再インストール |
driver の更新にはメンテナンス時間が必要 |
| dpkg のファイル上書きエラー | バージョンの異なる NVIDIA パッケージが同じファイルを上書きしようとした | 1 台のマシンでは driver パッケージの入手元をそろえる |
| DGX で NO_PUBKEY が出る | NVIDIA CUDA repository の署名鍵が変更された。鍵を更新して解消 | 外部の repository も変わる。更新に失敗したらまずメッセージを読む |
| cuDNN の検証サンプルがビルドできない | サンプルに追加の FreeImage ライブラリが必要だった | 検証手順そのものにも依存関係がある |
ほかにも、CUDA Toolkit 11.7 のインストール方法(deb local、deb network、runfile)と darknet での GPU 番号の指定(CUDA_VISIBLE_DEVICES と framework 側の設定)の 2 本は、GPU 環境を計画するときに今も見返す基本です。
デプロイと性能運用のチェックリスト
以下は一般的な確認項目で、実際の内容は環境に合わせて調整します。
デプロイ前
- OS、kernel、NVIDIA driver、CUDA、cuDNN、framework、モデルのバージョン、image tag の組み合わせをすべて記録する。
- Secure Boot の状態(
mokutil --sb-state)を確認し、kernel module を署名済みパッケージにするか自分で署名するかを決める。 - kernel と driver の更新方針を決め、自動更新で GPU 環境が気づかないうちに変わらないようにする。
- モデルサイズと想定する同時実行数から VRAM を見積もり、GPU ごとに動かすプロセス数と、
CUDA_VISIBLE_DEVICESによる割り当てを決める。
デプロイ後の検証
- Host:
lsmod | grep nvidiaで module がロードされていること、nvidia-smiが正常に応答することを確認する。 - Container:sample workload で container から GPU が見えることを確認する。
- アプリケーション:決まった短いテスト音声で一連の流れを実行し、出力の形式と内容が妥当か確認する。
- 再起動後にもう一度検証し、driver とサービスが自動で復旧することを確認する。
日常の監視
nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csvのようなコマンドや monitoring で、GPU 使用率とメモリを継続的に見る。- 処理時間とキューの長さを見て、GPU 不足なのか、前処理が遅いのか、I/O が詰まっているのかを切り分ける。
- 大量の繰り返しや、明らかに音声に含まれない内容など、認識結果の異常に注意する。
リスクのある操作
apt purge nvidia*、multi-user.targetへの切り替え、kernel module の削除や置き換えは、いずれも GPU サービスを止めます。メンテナンス時間に行い、事前に復旧手段を確認します。nvidia-ctk runtime configureは Docker daemon の設定を書き換えるため、既存の設定と合わせてレビューします。- 手間を省くために Secure Boot を無効にしないこと。どうしても必要な場合は、先にセキュリティへの影響を評価します。
学んだこと
- モデルはレイヤーのひとつにすぎない。 Speech AI が長期的に安定して動くかどうかは、driver、kernel、CUDA、container といった下のレイヤーがきちんと管理されているかに大きく左右されます。
- Driver は host に、それ以外は image に。 CUDA と framework を container に入れると、アップグレードも rollback もずっと単純になります。
- 自分の利用シーンで検証する。 Kaldi と Whisper にはそれぞれトレードオフがあります。公開 benchmark は参考にとどめ、最終的には実際に近い音声で比較します。
- ハマったことは書き残す。 ブログに記録した GPU の問題は、どれも後にデプロイ前後のチェック項目になりました。