LLM評価、なぜこんなに遅く不安定なのか?
LLMベースのシステムを運用していると、モデル学習よりも**評価(evaluation)**が難しいと実感します。モデルは非決定的(non-deterministic)で、同じ入力に対して異なる出力を返し、評価者(judge)も同じデータで再実行するとスコアが変わります。参照回答も毎回異なる文字列として再生成されます。
このような状況でスコアが2%動いた場合、モデルが改善されたのか、評価者がぶれたのか、参照データが変わったのか分かりません。結局は評価インフラの問題であり、モデル品質の問題ではないことが多いです。
Airbnbエンジニアリングチームはこの問題を解決するために4つのレイヤーを構築しました。各レイヤーは独立して動作しますが、互いに依存しています。1つ欠けると他も崩れます。
レイヤー1: ノイズに名前を付ける
評価ノイズには2つの源泉があります。
- データの不確実性: 参照回答が毎回異なる生成される
- 判定の不確実性: LLM評価者が同じ入力でも異なるスコアを出す
Airbnbの測定では、LLM生成の参照回答の約75%が同一入力に対してラベリング実行ごとに異なり、評価者も同じデータセットで約1%のドリフトが見られました。実際のシグナルが1〜3%程度であれば、観測された変化の多くはノイズです。
これを解決するために**二重の不確実性(dual indeterminacy)**フレームを導入しました。認識的不確実性(epistemic uncertainty、モデル/評価者の限界)と偶発的不確実性(aleatoric uncertainty、タスク自体の曖昧さ)を区別し、それぞれ異なる診断と解決策を適用します。この2つを混同すると誤った結論に至る可能性があります。
実務ではこれらが同時に現れやすいです。例えば、生成された回答がユーザーの好み情報を見逃した場合(偶発的)と、評価者がドメイン知識を持たず検証できない場合(認識的)が同時に発生します。
レイヤー2: 決定論的評価基盤
ノイズのある評価者への直感的な対応は確率的な方法(サンプリング、多数決、ベイズモデリング)です。しかし多数決は正確さではなく評価者の中央傾向に収束し、ベイズ法は集中管理ストアが必要で複雑です。
よりシンプルな方法は評価者の入力を安定化することです。Airbnbはワークロードを分析した結果、候補モデルの出力の半分以上が同一文字列であることを発見しました。つまり、ほとんどの実験変更は一部の入力にのみ影響し、同じベースモデルを共有する候補は論争のある例でのみ分岐します。
そこでサンプルごとのキャッシュを構築しました。
- 参照回答: サンプルIDと生成設定をキーにキャッシュ
- 評価者スコア: サンプル、モデル出力、評価者設定、メトリックをキーにキャッシュ
これにより、同一入力には常に同一結果が返ります。評価は決定的(deterministic)になり、効率的で、実行間の比較が可能になります。
# 例: 参照回答キャッシュ(擬似コード)
from functools import lru_cache
@lru_cache(maxsize=None)
def get_reference(sample_id: str, config: tuple) -> str:
# 実際にはDBやRedisにキャッシュ
return generate_reference(sample_id, config)
# 評価者スコアキャッシュ
@lru_cache(maxsize=None)
def get_judge_score(sample_id: str, model_output: str, judge_config: tuple, metric: str) -> float:
return judge(model_output, judge_config, metric)
キャッシュのおかげで部分進行も保存され、8,000番目の例で失敗しても中断した箇所から再開できます。これは次のレイヤーの前提条件です。
レイヤー3: 範囲が限定されたモデル変異
評価が決定的かつ高速になると、ボトルネックは「変更を迅速に行えるか」に移ります。全体アダプター再学習は遅く安全ではありません。代わりにAirbnbは**マイクロアダプター(micro adapter)**を使用します。
マイクロアダプターはランク50未満の小さなLoRAパッチで、既存の共有アダプターの上に重ねて使用します。特定のバグに対する最小限の修正のみを学習し、1時間以内にGPU1つで学習できます。ソフトウェアホットフィックスとしてデプロイされます。
研究によると、LoRAアダプターは数百の例までは安定して修正を吸収できますが、それを超えると推論性能が低下し、過信するようになります。したがって、マイクロアダプターはこの限界内でのみ使用する必要があります。
スタック管理ルール:
- 同時トリガーパッチの融合: 重複する入力で発火するパッチは互いに干渉するため、学習可能な融合で解決
- 累積時の再学習: 特定カテゴリのパッチが閾値に達したらクリーンな再学習に折り込む
- 未使用パッチのアンロード: 一定期間トリガーされないパッチは自動的にアンロード
このレイヤーはレイヤー2があって初めて機能します。決定的評価なしでは同日修正は不可能です。
レイヤー4: 継ぎ目でのエンドツーエンド検証
最後のレイヤーは最も見落とされやすい部分です。個々のコンポーネント(言語検出、前処理、モデリング、サービング)はすべて検証済みですが、結合された本番動作は依然として予期しない問題を引き起こします。
MLコンポーネントは従来のソフトウェアのような構成推論が不可能です。仕様がないため、継ぎ目での相互作用は経験的にのみ観察できます。これを解決するには代表入力セットを本番パス全体で実行し、品質とレイテンシを測定する必要があります。
Airbnbは以下を含む代表セットを構成しています。
- トラフィック加重サンプリング
- テール(低頻度ロケール、入力モダリティ、過去のインシデントパターン)の過大代表
- 過去のインシデントの回帰ケースのシード
このセットはすべてのリリース候補に対して効率的に実行できるほど小さく、継ぎ目で発生するバグをデプロイ前に表面化します。
注意点と限界
このアプローチは強力ですが、いくつか注意点があります。
- キャッシュ無効化: キャッシュキーに含まれる設定が変わると既存キャッシュが無効になります。設定変更時は注意が必要です。
- LoRAの限界: マイクロアダプターは数百の例まで有効です。それを超えると再学習が必要です。
- 代表セットの限界: 完全な代表性を保証できません。継続的な更新が必要です。
まとめ: 地味だが確実なシステムエンジニアリング
LLMパイプラインは10年前のMLパイプラインと根本的に変わりません。真ん中に非決定的コンポーネントがあるだけです。継ぎ目で負債が蓄積するため、コンポーネントではなく継ぎ目をテストすることが重要です。
Airbnbの事例は、LLM評価の革新が華やかなモデル改善ではなく、地味だが確実なシステムエンジニアリングパターンから生まれることを示しています。評価インフラを決定的にし、迅速な反復を可能にすれば、同日修正も可能になります。
この記事の根拠資料はAirbnbエンジニアリングブログで確認できます。
合わせて読みたい記事
次のステップの学習方向
- 決定的キャッシュを自身の評価パイプラインに導入してみましょう。
- マイクロアダプターを実験し、特定のバグ修正に適用してみましょう。
- CACE原則をチームと共有し、継ぎ目テストの文化を作りましょう。
