Together AI が世界最速の音声テキスト変換スタックを構築した方法
本文の状態
日本語全文を表示中
詳細モードで約14分の本文を読めます。
同じ出来事の情報源
この情報源を基点に整理
Together AI Blog
Together AI は、世界で最も高速な音声テキスト変換システムを開発し、その技術的アプローチとアーキテクチャの詳細を発表しました。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
NVIDIA TensorRT のマルチプロファイルエンジン、条件付き NVIDIA CUDA グラフ、イベント駆動型 I/O、共有メモリ、そして Together 社の ASR レイテンシ結果を支える Python GC(ガベージコレクション)の修正について。

Artificial Analysis が報告した速度係数(入力音声の 1 秒あたりにトランスクリプト化される秒数)—— 高いほど優れています。
モダリティが重要である
1M トークンのテキストプロンプトであれば、ハリー・ポッターシリーズ全体を収容しても重さは約 5 MB に過ぎません。この規模は巨大に聞こえますが、入力自体はコンパクトです。テキストも推論の直前までほぼ準備できており、トークン化し、バッチ処理し、モデル内を通過させるだけです。
オーディオは問題の形状を変えます。同じハリー・ポッターのコーパスをオーディオブックとして扱う場合、サイズは 5〜10 GB に達し、テキストと比較して約 3 オーダー(1000 倍)も大きくなります。GPU に到達する前に、サーバー側でコンテナのデコード、リサンプリング、ノイズフィルタリング、VAD(音声活動検出)、音声セグメンテーション、そしてオーディオ特徴量の計算を行う必要があります。
モデル側の構造も逆転します。現在の LLM(大規模言語モデル)は数百億から数兆ものパラメータを有するため、推論サービスは自然と GPU 内部に集中します:量子化、KV キャッシュ、アテンションカーネル、バッチ処理、並列化などです。一方、音声認識モデルははるかに小さく、パラメータ数は通常数億から数十億程度であるため、周辺データパスの重要性がより高まります。
これにより、ASR サービングは、GPU 実行、CPU プリプロセッシング、メモリアクセス、転送、接続スケジューリング、ランタイム動作にわたるフルパスのシステム問題となります。同じスタックは、スループットが最も重要となるオフライン文字起こしと、レイテンシとジッターが支配的となるストリーミング文字起こしの、2 つの異なるレジームも同時に処理する必要があります。
Together の ASR スタックは、Artificial Analysis によってランク付けされた、世界で最も低遅延な 2 つの音声からテキストへの変換モデル、すなわち NVIDIA の Parakeet-TDT 0.6B v3 と OpenAI の Whisper Large v3 をサポートしています。この 2 つのうちより高速な NVIDIA Parakeet-TDT 0.6B v3 は、ハリー・ポッター映画シリーズの上映時間である約 20 時間の音声を、10 秒未満で文字起こしすることができます。
本稿の後半では、この結果を実現したプロダクション環境における変更点について解説します。これには、実際の音声形状に合わせた TensorRT プロファイル(TensorRT profiles)、GPU 側でのデコーダー制御フロー、コピー数を削減した CPU パス、イベント駆動型のストリーミング I/O、そしてランタイム GC(Garbage Collection)制御が含まれます。
実際の音声形状向けにエンコーダをコンパイルする
Parakeet はエンコーダー・デコーダーアーキテクチャを採用しており、その重みの約 95% がエンコーダーに存在します。エンコーダーは可変長の音声セグメントを受け取り、デコーダー用の音響フレームを生成するため、最適化の最初の対象となりました。
音声入力は、200 ミリ秒のストリーミングパケットから 30 秒間の連続した音声まで、非常に幅広い長さの範囲にわたります。ある入力形状向けに調整されたカーネルプランは、別の形状では大幅に低速になる可能性があるため、エンジン側でコンパイル時に遭遇する形状の分布を把握しておく必要があります。
TensorRT を導入する前、私たちは torch.compile と CUDA グラフを活用した最適化された PyTorch パスを使用しており、同じ形状プロファイルに対してチューニングを施していました。これにより、PyTook チェーンから離れることなく、形状認識型の実行を実現し、強力なベースラインを確立しました。
TensorRT は、本番環境向けのより高速なエンコーダパスを提供します。これは事前に関数を実行計画として構築し、可能な限りカーネルを融合させ、メモリレイアウトを最適化し、提供が予想される形状範囲に対してカーネルのバリエーションをベンチマークします。
重要な詳細はプロファイルチューニングにあります。最大の入力形状のみでチューニングされた単一のエンジンを使用すると、短いオーディオセグメントはパディングされたパスに強制され、ストリーミングチャンクや短い発話において特にコスト高となります。一方、マルチプロファイルの TensorRT エンジンを使用すれば、メモリ上にエンコーダ重みのコピーを 1 つだけ保持しつつ、リクエストごとに最適な最適化プロファイルを選択できます。
メモリ削減効果は限定的で、約 6GB から 5GB の減少でした。より大きな成果は、不適切な形状マッチの回避と、チューニングされたプロファイルに対して最適化された PyTorch から TensorRT へ移行した点にあります。小入力領域では、形状認識型の TensorRT は、これらのリクエストを大きなパディング済みプロファイル経由で送信する場合に比べて数倍高速になる可能性があります。
エンコーダの最適化が完了した後、デコーダーループが次のボトルネックとなりました。
デコーダーループから CPU を排除する
Parakeet のデコーダーは、エンコーダの音響フレームを反復処理し、転写文を進めないフレームに対してトークンまたは BLANK を出力します。コードは本質的に以下のようになります:
state = init()
for frame in encoder_output:
token = predict(frame, state)
if token != BLANK:
emit(token)
state = update(state, token)
プロファイリングを行ったところ、predict と update の両方が高速であることが分かりました。1 回ごとの GPU での処理時間はマイクロ秒単位で測定されました。
コストのかかる行はブランチ部分でした:
if token != BLANK:
このブランチでは、CPU がどのパスを取るかを決定するために、GPU メモリからトークンを再度読み取る必要があります。このホスト同期により、デコードループが単一の CUDA グラフとしてキャプチャできず、毎回の反復で Python を経由して往復する必要があります。GPU は数マイクロ秒の処理を行い、CPU の応答を待ち、次のカーネルを開始し、そのパターンをリクエストあたり数千回繰り返します。
条件付き CUDA グラフノードにより、このブランチが GPU 上に移動しました。小さなデバイス側カーネルが条件を評価し、トークン発行と状態更新のサブグラフに入るかどうかを CUDA ランタイムに伝えます。ブランチは GPU を離れることなく解決されるため、デコーダーループ全体、カウンター、条件、emit、および状態更新を単一の CUDA グラフとしてキャプチャして起動することが可能になります。
CPU はデコーダーの内部ループから外れ、その結果、デコーダーが 2 倍から 3 倍高速化されました。

オーディオバイトのコピーを止めよう
エンコーダーとデコーダーが正常に動作するようになった後、残りのレイテンシはモデル周辺の CPU パスから発生していました。これが、私たちが監査した ASR(Automatic Speech Recognition:自動音声認識)コードのほとんどでレイテンシ予算の大部分を占めている箇所です。ここでは冗長なコピー、ホットパス上の不要なプロセスホップ、そして並列性を高めることで改善が見込めるシングルスレッド関数が問題となっています。
最初の対策は、不必要なプロセス境界を統合することでした。
オーディオ前処理(ファイルデコード、リサンプリング、音声活動検出 (VAD)、特徴量抽出、チャンク処理など)の多くは I/O またはネイティブ C/C++ の作業であり、Python のグローバルインタープリターロック (GIL) を解放します。典型的なマイクロサービスアーキテクチャでは、前処理を 3 つまたは 4 つの別々のプロセスに分割しており、ワークロードが不要とする隔離のためにコストを支払っています。この作業の大部分を少数のプロセスに統合することで、カーネルコピーやシリアライズ/デシリアライズのパスを削減でき、大規模ファイルでは数百ミリ秒もの遅延を防ぐことができます。
プロセス間通信が本当に必要な場合でも、ZeroMQ などの一般的なオプションには意味のあるオーバーヘッドが存在します。私たちのワークロードでは、永続的な Unix ドメインソケット上で生オーディオバイトを転送するシンプルな独自プロトコルが、高並行下で最も優れたパフォーマンスを発揮しました。これはフレーム構成を最小限に抑え、繰り返し接続設定を行う必要がないためです。
大規模ファイルの場合、ソケットは依然として2回のコピーを課します:送信側のユーザー空間からカーネルバッファへ、そしてカーネルバッファから受信側のユーザー空間へ。この経路を回避するために、私たちは共有メモリを使用しています。共有メモリでは、両方のプロセスが同じ物理領域をマッピングするため、プロデューサーによって書き込まれたデータは、カーネルとの往復通信なしにコンシューマーにも即座に可視化されます。これにより、ゼロコピーのデータパスを実現しています。
複雑性のコストは現実的なものなので、共有メモリを使用するのはデータ量がそれを正当化する場合に限るべきです。
ストリーミングにはイベント駆動I/Oを使用する
ストリーミングASR(自動音声認識)は、接続ライフサイクルという別の問題も追加します。
私たちの最初のストリーミング実装では、1接続あたり1スレッドを使用していました。数百のストリームが同時にチャンクを送信すると、数百のスレッドが一斉に起動し、GIL(グローバルインタプリタロック)の競合が爆発的に増大し、尾部レイテンシが急上昇しました。
私たちは、epollでブロックされる1スレッド方式へ移行しました。
epollを使用すれば、1つのスレッドで数千の接続を登録でき、カーネルに対して単一のシステムコールで「これらのいずれかにデータがあれば目覚めさせてください」と指示できます。メッセージが到着すると、カーネルは準備完了したセット全体を返します。そのスレッドは、再びスリープする前にアクティブなソケットを処理します。
同じ負荷でも、スケジューラの圧力は大幅に軽減されます。ストリーミングASRにおいては、この予測可能性が重要です。なぜなら、遅延した部分トランスクリプトがあると、平均レイテンシが良好に見えていても、音声システムが遅く感じられてしまうからです。

起動状態を凍結してGCの尾部レイテンシを排除する
私たちはこれをほぼ見逃していました。
ストリーミングワークロードの負荷下では、p50 および p90 のレイテンシは健全に見えたものの、p95 は定期的に約 200 ミリ秒スパイクしました。ログを確認するとキュー深度は小さく GPU 処理時間も正常でしたが、通常は 5 ミリ秒未満で完了する CPU 関数が突然 100 ミリ秒以上を要するようになりました。
何かがバックグラウンドでリクエストループの時間を奪っていました。
プロファイリングの結果、Python のガベージコレクタ (GC) が疑われました。Python はメモリ管理の大部分に参照カウントを使用し、参照サイクルを検出するためのサイクル検出型コレクタを備えています。このコレクタは世代別で動作します。最も古い世代には長寿命オブジェクトが含まれており、フルコレクションでは大規模なオブジェクトグラフ全体を走査することになります。
私たちはスタートアップ時に、定常状態での割り当て遅延を回避するために、バッファ、モデル状態、ルックアップテーブルの大規模プールを事前に割り当てていました。これらの長寿命オブジェクトは最も古い世代に配置されたため、フル GC パスでは数十万の参照が走査されました。これが 200 ミリ秒のストップの原因でした。
解決策は、スタートアップ後の事前割り当ての直後に 1 行追加するだけでした:
gc.freeze()
gc.freeze() は、Python に事前割り当てされた状態を今後の GC スキャンから除外するように指示します。これにより、通常のリクエストスコープ内のオブジェクトは依然として収集されますが、巨大な初期状態はそのまま維持されます。
p95 のスパイクは消え去り、p50 も改善しました。これはシステムがより滑らかなトラフィックパターンを維持できるようになったためです。
教訓は、モデルを超えてプロファイリングを継続することでした。GPU 処理時間、キュー深度、モデル実行時間はすべて正常に見えたものの、レイテンシスパイクの真の原因は Python ランタイム内にありました。
Voice latency is an end-to-end systems problem
Voice agents usually run as a cascade: ASR produces a transcript, an LLM generates the response, and TTS produces audio. ASR is the first stage in that path, so its latency and jitter set the earliest bound on user-visible response time.
The optimizations above target different parts of that path. TensorRT multi-profile engines tune encoder execution for real audio shapes. Conditional CUDA graphs remove CPU round trips from the decoder loop. Persistent Unix domain sockets, shared memory, and epoll reduce CPU-path overhead. gc.freeze() removes a runtime-level p95 failure mode.
The same constraint applies to the rest of the stack: every stage has to control both median latency and tail latency across model execution, preprocessing, transport, scheduling, and runtime behavior.
NVIDIA Parakeet-TDT 0.6B v3 and OpenAI Whisper Large v3 are available on Together. Reach out if you're scaling voice AI in production.
*Parakeet v3 は、Hugging Face Open ASR リーダーボードにおいて単一言語のスループットでペースを決定した英語専用モデルである v2 の後継です。v3 はその基盤を大幅に拡張し、言語サポートを英語から 25 ヶ国の欧州言語へ拡大し、言語プロンプトを必要としない自動言語検出機能を追加しました。また、NVIDIA の Granary 多言語コーパスを含む 170 万時間の音声データでトレーニングされています。
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み