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)可能性、繰り返し出力の傾向、言語・アクセント・方言による性能差が指摘されており、高リスクな意思決定の場面での利用は推奨されていません。

選び方

観点KaldiWhisper
設計複数コンポーネントによる 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 のレイヤーが丸ごと存在します。私はそれを次のように分け、問題が起きたときもこの順番で調べます。

  1. ハードウェアとファームウェア設定:GPU が認識されているか、Secure Boot が有効か。
  2. Kernel と NVIDIA kernel module:module が現在の kernel 向けにビルドされているか、ロードが許可されているか。
  3. NVIDIA driver:nvidia-smi が正常に応答するか。
  4. CUDA と関連ライブラリ:CUDA runtime、cuDNN などのバージョンが driver や framework と互換性があるか。
  5. Container runtime:container から GPU が見えるか。
  6. アプリケーションとモデル: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 の場合、パッケージをインストールした後に次を実行します。

BASH
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

nvidia-ctk は host の /etc/docker/daemon.json を変更するため、この手順は構成管理に含めるべきです。続いて公式の sample workload で、container から GPU が見えることを確認します。

BASH
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 の問題は、どれも後にデプロイ前後のチェック項目になりました。