LLMアプリを試作するときは、「正しい回答が返るか」に注目しがちです。
しかし、実際に使い続けてもらうには、回答品質だけでなく、待ち時間、費用、失敗時の振る舞いも設計する必要があります。
この記事では、LLMアプリの遅延を分解し、効果の大きい順に改善する方法と、APIや外部ツールが失敗しても安全に復旧する方法を説明します。
この記事のOpenAI APIに関する仕様は、2026年7月時点の公式ドキュメントをもとにしています。料金や対応モデルは変わるため、利用時は公式ページも確認してください。
このシリーズについて
「LLM・VLM実践学習」は、社内学習用に作成した教材を、一般公開できる形へ再構成したシリーズです。
これまでの記事では、LLMの生成処理、RAGによる知識の追加、Function Calling・MCP・Agentによる外部ツールの利用を扱いました。
- 第1回:LLMは文章をどう理解して生成するのか — トークン・Transformer・Attention
- 第2回:LLMへ知識を与える — Embedding・ベクトル検索・RAG
- 第3回:LLMへ道具を使わせる — Function Calling・MCP・Agentの違い
第4回では、作ったLLMアプリを継続的に使える状態へ近づけるために、品質を保ちながら遅延と費用を抑え、障害時にも安全に動かす設計を見ていきます。
速さだけを最適化しない
LLMアプリの運用では、品質・速度・コストの3つを一緒に見ます。大きなモデルや長い回答は品質を高める場合がありますが、待ち時間と費用も増えます。小さなモデルや量子化は軽量化に役立ちますが、代表タスクで品質確認が必要です。
最適化の目的は「最速」ではなく、品質条件を満たしながら待ち時間と費用を抑えることです。
「遅い」を1つの数字にしない
ユーザーが送信してから回答を読み終えるまでには、複数の待ち時間があります。
| 区間 | 主な原因 |
|---|---|
| 通信・待ち行列 | ネットワーク、混雑、レート制限 |
| 最初の出力まで | モデルの開始待ち、入力処理、推論 |
| 出力の生成 | 出力トークン数、モデルの生成速度 |
| 外部ツール | 検索、DB、API、複数手順の直列実行 |
平均値だけでなくp50・p95・p99を見ると、少数の非常に遅い体験を発見できます。
最初の出力が届くまでの時間は、TTFT(Time To First Token)と呼ばれます。
TTFTが短ければすぐに文章が表示され始めます。一方、出力が長い、外部ツールが遅い、処理を何段も直列にしている場合は、TTFTだけを改善しても全体時間は短くなりません。
まず、タスク、モデル、入力サイズ、利用ツールごとに、TTFTと全体時間を分けて記録します。
まずストリーミングで「無反応」をなくす
ストリーミングは、回答がすべて完成する前に、生成できた部分から画面へ届ける方法です。
処理そのものが大幅に速くならなくても、ユーザーは文章を読み始められます。複数のツールを使う場合は、「検索中」「在庫を確認中」のように、実際の進捗も表示します。
ただし、ストリーミングは遅い処理を隠す機能ではありません。タイムアウトや失敗を画面へ伝える状態も必要です。
出力トークンを減らす
文章の生成は、LLM処理の中でも待ち時間に影響しやすい区間です。
OpenAIの遅延最適化ガイドでは、一般的な目安として、出力トークンを50%減らすと遅延もおよそ50%減る可能性があると説明しています。
次のような方法で、必要な情報を保ったまま出力を短くします。
- 「結論、理由、次の行動」の3項目だけ返す
- 最大文字数や箇条書き数を指定する
- 構造化出力の不要な項目を削る
- 画面にすでにある情報を繰り返さない
単純に途中で切るのではなく、ユーザーが必要とする完了条件を先に決めます。
速くする順番を決める
遅延対策は、実装コストが低く、体感効果が大きいものから試します。
入力を短くするだけでなく、長い出力や直列の外部処理を先に疑います。
タスクの難しさでモデルを分ける
すべての処理に同じ大型モデルを使う必要はありません。
分類、形式変換、短い抽出などは小型モデル、複雑な判断や長い文書の統合は高性能モデルというように振り分けます。
ただし、モデル変更は速度と費用だけで判断しません。代表的な入力を固定し、正答率や形式遵守率を比較してから切り替えます。
同じ入力を何度も処理しない
長いシステム指示や共通の例を毎回使う場合は、プロンプトキャッシュを活用できます。
OpenAIのPrompt Cachingは、対象リクエストで自動的に働きます。キャッシュを使いやすくするには、共通の指示や例を前半へ置き、ユーザー固有の情報を後半へ置きます。完全に一致する先頭部分が再利用の単位です。
入力トークン数だけでなく、cached_tokensなどの利用量を記録し、実際にキャッシュが効いているか確認します。
独立した処理は並列にする
検索、権限情報の取得、複数データソースの読み取りなど、互いの結果を待つ必要がない処理は並列に実行できます。
一方、前の結果によって次の処理が変わる場合は直列です。無理な並列化は、不要なAPI呼び出しと費用を増やします。
費用は「1トークン」より「1タスク」で見る
LLMアプリの費用には、モデルの入力・出力だけでなく、Embedding、検索、外部API、再試行、ログ保存なども含まれます。
実務では「入力100万トークンの単価」だけでなく、1件の問い合わせや1文書の処理を完了する費用で比較します。
| 見る単位 | 分かること |
|---|---|
| 入力・出力トークン | どこでモデル費用が増えたか |
| キャッシュ済み入力 | 共通プロンプトが再利用されたか |
| ツール呼び出し回数 | 不要な検索やAPI実行がないか |
| 再試行回数 | 障害による隠れた費用がないか |
| 1タスクの完了費用 | 利用者へ価値を届ける総費用 |
リアルタイム性が不要な評価、分類、大量のEmbedding生成などは、バッチ処理へ分けられます。OpenAIのBatch APIは、同期APIに比べて50%の費用割引があり、処理は24時間以内に完了します。
画面で待つ処理は同期・ストリーミング、夜間集計や大量評価はバッチ、というように用途で分けます。
自前で動かすモデルでは量子化も選択肢になる
量子化は、モデルの重みをより少ないビット数で表現し、必要なメモリを減らす方法です。
8-bitや4-bitにすると、より小さいGPUでモデルを動かせる可能性があります。一方で、モデル、量子化方式、ハードウェアによって、品質や実際の速度は変わります。
そのため「4-bitなら必ず速い」とは考えず、次を同じ評価データで比較します。
- 正答率や形式遵守率
- TTFTと生成速度
- 最大メモリ使用量
- 同時処理数
- 1タスクの費用
外部のモデルAPIを利用する場合、提供側の量子化方式をアプリから選べないこともあります。その場合は、モデル選択、出力長、キャッシュ、バッチ化を優先します。
再試行する失敗と、止める失敗を分ける
外部APIは、タイムアウト、レート制限、サーバー側の一時障害などで失敗します。
429、5xx、タイムアウトなど、一時的な失敗には、回数上限を設けた指数バックオフを使います。再試行の間隔にはランダムな揺らぎ(jitter)を加え、同時に失敗した処理が同じ瞬間に再集中することを避けます。
認証エラー、入力不備、権限不足は、同じ内容を繰り返しても直りません。すぐに止め、原因を利用者または運用者へ伝えます。
書き込みは二重実行を防ぐ
メール送信、課金、Issue作成、データ削除のような副作用のある処理は、応答が途切れても実際には完了している場合があります。
同じ操作を再試行しても1回だけ実行されるように、処理識別子を付け、実行済みかサーバー側で確認します。結果が不明なままなら、自動で繰り返さず人へ引き継ぎます。
回復しないときの縮退動作を決める
すべての機能が成功するまで画面を止める必要はありません。
縮退時は通常時と同じ品質に見せず、何が未完了なのかを利用者へ明示します。
たとえば、次のような縮退動作を用意できます。
- 高性能モデルが混雑していれば、品質条件を満たす小型モデルへ切り替える
- 外部検索が失敗したら、推測で答えず「確認できない」と表示する
- Agent処理が上限時間を超えたら、途中結果と未完了項目を返す
- バックグラウンド処理へ移し、完了後に通知する
代替モデルや古いキャッシュを使う場合は、通常時と同じ品質だと見せず、利用者へ状態を明示します。
品質と運用を分けて監視する
応答が速くても間違っていれば使えません。正しくても、頻繁に失敗すれば運用できません。
「正しく答えたか」と「安定して届けたか」を両方測ります。
運用では、少なくとも次を記録します。
- p50・p95・p99のTTFTと全体時間
- 成功率、タイムアウト率、429・5xxの件数
- 入力・出力・キャッシュ済みトークン
- 1タスクの費用とツール呼び出し回数
- 再試行回数と縮退動作の発生率
品質側では、正答率、根拠との一致、出力形式、ツール選択の成功率、人の評価などを測ります。
変更前後は同じ代表タスクで比較します。全体平均だけでなく、タスク、モデル、入力サイズ、ツール経路ごとに分けると、改善の代わりに品質を落としていないか確認できます。
実装の順番
最初からすべての対策を入れる必要はありません。次の順番なら、効果を確認しながら進められます。
- 代表タスクと品質の合格条件を決める
- TTFT、全体時間、トークン数、エラーを記録する
- ストリーミングと明確な進捗表示を入れる
- 不要な出力を減らす
- タスク別のモデル選択を評価する
- キャッシュ、並列化、バッチ化を検討する
- タイムアウト、回数上限付き再試行、縮退動作を実装する
- 書き込み処理の二重実行を防ぐ
- p95の遅延、成功率、1タスクの費用、品質を継続監視する
まとめ
LLMアプリを速く安定して動かすには、モデルの生成速度だけを見るのではなく、ユーザーが依頼を完了できるまでの全体を設計します。
- 遅延をTTFT、出力生成、外部ツールに分解する
- ストリーミング、短い出力、モデル選択の順に改善する
- キャッシュ、並列化、バッチ処理で重複と待ちを減らす
- 429・5xx・タイムアウトだけを上限付きで再試行する
- 書き込みの二重実行を防ぎ、回復しないときは安全に縮退する
- 品質、p95遅延、成功率、1タスクの費用を一緒に確認する
速さ、安さ、品質、安定性は、どれか1つだけ最大化するものではありません。代表タスクと合格条件を決め、計測結果を見ながらバランスを調整します。
次回は、LLMアプリを安全に公開するためのPrompt Injection、権限、出力検証を扱います。
シリーズ記事
「LLM・VLM実践学習」は、LLMの仕組みからアプリ開発、運用、カスタマイズ、VLMまでを順に学ぶシリーズです。
- #1 LLMは文章をどう理解して生成するのか — トークン・Transformer・Attention
- #2 LLMへ知識を与える — Embedding・ベクトル検索・RAG
- #3 LLMへ道具を使わせる — Function Calling・MCP・Agentの違い
- #4 LLMを速く安定して動かす — 遅延・コスト・失敗を設計する(本記事)
参考資料
- OpenAI — Latency optimization
- OpenAI — Streaming API responses
- OpenAI — Prompt caching
- OpenAI — Batch API
- OpenAI — Error codes
- OpenAI — Rate limits
- Hugging Face Transformers — bitsandbytes
本記事は社内学習資料をもとに、公開向けに再検証して加筆したものです。記事内の図は、本シリーズのために作成したオリジナルの概念図です。



