この日本語版について
この日本語版は、Haggai Roitman 著 The Hitchhiker's Guide to Agentic AI: From Foundations to Systems(arXiv:2606.24937)を日本語に翻訳・編集した二次的著作物(翻案物)です。原著に対して、日本語への翻訳、mdBook 形式への変換、および表記・レイアウトの調整を行っています。
翻訳・校正には生成AI(GPT-5.6 Luna)を使用しています。AIが生成した訳文には誤りが含まれる可能性があります。訳文に疑義がある場合は、原著を参照してください。
原著はクリエイティブ・コモンズ 表示—継承 4.0 国際(CC BY-SA 4.0)の下で提供されています。この日本語版も同じ CC BY-SA 4.0 の下で提供します。
原著の献辞
最愛の妻ジャンナと、娘たちインバルとエイナブへ。
免責事項
本ドキュメントは、著者個人によって作成された独立した調査および教育用のリソースです。本書で表明されている見解、意見、および結論は著者個人のものであり、著者が現在または過去に所属していたいかなる雇用主、組織、または機関の見解を必ずしも反映するものではありません。
本書には、いかなる独占的、機密、または企業秘密に該当する情報は含まれていません。参照されているすべての資料は、査読付きの出版物、オープンアクセスのプレプリント、公式ドキュメント、オープンソースのリポジトリなど、公に利用可能なソースから取得したものです。
コンテンツは明示的か黙示的かを問わず、いかなる保証もない「現状のまま」で提供されます。著者は、特定の目的における情報の正確性、完全性、または適合性についていかなる表明も行いません。読者は、プロダクションシステムに適用する前に、主張、数式、または実装の詳細を独自に検証する必要があります。
フィードバックを歓迎します。 誤りや不正確な点を発見した場合、または改善の提案がある場合は、[first_name]r@gmail.com までフィードバックを送信することをお勧めします。
AIに関する開示。 調査および起稿の補助として大規模言語モデル(LLM)が使用されました。すべてのコンテンツは、著者が最善を尽くして編集および検証を行っています。
ライセンス。 本書は クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンス (CC BY-SA 4.0)の下でライセンスされています。適切なクレジットの表記、ライセンスへのリンクの提供、変更があったかどうかの表示を行い、二次的著作物を同じライセンスの下で配布することを条件に、営利目的を含め、いかなる目的でもこの素材を自由に共有(複製および再配布)し、改変(リミックス、変換、およびそれに基づく構築)することができます。ライセンスの全文:https://creativecommons.org/licenses/by-sa/4.0/
著者について
ハガイ・ロイトマン(Haggai Roitman) は、AI研究と大規模なプロダクションシステムの交差点で20年以上のキャリアを積んできました。基礎研究の発表から、数百万人のユーザーを抱えるシステムの実装に至るまで、彼の仕事は理論と実践の架け橋となっています。
彼の研究分野は、情報検索、推薦システム、自然言語処理、大規模言語モデル(LLM)、LLM向け強化学習、エージェントAIなど多岐にわたります。これまでに査読付き論文を100本以上執筆し、約100件の特許を保有しています。イスラエル工科大学(Technion)で理学士号(優等学位)および博士号を取得しました。
勾配の流れ(gradient flow)やKVキャッシュについて考えていないときは、ターンテーブルの前に立ち、プログレッシブ・トランスやディープ・ハウスをミックスしている姿が見られます。
まえがき
本ガイドが存在する理由
2026年においてインテリジェントなAIシステムを構築するには、途方もなく幅広い知識を習得する必要があります。これには、Transformerが内部でどのように言語を処理するのかという仕組みから、学習を可能にするハードウェアやシステム、効率化をもたらす最適化技術、モデルに推論を教えて人間の意図にアラインさせる強化学習アルゴリズム、さらには自律型システムを大規模に調整するマルチエージェントアーキテクチャに至るまでが含まれます。
これらの知識は、何百もの論文、ブログ記事、GitHubリポジトリ、そして一握りの研究所内の「暗黙知」として散逸しています。本ガイドが存在するのは、 実践者がスタック全体をカバーする単一の統合されたリファレンスを必要としている からです。そこには理論だけでなく、実際にシステムを動かすための実装の詳細も含まれています。
エージェントAIへの個人的な旅路
私がインテリジェントエージェントに魅了され始めたのは20年前、まだ情報システム工学の学士課程で学んでいた頃でした。私はエージェント指向ソフトウェア工学(AOSE)の講義を受け(Wooldridge et al. 2000)、JADE(Bellifemine et al. 2007)(Java Agent DEvelopment Framework)を使ってマルチエージェントシステムを構築する方法を学びました。これはFIPA準拠の(Intelligent Physical Agents 2002)プラットフォームであり、エージェントは構造化されたプロトコルを介して通信し、共有リソースを巡って交渉し、自律的に連携していました。ほぼ同時期に、Berners-Lee、Hendler、Lassilaによる記念碑的な論文「The Semantic Web」(Berners-Lee et al. 2001)は、エージェントが推論できる機械可読な知識のビジョンを描き出しました。これら2つの系譜――自律型エージェントアーキテクチャとセマンティック知識表現――は、それ以来私のキャリアを導く種となりました。このビジョンを具体化した初期のプロジェクトの1つが、将来の恩師となるアビグドール・ガル(Avigdor Gal)教授の指導の下、OntoBuilder(Gal et al. 2005)を用いて開発した「ショッピングエージェント」の構築でした。これは、オントロジーのマッチングとマッピングを通じて、さまざまな異種Webサイトにまたがるスキーマを理解し、製品の検索クエリや注文を自動的に入力できるシステムでした。セマンティックWebは、こうしたエージェントが構造化された機械可読データの世界で繁栄することを約束していました。しかし実際には、手作りのオントロジーの脆弱性、現実世界の製品データの雑多さ、そして堅牢な自然言語理解の欠如により、このビジョンは永久に「5年先」のものにとどまっていました。
その後の数年間、私は到来するAIの進歩の波を一つひとつ追いかけました。組合せ最適化のためのニューラルネットワークとヒューリスティック探索、ディープラーニングと表現学習、大規模な情報検索とパーソナライズ、そして最近では、大規模言語モデルの革命です。それぞれの波は強力な新しいツールをもたらしましたが、夢は同じままでした。それは、複雑な環境において自律的に「理解」し、「推論」し、「行動」するシステムです。
2024年から2026年にかけてが極めて特筆すべき時代である理由は、これらの系譜がようやく融合した点にあります。LLMが言語の理解と生成を提供し、強化学習がモデルに推論と人間の意図へのアライメントを教え、ツール利用プロトコル(MCP)が世界で行動するための「手」を与え、エージェントオーケストレーションフレームワークが20年前にJADEが構想した協調レイヤーを提供します。しかも、それは手書きのオントロジーではなく、基盤モデルによって駆動されているのです。本ガイドは、多くの意味で、その旅路の各ステップで私自身が欲しかったリファレンスそのものです。
2026年におけるAIの状況
今日のエージェントAIシステムに至る道のりは、アーキテクチャ、学習、デプロイにわたる30年間の画期的な進歩の積み重ねに基づいています。
-
アーキテクチャの基礎 (2017–2020): Transformer(Vaswani et al. 2017)は、汎用的なシーケンス処理のプリミティブとして自己注意機構を導入しました。スケーリング則により、より多くのデータで学習されたより大きなモデルが確実に向上することが明らかになりました。GPT-2とGPT-3は、デコーダーのみのTransformerを十分にスケールさせることで、有能なfew-shot学習器になることを実証しました。
-
システムと効率化 (2020–2023): Flash Attention(Dao et al. 2022)は、メモリのボトルネックを解消することで、学習を2〜4\(\times\)高速化しました。LoRA(Hu et al. 2022a)は、単一ノードで70B以上のモデルのファインチューニングを可能にしました。Mixture-of-Experts(MoE)は、モデルの容量と計算コストを切り離しました。vLLMなどの推論エンジンは、リアルタイムアプリケーションで実用可能なスループットを実現しました。
-
強化学習によるアライメント (2022–2024): RLHF(Ouyang et al. 2022)は、有能だが役に立たないベースモデルを、有用なアシスタントへと変貌させました。これがChatGPTの背景にあるレシピです。DPO(Rafailov et al. 2023)は、報酬モデルと強化学習ループを単一の教師あり損失に縮小し、アライメントの民主化をもたらしました。その後、多様なバリアントが登場しました:KTO(Ethayarajh et al. 2024)、IPO(Azar et al. 2024)、ORPO(J. Hong et al. 2024)、GRPO(Shao et al. 2024)。
-
推論と自律性 (2024–2026): DeepSeek-R1(DeepSeek-AI et al. 2025)やOpenAIのo1/o3は、強化学習が「推論そのもの」をモデルに教えられることを実証しました。モデルは自発的に思考の連鎖、バックトラッキング、自己検証を発見します。これと並行して、Model Context Protocol(MCP)がツールのアクセスを標準化し、Agent-to-Agent(A2A)がエージェント間の通信を可能にし、本番環境レベルのオーケストレーションフレームワークが成熟しました。
本ガイドの対象読者
本書は 実際にモノを作る実践者 に向けて書かれています:
- 機械学習(ML)エンジニア — Transformerの内部構造、学習インフラ、最適化手法、および学習が発散する理由を理解したい人。
- 特定のドメインに向けて、アーキテクチャ、ファインチューニング戦略、強化学習手法を評価している 応用研究者 。
- オーケストレーションパターン、メモリ構造、ツール統合(MCP)、およびマルチエージェント調整(A2A)を必要とする、プロダクションシステムを構築する エージェントデベロッパー 。
- 学習インフラ、GPUクラスター、分散学習、および推論デプロイを担当する システムエンジニア 。
- スタック全体にわたるアーキテクチャやリソースの決定を下す 技術リーダー 。
ニューラルネットワークと基本的な確率に関する知識があることを前提としています。 LLM、強化学習、あるいはシステムに関する事前の知識は必要ありません 。本ガイドは第一原理から順を追って構築されています。
本ガイドから得られること
本ガイドを読み終えると、以下のことができるようになります:
- LLMの内部構造の理解: 注意機構、位置エンコーディング、MoEルーティング、Flash Attention、およびアーキテクチャの選択が下流の能力にどのように影響するのか。
- システムについての考察: GPUメモリ容量の計算、分散学習戦略(FSDP、テンソル/パイプライン並列化)、推論の最適化、およびvLLMによるプロダクションデプロイ。
- 効率的な学習とファインチューニング: LoRA/QLoRA、量子化、知識蒸留(knowledge distillation)、最適化アルゴリズムの選択、および学習率のスケジューリング。
- 人間の好みへのモデルのアライメント: RLHF/DPO/GRPO/KTOパイプラインの実装、報酬ハッキング(reward hacking)やモード崩壊(mode collapse)のデバッグ、20以上の手法からの適切なアルゴリズムの選択。
- 推論モデルの構築: DeepSeek-R1、o1/o3、QwQが明示的なデモンストレーションなしに、強化学習を通じてどのように思考の連鎖(Chain-of-Thought)を発見するかの理解。
- エージェントシステムの設計: オーケストレーションパターンの選択、メモリの設計、MCPを介したツールの統合、A2Aによるエージェント間の調整、本番ベンチマークによる評価。
- 厳密な評価: モデルの品質とエージェントの能力の両方に対して、適切なメトリクス、ベンチマーク、およびLLM-as-Judgeパターンの適用。
本ガイドの構成
本ガイドは、6つの部に分かれた29の章で構成されています:
- 第I部 — 基礎 (第1章〜第3章): LLMアーキテクチャと最適化(Transformer、注意、位置エンコーディング、Flash Attention、LoRA、MoE)、システムの基礎(GPU階層、分散トレーニング、vLLM)、および古典的な強化学習理論(MDP、方策勾配、アクター・クリティック)。
- 第II部 — LLMのための強化学習手法 (第4章〜第12章): LLM向け強化学習の完全なツールキット。言語モデル向け強化学習の基礎から、PPO、DPO、GRPO、および選好最適化(preference optimization)のバリアント(Online DPO、KTO、IPO、ORPO、SimPO)の数学的解釈、報酬モデルの学習、SFTのベストプラクティス、大規模システムアーキテクチャ、軌跡レベルの強化学習を用いたエージェントの学習まで。
- 第III部 — 推論 (第13章): 大規模推論モデル(DeepSeek-R1、OpenAI o1/o3/o4-mini、QwQ)における、強化学習による思考の連鎖の自己発見、MCTS、プロセス報酬モデル(PRM)、およびテスト時計算量(test-time compute)のスケーリング。
- 第IV部 — 評価 (第14章): 包括的なLLM評価手法(パープレキシティ、pass@k、ELOなどのメトリクス)、LLM-as-Judgeパターン、データ汚染検出、ベンチマークスイート、およびエージェント能力の評価手法。
- 第V部 — エージェントAI (第15章〜第26章): 完全なエージェントスタック(エージェントAIの概要、RAGと情報検索、メモリシステム、オーケストレーションと文脈管理、デザインパターン、エージェント環境とベンチマーク、Model Context Protocol(MCP)、エージェントスキル、Agent-to-Agent通信(A2A)、マルチエージェントシステム、開発フレームワーク、およびエージェントUI)。
- 第VI部 — 評価とリファレンス (第27章〜第29章): すべてのトピックにわたる詳細な解説付きの108問の確認問題、主要な数式・APIリファレンス・失敗モードの診断をまとめたクイックリファレンス、および結論と今後の方向性。
本ガイドには、すべてのトピックを網羅する包括的な回答付きの100以上の詳細な確認問題が含まれており、さらに主要な数式、API参照、および失敗モードの診断を統合したクイックリファレンスの章が用意されています。
設計思想
本書を導く3つの原則は、以下のとおりです:
- 直感が先、数式は後: すべての数式の前に、それが何を意味し、なぜ重要であるかを平易な言葉で説明しています。
- 実装を意識する: 動かし方を知らなければ理論は役に立ちません。本書のいたるところに、コード、ハイパーパラメータの表、メモリ使用量の見積もり、アーキテクチャ図、およびデバッグ戦略を掲載しています。
- 何が機能するかに誠実であること: どの手法が本番環境で実証済みであり、どれがまだ研究段階の模索であるかを明確に述べています。
対象範囲と意図的な除外事項
本ガイドは、 テキスト入力・テキスト出力の言語モデル と、その周囲に構築される強化学習、システム、およびエージェントインフラストラクチャに焦点を当てています。以下の重要な領域は意図的に除外されています:
- マルチモーダルモデル (画像と言語、音声、動画): マルチモーダルアーキテクチャは、それぞれに特有の学習パイプライン(対照学習を用いた事前学習、モダリティ間のアライメント、モダリティ固有のエンコーダー)、データのキュレーションの課題、および評価プロトコルを必要とし、それぞれ単冊の書籍に値するテーマです。これらを含めると、本書の核である強化学習とエージェントの核心部分を深めることなく、本の範囲が2倍になってしまいます。
- ドメイン固有の展開 (医療、法務、金融、科学的発見): ドメイン適応には、規制の制約、特殊な評価、データアクセスの問題が伴い、これらは本書で提示する一般的な手法とは独立しています。本書で説明するアルゴリズムとアーキテクチャは、実践者がこれらのドメインに適応させるための構成要素ですが、適応の詳細は専用のリファレンスに委ねるのが適切です。
- パーソナライズと推薦システム: パーソナライズは、ユーザーモデリング、協調フィルタリング、およびインタラクション履歴のアーキテクチャに依存しており、これらは並行する別の研究分野を形成しています。推薦システム内でLLMが使用されるケースは増えていますが、その核となる技術(シーケンシャルモデル、バンディットに基づく探索、コールドスタート処理)は十分に異なっており、別途扱うべき領域です。
この境界線を維持することで、モダリティや業界別領域にわたって記述を断片化させることなく、「アーキテクチャの基礎とシステムインフラから、アラインメントと推論能力を備えたモデルを生み出す学習アルゴリズム、および自律型エージェントのオーケストレーションとデプロイに至るまで」という、単一の一貫したストーリーを維持しています。
— ハガイ・ロイトマン, 2026年
はじめに
全体像
本ガイドは、 第一原理からプロダクションシステムまで を案内します。本書は、実践者――研究者、エンジニア、および応用科学者――に向けて書かれており、Transformerアーキテクチャやそれを実行するハードウェアから、モデルを人間の意図にアラインさせ、推論を教える学習アルゴリズム、さらには自律システムとしてデプロイするためのエージェントアーキテクチャに至るまで、現代のAIのフルスタックを理解し、構築したい方を対象としています。
コアとなる主張はシンプルです。優れたAIシステムを構築するには、単一のレイヤーだけでなく、パイプライン全体を理解する必要があるということです。学習の実行をデバッグするエンジニアは、GPUのメモリ階層とオプティマイザのダイナミクスを理解する必要があります。ファインチューニングの実践者は、どのような場合にLoRAで十分であり、どのような場合に全パラメータ学習がコストに見合う価値があるかを知る必要があります。エージェントデベロッパーは、基盤となるモデルがどのように学習されたかを理解する必要があります。フレームワークを評価する技術リーダーは、それぞれがどのようなトレードオフを抱えているかを理解する必要があります。本ガイドは、その全体像を提供します。
エージェントAIへの道:簡単な歴史
今日のエージェントAIシステムは、何もないところから突如として現れたわけではありません。何十年にもわたる画期的なシステムの上に成り立っています。それらはそれぞれ狭い範囲の課題を解決してきましたが、共同で自律型エージェントを実現するための技術、ハードウェア、および大いなる野心を築き上げてきました。
-
Deep Blue (1997) (Campbell et al. 2002) — IBMのチェスエンジンは、手作りの評価関数を用いた総当たり探索(毎秒2億局面)により、世界チャンピオンのガルリ・カスパロフを破りました。これは、明確に定義された対戦ドメインにおいて機械が人間のパフォーマンスを上回り得ることを証明しましたが、他のドメインには一切汎用化できませんでした。
-
IBM Watson — Jeopardy! (2011) (Ferrucci et al. 2010) — Watsonは、情報検索、自然言語処理(NLP)、および大規模並列処理を組み合わせることで、オープンドメインのクイズ番組(Jeopardy!)で人間のチャンピオンたちを破りました。AIが大規模な非構造化テキストを処理できることを証明しましたが、ドメイン固有の開発に何年も要し、人間の多大な努力なしには新しいドメインを学習できませんでした。
-
AlexNetとディープラーニング革命 (2012) (Krizhevsky et al. 2012) — KrizhevskyらによるCNNは、圧倒的な差でImageNetコンテストを制し、GPUで学習された深層ニューラルネットワークが生データから表現を直接学習できることを証明しました。この1つの成果が現代のディープラーニング時代の到来を告げ、最終的にLLMを可能にするハードウェアへの投資を呼び込みました。
-
AlphaGo (2016) (Silver et al. 2016) — DeepMindのシステムは、深層強化学習(方策ネットワーク+価値ネットワーク+モンテカルロ木探索)を用いて囲碁の世界チャンピオンであるイ・セドルを破りました。Deep Blueの総当たり探索とは異なり、AlphaGoは囲碁の打ち方を「学習」しました。これは、探索だけでは扱えない領域(\(10^{170}\)通りの局面)を、強化学習で制覇できることを示しました。その後のAlphaGo Zero(2017)(Silver et al. 2017)は、人間の対局データに全く頼らず、完全な自己対戦のみから学習を行いました。
-
GPT-2/GPT-3 (2019–2020) (Brown et al. 2020) — OpenAIは、デコーダーのみのTransformerを数十億パラメータにスケールさせることで、創発的なfew-shot学習能力が生まれることを示しました。GPT-3(1750億パラメータ)は、文脈内のいくつかの例を与えるだけで、翻訳、算術、コード生成など、明示的に学習されたことのないタスクを実行できました。ここに基盤モデル(Foundation Models)の時代が幕を開けました。
-
AlphaFold (2020) (Jumper et al. 2021) — DeepMindは、50年にわたる課題であったタンパク質折り畳み問題を解決し、3Dタンパク質構造を原子レベルの精度で予測しました。AlphaFoldは、以前は数十年先と考えられていた根本的な科学的課題をディープラーニングが攻略できることを証明しました。また、画期的なアーキテクチャ(残基ペアに対する注意)と大規模計算の融合の威力も示しました。
-
ChatGPTとRLHF (2022) (Ouyang et al. 2022) — InstructGPTおよびChatGPTは、有能なベースモデルをRLHF(人間のフィードバックによる強化学習)でアラインさせることで、真に有用なアシスタントになり得ることを証明しました。これが変曲点となりました。AIは研究ツールから、数億人に利用されるコンシューマー向け製品へと進化しました。このアライメント手法(報酬モデル、PPO)は、その後のすべてのLLM事後学習の標準テンプレートとなりました。
-
GPT-4とマルチモーダルモデル (2023) (OpenAI 2023) — マルチモーダル能力(画像+言語)、より長いコンテキストウィンドウ、および推論能力の向上により、LLMは汎用的な認知機能に近づきました。ツール利用(コードインタープリター、Webブラウジング)は、エージェントとしての能力を示唆し始めました。
-
推論モデル (2024) (DeepSeek-AI et al. 2025) — OpenAIのo1およびDeepSeek-R1は、強化学習によってモデルに「推論」を教えられることを示しました。思考の連鎖、バックトラッキング、自己検証が報酬シグナルのみから自発的に創発しました。モデルは、競技レベルの数学や複雑なプログラミングタスクを解決し始めました。
-
エージェントAI (2025年〜現在) — 融合点:標準化されたツールアクセス(MCP)、エージェント間通信(A2A)、長期記憶、および洗練されたオーケストレーションフレームワークを備えた、推論能力を持つLLM。現在、エージェントは自律的にコードを書き、調査を行い、ワークフローを管理し、他のエージェントと連携します。これこそが本ガイドのメインテーマです。
Tip
各マイルストーンには共通する流れがあります: アーキテクチャの革新 \(+\) スケール \(+\) 学習シグナル \(=\) 突破口(ブレイクスルー) 。Deep Blueは手作りの探索を利用しました。AlphaGoは自己対戦から学びました。GPT-3はインターネットのテキストから学びました。今日のエージェントシステムは、人間のフィードバック、検証可能な報酬、および環境とのインタラクションから学習します。学習シグナルはゲームの勝敗から、オープンエンドな人間の選好へと拡大し、アーキテクチャもそれに合わせて拡張されてきました。
本ガイドは、基盤モデルの時代からストーリーを引き継ぎ、アライメント、推論、および自律的なエージェント機能へと進めていきます。
本書に期待できること
第I部:基礎 (第1章〜第3章) は、本ガイドの残りの部分が依存する基礎知識を構築します。LLMが内部でどのように機能するのかという能力を決定するアーキテクチャの決定から始め、次に学習と推論を可能にするハードウェアとシステムをカバーし、最後に強化学習を第一原理から導入します。
-
第1章 — LLMアーキテクチャと最適化: Transformerの内部構造(自己注意、マルチヘッドアテンション、RoPE、GQA)、Flash Attention、最適化手法(AdamW、学習率のスケジュール、勾配クリッピング)、混合精度、LoRA/QLoRA、量子化、知識蒸留、およびMixture of Experts。
-
第2章 — システムの基礎: GPUアーキテクチャ(A100/H100/B200)、メモリ階層、NVLink/NVSwitch、分散学習(FSDP、DeepSpeed ZeRO、テンソル/パイプライン並列化)、および高スループット推論のためのvLLM。
-
第3章 — 強化学習への導入: MDP、ベルマン方程式、TD学習、Q学習、方策勾配(REINFORCE)、アクター・クリティック手法、GAEなど、第II部を支えるアルゴリズムツールキット。
第II部:LLMのための強化学習手法 (第4章〜第12章) は、学習とアライメントの核心です。ここでは、詳細な数学的導出から動くコードまで、言語モデルのアライメント、改善、およびファインチューニングを行う方法を学びます。
-
第4章〜第8章: 数学、直感、およびTRLコードを用いた主要なすべての強化学習/選好アルゴリズム — PPO、DPO、GRPO、および選好最適化のバリアント(Online DPO、KTO、IPO、ORPO、SimPO、Best-of-N)。
-
第9章〜第10章: 報酬モデルの学習(Bradley–Terryモデル、スケーリング則、報酬ハッキング)およびSFTのベストプラクティス(データの品質、カリキュラム、フォーマット)。
-
第11章〜第12章: 大規模システムアーキテクチャ(分離された学習、耐障害性、GPU割り当て)およびLLMエージェント学習 — 軌跡レベルの強化学習を用いたエンドツーエンドのエージェント学習方法。
第III部:推論 (第13章) は、複数ステップの問題を通じてLLMに推論を教えるという、モデル能力のフロンティアをカバーします。
- 第13章 — 大規模推論モデル向け強化学習: DeepSeek-R1、OpenAI o1/o3/o4-mini、QwQ — 強化学習が思考の連鎖を発見する仕組み、MCTS、プロセス報酬モデル、およびテスト時計算量(test-time compute)のスケーリング。
第IV部:評価 (第14章) は、これらが実際に機能するかどうかを測定するための方法論を提供します。
- 第14章 — LLM評価: メトリクス(パープレキシティ、pass@k、ELO)、LLM-as-Judgeパターン、データ汚染検出、ベンチマークスイート、およびエージェントの評価手法。
第V部:エージェントAI (第15章〜第26章) は、学習済みモデルからデプロイされた自律システムへと進みます。これが最大のセクションであり、エージェントが現実世界で動作するために必要なすべてをカバーしています。
-
第15章 — エージェントAIへの導入: システムをエージェントたらしめるもの、チャットボットから自律型エージェントまでのスペクトル、および第V部の残りの部分の基礎概念。
-
第16章 — RAG: 検索手法、チャンキング、埋め込みモデル、ハイブリッド検索、リランキング、およびプロダクションアーキテクチャ。
-
第17章 — メモリシステム: 持続的なエージェント知識のためのワーキングメモリ、エピソードメモリ、セマンティックメモリ、およびプロシージャルメモリ。
-
第18章 — オーケストレーション: ReAct、Plan-and-Execute、LLM Compiler、リフレクションパターン、文脈管理、およびハーネス設計。
-
第19章 — デザインパターン: プロンプトの連鎖、ルーティング、並列化、評価主導のオーケストレーション、およびシンプルさの原則。
-
第20章 — 環境とベンチマーク: WebArena、SWE-bench、OSWorld、GAIA — エージェント能力の評価用環境。
-
第21章 — Model Context Protocol (MCP): アーキテクチャ、トランスポート層、ツール/リソース/プロンプトのプリミティブ、セキュリティ、およびデプロイ。
-
第22章 — エージェントスキル: スキルライブラリ、ツールの構成、および能力の抽象化。
-
第23章 — A2A(エージェント間)通信: GoogleのAgent-to-Agentプロトコル — エージェントカード(Agent Card)、タスクライフサイクル、ストリーミング、エンタープライズパターン。
-
第24章 — マルチエージェントシステム: 階層型、ディベート型、マーケットプレイス型、スウォーム(群れ)型アーキテクチャ — 大規模な連携。
-
第25章 — 開発フレームワーク: LangGraph、CrewAI、AutoGen、OpenAI Agents SDK、Google ADK — コード付き比較分析。
-
第26章 — エージェントUI: ストリーミングインターフェース、生成UI、キャンバスパラダイム、ツールの可視化、Human-in-the-Loopパターン。
現代のAIパイプライン
ベースモデルからデプロイされたエージェントまでの完全なパイプライン:
LLMのアーキテクチャと最適化手法
この章では、大規模言語モデルの基礎となるアーキテクチャと、学習・推論を効率化する主要な最適化手法を扱います。内容はカリキュラムとして順序付けられており、まずTransformerそのものを取り上げ、効率的な学習、低コストな適応、圧縮、スケーリング、推論の高速化へと進みます。
LLMの仕組み:直感的な概要
アーキテクチャの詳細に入る前に、大規模言語モデルがどのようにテキストをテキストへ変換するのか、直感を養いましょう。全体の処理は、単純なパイプライン テキスト \(\to\) トークン \(\to\) 表現 \(\to\) トークン \(\to\) テキスト に従います。
Important
4つの主要ステージ
トークン化 :学習済みの語彙を使い、生のテキストをサブワード片(文字でも完全な単語でもない)へ分割します。「unhappiness」は [「un」、「happiness」] または [「unhapp」、「iness」] になることがあります。
埋め込み :各トークンIDで学習済みの埋め込みテーブルを参照し、\(\mathbb{R}^d\) の密ベクトル(通常は \(d = 4096\))を生成します。これらのベクトルは意味を捉え、似た単語には似たベクトルが割り当てられます。
文脈処理 :Transformerスタックは、自己注意を使って各位置が他のすべての位置から「読み取れる」ようにしながら、すべての埋め込みを並列処理します。\(L\) 層後には、各位置の隠れ状態が豊かな文脈情報を符号化します。
予測 :最後の隠れ状態を語彙全体にわたる確率分布へ射影し、デコーディング戦略で次のトークンを選択します。
トークン化
トークン化は、生のテキストを言語モデルが扱う離散記号へ変換する重要な最初のステップです。トークナイザーの選択は、モデル品質、多言語対応能力、計算効率に直接影響します。
Tip
なぜサブワードなのか?
文字レベルのモデルは非常に長いシーケンスを必要とし、アテンションのコストが高くなります。単語レベルのモデルは、まれな単語や新しい単語を扱えません。サブワードトークン化はこのバランスを理想的に取ります。一般的な単語は単一トークン(「the」 \(\to\) [the])になり、まれな単語は既知の断片(「cryptocurrency」 \(\to\) [「crypt」、「ocur」、「rency」])へ分解され、語彙サイズも扱いやすい範囲(32K〜128Kトークン)に収まります。
なぜ文字でも単語でもないのか?
| 粒度 | 語彙サイズ | シーケンス長 | 問題点 |
|---|---|---|---|
| 文字 | \(\sim\)256 | 非常に長い | アテンションコスト \(O(n^2)\)。長距離の意味を学習しにくい |
| 単語 | \(\sim\)500K+ | 短い | まれな単語や新語を扱えず、埋め込みテーブルが巨大になる |
| サブワード | 32K〜128K | 中程度 | 最良のトレードオフ:短いシーケンスと開かれた語彙 |
異なるトークン化粒度のトレードオフ。
Byte-Pair Encoding(BPE)
BPE (Sennrich et al. 2016) は、GPT、Llama、Mistral、その他の多くの現代的なLLMで使われる主要なトークン化アルゴリズムです。
Important
BPEアルゴリズム
個々の文字(バイト)からなる語彙を用意する
学習コーパス内の隣接する記号ペアをすべて数える
最も頻度の高いペアを新しい記号へマージする
目的の語彙サイズに達するまで、ステップ2〜3を \(k\) 回繰り返す
その他のトークン化手法
| 手法 | 使用例 | 主な考え方 |
|---|---|---|
| BPE | GPT-4 (OpenAI 2023), Llama-3 (Grattafiori et al. 2024), Mistral (Jiang et al. 2023) | 頻度の高いペアをボトムアップにマージ。決定的 |
| WordPiece | BERT (Devlin et al. 2019), DistilBERT (Sanh et al. 2019) | BPEに似ているが、学習データの尤度を最大化 |
| Unigram LM | SentencePiece (T5 (Raffel et al. 2020), XLNet (Yang et al. 2019)) | トップダウン:大きな語彙から始め、尤度への影響で削減 |
| Byte-level BPE | GPT-2 (Radford et al. 2019)+ | 生バイトに対するBPE(未知語トークンが存在しない)。基礎語彙は256 |
サブワードトークン化アルゴリズムの比較。
トークン化のベストプラクティス
-
語彙サイズは重要 :32Kは最小限であり、128Kなら多言語対応とコード処理が向上します。Llama-3は128Kトークンを使用します。
-
特殊トークン :
<bos>、<eos>、<pad>、<unk>は必ず含めます。指示調整済みモデルでは、役割マーカー(<|user|>、<|assistant|>)を追加します。 -
Fertility(分割度) :言語ごとに単語あたりのトークン数を測定します。Fertilityが高い(1単語あたりのトークンが多い)場合、その言語のカバレッジが低いことを示します。
-
境界をまたいでトークン化しない :空白、句読点、数字は一貫して扱う必要があります。現代の多くのトークナイザーは、単語の先頭トークンと継続トークンを区別するために、空白マーカー(「the」)を前置します。
-
数字 :算術タスクでは数字レベルのトークン化を検討します。「2024」を [「2」、「0」、「2」、「4」] とすれば、桁ごとの推論が可能になります。
-
コード :空白(インデント)が効率的にトークン化されるようにします。Llama-3は連続する空白を単一トークンとしてトークン化します。
実践的なトークン化:HuggingFaceの例
transformersライブラリは、すべてのトークナイザーに統一インターフェースを提供します。以下では、現代的なLLMトークナイザーによるエンコードとデコードを示します。
from transformers import AutoTokenizer
# Load Llama-3 tokenizer (128K vocabulary, byte-level BPE)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B")
text = "Reinforcement learning optimizes long-term rewards."
# Encode: text -> token IDs
token_ids = tokenizer.encode(text)
print(token_ids)
# [128000, 29934, 262, 11008, 4815, 6900, 1317, 9860, 21845, 13]
# Decode individual tokens to see subword splits
tokens = tokenizer.convert_ids_to_tokens(token_ids)
print(tokens)
# ['<|begin_of_text|>', 'Re', 'inforce', 'ment', ' learning',
# ' optimizes', ' long', '-term', ' rewards', '.']
# Decode back to text (round-trip)
reconstructed = tokenizer.decode(token_ids, skip_special_tokens=True)
assert reconstructed == text # Perfect reconstruction
# Tokenize with attention mask (for batched inputs with padding)
batch = tokenizer(
["Short text.", "A much longer input sentence for comparison."],
padding=True, return_tensors="pt"
)
print(batch.keys()) # dict_keys(['input_ids', 'attention_mask'])
特殊トークンと構造化プロンプト
特殊トークンは、言語的な内容ではなく構造的な意味を持つ、予約済みの語彙エントリです。モデルの挙動を制御するうえで不可欠です。
| トークン | 別名 | 目的 |
|---|---|---|
<bos> / `< | begin_of_text | >` |
<eos> / `< | end_of_text | >` |
| `< | user | >` |
| `< | assistant | >` |
<pad> | PAD | バッチを同じ長さにそろえる。アテンションではマスクされる |
<unk> | UNK | 語彙外のプレースホルダー(BPEではまれ) |
[SEP] | SEP | セグメントを区切る(BERT形式) |
[CLS] | CLS | 分類トークン(BERT) |
[MASK] | MASK | MLM事前学習でマスクされるトークン |
LLMファミリーに共通する特殊トークン。
指示調整済みモデルの役割マーカー
現代のチャットモデルは、会話の構造を区切るために特殊トークンを使います。これらは意味を担うように学習されるのではなく、モデルが解析方法を学習する構造上の区切り記号です。
# Llama-3 chat template
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain PPO in one sentence."},
]
# apply_chat_template handles all special token insertion
prompt = tokenizer.apply_chat_template(messages, tokenize=False)
print(prompt)
# <|begin_of_text|><|start_header_id|>system<|end_header_id|>
#
# You are a helpful assistant.<|eot_id|><|start_header_id|>user<|end_header_id|>
#
# Explain PPO in one sentence.<|eot_id|><|start_header_id|>assistant<|end_header_id|>
#
#
Important
特殊トークンのベストプラクティス
特殊トークンを分割しない :特殊トークンは不可分でなければなりません。トークナイザーが文字列ではなく単一の単位として扱うことを確認します。
特殊トークンの損失をマスクする :SFTでは、構造トークン(役割マーカーや区切り記号)に対する損失を計算しません。モデルに書式の予測を「学習」させるべきではありません。
構造にはテンプレートを使う :自然言語の指示ではなく、特殊トークンでタスクの意味を符号化します。たとえば、
<|tool_call|>は「これからツールを呼び出します:」より信頼性が高くなります。ツール/関数呼び出し :
<|function|>や<|result|>のような専用トークンを定義し、推論と行動の間に曖昧さのない境界を作ります。RLで一貫して扱う :PPO/GRPOでは、参照モデルと方策モデルが同一のトークン化と特殊トークン処理を使うようにします。不一致があるとKLの計算が壊れます。
EOSを扱う :生成時には、EOSが行動空間に含まれるようにします。モデルがEOSを出力できないと、応答が際限なく長くなります(RLでよくある失敗モードです)。
Transformerアーキテクチャ
Transformer (Vaswani et al. 2017) は、現代のLLMすべての基盤です。その構成要素を理解することは、このガイドで扱うあらゆる最適化・学習手法を把握するうえで不可欠です。
全体構造
Decoder-Only Transformerは、埋め込み、繰り返し配置されたアテンション+FFNブロック、語彙ロジットへの最終射影を通して、トークンを逐次処理します。図1.1にアーキテクチャ全体を示します。
元祖Encoder-Decoder Transformer
Transformerはもともと、系列変換タスク(機械翻訳、要約)向けの Encoder-Decoder アーキテクチャとして導入されました (Vaswani et al. 2017)。現代のLLMは主にDecoder-Only版(GPT形式)を使いますが、完全なアーキテクチャを理解することは重要です。ここで生まれたクロスアテンションとマスク付き自己注意は、今も基本的な構成要素だからです。
エンコーダ
エンコーダは入力シーケンス全体を 双方向に 処理します。つまり各トークンは他のすべてのトークンに注意を向けます(因果マスクはありません)。これにより、各位置が入力全体の情報を符号化する、豊かな文脈表現 \(\mathbf{H}^{\text{enc}} \in \mathbb{R}^{n \times d}\) が得られます。
-
入力 :トークン埋め込み+正弦波位置エンコーディング
-
各レイヤー :マルチヘッド自己注意 \(\to\) Add & Norm \(\to\) FFN \(\to\) Add & Norm
-
因果マスクなし :位置 \(i\) はすべての位置 \(1, \ldots, n\) に注意を向ける
-
出力 :入力シーケンス全体の文脈表現
デコーダ — マスク付きマルチヘッド自己注意
デコーダは出力トークンを一度に1つずつ(自己回帰的に)生成します。モデルが「未来を見る」ことを防ぐため、デコーダの自己注意には 因果マスク を使います。
\[ \text{MaskedAttn}(Q, K, V) = \text{softmax}!\left(\frac{QK^T}{\sqrt{d_k}} + M\right) V \]
ここでマスク \(M\) は次のとおりです。
\[ M_{ij} = \begin{cases} 0 & \text{if } i \geq j \text{ (can attend)} \\ -\infty & \text{if } i < j \text{ (future token --- blocked)} \end{cases} \]
Tip
マスキングが重要な理由
学習中、デコーダはターゲットシーケンス全体を並列に処理します(教師強制)。しかし自己回帰性を保つには、各位置が直前までの位置だけに注意を向けなければなりません。このマスクにより、トークン \(t\) の生成にはトークン \(1, \ldots, t{-}1\) の情報だけが使われます。推論時はトークンを1つずつ生成するためマスクは暗黙的ですが、学習時には因果性を保ちながら並列計算を可能にします。
デコーダ — クロスアテンション
マスク付き自己注意の後、各デコーダレイヤーは クロスアテンション を適用します。ここではデコーダがエンコーダの出力表現に注意を向けます。これが、デコーダが入力を「読み取る」仕組みです。
\[ \text{CrossAttn}(Q_{\text{dec}}, K_{\text{enc}}, V_{\text{enc}}) = \text{softmax}!\left(\frac{Q_{\text{dec}} K_{\text{enc}}^T}{\sqrt{d_k}}\right) V_{\text{enc}} \]
-
クエリ はデコーダの直前のサブレイヤー(マスク付き自己注意の出力)から来る
-
キーとバリュー はエンコーダの最終出力 \(\mathbf{H}^{\text{enc}}\) から来る
-
マスクは 適用しない。すべてのデコーダ位置が、すべてのエンコーダ位置に注意を向けられる
-
これにより、デコーダは生成ステップごとに入力の異なる部分へ動的に焦点を当てられる(たとえば英語\(\to\)スペイン語の翻訳で「cat」から「gato」へ訳す際に「cat」へ注意を向ける)
完全なデコーダレイヤー
各デコーダレイヤーには、エンコーダの2つに対して3つのサブレイヤーがあります。
-
マスク付きマルチヘッド自己注意+残差+LayerNorm
-
マルチヘッドクロスアテンション (エンコーダ出力に対するもの)+残差+LayerNorm
-
フィードフォワードネットワーク+残差+LayerNorm
Encoder-DecoderからDecoder-Onlyへ
現代のLLM(GPT、Llama、Qwen)はデコーダだけを使い、エンコーダとクロスアテンションレイヤーを完全に取り除いています。重要な洞察は、生成言語モデリングには単一の因果(マスク付き)自己注意スタックで十分だということです。モデルは1回のパスで文脈の符号化と続きの生成を学習します。これによりアーキテクチャ、学習、推論が単純になり、より効果的にスケールできます。Encoder-Decoderモデル(T5、BART)は、入力と出力の構造が分かれるタスク(翻訳、要約)では依然として有用です。また、ビジョンエンコーダが言語デコーダへキー/バリューを提供するマルチモーダルモデルでは、クロスアテンションが再び現れます。
Decoder-OnlyとEncoder-Decoderの比較
現代のLLMはほぼ例外なくDecoder-Onlyアーキテクチャを使いますが、Encoder-Decoder設計とのトレードオフを理解すると、その理由が明確になります。
| アーキテクチャ | 例 | 用途 |
|---|---|---|
| Decoder-only | GPT-4 (OpenAI 2023), Llama (Grattafiori et al. 2024), Mistral (Jiang et al. 2023), Qwen (Q. Team 2024a) | 自己回帰生成。チャット/推論で主流 |
| Encoder-decoder | T5 (Raffel et al. 2020), BART (M. Lewis et al. 2020), Flan-T5 (Chung et al. 2024) | Seq2seq(翻訳、要約)。現在は少数派 |
| Encoder-only | BERT (Devlin et al. 2019), RoBERTa (Liu et al. 2019) | 分類/埋め込み。生成には使わない |
Warning
Decoder-Onlyが勝った理由
Decoder-Onlyモデルは、構造が単純で(1つのモデル、1つの損失)、よりよくスケールし(すべてのパラメータが生成に寄与し)、統一的な学習(事前学習=次トークン予測=ファインチューニングの目的)を実現します。Encoder-Decoderモデルは、純粋な生成タスクではエンコーダに容量を割いてしまいます。
埋め込み:離散トークンから連続空間へ
アテンションや計算を始める前に、Transformerは離散的なトークンIDを、ニューラルネットワークが処理できる連続ベクトルへ変換しなければなりません。これが 埋め込み層 の役割です。
埋め込みとは何か?
埋め込みとは、離散記号を学習された密ベクトルで表現したものです。「king」を、ほとんどがゼロであるサイズ \(\vert \mathcal{V}\vert = 128{,}000\) のone-hotベクトルで表す代わりに、その 意味 を捉える \(\mathbb{R}^d\) 内のコンパクトなベクトル(例:\(d = 4096\))で表します。
重要な洞察は、 似た概念には近いベクトルが割り当てられる ことです。十分に学習された埋め込み空間では、次のようになります。
-
「king」と「queen」は近い(どちらも王族)
-
「king」と「bicycle」は遠い(無関係)
-
ベクトル演算が関係を捉える:\(\vec{\text{king}} - \vec{\text{man}} + \vec{\text{woman}} \approx \vec{\text{queen}}\)
埋め込みテーブル
実際には、埋め込み層は単純な行列 \(\mathbf{E} \in \mathbb{R}^{\vert \mathcal{V}\vert \times d}\) です。行 \(i\) にはトークン \(i\) の埋め込みベクトルが格納されます。
\[ \text{embed}(x_t) = \mathbf{E}[x_t] \in \mathbb{R}^d \]
トークンIDのシーケンス \([x_1, x_2, \ldots, x_n]\) に対する埋め込みは、単純なテーブル参照(インデックス操作)です。
\[ \mathbf{H}_0 = [\mathbf{E}[x_1];; \mathbf{E}[x_2];; \ldots;; \mathbf{E}[x_n]] \in \mathbb{R}^{n \times d} \]
Important
Transformerにおける埋め込みテーブル
サイズ :\(\vert \mathcal{V}\vert \times d\)。Llama-3の場合、\(128{,}256 \times 4{,}096 = 525\)Mパラメータ(8Bモデルの6.5%)。
初期化 :ランダム(Xavier/正規分布)で初期化し、その後バックプロパゲーションで学習する。
重み共有 :多くのモデルは、埋め込み行列と出力射影ヘッドを 共有 します:\(W_{\text{head}} = \mathbf{E}^T\)。パラメータを節約し、対称的なエンコード・デコード構造を作ります。
入力 :トークンID(整数)\(\to\) 出力 :\(\mathbb{R}^d\) 内の密ベクトル。
勾配の流れ :学習中は、現在のバッチに含まれるトークンに対応する行だけが勾配更新を受ける(スパース更新)。
Tip
埋め込みが機能する理由
埋め込みテーブルはモデルの他の部分とエンドツーエンドで学習されます。モデルは次のトークンを予測するよう学習されるため、似た文脈に現れるトークンに似たベクトルを割り当てる表現を学習しなければなりません。これは分布仮説、すなわち「単語は共に現れる語によって知ることができる」という考えです (Firth 1957)。埋め込み層はこの統計的構造を密な幾何へ圧縮します。
異方性の問題
事前学習済み埋め込み(BERTやGPT-2など)を検索(RAG)や推薦システムの初期構築といった下流タスクに使うと、重大な問題が生じます。学習された表現は非常に 異方的 で、全方向に一様に分布するのではなく、埋め込み空間内の狭い円錐に集中します (Ethayarajh 2019)。
これが応用上重要な理由:
-
RAG/検索 :内容に関係なくすべての埋め込みのコサイン類似度が \(>0.7\) なら、検索順位はほぼランダムになります。システムは関連する文章と無関係な文章を区別できません。
-
推薦システム :事前学習済みLLMの埋め込みでアイテムやユーザーを表すには、幾何構造が意味のある類似性を保っていなければなりません。
-
クラスタリング :異方的な埋め込みではクラスタがつぶれ、自然なグループ分けを見つけられません。
解決策:ホワイトニング
単純で効果的な修正方法が ホワイトニング です (Su et al. 2021)。これは埋め込み分布を等方的(平均0、共分散が単位行列)にする線形変換です。
\[ \tilde{\mathbf{h}} = \mathbf{D}^{-1/2} \mathbf{U}^T (\mathbf{h} - \boldsymbol{\mu}) \]
ここで \(\boldsymbol{\mu}\) は平均埋め込み、\(\mathbf{U}\mathbf{D}\mathbf{U}^T\) は共分散行列 \(\Sigma = \frac{1}{N}\sum_i (\mathbf{h}_i - \boldsymbol{\mu})(\mathbf{h}_i - \boldsymbol{\mu})^T\) の固有値分解です。
Important
ホワイトニングの実践
働き :すべての方向の分散が等しくなるよう、埋め込み空間を回転・スケーリングする(共分散を単位行列にする)。
効果 :コサイン類似度が意味を持つようになる。意味的に似たペアは高く、異なるペアは低くなる。
利点 :上位 \(k\) 個の固有ベクトルだけを残すことで(PCAと同様に)次元削減も同時に行え、検索が高速になる。
コスト :代表的なコーパス上で共分散行列を計算する必要がある(1回だけ、\(O(N \cdot d^2)\))。変換自体は推論時の単純な行列積である。
代替手法 :対照学習によるファインチューニング(SimCSE)、フローベースの正規化、等方性を促す正則化を使った学習。
自己注意機構
自己注意は、各トークンがシーケンス内の他のすべてのトークンに注意を向け、関連度に基づく重み付き結合を計算できるようにする中核演算です。
Important
スケールド・ドット積アテンション
入力シーケンス \(X \in \mathbb{R}^{n \times d}\) に対して、次を計算します。 \[ Q = XW_Q, \quad K = XW_K, \quad V = XW_V \quad (W_Q, W_K, W_V \in \mathbb{R}^{d \times d_k}) \]
\[ \text{Attention}(Q, K, V) = \text{softmax}!\left(\frac{QK^T}{\sqrt{d_k}} + M\right) V \] ここで \(M\) は 因果マスク (自己回帰モデル用)です:\(i \geq j\) なら \(M_{ij} = 0\)、それ以外なら \(-\infty\) です。
直感 :各トークンはそれ以前のすべてのトークンに「注意を向け」、クエリとキーの類似度に基づいて、それらのバリューの重み付き平均を計算します。
計算量
素朴なアテンション計算のシーケンス長に対するコストは 二次 です。
-
時間 :\(O(n^2 \cdot d)\)。\(QK^T\) の計算には、次元 \(d_k\) の内積が \(n^2\) 個必要です。
-
メモリ :\(O(n^2)\)。softmaxを適用するには、アテンション行列全体を実体化する必要があります。
\(d = 4096\) の128Kトークン文脈では、アテンション行列だけで \(128\text{K} \times 128\text{K} = 16.4\) 十億エントリ(FP32で64 GB)になります。この二次スケーリングが、長文脈LLMにおける根本的なボトルネックです。
| シーケンス長 | アテンション演算 | 行列サイズ | 実用上の影響 | |
|---|---|---|---|---|
| 2K | 4M | 16 MB | 高速。SRAMに収まる | |
| 8K | 64M | 256 MB | FlashAttentionなら管理可能 | |
| 32K | 1B | 4 GB | メモリ効率のよいカーネルが必要 | |
| 128K | 16B | 64 GB | 単一GPUのHBMを超える | |
| 1M | 1T | 4 TB | 二次未満の手法なしには不可能 |
アテンションコストのスケーリング:長いシーケンスで素朴な実装が法外になる理由。
アテンションコストを抑えるアプローチ
この二次ボトルネックには、いくつかの解決策の系統があります。
-
IOを意識した厳密なアテンション(FlashAttention (Dao et al. 2022)) :計算量は減らしませんが、SRAMに収まるタイルでアテンションを計算することで、HBM上に \(n \times n\) 行列を実体化する必要をなくします。重要なのは、FlashAttentionが 直交する ことです。これはアテンションパターンではなく実行エンジンです。実運用システムでは、FlashAttentionとスライディングウィンドウやブロックスパースマスクを組み合わせ、I/O効率とFLOPs削減の両方を得るのが一般的です。アルゴリズムの詳細は1.6節で扱います。
-
スライディングウィンドウ/局所アテンション :各トークンは最も近い \(w\) 個のトークン(例:\(w = 4096\))にだけ注意を向けます。コストは \(O(n \cdot w)\) となり、\(n\) に対して線形です。Mistral (Jiang et al. 2023)(ウィンドウ \(= 4096\))やLongformer (Beltagy et al. 2020)で使われています。効率性の代わりにグローバルな文脈を一部失いますが、実際には大部分のアテンションが局所的なのでうまく機能します。現代のスタックでは、スライディングウィンドウマスクはFlashAttentionカーネル 内部 で実行されます。
-
スパースアテンションパターン :局所ウィンドウと周期的なグローバルトークン(例:512個ごとのトークンがすべてに注意を向ける)を組み合わせます。BigBird (Zaheer et al. 2020)やLongT5 (Guo et al. 2022)がこれを使います。コスト \(O(n\sqrt{n})\) で、長距離の接続性を一部保ちます。ここでもFlashAttentionは、ゼロでないアテンションブロックの基盤カーネルとして機能します。
-
線形アテンション/状態空間モデル :結合則を使って \(\text{softmax}(QK^T)V\) を \(\phi(Q)(\phi(K)^T V)\) に置き換えるか、漸化式として定式化し直します(Mamba (Gu and Dao 2023)、RWKV (Bo Peng et al. 2023))。理論上の総コストは \(O(n \cdot d^2)\) です。上の2〜3とは異なり、これらはモデルの表現力を変える アーキテクチャ置換 です。softmaxなしのアテンションは本質的に表現力が低く、正確な長距離検索や複雑な推論を必要とするタスクでは、実証的にもこれらのモデルはTransformerにまだ遅れています。
-
KVキャッシュ圧縮 :推論時に古いKVペアを圧縮・破棄し、メモリを一定範囲に抑えます。手法には、H\(_2\)O (Z. Zhang et al. 2023)(heavy-hitter oracle:アテンションの高いキーだけを残す)、StreamingLLM (Xiao et al. 2024a)(初期の「アテンションシンク」トークンと直近ウィンドウを残す)、量子化KVキャッシュ (Z. Liu et al. 2024)があります。
Tip
FlashAttention+スパースパターン=両方の利点
FlashAttentionはスパースアテンションの 代替 だという誤解があります。そうではありません。これはアテンションカーネルのI/O最適化であり、任意のアテンションマスクと自由に組み合わせられます。現代のプロダクションシステム(Mistral、DeepSeekなど)は、スライディングウィンドウまたはブロックスパースマスクの 下層 の実行エンジンとしてFlashAttentionを使います。これにより、スパース性によるFLOPs削減と、タイル化による最適なメモリアクセスパターンの両方が得られます。RingAttention (H. Liu et al. 2023) はこれをマルチデバイス環境へ拡張し、シーケンス方向にタイル化された計算をGPU間に分散します。
線形アテンションと状態空間モデル(Mamba、RWKV)は、本質的に異なるアーキテクチャ上の選択です。完全なペアごとの相互作用を犠牲にして \(O(n)\) の計算量を得ます。理論的には洗練されていますが、知識集約的なタスクや長距離推論タスクでTransformerの品質に追いついておらず、最先端の研究所は今も厳密なアテンション(FlashAttention+スパース性)をバックボーンとして使い続けています。
マルチヘッドアテンション
単一のアテンション関数を計算する代わりに、マルチヘッドアテンションは複数のアテンション演算を並列に実行します。各ヘッドは入力の異なる側面(構文、意味、位置など)に焦点を当てるよう学習します。
Important
マルチヘッドアテンション
\(d\) 次元のキー/バリューを持つ1つのアテンション関数の代わりに、次元 \(d_k = d/H\) の \(H\) 個のヘッドを並列に使います。 \[ \text{MultiHead}(X) = \text{Concat}(\text{head}_1, \ldots, \text{head}_H) W_O \] 各ヘッドは異なるアテンションパターンを学習できます(例:構文用、意味用、位置の近さ用にそれぞれ1つのヘッド)。
Grouped Query Attention(GQA) :Llama-3 (Grattafiori et al. 2024) は、Qヘッドより少ないK、Vヘッドを使います(例:32個のQヘッドで8個のKVヘッドを共有)。品質をほとんど損なわずにKVキャッシュのサイズを \(4\times\) 削減できます。
位置エンコーディング
Transformerは構造上、置換同変です。位置情報がなければ、モデルは「the cat sat on the mat」と「mat the on sat cat the」を区別できません。位置エンコーディングはシーケンス順序のシグナルを注入し、アテンションがトークン間の距離と方向を推論できるようにします。
| 手法 | 使用例 | 主な考え方 |
|---|---|---|
| Sinusoidal | 元祖Transformer | 異なる周波数の固定 \(\sin/\cos\)。学習しない。 |
| Learned Absolute | GPT-2 (Radford et al. 2019), BERT (Devlin et al. 2019) | 位置ごとに埋め込みを学習する。学習時の長さに制限される。 |
| RoPE (Rotary) | Llama (Grattafiori et al. 2024), Qwen (Q. Team 2024a), Mistral (Jiang et al. 2023) | 位置依存の角度でQ、Kベクトルを回転する。NTKを意識したスケーリングで外挿する。 |
| ALiBi | BLOOM (Workshop 2023), MPT (MosaicML 2023) | 位置埋め込みを使わず、アテンションスコアに線形バイアス \(-m\vert i-j\vert\) を加える。単純で外挿性が高い。 |
現代のLLMにおける位置エンコーディング手法。
正弦波(固定)位置エンコーディング
元祖Transformer (Vaswani et al. 2017) で導入されたこの手法は、等比間隔の周波数における固定正弦関数を使います。
\[ \text{PE}(pos, 2i) = \sin!\Bigl(\frac{pos}{10000^{2i/d}}\Bigr), \qquad \text{PE}(pos, 2i{+}1) = \cos!\Bigl(\frac{pos}{10000^{2i/d}}\Bigr) \]
ここで \(pos\) はトークン位置、\(i\) は次元インデックス、\(d\) はモデルの次元です。
動機 :各周波数は異なるスケールで位置を符号化します(2進数のカウントに似ています)。著者らは、\(\text{PE}(pos+k)\) を \(\text{PE}(pos)\) の線形関数として表せるため、モデルが相対位置へ注意を向けることを学習できると仮定しました。
長所 :学習パラメータがゼロ。決定的。理論上は任意の長さに対応する。
短所 :実際には学習時の長さを超えるとうまく外挿できない。モデルは絶対信号から相対位置を間接的に復号する必要があり、現在はほぼ置き換えられている。
学習型絶対位置埋め込み
GPT-2 (Radford et al. 2019) とBERT (Devlin et al. 2019)で使われています。学習可能な埋め込み行列 \(\mathbf{E}_{\text{pos}} \in \mathbb{R}^{L_{\max} \times d}\) をトークン埋め込みに加えます。
\[ h_0^{(pos)} = \text{TokenEmbed}(x_{pos}) + \mathbf{E}_{\text{pos}}[pos] \]
動機 :固定構造を押し付けるのではなく、タスクに最適な位置表現をモデル自身に学習させる。
長所 :柔軟性が最大。実装が単純。短いシーケンスでは正弦波を上回ることが多い。
短所 :最大長 \(L_{\max}\) がハードコードされ、それを超えて一般化できない。\(L_{\max}\) の末尾近くの埋め込みは学習不足になる。\(L_{\max} \times d\) 個のパラメータが増える。
回転位置埋め込み(RoPE)
RoPE (Su et al. 2024) は、2次元部分空間内でクエリとキーベクトルを 回転 させることで位置を符号化します。
\[ \text{RoPE}(x_m, m) = \begin{pmatrix} x_m^{(1)} \\ x_m^{(2)} \\ \vdots \\ x_m^{(d-1)} \\ x_m^{(d)} \end{pmatrix} \odot \begin{pmatrix} \cos m\theta_1 \\ \cos m\theta_1 \\ \vdots \\ \cos m\theta_{d/2} \\ \cos m\theta_{d/2} \end{pmatrix} + \begin{pmatrix} -x_m^{(2)} \\ x_m^{(1)} \\ \vdots \\ -x_m^{(d)} \\ x_m^{(d-1)} \end{pmatrix} \odot \begin{pmatrix} \sin m\theta_1 \\ \sin m\theta_1 \\ \vdots \\ \sin m\theta_{d/2} \\ \sin m\theta_{d/2} \end{pmatrix} \]
ここで \(\theta_i = 10000^{-2i/d}\)、\(m\) は位置インデックスです。重要な性質は、回転後のクエリとキーの内積が相対位置だけに依存することです。
\[ \langle \text{RoPE}(q_m, m),; \text{RoPE}(k_n, n) \rangle = f(q_m, k_n, m-n) \]
動機 :明示的なバイアス項なしに相対位置の符号化を実現し、線形アテンションやKVキャッシュとの互換性も保つ。
長所 :自然に相対的。追加パラメータ不要。効率的な推論と互換性がある。NTKを意識したスケーリング (Bowen Peng et al. 2023) やYaRN(\(\theta\) の基底を調整する、または周波数を補間する)で長い文脈へ拡張できる。
短所 :アテンション演算ごとの計算量がやや増える(回転+インターリービング)。外挿には明示的なスケーリング戦略が必要。2次元部分空間での回転が構造を課すため、すべてのタスクに最適とは限らない。
Tip
RoPEの長さ拡張
\(L\) で学習したRoPEモデルを文脈長 \(L' > L\) へ拡張するには、次のようにします。
位置補間 :すべての位置が \([0, L]\) に収まるよう、位置を \(L/L'\) 倍にスケールする。単純だが解像度が圧縮される。
NTKを意識したスケーリング :\(\theta\) の基底を増やす(例:\(10000 \to 10000 \cdot (L'/L)^{d/(d-2)}\))。低周波成分を保ちながら高周波成分を実質的に引き伸ばす。
YaRN (Bowen Peng et al. 2023):NTKスケーリングとアテンション温度補正 \(t = 0.1 \ln(s) + 1\) を組み合わせ、長距離で増えるエントロピーを補正する。
ALiBi(線形バイアス付きアテンション)
ALiBi (Press et al. 2022) は根本的に異なるアプローチを取ります。位置埋め込みをまったく使いません。その代わり、固定の線形ペナルティをアテンションスコアから差し引きます。
\[ \text{Attention}(Q, K, V) = \text{softmax}!\left(\frac{QK^T}{\sqrt{d_k}} - m \cdot \bigl[|i-j|\bigr]_{i,j}\right) V \]
ここで \(m\) はヘッド固有の傾きです(全 \(H\) 個のヘッドのうちヘッド \(h\) に対して、幾何的に \(m_h = 2^{-8h/H}\) と設定)。バイアス \(-m\vert i-j\vert\) は、幅がヘッドごとに変わる緩やかな局所アテンションウィンドウを作ります。
動機 :埋め込み空間に干渉せず、位置によって近いトークンへアテンションを偏らせるべきです(新しさの事前分布)。アテンションスコア空間だけで動作するため、ALiBiはトークン表現を位置シグナルで汚しません。
長所 :長さの外挿性が非常に高い(1Kで学習して8K以上で動作)。パラメータゼロ。実装が容易。ヘッド固有の傾きにより、複数スケールの局所性を持つ。
短所 :正確な長距離位置推論を必要とするタスク(例:「5番目の単語は何だったか?」)では表現力が低い。線形減衰という強い帰納バイアスがすべての領域に適するとは限らない。近年のモデルでは、短文脈での性能がより高いRoPEにほぼ取って代わられている。
| 正弦波 | 学習型絶対 | RoPE | ALiBi | |
|---|---|---|---|---|
| 追加パラメータ | なし | \(L_{\max} \times d\) | なし | なし |
| 位置の種類 | 絶対 | 絶対 | 相対 | 相対(暗黙) |
| 長さの外挿 | 低い | なし | 良好(スケーリングあり) | 優れている |
| 計算オーバーヘッド | 無視できる | 無視できる | 小さい | 無視できる |
| 主流の時期 | 2017〜19 | 2018〜20 | 2022〜現在 | 2022〜23 |
位置エンコーディングの比較:実用上のトレードオフ。
極端に長い文脈へのスケーリング(100K〜1M以上のトークン)
現代の最先端モデル(200K〜1M文脈のClaude (Anthropic 2024c)、1M以上のGemini 1.5 (Gemini Team 2024)、128KのGPT-4 (OpenAI 2023))には、学習時の長さをはるかに超えても忠実に機能する位置エンコーディングが必要です。現在の主流の解決策は次のとおりです。
-
周波数スケーリング付きRoPE :RoPEを学習時の長さを超えて拡張する標準的な方法です。再学習する代わりに、基底周波数 \(\theta\) を再スケーリングします。
\[ \theta'_i = \theta_i \cdot \left(\frac{L_{\text{target}}}{L_{\text{train}}}\right)^{2i/d} \]
その派生には次がある。
-
線形スケーリング(位置補間) (S. Chen et al. 2023):位置インデックスを係数 \(s\) で単純に割る。安価だが、拡張比が大きいと品質が低下する。
-
NTKを意識したスケーリング (Bowen Peng et al. 2023):基底周波数を \(\theta = 10000 \to 10000 \cdot s^{d/(d-2)}\) とスケールする。高周波(局所)情報を保ちながら、低周波(グローバル)の範囲を拡張する。
-
YaRN (Bowen Peng et al. 2023)(Yet another RoPE extensioN):NTKスケーリングとアテンション温度補正、小規模な長文脈コーパスでのファインチューニングを組み合わせる。Llama-3では学習時の8Kからデプロイ時の128Kへ拡張するために使われる。
-
Dynamic NTK (Bowen Peng et al. 2023):推論時の実際のシーケンス長に応じて、スケーリング係数をオンザフライで調整する。固定の拡張比は不要で、文脈が長くなるとモデルが適応する。
-
-
長いデータでの継続事前学習 :RoPEスケーリングを使っても、長い文書で短期間(1〜5Bトークン)の継続事前学習を行うとモデルは向上します。これにより、位置的に耐えられるだけでなく、遠い文脈を実際に 使う ことを学習します。Llama-3.1では8K \(\to\) 64K \(\to\) 128Kという段階的スケジュールを使いました。
-
Ring Attention/ブロック単位並列 (H. Liu et al. 2023):単一GPUのメモリを超えるシーケンス(1M以上のトークン)では、Ring Attentionがリング型トポロジーでシーケンスをGPU間に分散します。各GPUが1ブロックを保持し、KVブロックをリングに沿って渡しながら局所アテンションタイルを計算します。厳密なアテンションを保ったまま、GPU数に対してメモリを線形にスケールできます。
-
ハイブリッドアーキテクチャ :一部のシステムは、大部分のレイヤーで局所的なスライディングウィンドウ(例:4K)を使い、選択したレイヤー(例:4層ごと)では完全なアテンションを組み合わせます。これにより、グローバルな情報の流れを保ちながら、ほとんどの計算を \(O(n \cdot w)\) のコストで行えます。
Warning
長い文脈 $\neq$ 長い文脈利用
コンテキスト長が1Mのモデルが、1Mトークンすべてを必ずしも効果的に使えるとは限らない。「中間で失われる」現象(N. F. Liu et al. 2024a)は、モデルが長いコンテキストの先頭と末尾に集中し、中間の情報を十分に利用しない傾向を示す。長文脈を効果的に利用するには、位置エンコーディングのサポートと長距離検索に報いるタスクでの学習の両方が必要である。
フィードフォワードネットワーク(MLP)
各Transformerブロックには、各位置へ独立に適用されるMLPが含まれる。
\[ \text{FFN}(x) = W_2 \cdot \sigma(W_1 x + b_1) + b_2 \]
ここで \(W_1 \in \mathbb{R}^{d \times 4d}\)、\(W_2 \in \mathbb{R}^{4d \times d}\) です。現代のLLMでは次を使います。
-
SwiGLU活性化 :\(\text{FFN}(x) = W_2 (\text{Swish}(W_1 x) \odot W_3 x)\)。Llama (Grattafiori et al. 2024) やMistral (Jiang et al. 2023)で使われます。重み行列が3つ必要ですが、性能が向上します。
-
隠れ次元は通常 \(8/3 \times d\) です(Tensor Coreの効率のため256の倍数に丸めます)。
Tip
メモリとしてのFFN
近年の研究 (Geva et al. 2021) は、FFNレイヤーが キーバリューメモリ として機能すると示唆しています。\(W_1\) の行はキー(照合するパターン)、\(W_2\) の列はバリュー(出力する情報)です。FFNは現在の隠れ状態に基づき、保存された知識を「取り出します」。
Layer Normalization(層正規化)
Layer Normalizationは特徴次元にわたって活性値を正規化し、学習を安定させます。アテンション/FFNサブレイヤーに対する配置は、学習ダイナミクスに大きな影響を与えます。
LayerNormの仕組み
隠れ状態ベクトル \(\mathbf{x} \in \mathbb{R}^d\)(1つのトークンの表現)に対して、LayerNorm (Ba et al. 2016) は次を計算します。
\[ \text{LayerNorm}(\mathbf{x}) = \gamma \odot \frac{\mathbf{x} - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta \]
ここで、
-
\(\mu = \frac{1}{d}\sum_{i=1}^{d} x_i\)(\(d\) 個の特徴次元にわたる平均)
-
\(\sigma^2 = \frac{1}{d}\sum_{i=1}^{d} (x_i - \mu)^2\)(特徴にわたる分散)
-
\(\gamma, \beta \in \mathbb{R}^d\) は 学習される スケール・シフトパラメータ(次元ごと)
-
\(\epsilon \approx 10^{-5}\) はゼロ除算を防ぐ
BatchNormとの重要な違い :LayerNormはバッチ全体ではなく、1つの例の 特徴次元 にわたって正規化します。そのためバッチサイズに依存せず、学習時と推論時で同じように動作します。
RMSNorm — 現代的な簡略化
RMSNorm (Zhang and Sennrich 2019) は平均を中心化するステップを省き、二乗平均平方根だけで正規化します。
\[ \text{RMSNorm}(\mathbf{x}) = \gamma \odot \frac{\mathbf{x}}{\text{RMS}(\mathbf{x})}, \qquad \text{RMS}(\mathbf{x}) = \sqrt{\frac{1}{d}\sum_{i=1}^{d} x_i^2} \]
\(\beta\)(シフト)パラメータも平均の減算もなく、スケールだけを行います。トークンごとに1回のリダクション演算を節約でき、同等のモデル品質を保ちながらGPU上で \(\sim\) 5〜10%高速になります。現代のLLM(Llama、Mistral、Qwen)はすべてRMSNormを使います。
Important
Pre-LNとPost-LNの比較
Post-LN (元祖Transformer):\(h + \text{LayerNorm}(\text{Attn}(h))\)。慎重なウォームアップが必要で、学習が不安定になり得る。
Pre-LN (GPT-2以降、現代のLLMすべて):\(h + \text{Attn}(\text{LayerNorm}(h))\)。学習を安定させ、より高い学習率を可能にする。
RMSNorm (Llama (Grattafiori et al. 2024)、Mistral (Jiang et al. 2023)):平均中心化を行わない簡略版LayerNorm:\(\text{RMSNorm}(x) = x / \text{RMS}(x) \cdot \gamma\)。やや高速で品質は同じ。
Tip
深いネットワークで正規化が重要な理由
正規化がないと、活性値はレイヤーを通るにつれて指数的に増大・縮小します(活性値の爆発・消失)。LayerNormのない128層Transformerでは、最初と最後のレイヤーで大きさが \(10^{30}\times\) も変わり得ます。正規化は各レイヤーの出力を予測可能な範囲に制限し、安定した勾配の流れと、ネットワーク全体で一貫した学習率の利用を可能にします。
モデルサイズの目安
以下の表は、広く使われているオープンウェイトモデル(2025年時点の最新版)の主要なアーキテクチャパラメータをまとめたもので、規模と設計上の選択を理解するための早見表です。
| モデル | パラメータ | レイヤー | \(d\) | ヘッド | KVヘッド | 文脈 |
|---|---|---|---|---|---|---|
| Llama-3.1 8B (Grattafiori et al. 2024) | 8B | 32 | 4096 | 32 | 8 | 128K |
| Llama-3.1 405B (Grattafiori et al. 2024) | 405B | 126 | 16384 | 128 | 8 | 128K |
| Llama-4 Maverick (AI 2025) | 400B (17B active) | 48 | 5120 | 40 | 8 | 1M |
| Mistral Large 2 (AI 2024) | 123B | 88 | 12288 | 96 | 8 | 128K |
| Qwen-2.5 72B (Q. Team 2024a) | 72B | 80 | 8192 | 64 | 8 | 128K |
| DeepSeek-V3 (DeepSeek-AI 2024b) | 671B (37B active) | 61 | 7168 | 128 | MLA | 128K |
人気のオープンウェイトLLM(2024〜2025世代)のアーキテクチャパラメータ。
注:「active」と記されたパラメータを持つモデルはMixture-of-Experts(MoE)アーキテクチャを使います。総パラメータ数はモデル容量を示し、activeパラメータはトークンごとの計算コストを表します。DeepSeek-V3は標準GQAの代わりにMulti-head Latent Attention(MLA)を使い、KVを低ランクの潜在空間へ圧縮します。
アテンションの病理
アテンション機構は強力ですが、体系的な失敗モードも示します。特に長い文脈へスケールする場合や、モデルの挙動を解釈する場合には、実務者が理解しておく必要があります。
アテンションシンク
現象
Xiaoら (Xiao et al. 2024b) は、Transformerモデルが意味内容に関係なく、シーケンスの 最初のトークン に不釣り合いに高いアテンションスコアを割り当てることを発見しました。最初のトークンが意味を持たない <BOS> マーカーであっても、全レイヤーのアテンションヘッドが一貫してそこへ注意を向け、総アテンション量の20〜50%に達することさえあります。
なぜ起こるのか
Softmaxアテンションは妥当な確率分布(\(\sum_j \alpha_j = 1\))を生成しなければなりません。クエリに特に関連するキーがないとき、モデルには未使用のアテンション量を捨てる場所が必要です。学習中、最初のトークンは常に存在し位置的に予測しやすいため、このデフォルトのシンクになります。これは 何もしないアテンションの対象 として機能し、モデルは無関係なアテンションを予測不能に分散させず、そこへ送ることを学習します。
\[ \alpha_{\text{sink}} = \frac{\exp(q^\top k_0 / \sqrt{d})}{\sum_{j} \exp(q^\top k_j / \sqrt{d})} \gg \frac{1}{n} \quad \text{(even when } k_0 \text{ is semantically irrelevant)} \]
結果
-
ストリーミング推論の失敗 :スライディングウィンドウKVキャッシュを使うとき、最初のトークンを破棄するとパープレキシティが壊滅的に急上昇します。モデルがアテンションシンクを失うためです。
-
誤解を招く解釈可能性 :素朴なアテンション可視化では、単なる数学的な人工物である最初のトークンが「重要」に見えます。
-
文脈ウィンドウの浪費 :シンクトークンは有用な情報を持たないまま、KVキャッシュのスロットを占有します。
解決策
-
StreamingLLM (Xiao et al. 2024b:最近のスライディングウィンドウとともに、最初の\(k\)個のトークン(「アテンションシンク」)を常にKVキャッシュに残す。メモリを一定に保ったまま無限長の生成を可能にする。
-
設計によるシンクトークン :一部のモデル(Mistralなど)は学習時に専用のシンクトークンを前置します。これは残余アテンションを吸収する目的で明示的に使われます。
-
Softmaxの代替 :softmaxをReLUアテンションやシグモイドゲーティングへ置き換えます。これらではゼロアテンションを表現できるため、捨て先が不要です。
アテンションの希釈
現象
シーケンス長 \(n\) が増えると、各クエリはより多くのキーにアテンション予算を分配しなければなりません。トークンあたりの平均アテンション重みは \(O(1/n)\) で減少し、本当に関連する少数の位置へモデルが集中することが次第に難しくなります。これは アテンションの希釈 または アテンションの拡散 と呼ばれる問題です (N. F. Liu et al. 2024a)。
「中間で失われる」効果
Liuら (N. F. Liu et al. 2024a) は、LLMの検索曲線がU字型になることを示しました。長い文脈の 先頭 または 末尾 に置かれた情報は確実に検索されますが、中間 の情報はしばしば無視されます。これは、RoPE/ALiBiの位置バイアスとアテンション希釈が重なった直接的な結果です。
なぜ起こるのか
-
Softmaxの飽和 :キーが多いとsoftmax温度が実質的に低下し、分布がより一様になります(エントロピーが高くなる)。
-
位置による減衰 :RoPEの相対位置エンコーディングは距離に応じた自然な減衰を生み、先頭と末尾の両方から遠い中間位置へのアテンションを抑えます。
-
学習分布 :短いシーケンスで学習したモデルは、直近の文脈に偏ったアテンションパターンを身につけます。
軽減策
-
明示的な検索 :関連する文脈をプロンプトの先頭または末尾に置き、中間位置に頼らないようRAGを使う。
-
長文脈学習 :重要情報の位置を変えた長文書で学習する (Yao Fu et al. 2024)。
-
階層的アテンション :Mamba (Gu and Dao 2024) やRWKVのように、\(O(n^2)\) のアテンションボトルネックを完全に避けるアーキテクチャを使う。
-
ランドマークトークン :アテンションの「道しるべ」となる、検索可能なマーカーを文脈に挿入する。
-
温度スケーリング :一部の実装では、長いシーケンスの希釈に対抗するためアテンションロジットを \(\log n\) 倍にスケールする。
その他のアテンション現象
| パターン | 説明 | 含意 |
|---|---|---|
| アテンションヘッドの特化 | ヘッドごとに異なる役割を学習する:構文ヘッド、共参照ヘッド、位置ヘッド (Voita et al. 2019) | すべてのヘッドが同じだけ重要ではなく、多くは刈り込み可能 |
| 誘導ヘッド | [A][B]...[A] \(\to\) [B] のコピーを実装するヘッド (Olsson et al. 2022) | 文脈内学習に重要。2層以上のモデルで出現する |
| アテンション崩壊 | 深いネットワークではアテンション分布が収束し得る(すべてのヘッドが同じ位置に注意を向ける) | 表現力を損ない、アテンション多様性損失で対処する |
| 検索ヘッド | 特定のヘッドが文脈から事実情報を検索することに特化する (Zhengbao Wu et al. 2024) | 特定のヘッドを刈り込むとハルシネーションが急増する理由を説明する |
大規模Transformerで観測される追加のアテンションパターン。
解釈可能性のためのアテンション可視化
アテンション重みはモデルの推論を覗く窓になりますが、注意深く解釈しなければなりません。
アテンションの可視化手法
生のアテンションマップ
最も単純な方法は、各ヘッドとレイヤーについて、\(n \times n\) のアテンション行列 \(A = \text{softmax}(QK^\top/\sqrt{d})\) をヒートマップとして描画することです。BertViz (Vig 2019) のようなツールは、インタラクティブなマルチヘッド可視化を描画します。
アテンションロールアウト
単一レイヤーの生アテンションだけを見ると誤解を招きます。情報は すべて のレイヤーを通る残差接続を流れるためです。AbnarとZuidema (Abnar and Zuidema 2020) は アテンションロールアウト を提案しました。レイヤー間でアテンション行列を掛け合わせ、入力から出力までの総情報流を近似します。
\[ R^{(l)} = A^{(l)} \cdot R^{(l-1)}, \quad R^{(0)} = I \]
ここで \(A^{(l)}\) はレイヤー \(l\) の(ヘッド間で平均した)アテンション行列で、残差接続を含めるように調整されています:\(A^{(l)} = 0.5 \cdot A^{(l)}_{\text{raw}} + 0.5 \cdot I\)。
勾配重み付きアテンション
アテンション重みと勾配情報を組み合わせ、注意を向けたトークンのうち、実際に出力へ 影響を与える ものを特定します (Barkan et al. 2021)。
\[ \text{Relevance}(i) = \alpha_i \cdot \left|\frac{\partial y}{\partial h_i}\right| \]
これは、高いアテンション \(\neq\) 高い影響力とは限らないという批判に対処します(高いアテンションを受けても、ほぼゼロ重みの経路で処理されるトークンがあり得ます)。
Warning
アテンションは説明ではない
JainとWallace (Jain and Wallace 2019) は、アテンション重みが勾配ベースの特徴重要度と相関しないことが多く、敵対的なアテンション分布が同一の出力を生成できることを示しました。アテンション可視化は忠実な説明ではなく、仮説生成器 として使います。因果的な帰属には、勾配ベースの手法、プロービング、機械論的解釈可能性を優先します。
スパースオートエンコーダー(SAE)による機械論的解釈可能性
解釈可能性の問題
TransformerのMLPや残差ストリームの個々のニューロンは、通常 多義的 です。1つのニューロンが、互いに無関係な複数の概念(例:「青色」かつ「学術引用」かつ単語「the」)に反応します。そのため、ニューロンレベルの直接的な解釈は信頼できません。
スパースオートエンコーダー
Cunninghamら (Cunningham et al. 2024) とBrickenら (Bricken et al. 2023) は、モデルの活性値でスパースオートエンコーダー(SAE)を学習すると、多義的な表現を 単義的特徴 に分解できることを示しました。各特徴は単一の概念に対応する、解釈可能な方向です。
\[ h = W_{\text{dec}} \cdot \text{ReLU}(W_{\text{enc}} \cdot x + b_{\text{enc}}) + b_{\text{dec}} \]
ここで \(W_{\text{enc}} \in \mathbb{R}^{m \times d}\)、\(m \gg d\)(過完備基底)であり、ReLUとスパース性ペナルティにより、入力ごとに少数の特徴だけが活性化します。
SAEの解釈可能性から得られた主な知見
-
特徴は 単義的 である。各特徴が、人間に解釈可能な単一の概念(「Pythonのコード」「ゴールデンゲートブリッジへの言及」「一人称の語り」)を符号化する (Bricken et al. 2023)。
-
特徴は 操縦可能 である。特徴の活性値を高・低に固定すると、モデルの挙動を直接制御できる(例:「ゴールデンゲートブリッジ」特徴を強制的にオンにすると、モデルはすべての応答でそれに言及する) (Templeton et al. 2024)。
-
特徴は合成できる。単純な特徴の組み合わせから複雑な挙動が現れる。
-
SAEはスケールする。Templetonら (Templeton et al. 2024) はClaude 3 Sonnet上で最大34M特徴のSAEを学習し、安全性に関わる概念(欺瞞、迎合、危険な要求)について解釈可能な特徴を見つけた。
Important
SAEの学習レシピ
大規模コーパスから、特定のモデルレイヤーの活性値を収集する。
隠れ層に \(L_1\) ペナルティを加えたスパースオートエンコーダーを学習する:\(\mathcal{L} = \\vert x - \hat{x}\\vert _2^2 + \lambda \\vert z\\vert _1\)。
学習されたエンコーダ方向(\(W_{\text{enc}}\) の行)が候補特徴になる。
検証する:各特徴について最大活性化例を見つけ、意味的一貫性を確認する。
必要に応じて、特徴の吸収 と 死んだ特徴 を測定し、SAEの品質を評価する。
自然言語オートエンコーダー(Anthropic、2026年)
SAEは活性値を解釈可能な ベクトル に分解しますが、その特徴を理解するには最大活性化例を人間が調べる必要があります。Anthropicの自然言語オートエンコーダー(NLAE) (Anthropic 2026) は根本的に異なるアプローチを取ります。スパースなボトルネックを 自然言語の説明 に置き換え、解釈可能性を自動化します。
NLAEの仕組み
-
エンコーダ :言語モデルが隠れ活性値(または入力テキスト)を読み、活性化した概念の自然言語による説明を生成する。例:「このテキストはフランス料理について述べ、形式的な学術調の文体を使っている。」
-
デコーダ :別の言語モデルが自然言語の説明を読み、元の活性値を再構成する(または次のトークンを予測する)。
-
学習 :エンコーダとデコーダを再構成損失が最小になるようエンドツーエンドで学習する。ボトルネックはスパースベクトルではなく、可変長の自然言語文字列になる。
SAEに対する利点
-
自己解釈的 :特徴が 文字どおり 自然言語なので、手作業のラベル付けが不要。
-
合成的 :SAEの特徴が単一方向として表せない複雑な関係概念(「事実の主張に対する皮肉な応答」)を表現できる。
-
階層的 :同じ表現で、細粒度(単語レベル)と粗粒度(文書レベル)の性質を捉えられる。
-
監査可能 :ボトルネックの説明が人間に読めるため、モデルが存在すると「考えている」情報を直接調べられる。
制限
NLAEは言語モデルをループ内に導入するため計算コストが高く、他のモデル生成説明と同じ忠実性の懸念を受ける可能性があります。また、SAEが活性値の大きさとして自然に扱えるサブシンボリックな特徴(幾何パターン、正確な数値)を簡単には表現できません。
Tip
解釈可能性スタック
解釈可能性ツールを階層として考えます。
アテンションマップ :「モデルは何を見ているか?」(最も安価で、最も忠実性が低い)
プロービング分類器 :「このレイヤーにはどんな情報が符号化されているか?」
スパースオートエンコーダー :「どの単義的特徴が活性化しているか?」(スケール可能だが、人間によるラベル付けが必要)
自然言語オートエンコーダー :「モデルは何が起こっていると考えているか?」(自己解釈的だが高コスト)
因果トレーシング/パッチング :「どの構成要素が実際にこの出力を引き起こしたか?」(最も忠実で、最も高コスト)
各レベルは、説明のコスト、スケーラビリティ、忠実性の間でトレードオフを持ちます。
予測ヘッド:Transformerが出力するもの
Transformer本体は各位置について文脈化された隠れ状態 \(\mathbf{h}_t \in \mathbb{R}^d\) を生成します。これらの隠れ状態を どう使うか、つまり 予測ヘッド がタスクを定義します。同じTransformerバックボーンでも、ヘッドを交換するだけでまったく異なる目的に使えます。
言語モデリングヘッド(事前学習)
標準的なLMヘッドは、最後の隠れ状態を語彙ロジットへ射影し、次のトークンに対するクロスエントロピー損失で学習します。
\[ P(x_{t+1} | x_{\leq t}) = \text{softmax}(\mathbf{W}_{\text{head}} \cdot \mathbf{h}_t + \mathbf{b}) \]
ここで \(\mathbf{W}_{\text{head}} \in \mathbb{R}^{\vert \mathcal{V}\vert \times d}\) です(埋め込み行列と共有することが多い:\(\mathbf{W}_{\text{head}} = \mathbf{E}^T\))。
Important
LMヘッドの性質
学習目的 :因果言語モデリング(すべての位置で次のトークンを予測する)
損失 :\(\mathcal{L}_{\text{LM}} = -\frac{1}{T}\sum_{t=1}^{T} \log P(x_t \vert x_{<t})\)
ラベル :すべてのトークンが入力(右にシフト)とターゲット(左にシフト)の両方になる
使用段階 :大規模コーパス(数兆トークン)での事前学習
重要な洞察 :モデルは次トークン予測の副産物として、一般的な言語理解を学習する
条件付き生成ヘッド(SFT/指示追従)
教師ありファインチューニング(SFT)では、アーキテクチャはLMヘッドと 同一 です。語彙ロジットへの同じ線形射影を使います。違いは、どこに対して損失を計算するか だけです。
\[ \mathcal{L}_{\text{SFT}} = -\frac{1}{|y|}\sum_{t=1}^{|y|} \log P(y_t | x_{\text{prompt}}, y_{<t}) \]
Important
条件付きヘッドとLMヘッドの主な違い
損失マスキング :プロンプト/指示ではなく、応答 トークンにだけ損失を計算する。プロンプトは文脈を与えるが、勾配シグナルは与えない。
条件付け :モデルは特定の指示形式(システムプロンプト、ユーザークエリ、ツール呼び出し)を 条件として 応答を生成するよう学習する。
形式トークン :特殊トークン(
<|user|>、<|assistant|>)が構造化出力を生成するようモデルを導く。使用段階 :選別した指示・応答ペアでのSFT、およびRLの方策生成(行動/応答を生成する方策ヘッド)。
Tip
同じヘッド、異なる学習シグナル
LMヘッドとSFTヘッドはアーキテクチャ的に同一です(同じ \(\mathbf{W}_{\text{head}}\))。違いは、SFT中にプロンプトトークンの損失をマスクすることだけです。この小さな変更が、一般的なテキスト予測器を指示追従アシスタントへ変えます。ヘッドは条件付けされた文脈に基づき、異なる生成モードを「活性化」することを学習します。
価値ヘッド(RLの回帰)
強化学習(PPO、GRPO)では、状態が どれほど良いか を推定する必要があります。そのためには語彙ロジットではなくスカラー出力が必要です。 価値ヘッド はLM射影を単純な回帰層に置き換えます。
\[ V(s_t) = \mathbf{w}_{\text{value}}^T \cdot \mathbf{h}_t + b \in \mathbb{R} \]
ここで \(\mathbf{w}_{\text{value}} \in \mathbb{R}^d\)、\(b \in \mathbb{R}\) です。
Important
価値ヘッドの性質
出力 :単一スカラー(この状態から期待される累積報酬)
損失 :予測リターンと実際のリターンのMSE:\(\mathcal{L}_V = \frac{1}{T}\sum_t (V(s_t) - R_t)^2\)
アーキテクチャ :線形層 \(\mathbb{R}^d \to \mathbb{R}^1\)(小さなMLP:\(d \to 256 \to 1\) を使う場合もある)
バックボーン共有 :方策とTransformer本体を共有する(別の価値ヘッドを持つ)ことが多いが、完全に別のクリティックネットワークを使う場合もある
使用段階 :PPOのアドバンテージ推定(GAE)、報酬モデルのスコアリング
ヘッド選択のまとめ
| ヘッド | 出力 | 損失 | 段階 | 目的 |
|---|---|---|---|---|
| LMヘッド | \(\mathbb{R}^{\vert \mathcal{V}\vert }\) | クロスエントロピー(全トークン) | 事前学習 | 生テキストから言語を学ぶ |
| 条件付きヘッド | \(\mathbb{R}^{\vert \mathcal{V}\vert }\) | クロスエントロピー(応答のみ) | SFT | 指示に従うことを学ぶ |
| 価値ヘッド | \(\mathbb{R}^1\) | MSE | RL(PPO) | アドバンテージのため状態価値を推定 |
| 報酬ヘッド | \(\mathbb{R}^1\) | ペアワイズランキング | RM学習 | 応答品質をスコア化 |
本稿全体で使う予測ヘッドと、その学習コンテキスト。
Warning
ヘッドの初期化が重要
事前学習済みLMに価値ヘッドを追加するときは、ゼロ付近(小さなランダム重み)に初期化します。大きな値で初期化すると、初期価値推定が大きく外れ、巨大なアドバンテージと不安定なPPO更新を引き起こします。一般的には最終線形層を \(\mathcal{N}(0, 1/\sqrt{d})\) または単純なゼロで初期化します。
HuggingFaceでの実装
from transformers import (
AutoModelForCausalLM, # LM head (pretraining + SFT)
AutoModelForSequenceClassification, # Reward head
AutoTokenizer,
)
from trl import AutoModelForCausalLMWithValueHead # Value head (PPO)
import torch
model_name = "meta-llama/Llama-3.1-8B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
# === 1. LM Head (Pretraining / SFT) ===
# The default CausalLM model -- projects hidden states to vocab logits
lm_model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto",
)
# lm_model.lm_head: Linear(hidden_size -> vocab_size)
# Output: logits of shape (batch, seq_len, vocab_size)
inputs = tokenizer("The capital of France is", return_tensors="pt")
outputs = lm_model(**inputs)
next_token_logits = outputs.logits[:, -1, :] # (batch, vocab_size)
probs = torch.softmax(next_token_logits, dim=-1)
# === 2. Conditional Head (SFT) ===
# Architecturally identical to LM head -- difference is in loss masking
# During SFT, we only compute loss on response tokens:
messages = [
{"role": "user", "content": "What is 2+2?"},
{"role": "assistant", "content": "4"},
]
formatted = tokenizer.apply_chat_template(messages, return_tensors="pt")
labels = formatted.clone()
# Mask prompt tokens (set to -100 so cross-entropy ignores them)
prompt_len = len(tokenizer.apply_chat_template(messages[:1]))
labels[:, :prompt_len] = -100
loss = lm_model(input_ids=formatted, labels=labels).loss
# === 3. Value Head (PPO Critic) ===
# Adds a Linear(hidden_size -> 1) on top of the LM backbone
value_model = AutoModelForCausalLMWithValueHead.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto",
)
# value_model.v_head: Linear(hidden_size -> 1)
# Returns both LM logits AND per-token value estimates
inputs = tokenizer("Explain quantum computing", return_tensors="pt")
lm_logits, loss, values = value_model(
**inputs, return_dict=False
)
# values shape: (batch, seq_len, 1) -- scalar estimate per token
# === 4. Reward Head (Reward Model) ===
# Classification head: Linear(hidden_size -> 1) on last token
reward_model = AutoModelForSequenceClassification.from_pretrained(
model_name,
num_labels=1, # single scalar output
torch_dtype=torch.bfloat16,
device_map="auto",
)
# Scores entire sequence by pooling the last token's hidden state
inputs = tokenizer("Good response here", return_tensors="pt")
reward_score = reward_model(**inputs).logits # shape: (batch, 1)
Tip
重みの共有:LMヘッド=埋め込み行列の転置
現代のLLMの大半は、LMヘッドの重みを入力埋め込み行列と共有する:
lm_head.weight = model.embed_tokens.weight。つまりLMヘッドは独立に学習される層ではなく、埋め込みテーブルを再利用する。利点は、パラメータが少ない(\(\vert \mathcal{V}\vert \times d\)を節約)、一般化がよい、埋め込み空間の幾何構造がトークン確率を直接決めることである。HuggingFaceでは、model.lm_head.weight is model.model.embed_tokens.weightがほとんどのモデルでTrueを返すことで確認できる。
LLM学習の最適化理論
大規模言語モデルの学習とは、損失関数 \(\mathcal{L}(\theta)\)(通常は次トークンの負の対数尤度)を最小化するパラメータ \(\theta\)(数十億の重み)を見つけることです。これは非常に高次元の空間での最適化問題であり、この空間を探索するアルゴリズムによって、学習が成功するか、発散するか、停滞するかが決まります。
勾配降下法:基礎
勾配とは何か?
勾配 \(\nabla_\theta \mathcal{L}\) は、損失が 最も急に増加する 方向を指すベクトルです。各成分 \(\frac{\partial \mathcal{L}}{\partial \theta_i}\) は、パラメータ \(\theta_i\) を少し増やしたときに損失がどれだけ変わるかを示します。損失を 減らす には、反対方向へ進みます。
\[ \theta_{t+1} = \theta_t - \eta \nabla_\theta \mathcal{L}(\theta_t) \]
ここで \(\eta > 0\) はステップ幅である 学習率 です。これが 勾配降下法 です (Rumelhart et al. 1986)。
完全な勾配降下法が実用的でない理由
厳密な勾配を計算するには、学習データ全体(LLMでは数兆トークン)で損失を評価する必要があります。これは計算量が膨大で、勾配の1ステップだけで全データを1周しなければなりません。
確率的勾配降下法(SGD)
解決策は、データの小さなランダム部分集合( ミニバッチ )から勾配を推定することです (Robbins and Monro 1951)。
\[ \nabla_\theta \mathcal{L}(\theta) \approx \frac{1}{B}\sum_{i=1}^{B} \nabla_\theta \ell(\theta; x_i) \]
ここで \(B\) はバッチサイズです(LLMでは通常1K〜4Mトークン)。ミニバッチ勾配は、ノイズを含むが不偏な 真の勾配の推定値です。
Important
ミニバッチSGDが機能する理由
計算効率 :各ステップのコストは \(O(N_{\text{total}})\) ではなく \(O(B)\)。\(B = 4096\) トークン、総計15Tトークンなら、1ステップは \(\sim\) 40億\(\times\)安くなる。
正則化としてのノイズ :確率的ノイズが鋭い局所最小値からの脱出を助け、より汎化する平坦な領域を見つける。
GPU利用率 :ミニバッチはGPUの並列性を飽和させるのに十分大きい(行列積がメモリ律速ではなく計算律速になる)。
収束 :理論上は \(O(1/\sqrt{T})\) の速度で局所最小値へ収束する(厳密なGDの \(O(1/T)\) より遅いが、各ステップは数百万倍安い)。
SGDから適応的手法へ
モメンタム付きSGDは視覚モデル(CNN)ではうまく機能しますが、LLMの学習には 適応的オプティマイザー 、つまりパラメータごとの学習率を維持するアルゴリズムが必要です。
Vanilla SGDがLLMで失敗する理由
確率的勾配降下法は、次のように重みを更新する。
\[ \theta_{t+1} = \theta_t - \eta \nabla_\theta \mathcal{L}(\theta_t) \]
Warning
LLMにおけるSGDの問題
レイヤーごとに異なる勾配スケール :Transformerの前段レイヤーは後段より勾配がはるかに小さい(勾配消失)。単一の学習率 \(\eta\) は、あるパラメータには大きすぎ、別のパラメータには小さすぎる。
スパースな勾配 :埋め込み層は現在のバッチのトークンに対してだけ勾配を受ける。大部分の埋め込み行は勾配ゼロであり、モメンタム付きSGDはゼロ勾配の行にモメンタムを浪費する。
鞍点 :高次元の損失地形には多くの鞍点がある。SGDは停滞し得るが、適応的手法はより速く脱出する。
学習率への感度 :SGDには慎重な調整が必要で、\(\eta\) を2\(\times\)変えるだけで発散する可能性がある。
Adam — 適応的モーメント推定
Adam (Kingma and Ba 2015) は、パラメータごとに1次モーメント(勾配の平均)と2次モーメント(勾配の非中心化分散)の推定値を維持します。
Important
Adamの更新式
勾配 \(g_t = \nabla_\theta \mathcal{L}(\theta_t)\) とハイパーパラメータ \(\beta_1, \beta_2, \epsilon, \eta\) に対して、
ステップ1 — 1次モーメントのバイアス付き推定値を更新 \[ m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t \]
ステップ2 — 2次モーメントのバイアス付き推定値を更新 \[ v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2 \]
ステップ3 — バイアス補正 \[ \hat{m}_t = \frac{m_t}{1 - \beta_1^t}, \qquad \hat{v}_t = \frac{v_t}{1 - \beta_2^t} \]
ステップ4 — パラメータ更新 \[ \theta_{t+1} = \theta_t - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} \]
典型的な値 :\(\beta_1 = 0.9\)、\(\beta_2 = 0.95\) または \(0.999\)、\(\epsilon = 10^{-8}\)、\(\eta = 10^{-4}\)〜\(10^{-5}\)。
Tip
各項の働き
\(m_t\)( モメンタム ):勾配の指数移動平均。ノイズの多い勾配推定を平滑化する。\(\beta_1 = 0.9\) なら、現在の勾配が10%、履歴が90%寄与する。
\(v_t\)( 適応的学習率 ):二乗勾配のEMA。勾配が一貫して大きいパラメータは、より小さい実効学習率(\(\eta / \sqrt{v_t}\))を得る。勾配が小さいパラメータは、より大きい実効学習率を得る。これがレイヤーごとの異なる勾配スケールを扱う鍵である。
\(\hat{m}_t, \hat{v}_t\)( バイアス補正 ):\(t=1\) では \(m_1 = (1-\beta_1)g_1\) が真の平均よりかなり小さい。\((1-\beta_1^t)\) で割ることで、この初期化バイアスを補正する。これがないと初期ステップが小さすぎる。
\(\epsilon\)( 数値安定性 ):ゼロ除算を防ぐ。また実効学習率の下限としても働く。
AdamW — 分離された重み減衰
AdamW (Loshchilov and Hutter 2019) は、重み減衰と適応的オプティマイザーの相互作用に関する、微妙だが重要な問題を修正します。
Important
AdamでL2正則化 $\neq$ 重み減衰となる理由
L2正則化では損失が\(\mathcal{L} + \frac{\lambda}{2}\\vert \theta\\vert ^2\)となり、勾配は\(g_t + \lambda \theta_t\)になる。Adamでは、この正則化勾配が適応係数\(1/\sqrt{\hat{v}_t}\)でスケールされる。 \[ \theta_{t+1} = \theta_t - \eta \cdot \frac{\hat{m}_t + \lambda \theta_t}{\sqrt{\hat{v}_t} + \epsilon} \] \(v_t\)が大きい(勾配分散が大きい)パラメータほど正則化が弱くなる。これは望ましくない。重み減衰は一様であるべきだ。
Important
AdamW — 分離された重み減衰
AdamW(LoshchilovとHutter、2017)は、適応的スケーリングの外側で、重み減衰をパラメータへ 直接 適用します。 \[ \theta_{t+1} = \theta_t - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}
- \eta \lambda \theta_t \] 重み減衰項 \(\eta \lambda \theta_t\) は \(\sqrt{\hat{v}_t}\) で割られません。そのため、勾配履歴に関係なくすべてのパラメータに一様な正則化がかかります。
典型値 :LLM学習では \(\lambda = 0.1\)。
Warning
LLMでは常にAdamWを使い、通常のAdamは使わない
AdamとAdamWの違いは微妙ですが重要です。Adam+L2では、実効重み減衰が勾配分散の小さいパラメータ(バイアスやLayerNormパラメータなど)には強く、勾配分散の大きいパラメータ(アテンション重みなど)には弱くなります。AdamWなら意図した一様な正則化になります。多くのフレームワークはAdamWをデフォルトにしていますが、オプティマイザーのクラスを確認してください。
学習率 — 最も重要なハイパーパラメータ
Important
学習段階ごとの典型的な学習率
段階 典型的な学習率 注記 事前学習(ゼロから) \(1\text{e-}4\)〜\(3\text{e-}4\) 大規模モデル、大規模バッチ 継続事前学習 \(1\text{e-}5\)〜\(1\text{e-}4\) 知識を保つため小さい学習率 SFT(教師ありファインチューニング) \(1\text{e-}5\)〜\(2\text{e-}5\) 標準的な範囲 LoRAファインチューニング \(1\text{e-}4\)〜\(3\text{e-}4\) アダプター重みに対して高い学習率 RLの学習率(PPO、DPO、GRPO)については、§8.15を参照。
学習率ウォームアップ
Important
ウォームアップが必要な理由
学習開始時、\(v_t\)(2次モーメント推定値)はゼロに初期化される。バイアス補正後は\(\hat{v}_t = v_t / (1 - \beta_2^t)\)である。\(\beta_2 = 0.999\)の\(t=1\)では、\(\hat{v}_1 = v_1 / (1 - 0.999) = 1000 v_1\)となる。これは実効学習率が\(\eta / \sqrt{1000 v_1}\)、つまり意図よりはるかに小さくなることを意味する。
ただし、最初の勾配が異常に大きい場合(初期化時によくある)、2次モーメント推定値がこの外れ値に支配され、初期ステップが不規則になります。ウォームアップは非常に小さい学習率から始めて徐々に増やし、\(v_t\) が信頼できる推定値を蓄積する時間を与えることで、これを緩和します。
-
線形ウォームアップ :\(\eta_t = \eta_{\max} \times t / T_{\text{warmup}}\)
-
典型的なウォームアップ期間 :事前学習では全ステップの1〜5%、ファインチューニングでは3〜10%(短い実行ほど比例して長いウォームアップが必要)
-
SFTの場合 :50〜200ウォームアップステップが典型的
学習率スケジュール
(a) 一定
最も単純なスケジュール。学習率を減衰させすぎたくない短いファインチューニングに適しています。リスクは、アニーリングがないためモデルが最も鋭い最小値へ収束しない可能性があることです。
(b) コサイン減衰
\[ \eta_t = \eta_{\min} + \frac{1}{2}(\eta_{\max} - \eta_{\min}) \left(1 + \cos!\left(\frac{t - T_{\text{warmup}}}{T - T_{\text{warmup}}} \pi\right)\right) \]
事前学習とSFTの標準です。滑らかな減衰により、学習率の急変を避けます。\(\eta_{\min}\) は通常 \(\eta_{\max} / 10\) です。
(c) 線形減衰
コサインより単純で、実証結果は同程度です。任意のステップで予測可能な学習率が必要な場合に好まれます。
(d) WSD — Warmup-Stable-Decay
大規模事前学習の新しい標準です (S. Hu et al. 2024; Grattafiori et al. 2024)。3つの段階があります。
-
ウォームアップ :\(\eta_{\max}\) まで線形に増加(ステップの1〜5%)
-
安定 :学習の大部分で \(\eta_{\max}\) を一定に保つ
-
減衰 :\(\eta_{\min}\) まで高速なコサインまたは線形減衰(最後の10〜20%)
主な利点は、安定段階のどの時点でもチェックポイントを保存して学習を続けられることです。減衰段階は任意の実行の最後に適用できます。
(e) 再起動付きコサイン(SGDR)
周期的な再起動で学習率を \(\eta_{\max}\) に戻します。局所最小値からの脱出に役立つことがあります。LLMではあまり一般的でなく、小規模モデルにより有用です。
勾配クリッピング
Important
勾配クリッピング
勾配クリッピングは、勾配のグローバルノルムがしきい値を超えたときに勾配を再スケールします。 \[ g_t \leftarrow g_t \cdot \min!\left(1,; \frac{\tau}{|g_t|_2}\right) \] ここで\(\tau\)は
max_grad_norm(通常1.0)である。
Tip
勾配クリッピングと学習率削減の比較
勾配クリッピングと学習率の削減は、どちらもパラメータ更新の大きさを制限します。違いは、クリッピングが勾配の 方向 を保つ(大きさだけをスケールする)のに対し、学習率を下げるとすべての更新を一様にスケールすることです。クリッピングは、通常の学習ステップを遅くせずに、ときどき現れる大きな勾配へ対処するのに適しています。
まとめ:HuggingFaceのオプティマイザー設定
次のスニペットは、この節の概念——分離された重み減衰を持つAdamW(§1.6.6)、線形ウォームアップ付きコサイン学習率スケジュール(§1.6.7)、勾配クリッピング(§1.6.8)——を、HuggingFaceのtransformersライブラリで実際に組み合わせる方法を示す。
from transformers import TrainingArguments, Trainer
from transformers import get_cosine_schedule_with_warmup
import torch
# --- Option 1: Using TrainingArguments (recommended) ---
training_args = TrainingArguments(
output_dir="./checkpoints",
# AdamW optimizer (decoupled weight decay, S1.6.6)
optim="adamw_torch",
learning_rate=2e-5, # peak LR after warmup
adam_beta1=0.9, # first moment decay
adam_beta2=0.999, # second moment decay
adam_epsilon=1e-8, # numerical stability
weight_decay=0.01, # decoupled L2 penalty
# Learning rate schedule (S1.6.7)
lr_scheduler_type="cosine", # cosine decay to 0
warmup_ratio=0.1, # 10% of steps = linear warmup
# Gradient clipping (S1.6.8)
max_grad_norm=1.0, # clip by global L2 norm
# Mixed precision (S1.6.9)
bf16=True, # use BFloat16 on Ampere+ GPUs
# Training duration
num_train_epochs=3,
per_device_train_batch_size=8,
gradient_accumulation_steps=4, # effective batch = 8*4 = 32
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset,
)
trainer.train()
# --- Option 2: Manual control (for custom training loops) ---
from torch.optim import AdamW
# Separate weight-decay groups (don't regularize biases/norms)
no_decay = ["bias", "LayerNorm.weight", "layernorm.weight"]
param_groups = [
{
"params": [p for n, p in model.named_parameters()
if not any(nd in n for nd in no_decay)],
"weight_decay": 0.01,
},
{
"params": [p for n, p in model.named_parameters()
if any(nd in n for nd in no_decay)],
"weight_decay": 0.0,
},
]
optimizer = AdamW(param_groups, lr=2e-5, betas=(0.9, 0.999))
# Cosine schedule with linear warmup
total_steps = len(train_dataloader) * num_epochs
warmup_steps = int(0.1 * total_steps)
scheduler = get_cosine_schedule_with_warmup(
optimizer,
num_warmup_steps=warmup_steps,
num_training_steps=total_steps,
)
# Training loop with gradient clipping
for batch in train_dataloader:
outputs = model(**batch)
loss = outputs.loss
loss.backward()
# Clip gradients before optimizer step
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
scheduler.step()
optimizer.zero_grad()
Important
実践的なヒント
重み減衰から除外 :バイアス項とLayerNorm重みは正則化しない。パラメータが少なく、正則化すると性能が低下する (Loshchilov and Hutter 2019)。
ウォームアップ比率 :全ステップの5〜10%が標準。高い学習率でウォームアップが少なすぎると初期学習が不安定になる。
勾配蓄積 :限られたGPUメモリで大きなバッチをシミュレートする。クリッピングは 蓄積された 勾配に適用する。
BF16とFP16の比較 :Ampere以降のGPUでは
bf16=Trueを優先する(広いダイナミックレンジにより損失スケーリングを避けられる)。古いハードウェアではfp16=Trueへフォールバックする。
混合精度学習
Important
BF16とFP16の比較
形式 指数ビット 仮数ビット ダイナミックレンジ FP32 8 23 \(\sim 10^{-38}\)〜\(10^{38}\) BF16 8 7 FP32と同じ(指数が同じ) FP16 5 10 \(\sim 6 \times 10^{-5}\)〜\(65504\)
Tip
FP16よりBF16:LLM学習で精度より範囲が重要な理由
BF16はFP32と同じ指数範囲を持つため、仮数の精度は低いものの同じ値の範囲を表現できます。FP16のダイナミックレンジははるかに小さく、65504を超える勾配や活性値はオーバーフローします(NaN/Inf)。そのためFP16学習では 損失スケーリング(勾配をFP16の範囲に保つため損失を大きな定数倍すること)が必要ですが、BF16学習では通常不要です。A100とH100はBF16をネイティブサポートするため、FP16を使う特別な理由がない限りBF16を使います。
損失スケーリング(FP16のみ)
-
損失にスケール係数\(S\)を掛ける(例:\(S = 2^{15}\))。
-
FP16で勾配を計算する(\(S\)倍にスケール済み)。
-
オプティマイザーステップの前に、勾配を\(S\)で割る。
-
オーバーフロー(NaN/Inf)を確認し、見つかったらステップをスキップして\(S\)を下げる。
-
\(N\)ステップ連続でオーバーフローがなければ、\(S\)を増やす。
FP32マスター重み
混合精度学習では、重みをFP32(マスターコピー)で保存し、順伝播/逆伝播ではBF16/FP16へキャストする。オプティマイザーステップはFP32で行う。これは次の理由で重要である。
-
小さな勾配更新(\(\Delta\theta \ll \theta\))はBF16の精度では失われる(仮数7ビット、相対精度\(\approx\)0.8%)。
-
FP32マスター重みにより、多数のステップにわたる小さな更新を正確に蓄積できる。
-
メモリコスト:重みの保存量が2\(\times\)になる(FP32+BF16コピー)。
Warning
FP32マスター重みが重要な場面
FP32マスター重みは次の場面で特に重要である。
長い学習実行(多数の小さな勾配ステップが蓄積する)
小さな学習率(重みの大きさに比べて更新が極めて小さい)
大きな学習率を使う短いSFTでは、BF16だけの学習(FP32マスター重みなし)でもうまく動くことが多く、メモリを節約できる。RL学習ではFP32マスター重みが不可欠である。§8.15を参照。
実践的な混合精度:HuggingFace
# === HuggingFace TrainingArguments (simplest approach) ===
from transformers import TrainingArguments
# BF16 on Ampere+ GPUs (A100, H100, RTX 30xx/40xx)
args_bf16 = TrainingArguments(
output_dir="./out",
bf16=True, # BF16 forward/backward; FP32 master weights
bf16_full_eval=True, # also use BF16 during evaluation
# No loss scaling needed -- BF16 has FP32-equivalent range
)
# FP16 on older GPUs (V100, T4, RTX 20xx)
args_fp16 = TrainingArguments(
output_dir="./out",
fp16=True, # FP16 forward/backward
fp16_full_eval=False, # keep eval in FP32 for accuracy
# Loss scaling is automatic via PyTorch GradScaler
)
# === Manual PyTorch AMP (for custom training loops) ===
import torch
# Setup (PyTorch 2.x API)
use_fp16 = not torch.cuda.is_bf16_supported()
scaler = torch.amp.GradScaler("cuda", enabled=use_fp16) # only needed for FP16
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)
dtype = torch.float16 if use_fp16 else torch.bfloat16
for batch in train_dataloader:
optimizer.zero_grad()
# Autocast: run forward pass in reduced precision
with torch.autocast("cuda", dtype=dtype):
outputs = model(**batch)
loss = outputs.loss
if use_fp16:
# FP16 path: scale loss to prevent gradient underflow
scaler.scale(loss).backward()
scaler.unscale_(optimizer) # unscale before clipping
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
scaler.step(optimizer) # skips step on overflow
scaler.update() # adjust scale factor
else:
# BF16 path: no scaling needed
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
scheduler.step()
Tip
コードにおけるBF16とFP16の主な違い
BF16 :
autocast(dtype=torch.bfloat16)でラップするだけでよく、スケーラーは不要。コードが単純で数値的にも安定する。FP16 :勾配のアンダーフローを防ぐため
GradScalerが必要。スケーラーは乗数を動的に調整し、オーバーフロー(NaN)が検出されるとオプティマイザーステップをスキップしてスケールを下げる。勾配クリッピング+FP16 :
clip_grad_norm_の前に必ずscaler.unscale_(optimizer)を呼ぶ。そうしないとスケール済み勾配をクリップしてしまい、しきい値が誤る。メモリ削減 :活性値を16-bitで保存することで活性メモリを%削減する。重みメモリはFP32マスターコピーを保持するかどうかに依存する。
学習段階ごとの実践的なオプティマイザー設定
Important
オプティマイザーハイパーパラメータの参照表
段階 オプティマイザー LR WD ウォームアップ スケジュール 事前学習 AdamW \(3\text{e-}4\) 0.1 2000ステップ WSDまたはコサイン SFT AdamW \(2\text{e-}5\) 0.01 100ステップ コサイン LoRA SFT AdamW \(2\text{e-}4\) 0.01 100ステップ コサイン すべての設定で\(\beta_1{=}0.9\)、\(\beta_2{=}0.95\)、\(\epsilon{=}10^{-8}\)、
max_grad_norm=1.0、BF16を使う。RLの設定については§8.15を参照。
Note
学習不安定性の診断
# Monitor these metrics to diagnose optimizer issues: # 1. Gradient norm -- should be < max_grad_norm most of the time # 2. Loss scale (FP16) -- should be stable, not constantly decreasing # 3. Parameter update norm -- should be << parameter norm import torch def log_optimizer_stats(model, optimizer, step): # Gradient norm (before clipping) total_norm = 0.0 for p in model.parameters(): if p.grad is not None: total_norm += p.grad.data.norm(2).item() ** 2 total_norm = total_norm ** 0.5 # Adam second moment stats (proxy for adaptive LR) v_norms = [] for group in optimizer.param_groups: for p in group['params']: state = optimizer.state[p] if 'exp_avg_sq' in state: v_norms.append(state['exp_avg_sq'].mean().item()) print(f"Step {step}: grad_norm={total_norm:.3f}, " f"mean_v={sum(v_norms)/len(v_norms):.6f}") # Red flags: # grad_norm >> 1.0 repeatedly -> reduce LR or increase warmup # grad_norm == 0.0 -> gradient vanishing or wrong loss # loss_scale decreasing -> FP16 overflow, switch to BF16 # v very small -> Adam not warmed up yet, extend warmup
Tip
学習率は最も重要なハイパーパラメータ
実際には、学習率を正しく設定することが他のどのハイパーパラメータより重要である。LLMファインチューニングの経験則は次のとおり。
上の表の値から始める。
損失が発散する(最初は下がるが、その後増える)なら、LRが高すぎる。
損失が非常にゆっくり下がり早く頭打ちになるなら、LRが低すぎる。
損失が不安定(振動)なら、LRが高すぎるか、ウォームアップが短すぎる。
2番目に重要なハイパーパラメータはバッチサイズである(線形スケーリング則を通じて勾配ノイズと実効LRに影響する)。その他は二次的である。
Flash Attention — アルゴリズムとハードウェアへの対応
Flash Attention (Dao et al. 2022; Dao 2024) は、Transformerそのもの以来、ディープラーニングで最も影響力のあるアルゴリズム上の革新の1つです。アテンションの数学的結果は変えず、まったく同じ出力 を計算しますが、メモリアクセスパターンを再構成します。GPUの限られた高速SRAMに重い処理を担わせ、HBMフットプリントを \(O(n^2)\) から \(O(n)\) に削減し、典型的なワークロードでエンドツーエンドの実時間を2〜4\(\times\)改善します。
標準アテンションのメモリ問題
標準的なスケールド・ドット積アテンションは次のとおりである。
\[ \text{Attention}(Q, K, V) = \text{softmax}!\left(\frac{QK^T}{\sqrt{d_k}}\right) V \]
Important
標準アテンションのメモリ計算量
シーケンス長 \(n\)、ヘッド次元 \(d\) に対して、
\(Q, K, V \in \mathbb{R}^{n \times d}\):メモリ \(O(nd)\)
\(S = QK^T \in \mathbb{R}^{n \times n}\): メモリ \(O(n^2)\) — ボトルネック
\(P = \text{softmax}(S) \in \mathbb{R}^{n \times n}\):さらに \(O(n^2)\)
\(O = PV \in \mathbb{R}^{n \times d}\):\(O(nd)\)
\(n=8192\)、\(d=128\)、BF16では、アテンション行列だけでヘッドあたり \(8192^2 \times 2 \approx 134\) MB です。32ヘッドなら、1レイヤーのアテンションスコアだけで4.3 GBになります。
Tip
$O(n^2)$ が壊滅的な理由
アテンション行列はHBMへ書き込まれ(長いシーケンスではSRAMに収まらない)、softmaxのために読み戻され、さらに \(PV\) 積のために再び読み込まれます。HBMとの往復はどれも低速です。\(n=32768\)(32K文脈)では、アテンション行列はヘッドあたり \(32768^2 \times 2 \approx 2\) GB となり、保存するのは完全に非現実的です。
Flash Attentionの核心 — タイル化とオンラインsoftmax
核心となる洞察は、 完全な \(n \times n\) 行列を一度にメモリへ置く必要はない ということです。オンラインsoftmax のトリックを使えば、出力 \(O\) をブロック単位で計算できます。
オンラインsoftmax
softmaxには数値安定性のためグローバル最大値が必要です。
\[ \text{softmax}(x_i) = \frac{e^{x_i - m}}{\sum_j e^{x_j - m}}, \quad m = \max_j x_j \]
トリックは、新しいブロックを処理しながら最大値と正規化係数を 更新 し、行全体を実体化しないことです。
Important
オンラインsoftmaxの更新則
実行中の状態 \((m_{\text{old}}, \ell_{\text{old}}, O_{\text{old}})\) と新しいスコアブロック \(s_{\text{new}}\) に対して、
\(m_{\text{new}} = \max(m_{\text{old}},; \max(s_{\text{new}}))\)
[ \ell_{\text{new}} = e^{m_{\text{old}} - m_{\text{new}}} \cdot \ell_{\text{old}} + \sum_j e^{s_{\text{new},j} - m_{\text{new}}} ]
[ O_{\text{new}} = \frac{1}{\ell_{\text{new}}} \left( e^{m_{\text{old}} - m_{\text{new}}} \cdot \ell_{\text{old}} \cdot O_{\text{old}} + e^{s_{\text{new}} - m_{\text{new}}} \cdot V_{\text{new}} \right) ]
これはすべてのブロックに対して一度にsoftmaxを計算するのと数学的に等価です。
Flash Attentionアルゴリズム
Note
Flash Attentionのフォワードパス — ブロックタイル化
設定 :SRAMサイズ \(M\)。ブロックサイズは \(B_r = \lceil M / (4d) \rceil\)、\(B_c = \min(\lceil M / (4d) \rceil, d)\)。
\(Q\)を\(T_r = \lceil n / B_r \rceil\)個のブロック\(Q_1, \ldots, Q_{T_r}\)に分割する。
\(K, V\)を\(T_c = \lceil n / B_c \rceil\)個のブロック\(K_1, \ldots, K_{T_c}\)に分割する。
出力\(O \in \mathbb{R}^{n \times d}\)、実行中の最大値\(m \in \mathbb{R}^n\)、実行中の和\(\ell \in \mathbb{R}^n\)を初期化する(すべてHBM上)。
\(j = 1, \ldots, T_c\)について 外側ループ を行う。
\(K_j, V_j\)をHBMからSRAMへ読み込む。
\(i = 1, \ldots, T_r\)について 内側ループ を行う。
\(Q_i, O_i, m_i, \ell_i\)をHBMからSRAMへ読み込む。
\(S_{ij} = Q_i K_j^T / \sqrt{d}\)を計算する(SRAMに留める)。
オンラインsoftmaxの更新を適用し、新しい\(m_i, \ell_i, O_i\)を得る。
\(O_i, m_i, \ell_i\)をHBMへ書き戻す。
\(O\)を返す。
要点 :\(S_{ij}\)(アテンションタイル)はSRAMで計算して破棄します。HBMへ書き込むことはありません。
Important
Flash Attentionの計算量
標準アテンション Flash Attention メモリ(HBM) \(O(n^2)\) \(O(n)\) HBMの読み書き \(O(n^2 d)\) \(O(n^2 d / M)\) FLOPs \(O(n^2 d)\) \(O(n^2 d)\)(同じ) 高速化 1\(\times\) 2〜4\(\times\) フォワードパスの総FLOPsは \(O(n^2 d)\) のままで、標準アテンションと同じです。Flash Attentionは演算量を減らすのではなく、低速なHBMトラフィックを大幅に削ることで高速化します。(後方パスは再計算のため実際には より多くの FLOPsを行いますが、節約したメモリ帯域幅が支配的なので実時間は短くなります。)
Flash Attention 2 — 並列性の改善
Flash Attention 2 (Dao 2024) は3つの主要な改善を行いました。
-
行列積以外のFLOPs削減 :元のFAには内側ループに不要な再スケーリング演算がありました。FA2はこれを最小化するようループを再構成します。A100ではTensor Coreの行列積がスカラー演算を約16\(\times\)上回るため、内側ループで行列積以外の処理が少しあるだけでもレイテンシのボトルネックになります。
-
シーケンス次元の並列性向上 :FA1はバッチとヘッドだけを並列化しました。FA2はクエリのシーケンス次元も並列化し、小さいバッチサイズの長いシーケンスでGPU利用率を高めます。
-
因果マスキングの最適化 :自己回帰(因果)アテンションでは、およそ半分のブロックが完全にマスクされます。FA2はこれらのブロックを完全にスキップし、双方向アテンションに対して因果アテンションを \(\sim\) 2\(\times\)高速化します。
Flash Attention 3 — Hopperアーキテクチャ
Flash Attention 3 (Shah et al. 2024) はH100向けに設計され、Hopper固有の3つの機能を活用します。
-
TMA(Tensor Memory Accelerator) :H100にはHBMとSRAM間の非同期バルクデータ移動専用のハードウェアユニットがあります。FA3はTMAを使い、データ読み込みと計算を重ねてメモリレイテンシを隠します。
-
Warp-specialization :FA3は異なるワープに異なる役割を割り当てます(プロデューサーワープはTMAでデータを読み、コンシューマーワープはMMAを計算)。メモリシステムとTensor Coreを同時に稼働させるソフトウェアパイプライン技法です。
-
FP8サポート :H100はBF16の2\(\times\)のスループットでFP8(E4M3/E5M2)Tensor Core演算をサポートします。FA3は精度維持のためブロックごとの量子化を行うFP8アテンションをサポートします。
FA3はFP16アテンションで H100の理論ピークの最大75% を達成します。FA2は \(\sim\) 35%です。
Flash Attention 4 — Blackwellアーキテクチャ
Flash Attention 4(Zadouri et al. 2026)はNVIDIAのBlackwell GPU(B200/GB200)を対象とする。Tensor Coreのスループットを2.25 PFLOP/s(BF16)へ倍増する一方、行列積以外のユニット(指数演算、共有メモリ帯域)はより遅い割合でしか拡大しない。この非対称なハードウェアスケーリングによりボトルネックが移る。Blackwellでは、アテンションは行列積ではなく、softmaxの指数演算とその周辺の共有メモリトラフィックによって制限される。
FA4は4つの主要な技法でこれに対処する。
-
完全非同期のMMAパイプライン :BlackwellのMMA命令は完全に非同期である(完了時にまだブロックされるHopperのwgmmaとは異なる)。FA4はより大きなタイルサイズにわたってMMA、TMAロード、softmax再スケーリングを重ね合わせるようパイプラインを再設計し、すべてのハードウェアユニットを飽和させる。
-
ソフトウェアによる指数演算エミュレーション :スループットのボトルネックであるハードウェアの
ex2ユニットを呼ぶ代わりに、FA4ははるかに高速なTensor Core上で多項式近似を実行し、\(e^x\)をエミュレートする。指数演算ユニットの停止と、追加の行列積命令を交換する。 -
条件付きsoftmax再スケーリング :標準のFlashAttentionはタイルごとに実行中の\(\max\)を再スケールする。FA4は新しいタイルの最大値が実行中の最大値を超えない場合(実際にはよくある)に再スケーリングを省き、レジスタシャッフルと同期バリアの両方を節約する。
-
Tensor Memory+2-CTA MMAモード(逆伝播) :逆伝播ではBlackwellのTensor Memory(共有メモリより大きいSMごとのスクラッチパッド)と、2つのスレッドブロッククラスタにまたがる\(dQ\)の蓄積を融合する2-CTA協調モードを使い、共有メモリへの往復を半減する。
Important
FA4の実装:CuTe-DSL
FA4は、GPUカーネル向けのPython組み込みドメイン固有言語(CUTLASS 4.xの一部)である CuTe-DSL で書かれた最初のFlashAttentionバージョンである。CuTe-DSLはレジスタ割り当てとパイプラインスケジューリングを完全に制御しながら、C++のCUTLASSテンプレートより20〜30\(\times\)高速にコンパイルできる。これによりカーネル開発の反復時間が大幅に短くなる。
結果
BF16、ヘッド次元128のB200(因果、系列長8K)では、
-
1613 TFLOP/s ——Blackwellのピーク利用率の71%
-
1.3\(\times\) ——cuDNN 9.13(NVIDIA独自の融合カーネル)より高速
-
2.7\(\times\) ——同じハードウェア上のTritonより高速
Tip
ハードウェアとソフトウェアの共進化
FlashAttentionシリーズは、GPU世代ごとにボトルネックが移り、単なる再コンパイルではなく新しいアルゴリズムの発想が必要になるという原則を示します。A80 \(\to\) メモリ帯域幅律速(FA1/FA2:タイル化+再計算)。H100 \(\to\) データ移動律速(FA3:TMA+warp-specialization)。B200 \(\to\) 行列積以外の計算律速(FA4:ソフトウェアによるexpエミュレーション+条件付き再スケーリング)。ハードウェアのボトルネックがどこにあるか を理解することが、効率的なカーネルを書く前提です。
事前学習:ベストプラクティス
事前学習はLLM開発で最も高コストな段階です。数百万GPU時間を消費し、データ、計算、ハイパーパラメータの慎重な調整が必要です。この節では、Llama-3 (Grattafiori et al. 2024)、Chinchilla (Hoffmann et al. 2022)、GPT-4 (OpenAI 2023)から得られる主な教訓をまとめます。
学習目的
現代のDecoder-Only LLMはすべて 因果言語モデリング (CLM)を使います。
\[ \mathcal{L}_\text{CLM} = -\frac{1}{T}\sum_{t=1}^T \log P_\theta(x_t \mid x_{<t}) \]
この単純な目的は、十分なデータと規模があれば、明示的な教師なしに創発的能力(文脈内学習、推論、指示追従)を生みます (Brown et al. 2020)。
データパイプライン
Important
事前学習データのレシピ
規模 :最先端モデルでは1〜15兆トークン(Llama-3:15Tトークン)
ソース :ウェブクロール(80%)、コード(10%)、書籍/論文(5%)、選別データ(5%)
重複除去 :MinHash+完全な部分文字列重複除去により、記憶を減らす (Lee et al. 2022)
品質フィルタリング :パープレキシティベースの分類器、ヒューリスティックフィルタ(長さ、言語ID、有害性)
データ混合 :ドメイン間で温度重み付きサンプリングを行い、推論のためコードと数学を重くする
スケーリング則
Hoffmannら (Hoffmann et al. 2022) は、計算量最適な学習にはモデルサイズ \(N\) とデータサイズ \(D\) のバランスが必要だと示しました:\(N_\text{opt} \propto C^{0.50}\)、\(D_\text{opt} \propto C^{0.50}\)。70Bモデルは \(\sim\) 1.4Tトークンで計算量最適になります。実際には、推論コストが学習トークン数ではなくモデルサイズに比例するため、モデルは 過学習 されます(Chinchilla最適より多くのトークン)。小さく過学習させたモデルの方がデプロイ費用が安くなります。
主要ハイパーパラメータ
| Setting | Llama-3 405B | Llama-3 8B | Qwen-2.5 72B | Mistral 7B |
|---|---|---|---|---|
| Tokens | 15T | 15T | 18T | 8T |
| Batch size (tokens) | 16M | 4M | 4M | 4M |
| Peak LR | \(8\text{e-}5\) | \(3\text{e-}4\) | \(3\text{e-}4\) | \(3\text{e-}4\) |
| Schedule | WSD | WSD | Cosine | Cosine |
| Weight decay | 0.1 | 0.1 | 0.1 | 0.1 |
| Context length | 8192 | 8192 | 4096\(\to\)32K | 8192 |
公開モデルの事前学習ハイパーパラメータ。
よくある失敗モード
Warning
事前学習の落とし穴
損失スパイク :悪いデータバッチや数値不安定性による突然の損失増加。Llama-3ではチェックポイントへロールバックし、問題のバッチをスキップする。
記憶 :モデルが学習データをそのまま再現する。対策は積極的な重複除去と抽出攻撃の監視。
文脈長 :短いシーケンスで学習して長い文脈へデプロイすると失敗する。長文書での継続事前学習とRoPEスケーリングを使う。
教師ありファインチューニング(SFT)
SFTは選別したプロンプト・応答ペアで学習し、事前学習済み言語モデルを指示追従アシスタントへ変換します。これは生の言語モデリングとRLHFの橋渡しです。
SFTの目的
損失はCLMと同じですが、 応答トークン に対してだけ計算します。
\[ \mathcal{L}_\text{SFT} = -\frac{1}{|y|}\sum_{t=1}^{|y|} \log P_\theta(y_t \mid x_\text{prompt}, y_{<t}) \]
プロンプトトークンは文脈を提供しますが、勾配は受けません(ラベルを \(-100\) に設定)。
データ品質:LIMAの原則
Zhouら (C. Zhou et al. 2023) は、注意深く選別した1,000例で、ノイズの多い50K以上の例で学習したモデルに匹敵できることを示しました。主な要件は次のとおりです。
-
多様性 :QA、要約、コード、数学、創作、多ターン対話を網羅する
-
正確性 :すべての応答が事実として正確で、書式も整っている
-
長さのバランス :短い応答(1文)と長い応答(複数段落)を混ぜる
-
デコンタミネーション :評価ベンチマークとの重複を除去する
学習設定
from trl import SFTTrainer, SFTConfig
sft_config = SFTConfig(
output_dir="./sft_output",
max_seq_length=4096,
packing=True, # Pack short examples into full sequences
learning_rate=2e-5,
lr_scheduler_type="cosine",
warmup_ratio=0.1,
weight_decay=0.01,
max_grad_norm=1.0,
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=8,
bf16=True,
gradient_checkpointing=True,
)
trainer = SFTTrainer(model=model, args=sft_config,
train_dataset=dataset, processing_class=tokenizer)
trainer.train()
効率的な学習ソリューション
標準的なHuggingFace学習では、かなりの性能が取りこぼされます。いくつかのライブラリはSFTワークロードに、そのまま導入できる効率向上を提供します。
Liger Kernel (Hsu et al. 2024)
LinkedInによるオープンソースの Triton融合カーネル で、学習時に標準PyTorch演算子を置き換えます。主な融合は次のとおりです。
-
融合クロスエントロピー :最終線形射影、softmax、損失計算を単一カーネルに統合し、完全な \((\text{batch} \times \text{seq} \times \text{vocab})\) ロジットテンソルの実体化を避ける。
-
融合RMSNorm/SwiGLU/RoPE :一般的なLLM構成要素の中間メモリ割り当てをなくす。
-
チャンク演算 :大きなテンソルをタイルで処理し、ピークメモリを一定に保つ。
結果 :1行の統合(apply_liger_kernel_to_llama())でスループットが20%向上し、メモリを最大60%削減する。FSDP、DeepSpeed、LoRAと互換性がある。
Unsloth (Han and Han 2024)
カスタムCUDA/Tritonカーネル と積極的なメモリ最適化を組み合わせた、専門的なファインチューニングライブラリです。
-
LoRAレイヤーを通る手動バックプロパゲーション(autogradのオーバーヘッドを避ける)。
-
融合デ量子化付き4-bit QLoRA。単一の48 GB GPUで70Bモデルを学習できる。
-
アーキテクチャ(Llama、Mistral、Qwen、Gemma)ごとに最適化したRoPEとアテンションカーネル融合。
結果 :素のHuggingFace+PEFTより2〜5\(\times\)高速で、VRAMを60〜70%削減。単一GPUやコンシューマーハードウェアのワークフローで特に効果的です。
torchtune (P. Team 2024)
MetaのネイティブPyTorchファインチューニングライブラリ(開発は2025年に縮小)。モノリシックな抽象化ではなく 合成可能性 を中心に設計されています。
-
純粋なPyTorch。Trainerクラスはなく、レシピは読みやすい単一ファイルのスクリプト。
-
torch.compile、FSDP2、アクティベーションチェックポイントとのネイティブ統合。 -
QLoRA、完全なファインチューニング、知識蒸留を第一級にサポート。
-
学習後圧縮のための量子化認識学習(QAT)を組み込みで提供。
結果 :カスタムソリューションと同等の速度を、完全なデバッグ可能性とフレームワークロックインなしで実現します。
Important
効率スタックの選択
\(\leq\)1 GPUでの迅速なLoRA/QLoRA :Unsloth(学習時間が最短で、セットアップが最小)。
マルチGPUの完全なファインチューニング :TRL/DeepSpeed+Liger Kernel(大規模で最高のスループット)
研究/カスタム学習ループ :torchtune(透明で改造しやすいネイティブPyTorch)
これらは 相補的 です。LigerカーネルはTRLとtorchtuneの両方のワークフロー内で使えます。
ベストプラクティス
| 実践項目 | 詳細 |
|---|---|
| パッキング | 複数の短い例を1つのシーケンスへ連結する(EOSで区切る)。パディングの無駄を避ける。 |
| NEFTune (Jain et al. 2024) | 埋め込みに一様ノイズを加える(\(\alpha=5\))。コストなしにMT-Benchを5〜15%改善する。 |
| チャットテンプレート | 常にモデル固有のテンプレートを使う。不一致は品質を低下させる。 |
| エポック数 | 大規模データセットでは2〜3、小規模(\(<\)10K)の選別データでは最大5。過学習は書式の記憶を引き起こす。 |
SFT学習のガイドライン。
Tip
SFTだけでは不十分
SFTは書式と基本的な指示追従を教えますが、選好(どの応答がよいか — RLHF/DPOが必要)、拒否(いつ答えないか — 安全性学習が必要)、キャリブレーション(「分からない」と言うこと — 真実性報酬付きRLが必要)、複雑な推論(多段階の連鎖 — 検証可能な報酬付きRLが必要)を確実には教えられません。完全なパイプラインは、事前学習 \(\to\) SFT \(\to\) RLHF/DPOです。
LoRAとパラメータ効率のよいファインチューニング
70Bモデルの完全なファインチューニングには、70B個の学習可能パラメータとオプティマイザー状態を保存する必要があります(メモリ560 GB以上)。LoRA (Hu et al. 2022b)(Low-Rank Adaptation)は、パラメータの \(<\)1% だけで同等の品質を得るファインチューニング方法です。
LoRAの洞察
Important
LoRAの核心
完全な重み行列 \(W \in \mathbb{R}^{d \times d}\) を更新する代わりに、低ランクの摂動を学習します。 \[ W' = W + \frac{\alpha}{r} \cdot BA, \quad B \in \mathbb{R}^{d \times r}, ; A \in \mathbb{R}^{r \times d} \]
\(W\) は 凍結 される(勾配もオプティマイザー状態もない)
\(B\) と \(A\) だけを学習する:\(d^2\) ではなく \(2 \times d \times r\) パラメータ
ランク \(r=16\)、\(d=4096\) では、LoRAはレイヤーあたり \(2 \times 4096 \times 16 = 131K\) パラメータを追加する。完全行列では \(16.8M\)。
\(\alpha/r\) スケーリングが更新の大きさを制御する
Tip
低ランクが機能する理由
Aghajanyanら (Aghajanyan et al. 2021) は、ファインチューニングが非常に低次元の部分空間で行われることを示しました。ファインチューニングタスクの「内在次元」はモデルのパラメータ数よりはるかに小さいのです。175Bモデルのファインチューニングタスクの内在次元は \(<\)10,000かもしれません。LoRAはこれを直接利用し、ランク \(r\) によって重み行列ごとの更新を \(r\) 次元部分空間に制限します。
Tip
$\alpha/r$ スケーリングが重要な理由
スケーリングがなければ、ランク \(r\) を2倍にすると \(\Delta W = BA\) の大きさもおよそ2倍になります(\(B\) のより多くの列が和に寄与するため)。つまりランクを変えるとモデルへの摂動量も変わり、\(r\) を調整するたびに学習率を再調整する必要があります。
\(\alpha/r\) 係数は、ランクによらず更新の大きさがほぼ一定になるよう 正規化 します。 \[ W' = W + \frac{\alpha}{r} \cdot BA \]
\(\alpha\) を固定して \(r\) を変える :実効更新量はランクに関係なく \(\sim\alpha\) に保たれる。学習率を再調整せず \(r \in \{8, 16, 32, 64\}\) を試せる。
一般的な設定 :\(\alpha = r\)(\(\alpha/r = 1\))または \(\alpha = 2r\)(\(\alpha/r = 2\))にする。スケーリング係数が小さな整数になる便利なデフォルトです。
学習率を調整するだけではだめか? それも可能ですが、\(\alpha/r\) は ランクに依存しない 調整つまみになります。異なるランクの実験で学習率のレシピを共有できます。
rsLoRAの洞察 (Kalajdzievski 2023):高ランク(\(r \geq 64\))では、\(BA\) の分散が \(r\) ではなく \(\sqrt{r}\) に比例するため、\(\alpha/\sqrt{r}\) の方が \(\alpha/r\) より安定だという実証結果があります。
LoRAのハイパーパラメータ
LoRAのハイパーパラメータを正しく選ぶことは重要です。ランクやalphaを誤ると、アンダーフィット(制約が強すぎる)か、メモリ浪費(表現力が過剰)になります。
| ハイパーパラメータ | 典型値 | 指針 |
|---|---|---|
r(ランク) | 8、16、32、64 | 高いほど容量が増えるがメモリも増える。16から始める。 |
lora_alpha | 16、32(多くは\(= r\)または\(2r\)) | \(\alpha/r\)スケーリングで更新量を制御する。 |
target_modules | q_proj, k_proj, v_proj, o_proj | すべてのアテンション射影。完全にカバーするにはgate_proj, up_proj, down_projを追加する。 |
lora_dropout | 0.0〜0.1 | 正則化。小規模データセットでは通常0.05。 |
bias | "none" | バイアスを学習してもパラメータはわずかしか増えず、役に立つことは少ない。 |
| 学習率 | \(1\text{e-}4\)〜\(3\text{e-}4\) | 完全なファインチューニングより高い(アダプターだけが更新されるため)。 |
LoRAのハイパーパラメータガイド。
Warning
ランク選択の経験則
r=8 :単純なタスク(単一ドメインチャット、分類)。非常にメモリ効率がよい。
r=16 :汎用ファインチューニング。よいデフォルト。
r=32〜64 :複雑なタスク(数学、コード、多ターン推論)。完全なファインチューニング品質に近づく。
r=128以上 :収穫逓減。完全なファインチューニングや高ランクQLoRAを検討する。
重要な指標 :学習損失が完全なファインチューニングの損失よりかなり高いところで頭打ちなら、ランクを上げる。
LoRAの派生手法
| 手法 | 主な革新 | 使う場面 |
|---|---|---|
| QLoRA (Dettmers et al. 2023) | 4-bit量子化ベース+BF16のLoRA。NF4型+二重量子化。 | 48GB GPU 1台で70Bをファインチューニング。 |
| DoRA (S.-Y. Liu et al. 2024) | \(W\)を大きさと方向に分解し、LoRAは方向だけを更新する。 | 推論の一般化が向上。 |
| LoRA+ (Hayou et al. 2024) | \(A\)と\(B\)で異なるLR(\(\eta_B = \lambda \eta_A\)、\(\lambda \approx 16\))。 | 追加コストなしで2%向上。 |
| AdaLoRA (Q. Zhang et al. 2023) | 層間でランク予算を動的に配分(SVDベースの重要度)。 | 計算予算が非常に厳しい場合。 |
| rsLoRA (Kalajdzievski 2023) | \(\alpha/r\)ではなく\(\alpha/\sqrt{r}\)でスケールする。高ランクで安定。 | \(r \geq 64\)を使う場合。 |
| VeRA (Kopiczko et al. 2024) | 凍結したランダム\(A, B\)を共有し、対角スケーリングだけを学習する。 | 極端なパラメータ効率が必要な場合。 |
| LoRA-FA | 初期化後に\(A\)を凍結し、\(B\)だけを学習する。LoRAのメモリを半減。 | メモリ制約がある場面。 |
LoRAの派生手法とその革新。
主な拡張の説明
DoRA — 重み分解低ランク適応
DoRA (S.-Y. Liu et al. 2024) は、完全なファインチューニングでは重みベクトルの大きさより 方向 が変わりやすいことに着目します。標準LoRAでは両者が混ざります。DoRAは各重み列を大きさ \(m = \\vert W\\vert _\text{col}\) と方向 \(\hat{V} = W / \\vert W\\vert _\text{col}\) に分解し、LoRAを方向だけに適用します。
\[ W' = m \odot \hat{V}', \quad \hat{V}' = \frac{W + BA}{|W + BA|_\text{col}} \]
大きさ \(m\) は別の学習可能なベクトル(列ごとに1スカラー)です。追加の推論コストなし(デプロイ時にマージ)で、推論・指示追従ベンチマークにおいてLoRAを一貫して1〜3%上回ります。
LoRA+ — 非対称学習率
Hayouら (Hayou et al. 2024) は、LoRAの行列 \(A\) と \(B\) には異なる最適学習率があることを示しました。\(B\) はゼロで初期化されるため、\(\mathcal{N}(0, \sigma^2)\) で初期化される \(A\) とは異なる状態から始まります。\(\eta_B \approx 16 \times \eta_A\) にすると、収束速度と最終品質が \(\sim\) 2%向上します。設定を1行変えるだけの追加コストなしの改善です。
# LoRA+ in PEFT: set different LRs per matrix
optimizer_grouped_parameters = [
{"params": [p for n, p in model.named_parameters() if "lora_B" in n],
"lr": 2e-4 * 16}, # B matrix: higher LR
{"params": [p for n, p in model.named_parameters() if "lora_A" in n],
"lr": 2e-4}, # A matrix: base LR
]
VeRA — ベクトルベースのランダム行列適応
VeRA (Kopiczko et al. 2024) はパラメータ効率を極限まで高めます。\(A\) と \(B\) を学習する代わりに、全レイヤーで共有するランダム行列として 凍結 し、2つの対角スケーリングベクトル \(d_b \in \mathbb{R}^r\)、\(d_a \in \mathbb{R}^d\) だけを学習します。
\[ \Delta W = B \cdot \text{diag}(d_b) \cdot A \cdot \text{diag}(d_a) \]
これはLoRAと比べて学習可能パラメータを \(\sim\) 10\(\times\)削減し(レイヤーあたり \(r + d\) パラメータのみ)、LoRA品質の90〜95%を達成します。最小限のストレージで数百のタスク固有アダプターが必要な場面に最適です。
Note
QLoRAによるメモリ削減
70Bモデルの完全なファインチューニング :140 GB(重み)+280 GB(オプティマイザー)+140 GB(勾配)=560 GB(A100-80GBが7\(\times\))。
70B QLoRA(r=16、すべての線形層) :
NF4のベースモデル:\(70\text{B} \times 0.5 = 35\) GB
BF16のLoRAアダプター:\(\sim\)160 MB
オプティマイザー状態(アダプターのみ):\(\sim\)320 MB
活性値(勾配チェックポイント):\(\sim\)8 GB
合計:\(\sim\)44 GB ——48GB GPU 1台に収まる。
# QLoRA configuration with PEFT
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from transformers import BitsAndBytesConfig
import torch
# 4-bit quantization config
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NormalFloat4 - optimal for weights
bnb_4bit_compute_dtype=torch.bfloat16, # Compute in BF16
bnb_4bit_use_double_quant=True, # Quantize the quantization constants
)
# LoRA config
lora_config = LoraConfig(
r=16,
lora_alpha=32, # alpha/r = 2x scaling
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = prepare_model_for_kbit_training(model) # Prepare for QLoRA
model = get_peft_model(model, lora_config) # Add LoRA adapters
model.print_trainable_parameters()
# Output: trainable params: 83,886,080 || all params: 70,553,706,496 || 0.12%
その他のPEFT手法
LoRAは現在の実務で主流だが、パラメータ効率のよい手法はこれだけではない。ここでは主な代替手法を挙げる。
| 手法 | 仕組み | 長所 / 短所 | 状況 |
|---|---|---|---|
| LoRA (Hu et al. 2022b)(および派生手法) | 既存の重みに低ランク行列を追加 | 推論時にマージ可能(オーバーヘッドなし);広くサポート;すべてのアーキテクチャで動作 | 標準 |
| Adapters (Houlsby et al. 2019) | 層の間に小さなボトルネックMLPを挿入 | モジュール化・積層可能;推論レイテンシが増加(逐次層が追加される) | ほぼ使われない |
| Prefix Tuning (Li and Liang 2021) | 各層のキー・バリューの前に学習可能な「仮想トークン」を付加 | 重みを変更しない;生成タスクに有効;コンテキスト長を消費 | ニッチ |
| Prompt Tuning (Lester et al. 2021) | 入力の前に学習可能なソフトプロンプト埋め込みを付加 | パラメータが極めて少ない(\(<\)0.01%);複雑なタスクではLoRAより弱い | ニッチ |
| IA3 (H. Liu et al. 2022) | キー、バリュー、FFN活性を再スケーリングするベクトルを学習 | LoRAよりさらにパラメータが少ない;マージ可能;容量が限られる | 廃止傾向 |
| BitFit (Zaken et al. 2022) | バイアス項だけを学習 | ほぼゼロのパラメータ;単純なタスクには意外に有効;表現力が限られる | 歴史的手法 |
PEFT手法の系統。LoRAはLLMファインチューニングの事実上の標準であり、その他は歴史的背景とニッチな用途のために掲載している。
Tip
LoRAが主流になった理由
LoRAが標準になったのは、(1) 推論オーバーヘッドがゼロ ——アダプターをベース重みにマージでき、レイテンシを増やしたりコンテキストを消費したりするAdaptersやPrefix Tuningとは異なる、(2) 合成可能性 ——マルチテナント環境で、サービング時に複数のLoRAアダプターを交換できる、(3) エコシステムのサポート ——HuggingFace PEFT、TRL、vLLM、主要なフレームワークがすべてLoRAを第一級にサポートしている、(4) 大規模運用で実証済み ——Meta、Google、およびHuggingFace上のオープンソースのファインチューニングの大半で本番利用されている、という特徴を独自に兼ね備えるからである。LoRAでは満たせない具体的な制約がない限り、デフォルトの選択肢にすべきだ。
Mixture of Experts(MoE)
Mixture of Expertsモデル(Shazeer et al. 2017、Jiang et al. 2024)は、トークンごとにパラメータの一部だけを活性化することで、計算コストを比例して増やさずにモデル容量を拡大する。
アーキテクチャ
Important
MoE層
MoEトランスフォーマーでは、各ブロックのFFN層を、並列な\(N\)個の「エキスパート」FFNと、どのエキスパートを使うか選ぶ ルーター に置き換える。 \[ \text{MoE}(x) = \sum_{i=1}^{N} g_i(x) \cdot E_i(x), \quad g(x) = \text{TopK}(\text{softmax}(W_r x)) \]
\(E_i\) はエキスパートネットワーク(標準的なFFN層)
\(g_i(x)\) はルーターから得られるゲーティング重み(上位\(K\)個だけが非ゼロ)
通常、\(N=8\)〜64個のエキスパートのうち\(K=2\)個がトークンごとに活性化される
総パラメータ数は\(N\)に比例し、 活性パラメータ数 はFFNサイズの\(K/N\)に比例する
負荷分散
Warning
負荷分散の問題
制約がなければ、ルーターは大半のトークンを同じ1〜2個のエキスパートに送る可能性がある(「エキスパート崩壊」)。これは容量を無駄にし、GPU間の計算負荷の不均衡を生む(通常、各エキスパートは別々のGPUに配置される)。
解決策 :負荷分散の補助損失を追加する。 \[ \mathcal{L}_{\text{bal}} = \alpha \cdot N \sum_{i=1}^{N} f_i \cdot p_i \] ここで\(f_i\)はエキスパート\(i\)にルーティングされたトークンの割合、\(p_i\)はエキスパート\(i\)に対するルーター確率の平均である。これにより、エキスパートを均等に利用するよう促す。
Noisy Top-Kゲーティング:離散ルーティングを学習可能にする
MoEの中心的な課題は、 top-\(k\)選択が微分不可能 なことである——「上位2個を選ぶ」というハードな操作を通じて逆伝播できない。この問題を解決するため、研究分野では2つの主要な工夫が開発されてきた。
Tip
ルーティングの微分可能性の問題
ルーターは各エキスパートについてロジット\(h(x) = W_r \cdot x\)を計算し、top-\(k\)を選択する。しかし、
選択されたエキスパートは、ゲート重み(選択されたものに対するsoftmax)を通じて勾配を受け取る
選択の判断そのもの(どの\(k\)個を選ぶか)には勾配がない
工夫がなければ、ルーターは行き詰まる可能性がある。あるエキスパートが選ばれない \(\rightarrow\) 勾配信号を受け取れない \(\rightarrow\) その後も選ばれない、という循環に陥るためである
アプローチ1:Noisy Top-Kゲーティング (Shazeer et al. 2017)
top-\(k\)選択の前に、ルーターのロジットへ学習可能なガウスノイズを加える。
\[ \begin{aligned} h(x) &= W_g \cdot x && \text{(clean logits)} \\ H(x) &= h(x) + \epsilon \cdot \text{Softplus}(W_{\text{noise}} \cdot x), \quad \epsilon \sim \mathcal{N}(0, 1) && \text{(noisy logits)} \\ \text{TopK}(v, k)_i &= \begin{cases} v_i & \text{if } v_i \text{ is in the top } k \\ -\infty & \text{otherwise} \end{cases} \\ g(x) &= \text{softmax}\big(\text{TopK}(H(x),, k)\big) && \text{(sparse gates)} \end{aligned} \]
-
\(W_{\text{noise}}\) は学習されるノイズの大きさであり、モデルは各エキスパートにどの程度の探索が必要かを学習する
-
学習中はノイズによって「劣勢」のエキスパートがときどきtop-\(k\)に入り、勾配信号を受け取れる
-
推論時にはノイズを除き、決定的なルーティングのためにクリーンなロジット\(h(x)\)を使う
-
Softplusにより、ノイズのスケールは常に正になる
アプローチ2:Gumbel-Softmaxトリック(微分可能な離散サンプリング)
変分推論の文献に由来する代替手法である(Jang et al. 2017)。 Gumbel-Maxトリック はカテゴリ分布からの正確なサンプリングを提供する。
\[ z = \arg\max_i \left[ \log \pi_i + G_i \right], \quad G_i \sim \text{Gumbel}(0,1) \]
ここで、Gumbelノイズは\(G_i = -\log(-\log(U_i)),; U_i \sim \text{Uniform}(0,1)\)として生成される。
top-\(k\)ルーティング では、\((\log \pi_i + G_i)\)のtop-\(k\)を取ることで、\(\pi\)が定めるカテゴリ分布からの\(k\)個の非復元サンプルが得られる。
\(\arg\max\)は微分不可能なので、 Gumbel-Softmax 緩和ではこれを温度制御されたsoftmaxに置き換える。
\[ \hat{g}_i = \frac{\exp\left((\log \pi_i + G_i) / \tau\right)}{\sum_j \exp\left((\log \pi_j + G_j) / \tau\right)} \]
-
\(\tau \to 0\):ハードなone-hotに近づく(正確だが微分不可能)
-
\(\tau \to \infty\):一様分布に近づく(微分可能だが情報を持たない)
-
実際には、学習中に\(\tau\)を1.0から0.1〜0.5までアニーリングする
-
Straight-through推定量 :順伝播ではハードなtop-\(k\)を使い、逆伝播ではGumbel-Softmaxの勾配を使う——両者の長所を組み合わせられる
Important
実務ではどの手法を使うか
Sparsely-Gated MoE (Shazeer et al. 2017)、Mixtral (Jiang et al. 2024)、DeepSeek-V2 (DeepSeek-AI 2024a) :ガウスノイズを使うNoisy Top-Kを採用する。単純で効果的であり、大規模モデルで十分に実証されている。
Switch Transformer (Fedus et al. 2022) :ノイズなしのTop-1に簡略化している(負荷分散損失だけに依存する)。
研究用/小規模MoE :完全に微分可能なルーティングのためGumbel-Softmaxを使う例もある。特に、ルーティング自体の学習が研究目的である場合に用いられる。
重要な洞察 :どちらの手法も、ノイズ注入によって同じ問題(離散選択を学習可能にすること)を解決する。ガウスノイズはより単純であり、Gumbelノイズはカテゴリサンプリングに対する理論的保証がより強い。
代表的なMoEモデル
| モデル | 総パラメータ数 | 活性パラメータ数 | エキスパート | 新規性 |
|---|---|---|---|---|
| Switch Transformer (Fedus et al. 2022) | 1.6T | 100B | 128, Top-1 | 初の大規模MoE;ルーティングを簡略化 |
| Mixtral 8x7B (Jiang et al. 2024) | 47B | 13B | 8, Top-2 | オープンウェイト;Llama-2 70Bと同等の品質 |
| DeepSeek-V2 (DeepSeek-AI 2024a) | 236B | 21B | 160, Top-6 | 共有エキスパートとルーティングエキスパートを持つDeepSeekMoE |
| Qwen-MoE (Q. Team 2024a) | 14.3B | 2.7B | 60, Top-4 | 効率性のための細粒度エキスパート |
| DBRX (Databricks 2024) | 132B | 36B | 16, Top-4 | ブロックごとに4個の細粒度エキスパート |
LLM学習における多様性
学習データ、モデル出力、最適化の軌跡における多様性は、モード崩壊を防ぎ、頑健で汎用的なLLMを実現するうえで重要である。この節では、LLM学習のすべての段階に適用できる主要な多様性の仕組みを扱う。
サンプリングの多様性
Important
多様な生成のためのサンプリング戦略
温度 \(\tau\) :\(P(x_i) \propto \exp(\text{logit}_i / \tau)\)。\(\tau\)が高いほど分布は一様になり、多様性が増す。RLHFでの生成では通常\(\tau=0.7\)〜\(1.0\)。
Top-\(k\) :確率が最も高い\(k\)個のトークンだけからサンプリングする。確率の低い退化したトークンを防ぐ。
Top-\(p\)(nucleus) :累積確率が\(\geq p\)となる最小のトークン集合からサンプリングする。適応的なので、モデルが不確かなときほど多様になる。
Min-\(p\) :\(P \geq p_{\min} \times P_{\max}\)を満たすトークンだけを残す。top-\(k\)より原理に沿った方法である。
頻度/存在ペナルティ :応答中に現れたトークンにペナルティを与える。語彙の多様性を促す。
学習データの多様性
-
プロンプトの多様性 :異なる領域、難易度、形式をカバーする。ゴルディロックス原理に従い、プロンプトの成功率は20〜80%にする。
-
重複除去 :ほぼ重複する学習例(MinHash、n-gramの重なり)を削除する。重複は特定のパターンへの過学習を招く。
-
データ混合 :温度重み付きサンプリングやカリキュラム戦略を使い、タスクや領域間のバランスを取る。
多様性を促す手法
| 手法 | 多様性を促す仕組み |
|---|---|
| 温度スケーリング | \(\tau\)を高くすると分布が平坦になり、もっと多くのトークンが妥当になる。 |
| Top-\(p\) / Min-\(p\) | 適応的なしきい値により、モデルが不確かなときにより広くサンプリングできる。 |
| 頻度ペナルティ | 繰り返しトークンにペナルティを与え、応答内の語彙の多様性を強制する。 |
| データ重複除去 | 学習データからほぼ重複する例を除き、特定パターンへの過学習を防ぐ。 |
| 複数領域の混合 | 領域間で温度重み付きサンプリングを行い、広いカバレッジを確保する。 |
| 言語化サンプリング | 応答についての確率分布を明示的に言語化するようモデルに促す(J. Zhang et al. 2025)。§4.5を参照。 |
テキスト生成:デコーディング手法
学習済み言語モデルは各ステップで語彙上の確率分布\(P(x_t \vert x_{<t})\)を出力する。 デコーディング戦略 は、この分布から次のトークンをどう選ぶかを決める。この選択は出力の品質、多様性、一貫性に大きな影響を与える。
貪欲デコーディング
最も単純な戦略であり、常に確率が最も高いトークンを選ぶ。
\[ x_t = \arg\max_{v \in \mathcal{V}} P(v | x_{<t}) \]
直感 :文中で常に最も明白な次の単語を選ぶようなもの。「The capital of France is...」\(\to\)「Paris」(確率0.92)。
長所 :決定的で高速、ハイパーパラメータが不要。
短所 :反復的で一般的なテキストを生成する。序盤の確率が低いトークンが全体としてよりよい出力につながる高品質な系列を逃す。多様性がない。
ビームサーチ
\(B\)個(ビーム幅)の部分仮説を並列に保持し、それぞれをtop-\(k\)トークンで展開しながら、スコアの高い\(B\)個の完成系列を残す。
\[ \text{score}(y_{1:t}) = \sum_{i=1}^{t} \log P(y_i | y_{<i}) \]
短い系列が有利になるのを避けるため、 長さ正規化 を行う。
\[ \text{score}_{\text{norm}}(y) = \frac{1}{|y|^\alpha} \sum_{i=1}^{|y|} \log P(y_i | y_{<i}), \quad \alpha \in [0.6, 1.0] \]
直感 :迷路で複数の経路を同時に探索し、分岐点ごとに最も有望な\(B\)本だけを残すようなもの。
長所 :貪欲法より尤度の高い系列を見つける。「正解」が1つに近い翻訳や要約に適する。
短所 :自由生成では一般的で反復的なテキストに偏りやすい。計算量は\(B \times\)になり、すべてのビームが似た出力に収束することも多い。
多様ビームサーチ
標準的なビームサーチでは、ほぼ重複するビームが生じる。多様ビームサーチ(Vijayakumar et al. 2018)はビームを\(G\)個のグループに分け、グループ間に 非類似度ペナルティ を加える。
\[ \text{score}_g(y_t) = \log P(y_t | y_{<t}) - \lambda \sum_{g'<g} \Delta(y_t, Y_{g'}) \]
ここで\(\Delta\)は先行グループがすでに選んだトークンとの重なり(例えばハミング多様性)を測り、\(\lambda\)は多様性の強さを制御する。
直感 :ブレインストーミングのグループに異なるアイデアを出させるようなもの。各サブグループは、先行サブグループの発言を繰り返すとペナルティを受ける。
長所 :本当に異なる候補系列を生成し、再ランキングのパイプラインに有用。
短所 :多様性ペナルティによって個々のビームの品質が低下しうる。ハイパーパラメータ(\(G\)、\(\lambda\))も増える。
Top-\(k\) サンプリング
最も確率の高い\(k\)個のトークンだけからサンプリングし、確率質量を再配分する。
\[ P'(v | x_{<t}) = \begin{cases} \dfrac{P(v | x_{<t})}{\sum_{v' \in \text{Top-}k} P(v' | x_{<t})} & \text{if } v \in \text{Top-}k \\[6pt] 0 & \text{otherwise} \end{cases} \]
直感 :「The cat sat on the...」の後では、もっともらしい上位\(k\)個の続き(「mat」「floor」「couch」など)だけを考え、極端にありそうにないもの(「quantum」「archipelago」など)は無視する。
長所 :裾のノイズを除き、実装が簡単。
短所 :固定された\(k\)は、尖った分布では制限が強すぎて確率質量を無駄にし、平坦な分布では緩すぎて不要なトークンを含めてしまう。
Top-\(p\)(Nucleus)サンプリング
累積確率が\(p\)を超える最小のトークン集合からサンプリングする。
\[ \text{Top-}p = \min \left\{ S \subseteq \mathcal{V} : \sum_{v \in S} P(v | x_{<t}) \geq p \right\} \]
トークンを確率の降順に並べ、しきい値\(p\)に達するまで追加する。
直感 :候補集合の大きさを適応的に変える。モデルが確信している場合(「Paris」が95%)はnucleusが小さくなり、不確かな場合(「The movie was...」)はもっともらしい形容詞を多く含むようnucleusが広がる。
長所 :分布の形状に適応し、広く使われるデフォルト(\(p=0.9\)〜\(0.95\))である。
短所 :nucleusの末尾に低品質なトークンが含まれることがある。しきい値は単一のグローバルなハイパーパラメータである。
Tip
Top-kkとTop-pp
次の単語を予測する場合を考えよう。
「2 + 2 =」の後では分布が尖っており、top-1トークン(「4」)が99%の質量を持つ。Top-\(k\)=50では、間違った答え49個まで無駄に検討する。Top-\(p\)=0.9なら「4」だけを正しく選ぶ。
「I enjoy eating」の後では分布が平坦で、多くの食べ物がもっともらしい。Top-\(k\)=5では制限が強すぎる。Top-\(p\)=0.9なら50個以上のトークンを含むことがあり、実際の不確かさに合う。
Top-\(p\)は適応するが、top-\(k\)は適応しない。実際には両者を組み合わせ、top-\(p\)とtop-\(k\)の共通部分からサンプリングすることが多い。
Min-\(p\) サンプリング
相対的な 確率の下限を設定する近年の代替手法(Nguyen 2024):
\[ \text{Min-}p = \left\{ v \in \mathcal{V} : P(v | x_{<t}) \geq p_{\min} \cdot \max_{v'} P(v' | x_{<t}) \right\} \]
最上位トークンの確率の少なくとも\(p_{\min}\)倍の確率を持つトークンだけを残す。
直感 :「最良のトークンの少なくとも10%の確率を持つトークンだけを考える」。最上位トークンの確率が0.8なら、0.08を超えるトークンだけが残る。最上位トークンの確率が0.05(非常に不確か)なら、0.005を超えるトークンが残り、候補集合は自然に広がる。
長所 :モデルの確信度に自然に応じて変化する。尖った分布ではtop-\(p\)より退化したサンプルが少なく、直感的な単一パラメータで済む。
短所 :新しく、十分に実績が蓄積されていない。すべての推論フレームワークで標準になっているわけではない。
温度スケーリング
どのサンプリング戦略を適用する前にも、ロジットを温度\(T\)で割る。
\[ P_T(v | x_{<t}) = \frac{\exp(z_v / T)}{\sum_{v'} \exp(z_{v'} / T)} \]
-
\(T < 1\):分布が尖る \(\to\) より決定的で焦点の合った出力になる。
-
\(T = 1\):モデルの分布をそのまま使う。
-
\(T > 1\):分布が平坦になる \(\to\) よりランダムで創造的な出力になる。
-
\(T \to 0\):貪欲デコーディングになる。\(T \to \infty\):一様サンプリングになる。
一般的な設定 :事実に関するタスクでは\(T=0.7\)、創作では\(T=1.0\)〜\(1.2\)、コードや数学では\(T=0.0\)(貪欲法)。
対照的デコーディング
対照的デコーディング(X. Li et al. 2023)は、強いモデル(エキスパート)と弱いモデル(アマチュア)の差を利用して、エキスパート固有の知識を増幅する。
\[ x_t = \arg\max_{v \in \mathcal{V}(x_{<t})} \left[ \log P_{\text{expert}}(v | x_{<t}) - \log P_{\text{amateur}}(v | x_{<t}) \right] \]
ここで\(\mathcal{V}(x_{<t}) = \{v : P_{\text{expert}}(v \vert x_{<t}) \geq \alpha \cdot \max_{v'} P_{\text{expert}}(v' \vert x_{<t})\}\)は適応的な妥当性制約である。
直感 :アマチュアモデルは一般的で明白なパターン(よくある単語や反復)を捉える。その対数確率を引くことで、この「一般的な信号」を取り除き、エキスパートの特徴的な知識と推論を残す。録音から背景ノイズを除き、信号を聞き取るようなものだ。
長所 :反復や一般的な言い回しを減らし、追加学習なしに事実性と一貫性を高める。任意のモデルペアで動作する。
短所 :2つのモデルを実行する必要がある(計算量は2\(\times\))。アマチュアモデルの選択に敏感で、妥当性しきい値\(\alpha\)の調整も必要である。
反復ペナルティ
サンプリング戦略とは独立に、反復ペナルティはモデルがトークンを繰り返すのを抑制する。トークン\(v\)の生ロジット\(z_v\)(つまりsoftmax前にLMヘッドが出力する正規化前のスコア)が与えられたとき、ペナルティ後のロジットは次のようになる。
\[ z_v' = \begin{cases} z_v / \theta & \text{if } v \in \text{generated tokens and } z_v > 0 \\ z_v \cdot \theta & \text{if } v \in \text{generated tokens and } z_v < 0 \end{cases} \]
ここで\(\theta > 1\)はペナルティ係数(通常1.1〜1.3)である。どちらの場合も、ロジットを0へ近づけることで、以前に生成したトークンの確率を下げる。頻度ペナルティと存在ペナルティは、OpenAI APIで使われるより単純な加算型の変種である。
\[ z_v' = z_v - \alpha \cdot \text{count}(v) - \beta \cdot \mathbf{1}[v \in \text{generated}] \]
ここで\(\alpha\)は頻度ペナルティ(\(v\)が現れた回数に比例)で、\(\beta\)は存在ペナルティ(過去に一度でも現れた場合に一律で課すペナルティ)である。
実用比較
| 手法 | 決定的 | 多様性 | 品質 | 適した用途 |
|---|---|---|---|---|
| 貪欲法 | あり | なし | 中 | コード、事実性QA |
| ビームサーチ(\(B\)=4〜8) | あり | 低 | 高(狭い) | 翻訳、要約 |
| 多様ビームサーチ | あり | 中 | 高 | 再ランキング用の候補生成 |
| Top-\(k\)(\(k\)=50) | なし | 中 | 中 | 汎用生成 |
| Top-\(p\)(\(p\)=0.9) | なし | 適応的 | 高 | 自由生成タスクのデフォルト |
| Min-\(p\)(\(p_{\min}\)=0.1) | なし | 適応的 | 高 | top-\(p\)の頑健な代替 |
| 対照的 | あり | 低 | 非常に高 | 事実性の高い一貫した長文 |
LLMテキスト生成におけるデコーディング手法の比較。
Note
実際のデコーディング:「Once upon a time」
プロンプト「Once upon a time」が与えられた場合:
貪欲法 :「there was a young girl who lived in a small village...」(一般的なおとぎ話)
Top-\(p\)=0.9、\(T\)=1.0 :「the rivers ran backwards and the fish learned to fly...」(創造的で意外)
Top-\(p\)=0.9、\(T\)=0.3 :「there was a kingdom ruled by a wise and just king...」(一貫していて慣習的)
対照的 :「in the amber-lit corridors of a collapsing star, two minds argued about the nature of time...」(特徴的で、決まり文句を避ける)
同じモデル、同じプロンプトでも、デコーディング戦略が出力の性格を決める。
制約付きデコーディング(構造化生成)
上記の手法はすべて、各ステップで語彙全体からサンプリングする。 制約付きデコーディング は許可されるトークン集合を制限し、出力が形式文法(通常はJSONスキーマ、正規表現、または文脈自由文法(CFG))に従うことを保証する。
中核メカニズム
各デコーディングステップ\(t\)で、現在のパーサ状態から トークンマスク\(M_t \subseteq \mathcal{V}\)を計算する。\(M_t\)内のトークンだけが元のロジットを受け取り、それ以外はsoftmaxの前に\(-\infty\)へ設定する。
\[ P'(v | x_{<t}) = \begin{cases} P(v | x_{<t}) / Z & \text{if } v \in M_t \\ 0 & \text{otherwise} \end{cases} \]
ここで\(Z = \sum_{v \in M_t} P(v \vert x_{<t})\)は再正規化を行う。マスクは各ステップで変化し(それまでに生成された内容に依存する)、制約は逐次的に適用される。そのためモデルはどの時点でも無効な接頭辞を生成できない。
スキーマからマスクへ
コンパイルのパイプラインは次のとおりである。
\[ \text{JSON Schema} ;\xrightarrow{\text{compile}}; \text{Regex} ;\xrightarrow{\text{compile}}; \text{FSM (DFA)} ;\xrightarrow{\text{index}}; \text{Token Mask per State} \]
FSMの状態は正規表現内の位置に対応する。各状態について、文字列をその言語内に保てるすべての語彙トークンをインデックスへ事前計算する(スキーマごとに一度だけ必要なコスト)。実行時のマスク検索は\(O(1)\)のテーブル参照なので、各デコーディングステップへのレイテンシ追加はごくわずかである。
主要ライブラリ
-
Outlines (Willard and Louf 2023:JSONスキーマと正規表現をFSMにコンパイルし、FSMに導かれる生成へ組み込む。ロジットインターフェースを持つ任意のモデルをサポートする。
-
lm-format-enforcer1:同様のFSM方式で、サービングフレームワーク(vLLM、TGI)との統合に重点を置く。
-
Guidance2(Microsoft):制約付き生成を制御フロー(ループ、条件分岐)と組み合わせ、単純なスキーマを超える複雑な構造化出力を可能にする。
-
XGrammar (Dong et al. 2024:プッシュダウンオートマトンに基づくエンジンで、正規言語だけでなく完全な文脈自由文法をサポートする。MLC-LLMやvLLMで文法モードのデコーディングに使われる。
トレードオフ
制約付きデコーディングは構文的な妥当性を保証し、事後のパース失敗やリトライをなくす。ただし、
-
意味的品質 :モデルが「正しい」答えに割り当てる確率質量が文法の外にある場合、構造を強制すると内容の品質が低下しうる。適切に設計されたスキーマと十分に学習されたモデルでは、実際にはまれである。
-
コンパイルコスト :FSMインデックスはスキーマごとに構築する必要がある。複雑なスキーマでは1〜5秒かかることがあるが、そのスキーマを使うすべてのリクエストに対して償却される。
-
文法のカバレッジ :正規表現/FSMはJSON、YAML、SQL断片、その他大半の構造化形式を扱う。完全なCFG(XGrammarやLALRパーサーによるもの)はPythonやXMLのような言語をカバーする。
Important
制約付きデコーディングを使う場面
モデル出力の利用者が人間ではなくプログラムである場合は、制約付きデコーディングを使うとよい。ツール呼び出しエージェント、APIバックエンド、データ抽出パイプラインはいずれも、妥当な構造が保証されることで恩恵を受ける。自由形式の文章や創作テキストには、制約なしのサンプリングが適している。
プロンプトエンジニアリング
プロンプトエンジニアリングとは、モデルの重みを変更せずに、望ましい振る舞いをLLMから安定して引き出す入力を設計する分野である。ファインチューニングがモデルを変更するのに対し、プロンプトエンジニアリングは、適切な枠組み、例、構造によってモデルの既存の能力を引き出す。LLMの出力を改善する最も速く、安価で、利用しやすい手段であり、ファインチューニング済みモデルを使う場合でも不可欠である。
文脈内学習(ICL)
文脈内学習(Brown et al. 2020)は、勾配更新なしに、プロンプトに与えられた例だけから推論時にタスクを学習する大規模言語モデルの注目すべき能力である。モデルは入力と出力のペアのパターンから暗黙にタスクを推測し、新しい入力へ一般化する。
Important
文脈内学習が機能する理由
暗黙のベイズ推論 :モデルは事前学習中に数百万のタスクを見ている。プロンプトの例は、モデルが学習した分布の中で関連するタスクを特定する(Xie et al. 2022)。
誘導ヘッド :特定のアテンションヘッドがパターン(「A is to B as C is to 」)をコピーすることを学び、文脈内での一般化を可能にする(Olsson et al. 2022)。
タスクベクトル :ICLは残差ストリームに暗黙のタスク表現を作り、実演された形式と内容へ生成を誘導する(Todd et al. 2024)。
スケーリングの挙動
ICLは主に\(\sim\)1Bパラメータを超えるモデルで現れ、モデル規模とともに対数線形に向上する(Brown et al. 2020)。小規模モデルは例を記憶できても、同じコンテキストウィンドウ内の未知の入力へ一般化するのは難しい。
ゼロショットプロンプティング
ゼロショットプロンプティングでは例を一切与えず、タスクの説明または指示だけを与える。モデルは正しい形式と内容を生成するため、事前学習で得た知識と指示チューニングだけに頼らなければならない。
Note
ゼロショット分類
次の映画レビューをPOSITIVEまたはNEGATIVEに分類せよ。
レビュー:「映像は息をのむほど素晴らしかったが、筋書きは 急ぎ足で予測可能に感じられた。」 感情:
ゼロショットがうまくいく場合
-
事前学習/SFTでモデルが十分に見てきたタスク(翻訳、要約、感情分析)
-
出力形式が曖昧でない、明確に定義された指示
-
指示チューニングされたモデル(ChatGPT、Claude、Llama-3-Instructなど)は、ゼロショットタスクでベースモデルを大きく上回る(Ouyang et al. 2022)
ゼロショットが失敗する場合
新しい形式、領域固有のラベル体系、または指示だけでは正確な要件を推測できない曖昧なタスクでは失敗する。
few-shotプロンプティング
Few-shotプロンプティング(Brown et al. 2020)では、実際のクエリの前に\(k\)個の入出力例(「ショット」)を与える。これは文脈内学習で最も一般的な形であり、現在でも最も効果的なプロンプティング戦略の一つである。
Note
Few-shot固有表現認識
テキストから固有表現を抽出せよ。形式:ENTITY
テキスト:「Apple released the iPhone 15 in Cupertino.」 固有表現:[Apple](ORG)、[iPhone 15](PRODUCT)、[Cupertino](LOC) テキスト:「Elon Musk announced Tesla's new factory in Berlin.」 固有表現:[Elon Musk](PER)、[Tesla](ORG)、[Berlin](LOC) テキスト:「OpenAI partnered with Microsoft to deploy GPT-4.」 固有表現:
few-shot例の設計原則
-
多様性 :想定される入力の範囲(異なる長さ、エッジケース、カテゴリ)をカバーする。
-
順序 :難しい例や代表的な例を最後に置く(新近性バイアス)(Lu et al. 2022)。
-
ラベルのバランス :分類では、最多クラスへのバイアスを避けるため、すべてのクラスの例を含める。
-
形式の一貫性 :すべての例がまったく同じ構造に従うようにする。モデルはそのパターンを模倣する。
-
関連性 :最良の結果を得るには、対象クエリと意味的に似た例を使う(J. Liu et al. 2022)。
何ショット必要か?
性能は通常、例が0個から4〜8個になるまで向上し、その後は頭打ちになる。\(\sim\)20例を超えると改善はわずかで、コンテキストウィンドウを埋めるリスクがある。Minら(Min et al. 2022)は、例の形式とラベル空間がラベルの正しさより重要であることを示した。ランダムなラベルでも役に立つ(ただし正しいラベルの方がより有効)。
指示追従プロンプト
指示チューニングされたモデルは、明確で構造化された指示に最もよく応答する。重要なのは、プロンプトを提案ではなく仕様として扱うことである。
Important
効果的な指示プロンプトの構成
役割/ペルソナ :モデルが何者かを定義する(「あなたはシニアデータサイエンティストです……」)
タスク :何をするかを明確かつ曖昧さなく述べる
コンテキスト :モデルに必要な背景情報
制約 :長さの上限、語調、避けること、出力形式
例 (任意):望ましい出力形式を示す
入力 :処理対象の実データ
Note
制約付き指示プロンプト
役割:あなたは医学文献のレビュアーです。
タスク:次の研究要旨を一般読者向けに要約せよ。 制約: - 最大3文 - 専門用語を使わない(技術用語は説明する) - 主要な発見とその臨床的含意を含める - 要旨に書かれている以上の推測を絶対にしない Abstract: [...]
システムプロンプトとユーザープロンプト
現代のチャットAPIでは、システムプロンプト(永続的な指示、役割定義)とユーザーメッセージ(ターンごとの入力)を分離する。ほとんどのモデルではシステムプロンプトがより高いアテンション優先度で処理され、役割定義、制約、出力形式の仕様を置く自然な場所となる(OpenAI 2023)。
構造化出力プロンプト(JSON/XML)
プログラムから利用する場合、最も重要なプロンプティング技法は、特にJSONのような構造化出力を強制することである。
Note
JSON出力プロンプト
顧客メールから次の情報を抽出せよ。 有効なJSONだけで応答し、他のテキストは出力しないこと。
Schema: { "intent": "refund|complaint|question|praise", "urgency": "low|medium|high", "product_mentioned": "string or null", "summary": "one sentence summary" } Email: [...]
信頼できる構造化出力の技法
-
スキーマファースト :入力より前に正確なJSONスキーマを示す。モデルはそれをテンプレートとして扱う。
-
制約付きデコーディング :文法ベースのサンプリング(Outlines(Willard and Louf 2023)、Guidanceなど)を使い、トークンレベルで構文的に妥当なJSONを保証する。
-
XMLタグ :入れ子構造や複数部分の出力では、XMLタグ(例:
<thinking>...</thinking>)が曖昧さのない区切りとなり、モデルは安定して従える。 -
Pydantic/TypeScript型 :型定義を与えると、モデルがフィールドの制約を理解しやすい(OpenAIの関数呼び出しは内部でJSON Schemaを使う)。
Warning
プロンプト内のJSON——よくある落とし穴
モデルがMarkdownのフェンス(
‘‘‘json ... ‘‘‘)を追加することがある。生のJSONを出力するよう明示する。入れ子のオブジェクトや配列は幻覚のリスクを高める。可能ならスキーマを平坦化する。
Enumフィールド(選択肢が固定されたフィールド)は自由記述フィールドよりはるかに信頼できる。
必ずプログラムで出力を検証する。制約付きデコーディングなしに、100%の遵守を保証するプロンプトはない。
JSONプロンプティング:入力を構造化する
これとは別だが補完的な技法がJSONプロンプティングである。自然言語ではなく、プロンプト自体をJSONとして整形する。これは構造化データ(API、設定、コード)に対するモデルの豊富な事前学習を利用し、指示への遵守を高め、曖昧さを減らし、複数フィールドの要求を決定的にパースできるようにする。
Note
システムプロンプトを使ったJSONプロンプティング
=== SYSTEM === あなたはシニアコードレビュアーです。コードのバグ、 セキュリティ問題、スタイル違反を分析してください。常に 与えられたJSONスキーマで応答してください。
=== USER (JSON prompt) === { "task": "code_review", "language": "python", "severity_filter": "high", "code": "def login(user, pw):\n query = ...", "output_schema": { "issues": [{ "line": "int", "severity": "critical|high|medium|low", "category": "security|bug|style|performance", "description": "string", "fix": "string" }], "overall_risk": "critical|high|medium|low" } }JSONプロンプティングが機能する理由:
曖昧さのないフィールド境界 :ある指示が終わり、別の指示が始まる場所を混同しない。
型付き制約 :フィールド
"severity_filter": "high"は、「重大度の高い問題だけを表示せよ」より明確である。契約としてのスキーマ :入力に
output_schemaを含めることは、モデルが事前学習中に大量に見てきたAPI設計パターンを反映する。システムプロンプトは依然として不可欠 :システムメッセージは、JSONペイロードには自然に収まらない役割、語調、振る舞いの制約を与える。
Chain-of-Thought(CoT)プロンプティング
Chain-of-thoughtプロンプティング(Wei et al. 2022)は、最終回答の前に中間的な推論ステップを生成するようモデルに求める。この単純な技法により、算術、論理、常識推論、コード生成など、複数ステップの推論を必要とするタスクの性能が大きく向上する。
CoTが機能する理由
-
計算を逐次化する :Transformerの深さは固定だが、生成の長さは可変である。CoTは並列的で難しい問題を逐次的で簡単なステップに変換し、モデルの計算予算を実質的に増やす。
-
誤差の累積を減らす :各ステップがより単純な部分問題となり、ステップごとの誤り率が下がる。
-
中間状態を露出させる :推論を監査・デバッグ可能にする。
Important
Chain-of-Thoughtの変種
手法 説明 Zero-shot CoT (Kojima et al. 2022) 任意のプロンプトに「一歩ずつ考えよう」を追加する Few-shot CoT (Wei et al. 2022) 明示的な推論連鎖を含む例を与える Self-Consistency (X. Wang, Wei, Schuurmans, Q. Le, et al. 2023) \(N\)本のCoT経路をサンプリングし、最終回答を多数決する Tree of Thoughts (S. Yao, Yu, et al. 2023) バックトラッキングしながら複数の推論分岐を探索する Plan-and-Solve (L. Wang et al. 2023) まず手順を計画し、その後各ステップを実行する ReAct (S. Yao, Zhao, et al. 2023) 推論と行動(ツール利用)を交互に行う
Note
ゼロショットChain-of-Thought
Q:店にリンゴが45個ある。午前にその3/5を売り、 午後に残りの1/3を売った。何個残っているか。
一歩ずつ考えてみよう。 A:午前の販売:45 * 3/5 = 27個を販売。 午前後の残り:45 - 27 = 18。 午後の販売:18 * 1/3 = 6個を販売。 残り:18 - 6 = 12個。
自己整合性
Wangら(X. Wang, Wei, Schuurmans, Q. Le, et al. 2023)は、複数のchain-of-thought推論経路をサンプリングし、最終回答を多数決すると、単一経路のCoTを大きく上回ることを示した。直感的には、正しい推論経路は同じ答えに収束しやすい一方、誤りは個別的である。この方法は、計算量(\(N\)個のサンプル生成)と引き換えに精度を得るもので、レイテンシより正しさが重要な場合に実用的である。
CoTが害になる場合
CoTが常に有益とは限らない。単純なタスク(1ステップの分類、検索、整形)では、CoTは不要なトークンを追加し、レイテンシを増やし、考えすぎによって誤りを生むことさえある。複数ステップの推論が必要だと予想されるタスクに限定してCoTを使うべきである。
高度なプロンプティング技法
検索拡張生成(RAG)
モデルのパラメトリック記憶だけに頼るのではなく、RAG(P. Lewis et al. 2020)は関連文書を検索し、プロンプトに含める。
コンテキスト(検索結果):[文書の断片]
質問:[ユーザークエリ]
与えられたコンテキストだけに基づいて回答せよ。
これによりモデルの応答を検証可能な情報源に基づかせ、知識集約型タスクでの幻覚を大きく減らせる。
プロンプト連鎖と分解
複雑なタスクは、より単純なプロンプトのパイプラインへ分解すると効果的である。あるプロンプトの出力を次の入力にする。
-
抽出:文書から重要な事実を取り出す
-
推論:抽出した事実について推論する
-
整形:最終回答を整形する
各ステップで異なるプロンプトテンプレート、モデル、温度設定を使える。単一の巨大なプロンプトより制御しやすく、対象を絞ったデバッグも可能になる。
Constitutional AI/自己批評
Baiら(Bai et al. 2022)は、一連の原則に照らして自分の出力を批評・修正するようモデルに求めるプロンプトを導入した。
[初期応答を生成]
批評:この応答は次の原則のいずれかに違反しているか。
[原則の一覧]
修正:批評に対応するよう応答を書き直す。
メタプロンプティングとプロンプト最適化
プロンプトを手作業で作るのではなく、近年の研究では設計を自動化している。
-
APE (Y. Zhou et al. 2023:LLMを使って候補プロンプトを自動生成し、スコア付けする。
-
DSPy (Khattab et al. 2024:宣言的なタスク記述を、学習されたfew-shot例を持つ最適化済みプロンプトパイプラインへコンパイルする。
-
OPRO (C. Yang et al. 2024:LLMをオプティマイザーとして使い、プロンプト最適化を最適化問題として扱う。
注意を向けた推論クエリ(ARQ)
ARQ(Yang et al. 2025)は、標準的なプロンプティングの根本的な弱点に対処する。コンテキストが長くなるほど、モデルはプロンプト中央の重要情報を失いやすくなる(lost-in-the-middle効果)。ARQは複雑なクエリを複数の焦点化されたサブクエリに分解し、それぞれでコンテキストの特定部分へモデルの注意を向けることで、これを緩和する。
-
クエリ分解 :ユーザーの質問を、狭い側面をそれぞれ対象とする原子的なサブクエリに分ける。
-
注意を向けた検索 :各サブクエリについて、関連するコンテキストの一部分だけを検索または強調し、モデルがそこに注意を向けるようにする。
-
集約 :サブ回答を一貫した最終応答にまとめる。
これは長文書QA、大規模な検索結果集合に対するマルチホップ推論、コンテキストウィンドウに多数のツール出力が含まれるエージェント型タスクで特に有効である。ARQは、モデルがどのように推論するかだけでなく、どこを見るかを明示的に管理する、構造化されたchain-of-thoughtとみなせる。
ベストプラクティス:効果的なプロンプトの作成
文献の実証的知見と実務経験に基づくと、次の原則はプロンプトの品質を安定して高める。
Important
プロンプトエンジニアリングのチェックリスト
具体的かつ明確にする :「これを要約して」ではなく、「実行可能な発見に焦点を当て、各20語未満の箇条書き2〜3個で要約して」と書く。
説明するより示す :良い例1つは指示100語に値する。迷ったらfew-shot例を追加する。
出力形式を明示的に定義する :JSONスキーマ、箇条書き、表形式、正確な区切り文字などを指定する。形式を解釈任せにしない。
入力データに区切り文字を使う :ユーザー入力を明確な区切り文字(
"""、<input>...</input>、---)で囲み、指示とデータを分ける。役割を割り当てる :「あなたは[特定の振る舞い]をする[領域の専門家]です」と書くと、関連する知識と語調が引き出される。
してはいけないことを指定する :「推論を説明しない」「5項目を超えて出力しない」のような否定的制約は、肯定的制約より効果的なことが多い。
推論タスクにはchain-of-thoughtを加える :「一歩ずつ考えて」と追加するか、数学、論理、マルチホップ問題の解答例を与える。
温度を適切に制御する :事実的・決定的なタスクには\(T \approx 0\)、創造的・多様な出力には\(T \approx 0.7\)〜\(1.0\)を使う。
実証的に反復する :プロンプトをコードとして扱い、バージョン管理し、A/Bテストし、代表的な評価セットで性能を測る。
新近性バイアスを活用する :最も重要な指示と例をプロンプトの末尾(生成位置に最も近い場所)に置く。
| 失敗モード | 症状 | 解決策 |
|---|---|---|
| 指示忘却 | 長いプロンプトでモデルが制約を無視する | 制約を末尾に移す;重要な規則を繰り返す;システムプロンプトを使う |
| 形式のずれ | 生成が長くなると出力が正しい形式から崩れる | 制約付きデコーディングを使う;短い連鎖プロンプトに分ける |
| 迎合 | モデルがプロンプト内の誤った前提に同意する | 「誤りなら前提に異議を唱える」と追加する;システムレベルの指示を使う |
| 詳細の幻覚 | モデルが与えられたコンテキストにない事実を作る | 「不明なら分からないと言う」と追加する;出典を付けたRAGを使う |
| 拒否の過剰発動 | 安全学習のためモデルが無害な要求を拒否する | 正当な意図が明確になるよう言い換える;要求が適切な理由を明示的なコンテキストとして与える |
プロンプティングでよくある失敗モードと解決策。
Tip
プロンプトエンジニアリングの考え方
プロンプトエンジニアリングを自然言語によるプログラミングと考える。モデルは強力だが字義通りのインタープリターであり、学習分布から最もありそうな形に解釈して、求めたことをそのまま実行する。ソフトウェア工学の一般的な原則が適用できる。
DRY(Don’t Repeat Yourself) :長いコンテキストで注意の減衰と戦う場合を除き、同じことを繰り返さない
関心の分離 :役割、制約、例、入力をプロンプトの別々のセクションにする
テスト駆動開発 :プロンプトを書く前に期待する出力を定義する
バージョン管理 :プロンプトの反復と評価スコアを追跡する
モジュール性 :再利用可能なプロンプトテンプレートを作り、可変部分をパラメータ化する
体系的に反復してもプロンプティングで望ましい品質に達しないなら、ファインチューニング(SFT)や強化学習(RLHF/DPO)へ移る合図である。
モデル圧縮手法
モデル圧縮は品質を保ちながら、モデルサイズと推論コストを削減する。主なアプローチは3つある。量子化(精度を下げる)、刈り込み(パラメータを削除する)、蒸留(大きなモデルを模倣する小さなモデルを学習する)である。
量子化
量子化は、重み(必要に応じて活性値も)をより低精度の形式で表現し、モデルサイズと推論コストを削減する。中心となるトレードオフは、圧縮率と品質低下のバランスである。
Important
量子化の概要
量子化は、モデルの重み(必要に応じて活性値も)の数値精度をFP32/BF16から低ビット形式へ下げる。 \[ x_q = \text{round}!\left(\frac{x - z}{s}\right), \quad x_{\text{dequant}} = s \cdot x_q + z \] ここで\(s\)はスケール係数、\(z\)はゼロ点である。
| 手法 | ビット数 | 種類 | 主な考え方 |
|---|---|---|---|
| GPTQ (Frantar et al. 2023) | 4-bit | PTQ、重みのみ | 最適脳外科法により、\(\\vert WX - \hat{W}X\\vert ^2\)を最小化する層単位の量子化。 |
| AWQ (Lin et al. 2024) | 4-bit | PTQ、重みのみ | 重要な重み(活性値が大きいもの)を保護する。重みの1%が重要度の99%を担う。 |
| GGUF (Gerganov 2023) | 2〜8 bit | PTQ、重みのみ | CPU最適化形式(llama.cpp)。複数の型を使うブロック単位の量子化。 |
| FP8 (E4M3) | 8-bit | 学習+推論 | H100がネイティブにサポートする。BF16に対してスループット2\(\times\)。 |
| SmoothQuant (G. Xiao et al. 2023) | W8A8 | PTQ、重み+活性 | 量子化前に活性値の外れ値を重みへ滑らかに移す。INT8 GEMMを可能にする。 |
| QAT (Z. Liu et al. 2023) | 4-bit | QAT | 量子化をシミュレートしながら学習する。品質は最高だが高コスト。 |
| AQLM (Egiazarian et al. 2024) | 2-bit | PTQ、加法コード | 学習された加法量子化コードブックにより極端な圧縮を実現する。 |
LLMの量子化手法。
Tip
量子化するタイミング
推論サービング :常に量子化する。W4A16(4-bit重み、BF16活性値)が最適点で、70B以上のモデルではメモリを2\(\times\)削減し、品質低下は\(<\)1%に抑えられる。
学習 :H100上のFP8は品質をほとんど損なわずスループットを2\(\times\)にする。小規模モデルでは現在もBF16がデフォルトである。
エッジ展開 :民生ハードウェアでのローカル推論にはGGUF Q4_K_Mを使う。
RLHF :凍結したモデル(参照モデル、報酬モデル)をINT8/FP8へ量子化する。学習精度のため、方策はBF16のままにする。
刈り込み
なぜ刈り込むのか?
現代のLLMは数十億のパラメータを持つが、実証研究は一貫して、その大部分の重みがモデル出力にほとんど寄与しないことを示している。刈り込みはこの過剰パラメータ化を利用する。冗長な重みを削除することで、 メモリフットプリント (小さなGPUやエッジデバイスへの展開を可能にする)、 推論レイテンシ (順伝播ごとの積和演算を減らす)、 サービングコスト (1ドルあたりのスループットを高める)を削減する。全重みの精度を一様に下げる量子化とは異なり、刈り込みは重みを選択的に除去するため、量子化と組み合わせると乗算的な削減が可能になる(例えば50%スパースな4-bitモデルは、密なBF16ベースラインよりメモリ使用量が\(4\times\)少ない)。生成品質を損なわずに高いスパース性を達成することが課題であり、再学習を必要としない原理的な一括手法の開発につながった。
Important
刈り込み手法
非構造化刈り込み :しきい値未満の個々の重みをゼロにする。高いスパース性(50〜90%)が可能。スパースGEMMカーネル(A100/H100では2:4)が必要。
構造化刈り込み :アテンションヘッド、層、FFNニューロンを丸ごと削除する。特殊なカーネルなしにFLOPSを直接削減できる。
SparseGPT (Frantar and Alistarh 2023:近似逆ヘッセ行列を使う一括刈り込み。175Bモデルで品質をほとんど損なわず50%の非構造化スパース性を実現する。
Wanda (Sun et al. 2024:\(\vert w\vert \times \\vert x\\vert\)(重みの大きさと入力活性値ノルムの積)で刈り込む。キャリブレーションデータは不要で、SparseGPTと競合する。
Warning
NVIDIAの2:4構造化スパース性
A100/H100のTensor Coreは2:4スパース性をネイティブにサポートする。4要素ごとに、非ゼロなのは最大2つである。対応する演算ではハードウェアアクセラレーションにより、正確に2\(\times\)の高速化が得られる。制約は、この特定のパターンでちょうど50%のスパース性を達成しなければならず、任意のスパース性に比べて柔軟性が低いことである。
知識蒸留
知識蒸留(Hinton et al. 2015)は、大きな教師モデルが学習した振る舞いを、より小さく安価な生徒モデルへ移す。中心的な考え方は、教師のトークン上の出力分布が、正解のハードラベルだけよりはるかに豊かな信号を持つことである。クラス間の類似性、キャリブレーション、不確実性が明らかになり、生徒はこれらを利用できる。
温度スケールsoftmax
教師のロジットに含まれる「暗黙の知識」を引き出すため、温度\(T > 1\)で分布を軟化する。
\[ p_i^{(T)} = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)} \]
高い温度では確率質量がより多くのトークンへ広がり、惜しい代替候補が見えるようになる。学習中は生徒にも同じ温度を適用し、推論時には生徒が\(T=1\)を使う。
一般的な蒸留損失
\[ \mathcal{L}_{\text{distill}} = \alpha , T^2 \cdot \text{KL}!\bigl(P_{\text{teacher}}^{(T)} ;|; P_{\text{student}}^{(T)}\bigr) ;+; (1-\alpha) \cdot \mathcal{L}_{\text{CE}}(y, P_{\text{student}}^{(1)}) \]
\(T^2\)の因子は、軟化された分布で勾配の大きさが小さくなることを補償する。典型的な値は\(T \in [2, 20]\)、\(\alpha \in [0.5, 0.9]\)である(教師の品質が高いほどKLに大きな重みを置く)。
| パラダイム | 仕組み | 長所 | 短所 |
|---|---|---|---|
| オフライン/ホワイトボックス | 教師のロジットを事前計算し、生徒は完全な分布で学習 | 分布全体の信号;教師コストは一度だけ | データが古くなる;ストレージを大量に消費 |
| オンライン/協調学習 | 教師がオンザフライで生成し、生徒は新しいロジットを見る | 生徒の弱点に適応 | 計算量が\(2\times\);インフラが複雑 |
| ブラックボックス(API) | 教師のテキスト出力だけ利用可能(ロジットなし) | 独自モデルでも動作 | 暗黙の知識を失う;SFTに近い |
| 自己蒸留 | モデル自身の小さい版へ蒸留 | 別の教師が不要 | 教師は同じモデル系列;性能上限がある |
LLMの知識蒸留パラダイム。
オフライン(ホワイトボックス)蒸留
各学習トークンについて、教師の完全なロジットベクトル(またはストレージ効率のためtop-\(k\)ロジット)を記録する。生徒は保存された分布に対するKLダイバージェンスを最小化する。教師へ自由にアクセスできる場合、これは最もデータ効率のよいパラダイムである。
動機 :教師の推論と生徒の学習を分離する。高性能ハードウェアで教師を一度実行し、その後、多数の生徒を安価に学習する。
長所 :決定的で再現可能。教師のコストを償却でき、分布全体の信号を利用できる。
短所 :トークンごとに\(\vert V\vert\)次元のベクトルを保存する必要がある(top-\(k\)刈り込みで緩和可能)。教師は生徒の失敗に適応できない。
オンライン(協調学習)蒸留
教師と生徒を同時に実行し、教師が生徒の現在の学習バッチに対するロジットを生成する。
動機 :教師が、生徒が現在苦手としている入力に集中できるようにする(カリキュラムに似た仕組み)。
長所 :新鮮な信号を得られ、生徒が生成した入力をオンポリシー蒸留に使える。
短所 :GPUコストが倍になる。同期間の複雑さがあり、スケールしにくい。
ブラックボックス(API)蒸留
テキスト出力だけが利用可能な場合(例えば独自APIから蒸留する場合)、生徒を教師の生成結果に対するSFTで学習し、必要に応じてchain-of-thoughtの軌跡を加える。
動機 :実務上の現実として、最先端モデルの大半はロジットを公開していない。
長所 :パイプラインが単純で、APIの背後にある任意のモデルで動作する。
短所 :ソフトラベルの信号がない。幻覚を増幅しやすく、実質的には教師ありファインチューニングである。
自己蒸留
同じアーキテクチャ系列のより大きな版(例えばLlama-3 70B \(\to\) 8B)から、または学習中の自分自身のチェックポイントから蒸留する。
動機 :別の教師を学習する必要をなくし、異なる規模におけるモデル自身の能力を利用する。
長所 :アーキテクチャの互換性があり、外部依存がない。
短所 :教師の上限がモデルの上限と等しく、本当に新しい知識は導入できない。
Tip
暗黙の知識
「The capital of France is」の次の単語を予測する言語モデルを考える。ハードラベルでは「Paris」だけが正解だとする。しかし教師のソフト分布は「Lyon」に5%、「Marseille」に2%、「banana」にほぼ0%を割り当てるかもしれない。これは生徒にどの誤りが妥当かを伝え、キャリブレーションと一般化を大きく改善する。
LLM蒸留の実践的考慮事項
-
系列レベルとトークンレベル :トークンレベルのKLが標準である。系列全体でKLを最小化する系列レベル蒸留は長距離の一貫性をよりよく捉えるが、最適化は難しい。
-
層ごとのヒント :中間表現(アテンションマップ、隠れ状態)を一致させると追加の学習信号が得られる。生徒のアーキテクチャが異なる場合に特に有用である。
-
データ選択 :蒸留データの品質は重要である。多様で難しい例を選ぶ方が、ランダムサンプリングよりよい生徒を生む。
-
生徒の容量 :教師パラメータの\(\sim\)10%未満では収穫逓減が起きる。極端な圧縮では、アーキテクチャの変更(例えばMoE \(\to\)密結合)が必要になる場合がある。
-
量子化との組み合わせ :蒸留と4-bit量子化(例えばQLoRA蒸留モデル)により、\(20\times\)圧縮で教師に近い品質を達成できる。
Note
圧縮手法の比較——70Bモデル
手法 サイズ 速度 品質 用途 BF16(ベースライン) 140 GB 1\(\times\) 100% 学習、参照 FP8(E4M3) 70 GB 2\(\times\) 99.5% H100推論 INT8(SmoothQuant) 70 GB 1.8\(\times\) 99% A100推論 4-bit(AWQ) 35 GB 2.5\(\times\) 97〜98% 大規模サービング 2-bit(AQLM) 17.5 GB 3\(\times\) 90〜93% エッジ、実験 50%刈り込み(2:4) 70 GB 1.8\(\times\) 97% 構造化高速化 蒸留8B 16 GB 10\(\times\) 80〜85% モバイル、エッジ
投機的デコーディング手法
投機的デコーディング(Leviathan et al. 2023)は、複数のトークンを同時に予測し、対象モデルの1回の順伝播で検証することで自己回帰生成を高速化する。標準デコーディングと 同一の出力分布 (品質低下なし)を保ちながら、2〜3\(\times\)の高速化を達成する。
基本原理
Important
投機的デコーディングの枠組み
高速な ドラフト機構 が\(k\)個の候補トークン\(\hat{x}_1, \ldots, \hat{x}_k\)を提案する。
大きな 対象モデル が\(k\)個すべてのトークンに対して1回の順伝播を行う(バッチ処理)。
検証 :\(P_{\text{target}}(\hat{x}_i) \geq r_i \cdot P_{\text{draft}}(\hat{x}_i)\)である間、左から右へトークンを受理する(\(r_i \sim U[0,1]\))。
位置\(j\)で初めて拒否したら、調整済み分布から\(x_j\)を再サンプリングし、\(\hat{x}_{j+1}, \ldots, \hat{x}_k\)を破棄する。
重要な性質 :この受理/拒否方式により、最終分布が厳密に\(P_{\text{target}}\)と等しくなることが保証される。
高速化 :受理率が\(\alpha\)なら、1ステップあたりの期待トークン数は\(\frac{1 - \alpha^{k+1}}{1 - \alpha}\)である。\(\alpha=0.8\)、\(k=5\)では、標準デコーディングの1トークンに対して1ステップあたり3.4トークンが期待される。
手法の比較
| 手法 | ドラフトの供給元 | 高速化 | 主な考え方 |
|---|---|---|---|
| 標準 (Leviathan et al. 2023) | 小規模モデル(1〜7B) | 2〜3\(\times\) | 別のドラフトモデルが候補を生成する。単純だが2つのモデルを読み込む必要がある。 |
| Medusa (Cai et al. 2024) | 並列LMヘッド | 2〜3\(\times\) | 対象モデルに\(k\)個の予測ヘッドを追加する。各ヘッドが\(+1, +2, \ldots, +k\)の位置のトークンを予測する。 |
| Eagle (Li et al. 2024) | 特徴レベル | 2.5〜3.5\(\times\) | 軽量デコーダが対象モデルの隠れ状態からドラフトトークンを生成する。Medusaより受理率が高い。 |
| Eagle-2 (Li et al. 2024) | コンテキスト対応 | 3〜4\(\times\) | 信頼度に基づき拡張する動的ドラフト木。最先端の受理率。 |
| N-gram Lookup | N-gramキャッシュ | 1.5〜2\(\times\) | プロンプトのn-gramを過去に生成したテキストと照合する。コストゼロで、反復的な出力に適する。 |
| Lookahead (Yichao Fu et al. 2024) | Jacobi反復 | 2〜2.5\(\times\) | n-gram検証を伴う並列Jacobiデコーディング。ドラフトモデルは不要で、対象モデル自体を使う。 |
| Multi-token (Gloeckle et al. 2024) | 変更アーキテクチャ | 2〜3\(\times\) | 1ステップで複数トークンを本来の機能として予測するようモデルを学習する(LlamaにおけるMetaの方式)。 |
現代の推論エンジンがサポートする投機的デコーディング手法。
Medusa:マルチヘッド投機的デコーディング
Important
Medusaの仕組み
MedusaはLLMに\(k\)個の追加「予測ヘッド」を加える(同じバックボーンを共有する)。
ヘッド0(元のヘッド):位置\(t+1\)のトークンを予測する(標準的な次トークン予測)
ヘッド1:位置\(t+2\)のトークンを予測する(1つ飛ばす)
ヘッド\(i\):位置\(t+i+1\)のトークンを予測する
すべてのヘッドを1回の順伝播で並列に実行する
木構造の 検証により、複数の候補系列を同時に検証する
学習 :Medusaヘッドだけをファインチューニングする(バックボーンは凍結)。コストは代表的なデータで\(\sim\)1エポック。
利点 :別のドラフトモデルが不要で、ヘッドは小さい(各1線形層)。メモリオーバーヘッドは\(<\)1%。
Eagle:特徴レベルのドラフティング
Tip
EagleがMedusaを上回る理由
Medusaのヘッドは各位置を独立に予測するため、自身の過去の予測を条件にできない(\(t+2\)のトークンは\(t+1\)で何が予測されたかを知らない)。Eagleは、対象モデルの隠れ状態上で動作する軽量な自己回帰デコーダによってこれを解決する。
対象モデルの最終層から隠れ状態を抽出する。
小さな(1層の)デコーダへ入力し、過去の隠れ状態を条件として自己回帰的にドラフトトークンを生成する。
これにより、Medusaが捉えられないトークン間依存を捉える。
結果:Eagleの受理率は85〜95%で、Medusaの60〜80%を上回る。
N-gram投機的デコーディング
Important
N-gram Lookup法
最も単純な投機的デコーディングであり、追加のモデルも学習も必要としない。
プロンプトと過去に生成したテキストのn-gramキャッシュを維持する。
各ステップで、現在のコンテキストの最後の\(n-1\)トークンがキャッシュ済みのn-gramと一致するか確認する。
一致すれば、その続きのトークンをドラフトとして提案する。
通常どおり対象モデルに対して検証する。
適した用途 :コード生成(反復パターン)、構造化出力(JSON/XML)、要素が繰り返されるプロンプト。 コスト :実質ゼロ。
vLLMとの統合
from vllm import LLM, SamplingParams
# Standard speculative decoding (separate draft model)
llm = LLM(
model="meta-llama/Llama-3-70B",
tensor_parallel_size=4,
speculative_config={
"model": "meta-llama/Llama-3-8B",
"num_speculative_tokens": 5,
},
)
# N-gram speculation (zero-cost, no draft model needed)
llm = LLM(
model="meta-llama/Llama-3-70B",
speculative_config={
"method": "ngram",
"num_speculative_tokens": 5,
"prompt_lookup_max": 4, # Match up to 4-grams from prompt
},
)
# EAGLE-style (feature-level draft, high acceptance rate)
llm = LLM(
model="meta-llama/Meta-Llama-3-8B-Instruct",
tensor_parallel_size=4,
speculative_config={
"model": "yuhuili/EAGLE-LLaMA3-Instruct-8B",
"num_speculative_tokens": 2,
"method": "eagle",
"draft_tensor_parallel_size": 1,
},
)
# MLP speculator (IBM-style, lightweight head)
llm = LLM(
model="meta-llama/Meta-Llama-3.1-70B-Instruct",
tensor_parallel_size=4,
speculative_config={
"model": "ibm-ai-platform/llama3-70b-accelerator",
"draft_tensor_parallel_size": 1,
},
)
Warning
投機的デコーディングを使わない場面
大きなバッチサイズ :バッチが\(\geq 64\)では、生成はすでに計算効率がよい。投機ではオーバーヘッド(ドラフト生成+検証)が加わり、効果が得られない。
分布が大きく異なる場合 :ドラフトモデルが対象モデルと大きく異なると、受理率が50%未満に下がり、投機の方が標準デコーディングより遅くなる。
短い出力 :\(<\)20トークンの出力では、投機のセットアップコストが節約分を上回る。
経験則 :投機は、レイテンシが重要な単一ストリーム生成(チャットボット、対話的なコード補完)で最も有効である。
ハルシネーション検出
LLMは流暢だが事実として誤ったテキストを生成することがあり、これは ハルシネーション と呼ばれる(Ji et al. 2023)。この節では、外部検索やマルチエージェント検証を使わず、モデルレベルで検出する基本手法を扱う。
ハルシネーションの種類
Important
ハルシネーションの分類
内在的 :与えられた入力/コンテキストと矛盾する(例えば要約が原文と反対のことを述べる)。
外在的 :入力から検証できず、事実として誤った主張を生成する。
忠実性 :出力が指示や指定された制約から逸脱する。
検出手法(モデルレベル)
| 手法 | 仕組み | シグナル |
|---|---|---|
| トークンレベルのエントロピー | 生成時の高いエントロピーは不確実性を示す(Kadavath et al. 2022) | \(H(P(x_t)) > \tau\) |
| 系列対数確率 | 出力の平均対数確率が低いと、作話が示唆される | \(\frac{1}{T}\sum_t \log P(x_t)\) |
| 一貫性サンプリング | \(N\)個の応答を生成し、一致度が低ければ\(=\)ハルシネーションの可能性が高い(Manakul et al. 2023) | 矛盾率 |
| 意味エントロピー | 文字列ではなく意味をクラスタ化し、意味エントロピーが高ければ\(=\)不確か(Kuhn et al. 2023) | クラスタの多様性 |
| DoLA | 後段と前段の層のロジットを対照し、事実知識を増幅する(Chuang et al. 2024) | 層間の乖離 |
モデルレベルで動作する基本的なハルシネーション検出手法。
意味エントロピー
Kuhnら(Kuhn et al. 2023)は、トークンレベルのエントロピーは信頼できないと観察した(言い換えはトークンが異なっても同じ意味を持つ)。そこで複数の応答を生成し、意味的同値性(NLIによる)でクラスタ化し、意味クラスタ上のエントロピーを計算する。
\[ SE = -\sum_{c \in \text{clusters}} P(c) \log P(c) \]
SEが高いとは、モデルが意味的に異なる答えを生成しているということであり、ハルシネーションの強いシグナルとなる。
SelfCheckGPT
Manakulら(Manakul et al. 2023)は、自己一貫性を確認してハルシネーションを検出する。複数の応答を生成し、主応答の主張が別の応答によって支持されるか検証する。モデルが「自分自身と意見が食い違う」なら、その主張はハルシネーションである可能性が高い。外部知識は不要である。
DoLA(層の対照によるデコーディング)
Chuangら(Chuang et al. 2024)は、事実知識がTransformerの後段の層に現れる一方、前段の層にはより一般的で不確かな表現が残ると観察した。DoLAは各デコーディングステップで、後段の「成熟した」層と前段の「未成熟な」層のロジット分布を対照する。
\[ \text{DoLA}(x_t) = \text{softmax}!\bigl(\log P_{\text{late}}(x_t) - \log P_{\text{early}}(x_t)\bigr) \]
DoLAは深い層に符号化された事実知識の信号を増幅することで、再学習なしに推論時のハルシネーションを減らす。必要なのは対照する層を通る追加の順伝播1回だけである。サンプリングベースの手法を補完し、それらと組み合わせられる。
Warning
モデルレベル検出の限界
これらの手法が検出するのは不確実性であって、誤りではない。モデルは確信を持って誤ることがある(エントロピーが低く、応答も一貫しているが、事実として誤っている)。信頼できる検出には、検索ベースの検証(RAG)や外部ファクトチェックツールを組み合わせる。
LLMの安全性と責任あるAI
安全性は後付けではなく、LLM学習パイプラインの不可欠な一部である。この節では、LLMの安全性に関する主要な側面と、責任ある振る舞いを実施する仕組みを扱う。
脅威の分類
| カテゴリ | 説明と例 |
|---|---|
| 有害なコンテンツ | 毒性、暴力、違法行為に関する指示を生成する(生物兵器、CSAMなど) |
| バイアスと差別 | 固定観念を助長し、人口集団間で不公平に扱う(Gallegos et al. 2024) |
| プライバシー侵害 | 学習データからPIIを漏えいさせる;記憶化攻撃(Carlini et al. 2021) |
| ジェイルブレイク | 安全ガードレールを回避する敵対的プロンプト(Zou et al. 2023) |
| 誤情報 | 説得力はあるが偽の主張を生成する(大規模なハルシネーション) |
| デュアルユース | 正当な能力(コーディング、化学)が害のために悪用される |
LLMの安全性に関する脅威のカテゴリ。
安全性学習パイプライン
主な安全性メカニズム
Important
安全性の技法
データフィルタリング :事前学習コーパスから、毒性、バイアス、PIIを含むテキストを除去する。
安全性SFT :適切な拒否の例(「その理由ではお手伝いできません……」)で学習する。
Constitutional AI (Bai et al. 2022:原則を使って自己批評し、規則の憲法に照らして自分の出力を修正する。
安全性報酬モデル :安全性の注釈付きペアで別のRMを学習し、RLHF中に有用性RMと重み付き和で組み合わせる。
ガードレール :サービング時に有害な要求や応答を遮断する入力/出力分類器。
レッドチーミング (Perez et al. 2022:展開前に失敗モードを見つける体系的な敵対的評価。
有用性と安全性のトレードオフ
Tip
有用性と安全性のバランス
安全性を過度に最適化すると、過剰拒否の問題が生じる。モデルが無害な要求を拒否してしまうためである(例えば教育的な文脈で歴史上の暴力について議論することを拒否する)。目標は、安全性の制約内で最大限に有用なパレート最適方策である。 \[ \max_\theta ; \mathbb{E}[R_\text{helpful}] \quad \text{subject to} \quad \mathbb{E}[R_\text{safety}] \geq \tau \] 実際には、\(R = \alpha R_\text{helpful} + (1-\alpha) R_\text{safety}\)という重み付き報酬として実装し、\(\alpha\)(通常0.6〜0.8)を慎重に調整する。MetaのLlama-3は、マージンベースの重み付けを用いて安全性と有用性に別々の報酬モデルを使うと報告している(Grattafiori et al. 2024)。
評価
-
安全性ベンチマーク :ToxiGen、RealToxicityPrompts、BBQ(バイアス)、CrowS-Pairs
-
ジェイルブレイクへの頑健性 :GCG攻撃(Zou et al. 2023)、マルチターンのジェイルブレイク、エンコードされたプロンプト
-
過剰拒否率 :無害なプロンプトに対する誤検知の拒否を測定する(目標\(<\)5%)。
-
レッドチーム評価 :領域専門家(バイオセキュリティ、サイバーセキュリティ)による人手の敵対的テスト
Warning
安全性に終わりはない
どのような技法の組み合わせでも絶対的な安全性は得られない。新しい攻撃経路は継続的に発見される(マルチモーダルなジェイルブレイク、安全性学習を除去するファインチューニング攻撃、多数ショットプロンプティングなど)。安全性には継続的な監視、新たな脅威への迅速な対応、多層防御(複数の独立した層)が必要である。
LLMシステムの基礎
GPUアーキテクチャ — シリコンからLLM学習まで
現代の大規模言語モデルは、ほぼ例外なくGPU(Graphics Processing Unit、画像処理装置)上で学習・提供されます。GPUアーキテクチャを理解することは、並列化戦略、メモリ管理、カーネル最適化、インフラ規模のサイジングについて、情報に基づく判断を行うために不可欠です。ここでは、LLMワークロードに関係するGPUハードウェアを包括的に紹介します。
なぜ深層学習にGPUを使うのか?
GPUとCPUは、根本的に異なるハードウェア設計思想を持っています。この違いを理解すると、LLMの学習がGPU上では100〜1000\(\times\)高速になる理由が分かります。
Tip
CPUとGPU — 根本的な設計思想
CPU はレイテンシ向けに最適化されています。大容量キャッシュ、分岐予測、アウト・オブ・オーダー実行を備え、少数のスレッドを可能な限り高速に実行します。現代のCPUは8〜96コアです。
GPU はスループット向けに最適化されています。それぞれが単純な処理を行う数千のスレッドを並列に実行します。現代のGPUには数千の「コア」(実行ユニット)があり、ストリーミング・マルチプロセッサ(SM)にグループ化されています。
深層学習のワークロードは、行列乗算(\(O(n^3)\)の演算を\(O(n^2)\)のデータに対して行う)に支配されます。行列乗算は非常に並列化しやすい処理です。70BモデルのTransformerの1回のフォワードパスは、トークンあたり約\(\sim\)140 TFLOPの計算を必要とするため、GPUのスループットに最適です。
NVIDIA GPUマイクロアーキテクチャの世代
NVIDIAはGPUアーキテクチャを継続的にリリースしており、それぞれが深層学習に重要な革新をもたらしています。
| アーキテクチャ | 年 | 代表製品 | 深層学習における主な革新 |
|---|---|---|---|
| Pascal | 2016 | P100 | 初のHBM GPU、FP16対応、NVLink 1 |
| Volta | 2017 | V100 | Tensor Core (第1世代)、混合精度学習 |
| Turing | 2018 | T4 | INT8推論、RTコア(ML向けではない) |
| Ampere | 2020 | A100 | BF16 Tensor Core、TF32、第3世代NVLink、MIG |
| Hopper | 2022 | H100 | FP8 Tensor Core、TMA、Transformer Engine、NVLink 4 |
| Blackwell | 2024 | B200 | 第2世代Transformer Engine、NVLink 5(1.8 TB/s)、FP4 |
深層学習向けNVIDIA GPUマイクロアーキテクチャのタイムライン。
LLMの学習と推論に使われる主なGPU
| GPU | アーキテクチャ | HBM | BF16 TF | HBM帯域幅 | NVLink | LLMでの役割 |
|---|---|---|---|---|---|---|
| V100-32GB | Volta | 32 GB | 125 TF* | 900 GB/s | 300 GB/s | レガシー、小規模モデルのファインチューニング |
| A100-40GB | Ampere | 40 GB | 312 TF | 1.5 TB/s | 600 GB/s | 低予算の学習・推論 |
| A100-80GB | Ampere | 80 GB | 312 TF | 2.0 TB/s | 600 GB/s | 標準的なRLHF(70Bで8〜64枚) |
| H100 SXM | Hopper | 80 GB | 990 TF | 3.35 TB/s | 900 GB/s | 学習が3\(\times\)高速 |
| H200 SXM | Hopper | 141 GB | 990 TF | 4.8 TB/s | 900 GB/s | より少ないGPUで70Bの方策+参照モデルを収容 |
| B200 SXM | Blackwell | 192 GB | 2250 TF | 8.0 TB/s | 1800 GB/s | 次世代、H100の2\(\times\) |
| MI300X | CDNA3 | 192 GB | 1300 TF | 5.3 TB/s | N/A | 最大のメモリ容量、ROCm |
| TPU v5e | 16 GB | 197 TF | 1.6 TB/s | ICI 1.6 TB/s | クラウド専用、JAX/XLA |
Warning
どのGPUを選ぶべきか?
70B以上のモデルを学習する場合 :NVLinkを備えたH100/B200ノード(テンソル並列化には高速なインターコネクトが必要)。1インスタンスあたり最低8\(\times\)H100。
推論(レイテンシ重視) :高帯域幅が必要ならH100/H200、メモリ帯域律速(巨大なKVキャッシュ)ならMI300X。
7B〜13Bのファインチューニング :A100-80GBが費用対効果に優れています。LoRAなら単一GPUで可能です。
低予算 :A100-40GB、あるいは7BモデルのLoRAならA10(24GB)でも可能です。
GPU内部アーキテクチャ — ストリーミング・マルチプロセッサ(SM)
GPUは ストリーミング・マルチプロセッサ(SM) の配列として構成されます。各SMは、専用のレジスタファイル、共有メモリ、実行ユニットを持つ独立したプロセッサです。GPUの性能を理解するには、SMを理解することが重要です。
Important
SMの主要コンポーネント
CUDAコア :FP32/INT32演算用のスカラーALU。A100ではSMあたり64個。要素単位の演算、リダクション、行列以外の演算に使われます。
Tensor Core :行列積和(MMA)に特化したユニット。各ユニットは1サイクルで\(4{\times}4{\times}4\)の融合積和を実行します。A100ではSMあたり4個で、対応する精度ではCUDAコアの\(16\times\)のスループットを実現します。
レジスタファイル :最速の記憶領域(レイテンシ1サイクル)。アクティブな全スレッドで共有されます。L1へのレジスタ・スピルは大幅な低速化を引き起こします。
共有メモリ/L1 :プログラマーが明示的に管理するオンチップSRAM。Flash Attentionの性能の鍵です(タイル全体を共有メモリに収められます)。
ワープ・スケジューラ :各SMにはワープ・スケジューラが4個あります(A100)。ワープはロックステップで実行される32スレッドの単位です(SIMTモデル)。スケジューラはワープを切り替えてメモリレイテンシを隠蔽します。
Tip
SIMT実行モデル
GPUはSingle Instruction, Multiple Threads(SIMT)実行を使います。ワープ(32スレッド)の内部では、全スレッドが同じ命令を異なるデータに対して実行します。スレッドが分岐すると(例:
if/else)、両方の経路が直列化されます。これをワープ分岐と呼びます。そのため、GPUカーネルでは分岐を最小限に抑える必要があります。LLMワークロードの主要な演算(GEMM、アテンション、softmax)はスレッド間で制御フローが均一であり、SIMT実行に適しています。
世代をまたぐGPUチップのスケーリング
NVIDIA GPUアーキテクチャの進化では、計算密度、オンチップメモリ、深層学習用の専用ユニットが一貫して拡大してきました。
| アーキテクチャ | SM数 | TC/SM | SRAM/SM | L2 | 主な変化 |
|---|---|---|---|---|---|
| Volta (V100) | 80 | 8 | 128 KB | 6 MB | Tensor Coreを導入 |
| Ampere (A100) | 108 | 4 | 192 KB | 40 MB | BF16/TF32、L2の大容量化 |
| Hopper (H100) | 132 | 4 | 256 KB | 50 MB | TMA、FP8、Thread Block Cluster |
| Blackwell (B200) | 148 | 4 | 256 KB | 128 MB | 2\(\times\)ダイ、FP4、TMEM、NVLink 5 |
NVIDIAアーキテクチャにおけるSMレベルのスケーリング。
GPUメモリ階層と帯域幅
現代のGPUにおける学習・推論性能は、メモリ階層をまたぐデータ移動をどれだけうまく管理できるかによって、ほぼ完全に決まります。メモリ階層を理解することは任意ではありません。後の各最適化手法の基盤です。
Important
GPUメモリ階層 — A100 80GBの基準値
レベル 容量 帯域幅 レイテンシ 場所 レジスタ \(\sim\)256 KB/SM \(>\)100 TB/s 1サイクル オンチップ、スレッド単位 SRAM(共有) 164 KB/SM \(\sim\)19 TB/s \(\sim\)20サイクル オンチップ、SM単位 L2キャッシュ 合計40 MB \(\sim\)5 TB/s \(\sim\)200サイクル オンチップ、共有 HBM2e(VRAM) 80 GB 2 TB/s \(\sim\)200 ns パッケージ上(5スタック) CPU DRAM 512 GB以上 \(\sim\)25 GB/s \(\sim\)10 \(\mu\)s ホスト(PCIe 4) NVMe SSD TB単位 7 GB/s \(\sim\)100 \(\mu\)s ホストストレージ
Tip
なぜ差がこれほど大きいのか
階層の各レベルは、その1つ上のレベルに比べておおむね 10\(\times\)遅く、100〜1000\(\times\)大きい ものです。A100はBF16 Tensor Coreで312 TFLOP/sのスループットを持ちますが、HBM帯域幅は2 TB/sしかありません。つまり、HBMからロードした1バイトごとに、次のバイトが到着するまでに\(312 \times 10^{12} / (2 \times 10^{12}) \approx 156\)回の浮動小数点演算を実行できます。カーネルが1バイトあたり156 FLOP未満しか実行しない場合、そのカーネルはメモリ律速です。計算ユニットはデータを待ってアイドル状態になります。
レジスタ
各CUDAスレッドはプライベートなレジスタファイルを利用できます。レジスタはチップ上で最速の記憶領域です。調停なしに1クロックサイクルで読み書きできます。A100にはSMあたり65,536個の32ビットレジスタがあります。レジスタをローカルメモリ(L1/L2)へスピルさせることは、大きな性能上の危険要因です。
SRAM — 共有メモリ/L1
各SMには、A100で192 KB(H100では256 KB)のL1/共有メモリ統合プールがあり、A100では最大164 KBを共有メモリとして構成できます。共有メモリはプログラマー(または新しいCUDAバージョンのコンパイラー)が明示的に管理します。たとえばFlash Attentionは、アテンションのタイル計算をSRAMに収められるという洞察を中心に構築されています。
L2キャッシュ
A100の40 MBのL2は、108個すべてのSMで共有されます。SRAMとHBMの間のステージング領域として機能します。空間的局所性が高いワークロード(たとえばバッチ内で重み行列に繰り返しアクセスする場合)では、L2のヒット率が実効的なHBMトラフィックを大幅に削減します。
HBM — High Bandwidth Memory
HBMはGPUパッケージに直接搭載された積層DRAMで、広いインターポーザーを介して接続されています。A100 SXMは、2 TB/sで80 GBのHBM2eを備えています。H100 SXM5は、3.35 TB/sで80 GBのHBM3を備えています。これは、モデルの重み、KVキャッシュ、アクティベーション、オプティマイザー状態の主要な作業メモリです。
PCIe経由のCPU DRAM
GPUのHBMとCPUのDRAM間のデータ転送は、PCIeバスを通過します。PCIe Gen4 \(\times\)16は、方向あたり約\(\sim\)32 GB/s(双方向で64 GB/s)を提供し、Gen5ではその倍になります。これはHBM(片方向)と比べて約\(\sim\)60\(\times\)帯域幅が小さい値です。CPUオフロード(ZeRO-Infinity、DeepSpeed)はこのリンクを利用しますが、ボトルネックにならないよう注意して使う必要があります。
NVMe
NVMe SSD(Samsung 990 Proなど)は、シーケンシャル読み出しで約\(\sim\)7 GB/sに達します。ZeRO-Infinityはオプティマイザー状態をNVMeへオフロードできますが、これは計算とI/Oの比率が非常に高い場合(大きなバッチサイズ、遅い学習ステップ)に限って実用的です。
算術強度とRooflineモデル
Important
算術強度
\[ I = \frac{\text{FLOPs}}{\text{Bytes accessed from HBM}} \quad \text{(FLOPs / Byte)} \] カーネルは、\(I < I_{\text{ridge}}\)のとき メモリ律速 、\(I > I_{\text{ridge}}\)のとき 計算律速 です。ここで \[ I_{\text{ridge}} = \frac{\text{Peak FLOP/s}}{\text{Peak Bandwidth}} = \frac{312 \times 10^{12}}{2 \times 10^{12}} = 156 \text{ FLOP/Byte (A100 BF16)} \]
Note
アテンションの算術強度
シーケンス長\(n=4096\)、ヘッド次元\(d=128\)の単一アテンションヘッドを考えます。
FLOP :\(QK^T\)には\(2n^2d\)、softmaxには\(O(n^2)\)、\(\text{Attn} \times V\)には\(2n^2d\)のコストがかかります。合計は\(\approx 4n^2 d = 4 \times 4096^2 \times 128 \approx 8.6\) GFLOPです。
メモリトラフィック (標準的な非Flash実装):
\(Q, K\)を読み出す:\(2 \times n \times d \times 2 = 2\) MB
アテンションスコア\(S = QK^T\)を書き出す:\(n^2 \times 2 = 33.5\) MB
softmaxのために\(S\)を読み出す:\(n^2 \times 2 = 33.5\) MB
softmax出力\(P\)を書き出す:\(n^2 \times 2 = 33.5\) MB
最終行列乗算のために\(P\)と\(V\)を読み出す:\(n^2 \times 2 + n \times d \times 2 = 34.5\) MB
出力\(O\)を書き出す:\(n \times d \times 2 = 1\) MB
総メモリ量 :\(\approx 138\) MB(\(n^2\)のアテンション行列を4回走査することが支配的です)。
算術強度 : \[ I = \frac{8.6 \times 10^9}{138 \times 10^6} \approx 62 \text{ FLOP/Byte} \]
これはA100のリッジポイントの\(62/156 = 40%\)であり、 明確にメモリ律速 です。GPUはメモリを待って60%の時間アイドルになります。
Flash Attentionによる解決 :\(n \times n\)行列を実体化せず(\(Q, K, V\)をSRAMでタイル化する)、Flash AttentionはHBMトラフィックを\(Q, K, V\)の読み出しと\(O\)の書き出しだけに削減します:\(4 \times n \times d \times 2 = 4\) MB。ロードした各バイトは\(O(n)\)回の計算で再利用されます(各クエリがすべてのキーにアテンションするため)。したがって、 \[ I = \frac{4n^2 d}{4 \cdot n \cdot d \cdot 2} = \frac{n}{2} = \frac{4096}{2} = 2048 \text{ FLOP/Byte} \]
これはリッジポイント(156)の\(13\times\)上であり、 深く計算律速 です。GPUはピークの312 TFLOPSに達し、必要な帯域幅は\(312\text{T}/2048 \approx 152\) GB/s(HBM容量の7.6%)にすぎません。もはやメモリはボトルネックではありません。
アテンションはメモリ律速、FFNは計算律速
Tip
Transformerにおける2つの領域
Transformerブロックには、算術強度が大きく異なる2つの主要コンポーネントがあります。
アテンション :\(n \times d\)テンソルを処理します。\(QK^T\)積は\(O(n^2 d)\) FLOPを必要としますが、アテンションスコアのために\(O(n^2)\)のメモリを必要とします。長いシーケンスではメモリトラフィックが支配的になり、アテンションはメモリ律速です。
FFN(MLP) :形状\([d_{\text{model}}, 4d_{\text{model}}]\)の重み行列を持つ2つの大規模線形層です。これらは算術強度の高い大規模GEMMであり、FFNは計算律速です。
そのため、Flash Attention(メモリ最適化)はアテンションには有効ですがFFNには効きにくく、量子化(重みサイズの削減)はアテンションよりFFNに大きな効果があります。
Tensor Core
Important
Tensor Coreとは何か?
Tensor Coreは、Volta(2017年)で導入された行列積和(MMA)専用ユニットです。各Tensor Coreは、1クロックサイクルで\(4\times4\times4\)の行列乗算を実行します。 \[ D = A \times B + C \quad (4\times4 \text{ matrices}) \] A100には、108個のSM全体で 432個のTensor Core があります(SMあたり4個、各サブパーティションに1個)。BF16精度では312 TFLOP/sを実現し、FP32 CUDAコアのスループットの約\(16\times\)です。
-
対応精度 :FP64、TF32、BF16、FP16、INT8、FP8(H100以降)。
-
累積 :BF16入力であっても、内部では常にFP32で累積します。これにより、ドット積での壊滅的な桁落ちを防ぎます。
-
要件 :Tensor Coreは、行列の次元が8(BF16)または16(FP8)の倍数のときに最も効率的です。こうした倍数に合わせたパディングは、しばしば実施する価値があります。
-
WGMMA(H100) :Hopperでは、より大きなタイル(64\(\times\)256\(\times\)16)を処理するワープグループ単位のMMA命令が導入され、TMA(Tensor Memory Accelerator)によるデータ移動とパイプライン化できます。
Warning
Tensor Coreの落とし穴
Tensor Coreが役立つのは、カーネルが計算律速の場合だけです。小さなバッチ(バッチサイズ1の推論など)ではGEMMのタイルが小さく、Tensor Coreの利用率が低くなり、再びメモリ律速の領域に戻ります。そのため推論エンジンはリクエストを積極的にバッチ化します。
通信アーキテクチャ — NVLink、InfiniBand、PCIe
分散LLMの学習と推論では、GPU、ノード、ストレージ間で膨大なデータを移動する必要があります。大規模学習では、この通信ファブリックがボトルネックになることがよくあります。
PCIe — ホストとデバイスを結ぶリンク
Important
PCIeの世代
世代 x16帯域幅(各方向) 双方向 注記 PCIe Gen3 16 GB/s 32 GB/s 古いサーバーで一般的 PCIe Gen4 32 GB/s 64 GB/s A100 PCIe、現在の大半のサーバー PCIe Gen5 64 GB/s 128 GB/s H100 PCIe、普及途上
PCIeは次の用途に使われます。
-
CPU \(\leftrightarrow\) GPU間のデータ転送(モデルの読み込み、CPUオフロード)
-
NVLinkが利用できない場合のノード間GPU通信(まれで非常に低速)
-
NVMeストレージへのアクセス(CPU経由)
Warning
PCIeはGPU間通信向けではない
NVLinkが利用できる場合は、GPU間通信をPCIe経由にしてはいけません。PCIeの帯域幅(32 GB/s)はNVLink 4(900 GB/s)より28\(\times\)低速です。NVLinkのないマルチGPUサーバー(一般向けGPUなど)ではGPU間帯域幅がPCIeに制限されるため、テンソル並列化は極めて遅くなります。
NVLink — ノード内高速インターコネクト
Important
NVLinkの世代
世代 リンク数 総帯域幅 GPU NVLink 2 6 300 GB/s V100 NVLink 3 12 600 GB/s A100 NVLink 4 18 900 GB/s H100 NVLink 5 18 1800 GB/s B200 (Blackwell)
NVLinkは同じノード上のGPU間を結ぶポイント・ツー・ポイントのインターコネクトです。各リンクは双方向です。H100 SXM5にはNVLink 4のリンクが18本あり、1本あたり双方向で50 GB/s、合計900 GB/sを提供します。
NVSwitch
DGX H100システムでは、8枚すべてのGPUがNVSwitchを介して接続されます。NVSwitchは完全二分帯域幅を提供する専用スイッチングチップです。これにより、リング内の隣接GPUだけでなく、任意のGPUが他の任意のGPUと、同時にNVLinkの最大速度で通信できます。
Tip
リングと完全二分帯域幅
リングトポロジ(8 GPU)では、AllReduceのデータはリングを一周する必要があります。各リンクは総データ量の\(\frac{2(N-1)}{N}\)を運ぶため、アルゴリズム上の帯域幅は\(B_{\text{link}} \times \frac{N}{2(N-1)}\)(\(N=8\)では約\(0.57 \times B_{\text{link}}\))です。NVSwitchの完全二分帯域幅では、AllReduceがツリーベースのアルゴリズムで全リンクを同時に利用でき、ピークに近い帯域幅を達成します。実際のDGX H100では、リングは約\(\sim\)700 GB/sのバス帯域幅、NVSwitchは約\(\sim\)900 GB/sを実現します。
InfiniBand — ノード間通信
ノード(サーバー)間の通信には、InfiniBandがGPUメモリへのダイレクトアクセスを備えた、高帯域幅・低レイテンシのネットワークを提供します。
Important
InfiniBand NDR
NDR 400Gb/s = ポートあたり50 GB/s(単方向)
HDR 200Gb/s = ポートあたり25 GB/s(前世代)
RDMA :Remote Direct Memory Access(リモート・ダイレクト・メモリアクセス)。GPUはリモートCPUを介さずに、リモートGPUのメモリを読み書きできます。
GPUDirect RDMA :データはCPUとシステムDRAMを完全にバイパスし、HBM \(\to\) NIC \(\to\) ネットワーク \(\to\) NIC \(\to\) HBMへ直接流れます。
レイテンシ :小さなメッセージでは約\(\sim\)1〜2 \(\mu\)s(TCP/IPでは約\(\sim\)100 \(\mu\)s)。
Fat-Treeトポロジ
大規模GPUクラスターではFat-Treeネットワークトポロジが使われます。\(k\)ポートのスイッチを使う3層Fat-Treeは、完全二分帯域幅で\(k^3/4\)ノードをサポートします。\(k=64\)ポートの400Gb/s NDRスイッチでは、\(64^3/4 = 65{,}536\)ノードです。
レール最適化トポロジ
実際のクラスターでは、各ノード内の各GPUを異なるトップ・オブ・ラックスイッチに接続するレール最適化トポロジが使われます。これにより、すべてのGPUが関係するAllReduce操作で、すべてのネットワークリンクを同時に使い、帯域幅を最大化できます。
分散LLM学習における通信パターン
分散学習は、集合通信プリミティブに依存します。どのプリミティブを選ぶかによって、必要な帯域幅とスケーリングの挙動が決まります。
Important
通信プリミティブ
プリミティブ 用途 通信量 AllReduce 勾配同期(DDP、FSDP) \(2(N-1)/N \times\)パラメータサイズ AllGather シャード化された重みを収集(FSDP) \((N-1)/N \times\)パラメータサイズ ReduceScatter 勾配を分散(FSDP) \((N-1)/N \times\)パラメータサイズ AllGather テンソル並列化のアクティベーション アクティベーションサイズ Point-to-Point パイプライン並列化(send/recv) マイクロバッチのアクティベーション Broadcast 重みの同期(新しいワーカー) モデル全体のサイズ
Note
帯域幅の計算 — 70Bモデルの勾配AllReduce
設定 :70Bパラメータモデル、BF16勾配、8ノード\(\times\)8 GPU = 64 GPU。データ並列度 = 64。
勾配サイズ :\(70 \times 10^9 \times 2\)バイト\(= 140\) GB。
GPUあたりのAllReduce通信量 (リング):\(2 \times (64-1)/64 \times 140 \approx 275\) GB。
利用可能なノード間帯域幅 :ノードあたり8 GPU\(\times\) GPUあたり50 GB/s \(= 400\) GB/s(レール最適化トポロジで8個すべてのNICがアクティブ)。
AllReduce時間 :\(275 / 400 \approx 0.69\)秒/ステップ。
意味 :計算ステップが1秒の場合、通信によって0.69秒(ステップ時間全体の41%)が追加されます。これが、勾配圧縮、混合精度、FSDP(通信と計算をオーバーラップさせる)が重要な理由です。
ネットワークトポロジ図
次の図は、ノード内(NVLink)とノード間(InfiniBand)の通信経路を示す、典型的な2ノードGPUクラスターのトポロジを表しています。
Tip
帯域幅に基づく並列化の選択
テンソル並列化(TP) :各層でAllReduceが必要です。ノード内でNVLinkを使う場合に限って使用します。TP=8はH100 DGXノードで標準的です。
パイプライン並列化(PP) :ステージ間のポイント・ツー・ポイント通信です。ノードをまたげますが、パイプライン・バブルのオーバーヘッドが追加されます。TPだけではモデルを収められない場合に使います。
データ並列化(DP) :勾配のAllReduceです。IB経由でノードをまたげます。高速なIBがあれば適切にスケールします。
FSDP/ZeRO :AllGather+ReduceScatterです。DPと似ていますが、オプティマイザー状態をシャード化します。大規模モデルではDPより推奨されます。
vLLM — PagedAttentionと高スループット推論
vLLM (Kwon et al. 2023)は、PagedAttentionを導入しました。これは、オペレーティングシステムがRAMに使うページング抽象化を借用し、GPUのKVキャッシュに適用したものです。LLM推論では、過去のすべてのトークンのキー・テンソルとバリュー・テンソルを保存するKVキャッシュが、メモリ消費の大部分を占めます。これを効率的に管理することが、高スループット推論の中心的な課題です。
KVキャッシュの断片化問題
Important
KVキャッシュのメモリ公式
\(L\)層、\(H\)ヘッド、ヘッド次元\(d\)、\(n\)トークンのシーケンスを持つモデルの場合: \[ \text{KV cache size} = 2 \times L \times H \times d \times n \times \text{bytes_per_element} \] Llama-3 70B(BF16)の場合:\(L=80\)、\(H=8\)(GQA)、\(d=128\): \[ = 2 \times 80 \times 8 \times 128 \times n \times 2 = 327{,}680 \times n \text{ bytes} \] \(n=4096\)トークンの場合:シーケンスあたり\(\approx 1.3\) GB。
Tip
内部断片化と外部断片化
従来の推論システムは、各シーケンスのKVキャッシュ用に、最大シーケンス長に合わせた連続メモリ領域をあらかじめ確保します。これにより、2種類の無駄が生じます。
内部断片化 :500トークンしか生成しないシーケンスでも、4096トークン用に予約された領域を保持します。未使用の3596トークン分が無駄になります。
外部断片化 :多数のシーケンスが完了すると、空きメモリは小さく不連続な隙間に分かれます。空き容量の合計が十分でも、単一の連続領域が十分に大きくないため、新しい長いシーケンスを割り当てられません。
実際には、単純な割り当てでのGPUメモリ利用率は20〜40%にすぎないことがよくあります。
PagedAttention — KVキャッシュの仮想メモリ
PagedAttention(Kwon et al., 2023)は、オペレーティングシステムのページング抽象化を借用します。シーケンスごとに1つの連続領域を持つ代わりに、KVキャッシュを固定サイズの ページ (ブロック)に分割します。そしてCPUのページテーブルに相当する間接参照テーブルが、各シーケンスの論理的なトークン位置を、GPUメモリ上に分散した物理アドレスへ変換します。
Important
PagedAttentionの中核概念
ブロックサイズ :通常は1ブロックあたり16トークン(調整可能)。各ブロックには\(16 \times 2 \times L \times H \times d\)個の要素が格納されます。
ブロックテーブル :シーケンスごとに、論理ブロックのインデックスをGPUメモリプール内の物理ブロックのインデックスへ対応付けます。
物理ブロックプール :固定サイズのブロックを事前に確保したプール。割り当ては\(O(1)\)で、空きリストから取り出すだけです。
アテンションカーネル :アテンション計算中にブロックテーブルを使い、不連続な物理位置からKVブロックを収集するよう変更されています。
Note
ブロックテーブルの例
ブロックサイズを4トークンとし、2つのシーケンスがあるとします。
シーケンスA(7トークン):論理ブロック [0,1] \(\to\) 物理ブロック [3, 7]
シーケンスB(5トークン):論理ブロック [0,1] \(\to\) 物理ブロック [1, 5]
物理ブロック3にはシーケンスAのトークン0〜3が格納されます。物理ブロック7にはシーケンスAのトークン4〜6が格納されます(部分的に使用)。シーケンスAのアテンションカーネルは、間接参照層としてブロックテーブルを使い、物理ブロック3、7の順に読み出します。
PagedAttentionの利点
ほぼゼロの無駄
内部断片化は、シーケンスごとに最大でも部分的に埋まった最後のブロック1つ分に抑えられます。ブロックサイズが16なら、最悪の場合の無駄はシーケンスあたり15トークンであり、無視できます。ブロックは固定サイズで交換可能なので、外部断片化はなくなります。
動的割り当て
ブロックはシーケンスの成長に応じてオンデマンドで割り当てられます。最終的なシーケンス長を事前に知る必要はありません。出力長が未知である生成処理では、これは非常に重要です。
プレフィックス共有(コピーオンライト)
共通のプレフィックス(システムプロンプトなど)を持つ複数のシーケンスは、そのプレフィックスに対して 同じ物理ブロック を共有できます。ブロックテーブルは、複数のシーケンスを同じ物理ブロックへ指し示すだけです。シーケンスが共有ブロックへの書き込みを必要とするとき(プレフィックスから分岐するとき)、コピーオンライトが発生します。
Tip
プレフィックス共有による節約
1000トークンのシステムプロンプトを128人の同時ユーザーに提供するチャットボットを考えます。
プレフィックス共有なし:システムプロンプトのKVキャッシュだけで\(128 \times 1000 \times 327{,}680 / 10^9 \approx 42\) GB
プレフィックス共有あり:\(1 \times 1000 \times 327{,}680 / 10^9 \approx 0.33\) GB
節約量:共有プレフィックス部分で約\(\sim\)128\(\times\)
スワップによるプリエンプション
GPUメモリが枯渇すると、vLLMはKVブロックをCPU DRAMへスワップすることで、シーケンスをプリエンプトできます(またはブロックを単純に破棄し、後で再計算できます)。ブロックが自己完結していて不連続であるからこそ可能な方法です。連続領域の割り当てをスワップするには、バッファ全体をコピーしなければなりません。
継続的バッチ処理
従来のバッチ処理(「静的バッチ処理」)は、すべてのシーケンスが完了するまで新しいシーケンスを開始しません。あるシーケンスが500トークン、別のシーケンスが10トークンを生成する場合、短いシーケンスについてGPUは490ステップの間アイドルになります。これは非常に非効率です。
Important
継続的バッチ処理
継続的バッチ処理(イテレーションレベル・スケジューリングとも呼ばれます)は、一度に1つのデコードステップを処理します。各ステップの後に次を行います。
生成されたEOSトークンを確認し、完了したシーケンスを調べる
完了したシーケンスをバッチから削除し、KVブロックを解放する
待機中の新しいシーケンスを追加し、空いたスロットを埋める
更新されたバッチで次のデコードステップを実行する
バッチの構成は各ステップで変わり、シーケンスが動的に参加・離脱します。これによりGPU利用率をほぼ100%に保ち、スループットを大幅に改善できます(静的バッチ処理に比べて1.5〜3\(\times\))。ここではPagedAttentionが不可欠です。バッチ途中でのシーケンス追加・削除にはKVブロックの動的な割り当て・解放が必要であり、ページ化メモリでのみ効率的に実行できるからです。
vLLMにおける投機的デコーディング
投機的デコーディングは、小さなドラフトモデル(例:1Bパラメータ)を使って\(k\)個の候補トークンを素早く提案し、大きなターゲットモデルが1回のフォワードパスで検証します。最初に拒否されるまでのすべてのトークンが受理されます(検証ステップあたりの期待受理数は3〜5トークン)。これにより、品質を損なわず、レイテンシ重視の単一シーケンス生成を2〜3\(\times\)高速化できます。
vLLMは投機的デコーディングをPagedAttentionと統合しています。
-
ドラフトトークンには投機的KVブロックが割り当てられる
-
拒否された場合、投機的ブロックは解放される(ページ化割り当てなら低コスト)
-
受理された場合、投機的ブロックはメインシーケンスへ昇格される
-
ブロックテーブルの更新は\(O(k)\)で、数個のテーブルエントリを更新するだけ
大規模環境での具体的なメモリ節約 — 70Bモデル
Note
メモリ予算 — 70B BF16推論
設定 :Llama-3 70B、BF16、単一のA100 80GBノード(8 GPU、テンソル並列化)。
モデルの重み :\(70 \times 10^9 \times 2\)バイト\(= 140\) GB \(\div\) 8 GPU \(= 17.5\) GB/GPU。
KVキャッシュ用の残り :\(80 - 17.5 - 3\)(オーバーヘッド)\(= 59.5\) GB/GPU。
GPUあたりのトークンごとのKVキャッシュ (TP=8で、各GPUはヘッドの\(1/8\)を保持):\(2 \times 80 \times 1 \times 128 \times 2 = 40{,}960\)バイト\(\approx 40\) KB/トークン。
KVキャッシュ内の最大トークン数 :\(59.5 \times 10^9 / 40{,}960 \approx 1.45\)百万トークン。
4096トークンのシーケンスを128本同時実行する場合 :\(128 \times 4096 = 524{,}288\)トークンで、予算内に十分収まります。
PagedAttentionなし (各シーケンスに最大長4096を事前割り当て):計算は同じですが、平均で約\(\sim\)50%が断片化によって無駄になります \(\to\) 収容できるのは64シーケンスだけです。
Warning
ブロックサイズのトレードオフ
ブロックサイズを大きくすると、ブロックテーブルのオーバーヘッドが減り、メモリアクセスの局所性が改善します(分散した読み出しが減ります)。一方、ブロックサイズを小さくすると、内部断片化が減り、より細粒度のプレフィックス共有が可能になります。vLLMのデフォルトは16トークン/ブロックで、バランスのよい設定です。非常に長いシーケンス(10万トークン以上)では、より大きなブロック(32〜64)が適している場合があります。
vLLM:エンドツーエンドシステム
vLLMは、PagedAttentionを完全なサービングスタックの内部に組み込みます。継続的バッチ処理、プレフィックスキャッシュ、投機的デコーディング、テンソル並列モデルのシャーディングが連携し、GPU 1ドルあたりのスループットを最大化します。
アーキテクチャの概要
中核コンポーネント
-
API Server :OpenAI互換のリクエスト(completion、chat)を受け付けます。入力をトークン化し、ビームサーチや複数サンプルのための「シーケンスグループ」を作成します。
-
Scheduler :vLLMの司令塔です。次の3つのキューを管理します。
-
waiting:まだ開始されていない新しいリクエスト(プリフィル待ち) -
running:トークンをアクティブに生成しているリクエスト(デコード段階) -
swapped:KVキャッシュがCPUへオフロードされたプリエンプト済みリクエスト
各イテレーションで、Schedulerは利用可能なGPUメモリブロックに基づき、どのリクエストを実行するか決定します。
-
-
Block Manager :KVキャッシュの仮想メモリ抽象化を実装します。論理ブロック(シーケンスごと)を物理ブロック(GPUメモリプール内)へ対応付けます。次を処理します。
-
割り当て(新しいトークンが生成される \(\rightarrow\) 新しいブロックが必要)
-
コピーオンライト(ビームサーチ用:複数のビームがプレフィックスブロックを共有し、分岐時だけコピー)
-
スワップ(プリエンプト/再開時のGPU \(\leftrightarrow\) CPU移行)
-
プレフィックスキャッシュ(プロンプトが共通プレフィックスを持つとき、キャッシュ済みブロックを再利用)
-
-
Model Executor :実際のLLMフォワードパスを実行します。GPU間のテンソル並列化を管理し、ページ化KVキャッシュブロックを読み取るアテンションカーネルをディスパッチします。
-
KV Cache Pool :固定サイズのブロックに分割された事前割り当てGPUメモリ(デフォルト:16トークン \(\times\) num_heads \(\times\) head_dim \(\times\) 2バイト/ブロック)。実行時に動的割り当てを行わない \(\rightarrow\) 断片化はゼロです。
リクエストのライフサイクル(エンドツーエンドの流れ)
-
到着 :クライアントがプロンプトを送信します。APIサーバーはトークン化し、
SequenceGroupを作成してwaitingキューに入れます。 -
スケジューリング :各ステップでSchedulerは次を実行します。
-
swappedのシーケンスを再開できるか確認する(空きブロックが十分か)。 -
waitingのシーケンスがプリフィルを開始できるか確認する(プロンプト全体に十分なブロックがあるか)。 -
runningのシーケンスに残りのブロックを割り当てる(現在のブロックが満杯なら、ステップごとにシーケンスあたり1つの新しいブロックが必要)。 -
予算を超える場合、優先度の低い
runningのシーケンスをプリエンプトする(KVをCPUへスワップするか、後で再計算する)。
-
-
プリフィル (リクエストの最初のイテレーション):プロンプト全体を1回のフォワードパスで処理します。すべてのプロンプトトークンのKVキャッシュを計算し、割り当てられたブロックに格納します。これは計算律速です(大量のトークンをまとめて処理するため)。
-
デコード (後続のイテレーション):各シーケンスについて、ステップごとに新しいトークンを1つ生成します。実行中の全シーケンスをまとめてバッチ化します(継続的バッチ処理)。これはメモリ律速です(KVキャッシュ全体を読み出し、1トークンを生成するため)。
-
ブロック割り当て :各デコードステップの後、シーケンスの最後のブロックが満杯なら、Block Managerが新しい物理ブロックを割り当て、次の論理ブロックへ対応付けます。
-
完了 :シーケンスがEOSまたは最大長に達すると、
runningから削除されます。その物理ブロックは直ちに解放されます \(\rightarrow\) 他のシーケンスが利用できるようになります。レスポンスはクライアントへストリーミングされます。
プレフィックスキャッシュ(自動プロンプトキャッシュ)
複数のリクエストが共通プレフィックス(システムプロンプト、few-shot例など)を持つ場合:
-
各論理ブロックのトークン内容をハッシュする。
-
新しいリクエストの到着時に、プレフィックスブロックがすでにキャッシュにあるか確認する。
-
ヒットした場合、そのトークンのプリフィルをスキップし、物理KVブロックを直接再利用する。Time-to-first-tokenは大幅に短縮される。
-
追い出しにはLRUポリシーを使う。メモリ圧迫が必要になるまで、キャッシュ済みブロックは解放しない。
効果 :長いシステムプロンプト(全ユーザーで共有される2000トークン以上)を持つチャットアプリケーションでは、プレフィックスキャッシュによってTTFTが60〜80%短縮されます。
vLLMにおけるガイド付き(制約付き)デコーディング
vLLMは、プラグイン可能なバックエンドを通じて制約付きデコーディング(セクション1.12.11)をネイティブにサポートしています。これにより、性能オーバーヘッドを最小限に抑えながら、サービング時に 構造化出力を保証 できます。
サポートされる制約の種類
OpenAI互換APIは、guided_*パラメータまたはresponse_formatフィールドを通じて制約を受け付けます。
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1")
# --- JSON Schema制約 ---
response = client.chat.completions.create(
model="meta-llama/Llama-3-70B-Instruct",
messages=[{"role": "user",
"content": "Extract: name, age, city from: "
"'John is 30 and lives in NYC'"}],
extra_body={
"guided_json": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"city": {"type": "string"}
},
"required": ["name", "age", "city"]
}
}
)
# スキーマに適合する有効なJSONが保証される
# --- 正規表現制約 ---
response = client.completions.create(
model="meta-llama/Llama-3-70B-Instruct",
prompt="Generate an IPv4 address: ",
extra_body={
"guided_regex": r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"
}
)
# --- 選択肢制約 ---
response = client.completions.create(
model="meta-llama/Llama-3-70B-Instruct",
prompt="Sentiment: ",
extra_body={"guided_choice": ["positive", "negative", "neutral"]}
)
バックエンドアーキテクチャ
vLLMはマスク計算をバックエンドエンジンへ委譲します。
-
XGrammar (v0.7以降のデフォルト):JSONスキーマ、正規表現、任意のEBNF文法をサポートするプッシュダウンオートマトンエンジン。効率的なC++コアにより、複雑なスキーマでは最速です。
-
Outlines (Willard and Louf 2023):FSMベースで、JSONと正規表現をサポートします。XGrammarが利用できない場合のフォールバックとして使われます。
マスクは、モデルのフォワードパスがロジットを生成した後、サンプリングの前に適用されます。FSM/PDAの状態遷移と事前計算済みインデックスのルックアップは\(O(1)\)なので、実際のオーバーヘッドは1ステップあたり\(<\)1 msです。
性能への影響
制約はロジットをマスクするだけで、アテンションやFFNの再計算を行わないため、スループットの低下は無視できます(ベンチマークで\(<\)2%)。主なコストは、スキーマをFSM/PDAインデックスへコンパイルすることです。スキーマの複雑さに応じて0.5〜5秒かかります。vLLMはリクエスト間でコンパイル済みスキーマをキャッシュするため、このコストは一意なスキーマごとに1回だけ発生します。
Warning
構造化出力 $\neq$ 正しい出力
制約付きデコーディングは、出力が構文的に有効であること(JSONとして解析でき、スキーマの型に一致すること)を保証します。しかし、意味的な正しさを保証するものではありません。モデルは、正しく解析できるものの事実としては誤った値を、依然としてハルシネーションする可能性があります。下流で必ずビジネスロジックを検証してください。
| 指標 | vLLM | HF Generate | 理由 |
|---|---|---|---|
| スループット(tok/s) | 2,500〜4,000 | 300〜600 | 継続的バッチ処理+PagedAttention |
| メモリ利用率 | 90〜95% | 50〜60% | 断片化ゼロ、動的ブロック割り当て |
| 最大同時実行シーケンス数 | 200〜500 | 16〜32 | ページ化KVによりシーケンスごとの予約が不要 |
| Time-to-first-token | 100〜300 ms | 500〜2000 ms | 繰り返し使うシステムプロンプトのプレフィックスキャッシュ |
vLLMと代替手法の性能比較(70Bモデル、A100 \(\times\) 4、TP=4)。
強化学習への導入
強化学習(Reinforcement Learning、RL)は、 エージェント が 環境 との相互作用を通じて逐次的な意思決定を学び、フィードバックとして 報酬 を受け取り、時間の経過に伴う累積報酬を最大化するように 方策 を最適化する枠組みです (Sutton and Barto 2018)。ラベル付きの入出力ペアを必要とする教師あり学習とは異なり、RLは試行錯誤を通じて最適な振る舞いを発見します。
マルコフ決定過程(MDP)
MDPは5つ組 \((S, A, P, R, \gamma)\) です。
-
\(S\):状態空間 — 環境が取りうるすべての構成
-
\(A\):行動空間 — エージェントが利用できるすべての行動
-
\(P(s'\vert s, a)\):遷移関数 — 行動 \(a\) を取った後に状態 \(s\) から状態 \(s'\) に到達する確率
-
\(R(s, a, s')\):報酬関数 — 遷移に対する即時のスカラー・フィードバック
-
\(\gamma \in [0, 1]\):割引率 — 即時の報酬に対して将来の報酬をどの程度重視するか
マルコフ性 :未来は履歴ではなく現在の状態だけに依存します。
\[ P(s_{t+1} | s_t, a_t, s_{t-1}, a_{t-1}, \ldots) = P(s_{t+1} | s_t, a_t) \]
これにより、問題を扱いやすくできます。
Tip
エージェントと環境の相互作用ループ
各時刻 \(t\) で次を行います。
エージェントが状態 \(s_t\) を観測する
エージェントが方策 \(\pi(a\vert s)\) に従って行動 \(a_t\) を選択する
環境が \(s_{t+1} \sim P(\cdot\vert s_t, a_t)\) へ遷移する
エージェントが報酬 \(r_t = R(s_t, a_t, s_{t+1})\) を受け取る
終端状態またはホライズン \(T\) に達するまで繰り返す
中核概念と定義
方策(Policy) \(\pi(a\vert s)\):状態から行動確率への写像。決定論的な場合は \(a = \pi(s)\)、確率論的な場合は \(a \sim \pi(\cdot\vert s)\) です。
リターン(Return) (累積割引報酬):
\[ G_t = \sum_{k=0}^{\infty} \gamma^k r_{t+k} = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \cdots \]
価値関数(Value Function) (方策 \(\pi\) の下で状態 \(s\) から得られる期待リターン):
\[ V^\pi(s) = \mathbb{E}_\pi\left[G_t \mid s_t = s\right] = \mathbb{E}_\pi\left[\sum_{k=0}^{\infty} \gamma^k r_{t+k} \mid s_t = s\right] \]
行動価値関数(Action-Value Function) (状態 \(s\) で行動 \(a\) を取り、その後 \(\pi\) に従う場合の期待リターン):
\[ Q^\pi(s, a) = \mathbb{E}_\pi\left[G_t \mid s_t = s, a_t = a\right] \]
アドバンテージ関数(Advantage Function) (行動 \(a\) が平均と比べてどれだけ優れているか):
\[ A^\pi(s, a) = Q^\pi(s, a) - V^\pi(s) \]
ベルマン方程式(Bellman Equations) (再帰的な関係):
\[ \begin{aligned} V^\pi(s) &= \sum_a \pi(a|s) \sum_{s'} P(s'|s,a)\left[R(s,a,s') + \gamma V^\pi(s')\right] \\ Q^\pi(s,a) &= \sum_{s'} P(s'|s,a)\left[R(s,a,s') + \gamma \sum_{a'} \pi(a'|s') Q^\pi(s', a')\right] \end{aligned} \]
Important
最適方策とベルマン最適性
最適方策 \(\pi^*\) は次を満たします。 \[ V^*(s) = \max_a \sum_{s'} P(s'|s,a)\left[R(s,a,s') + \gamma V^*(s')\right] \]
\[ Q^*(s,a) = \sum_{s'} P(s'|s,a)\left[R(s,a,s') + \gamma \max_{a'} Q^*(s', a')\right] \] \(Q^*\) が分かれば、最適方策は単純に \(\pi^*(s) = \arg\max_a Q^*(s,a)\) となります。
RL手法の分類
強化学習アルゴリズムは、いくつかの軸に沿って分類できます。この分類を理解すると、問題に適したアプローチを選びやすくなります。
Important
分類における重要な違い
モデルフリー(Model-Free)とモデルベース(Model-Based) :
モデルフリー :経験から方策または価値関数を直接学ぶ。環境ダイナミクスに関する知識は持たない。LLMでは言語ダイナミクスをモデル化するのが困難なため、最も実用的です。
モデルベース :環境の遷移 \(P(s'\vert s,a)\) のモデルを学習または利用する。先の計画を立てられるため、サンプル効率は高い一方、正確なモデルが必要です。
価値ベース(Value-Based)と方策ベース(Policy-Based) :
価値ベース :\(Q(s,a)\) または \(V(s)\) を学び、\(\arg\max_a Q(s,a)\) として方策を導く。離散的で小さな行動空間(例:Atari)ではうまく機能します。連続的または大規模な行動空間では苦戦します。
方策ベース :\(\pi_\theta(a\vert s)\) を直接パラメータ化して最適化する。連続的または高次元の行動空間に自然に適用できます。LLMには不可欠です(語彙が32K〜128K個の行動に相当するため)。
アクター・クリティック(Actor-Critic) :両者を組み合わせる。方策(アクター)が行動を提案し、価値関数(クリティック)が評価します。LLM向けのPPOはアクター・クリティックです。
オンポリシー(On-Policy)とオフポリシー(Off-Policy) :
オンポリシー :現在の方策が生成したデータから学ぶ。更新のたびにデータを再生成する必要があります。例:REINFORCE、PPO、A2C。より安定しますが、サンプル効率は低くなります。
オフポリシー :任意の方策(古いバージョンや他のエージェントを含む)が生成したデータから学ぶ。過去の経験を再利用できます。例:Q学習、DQN、SAC。サンプル効率は高い一方、安定化が難しくなります。
TD学習(Temporal Difference Learning)
TD学習 (Sutton 1988) はブートストラップを行います。つまり、エピソード全体が終わるのを待たず、別の価値推定を使って価値推定を更新します。
TD誤差を理解する:「驚き」を学習シグナルにする
TD誤差 は、エージェントが持つ将来報酬の 現在の推定 と、1ステップ進んだ後の 新しく更新された推定 とのずれを測ります。簡単に言えば、エージェントが「起こると思っていたこと」と「実際に起こったことに、次に起こると予想することを加えたもの」の差です。これはエージェントの「驚き」を表します。
Note
運転にたとえると
車で自宅に帰るとき、所要時間は30分だと予想しているとします。
予測 :合計30分。
現実の変化 :10分走ったところで、予期せぬ道路工事に遭遇しました。GPSが更新され、残り35分かかると表示しました。
TD誤差 :現在の合計予想時間は45分(経過10分+残り35分)です。新しい推定(45分)と古い推定(30分)の差は、 +15分のTD誤差 です。この「驚き」を使って、次回はルートを変更します。
正のTD誤差 は、結果が予想より良かったことを意味します \(\rightarrow\) この状態の価値を高めます。\ 負のTD誤差 は、結果が予想より悪かったことを意味します \(\rightarrow\) この状態の価値を下げます。
TD誤差の式
\[ \boxed{\delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t)} \]
-
\(R_{t+1}\):行動を取った後に受け取る 即時報酬 。
-
\(\gamma V(S_{t+1})\):次の状態の推定 割引価値 (次の状態以降で得られるとエージェントが予想するものを、割引率 \(\gamma\) でスケールした値)。
-
\(V(S_t)\):現在の状態の価値に対する 元の推定 。
\((R_{t+1} + \gamma V(S_{t+1}))\) の合成項を TDターゲット と呼びます。したがって、次のようになります。
\[ \text{TD Error} = \text{TD Target} - \text{Old Estimate} \]
エージェントはTD誤差をどう使うか
エージェントは、TD誤差をゼロに近づけるように価値関数を調整します。
\[ \boxed{V(S_t) \leftarrow V(S_t) + \alpha \cdot \delta_t} \]
-
\(\delta_t > 0\) の場合:結果が予測より良かった \(\rightarrow\) エージェントがこの状態を求めるように \(V(S_t)\) を増やします。
-
\(\delta_t < 0\) の場合:結果が予測より悪かった \(\rightarrow\) エージェントがこの状態を避けるように \(V(S_t)\) を減らします。
-
\(\delta_t = 0\) の場合:予測が完全だった \(\rightarrow\) 更新は不要です(収束)。
Tip
TDとモンテカルロ法
モンテカルロ法 :エピソードが終わるまで待ち、実際のリターン \(G_t\) を使います。不偏ですが、分散が大きくなります(1本の軌跡は代表的でない可能性があります)。
TD :推定した将来価値 \(\gamma V(s_{t+1})\) を使って、各ステップの後に更新します。偏りがあります(\(V\) の精度に依存します)が、分散は大幅に小さくなります(1ステップ更新なので、ノイズが累積しません)。
TD(\(\lambda\)) :TD(0)とモンテカルロ法の間を補間します。\(\lambda=0\) は純粋なTD、\(\lambda=1\) は純粋なMCです。PPOでGAEが行っていることはまさにこれです(\(\lambda=0.95\))。
TDターゲット :\(y_t = r_t + \gamma V(s_{t+1})\) — 近づけていく「より良い推定」。
マルチステップTD (nステップ・リターン):
\[ G_t^{(n)} = r_t + \gamma r_{t+1} + \cdots + \gamma^{n-1} r_{t+n-1} + \gamma^n V(s_{t+n}) \]
Q学習
Q学習 (Watkins 1989) は、基礎的な オフポリシー・価値ベース アルゴリズムです。実際に従っている方策にかかわらず、最適な \(Q^*\) を直接学びます。
更新則 :
\[ \boxed{Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha\left[r_t + \gamma \max_{a'} Q(s_{t+1}, a') - Q(s_t, a_t)\right]} \]
Tip
Q学習がオフポリシーである理由
更新では \(\max_{a'} Q(s_{t+1}, a')\) を使います。これは、エージェントが実際にどの行動を取ったかにかかわらず、次の状態での最良の行動の価値です。したがって、振る舞い方策がランダムに探索していても(\(\epsilon\)-greedy)、ターゲットは常に最適方策の下で計算されます。
これが、Q学習がリプレイバッファ、デモンストレーション、その他あらゆる経験源から学べる理由です。データが現在の方策から生成されている必要はありません。
SARSA (Rummery and Niranjan 1994)(オンポリシーの代替手法)は、最大値の代わりに実際に取った行動を使います。
\[ Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha\left[r_t + \gamma Q(s_{t+1}, a_{t+1}) - Q(s_t, a_t)\right] \]
Deep Q-Network(DQN) (Mnih et al. 2015):表形式の \(Q(s,a)\) をニューラルネットワーク \(Q_\theta(s,a)\) に置き換えます。主な革新は、経験リプレイバッファ(オフポリシーデータの再利用)、ターゲットネットワーク(安定性)、\(\epsilon\)-greedy探索です。
DQNの損失関数 :リプレイバッファからサンプリングしたミニバッチについて、平均二乗TD誤差を最小化するようネットワークを学習します。
\[ \boxed{\mathcal{L}(\theta) = \mathbb{E}_{(s,a,r,s') \sim \mathcal{B}}\!\left[\left(r + \gamma \max_{a'} Q_{\bar{\theta}}(s', a') - Q_\theta(s, a)\right)^2\right]} \]
ここで \(Q_{\bar{\theta}}\) は ターゲットネットワーク です。これは \(Q_\theta\) の凍結コピーで、\(C\) ステップごとにだけ更新されます(例:\(C = 10{,}000\))。これにより移動するターゲット問題を防ぎます。ターゲットネットワークがなければ、予測とターゲットが同時に変化し、発散を引き起こします。
勾配更新 :損失を \(\theta\) について微分します(ターゲット \(y\) は定数として扱い、\(\bar{\theta}\) を通じて勾配は流れないことに注意してください)。
\[ \nabla_\theta \mathcal{L} = -\mathbb{E}\!\left[\underbrace{\left(r + \gamma \max_{a'} Q_{\bar{\theta}}(s', a') - Q_\theta(s, a)\right)}_{\text{TD error } \delta}; \nabla_\theta Q_\theta(s, a)\right] \]
\[ \theta \leftarrow \theta - \alpha \cdot \delta \cdot \nabla_\theta Q_\theta(s, a) \]
学習手順 (学習ステップごと):
-
行動 :\(\epsilon\)-greedyで行動を選択します。確率 \(\epsilon\) でランダムな行動を取り、それ以外では \(a = \arg\max_a Q_\theta(s, a)\) とします。最初の100万ステップで \(\epsilon\) を1.0 \(\rightarrow\) 0.01へアニーリングします。
-
保存 :遷移 \((s, a, r, s', d)\) を容量約1Mのリプレイバッファ \(\mathcal{B}\) に保存します。
-
サンプリング :\(\mathcal{B}\) から32個の遷移を一様にミニバッチとして取り出します。
-
ターゲット計算 :\(y = r + \gamma(1 - d)\max_{a'} Q_{\bar{\theta}}(s', a')\) とします(終端状態なら将来価値をゼロにする)。
-
更新 :\((y - Q_\theta(s,a))^2\) に対して勾配降下を行います。勾配を \([-1, 1]\) にクリップします(Huber損失の変種)。
-
ターゲット同期 :\(C\) ステップごとに \(\bar{\theta} \leftarrow \theta\) としてコピーします。
リプレイバッファを理解する
リプレイバッファ (Lin 1992)(経験リプレイ)は、過去の経験を保存し、後からエージェントが再び学習できるようにするデータ保存機構です。行動の直後にデータを捨てるのではなく、エージェントは遷移をメモリーバンクに保存し、ランダムなミニバッチをサンプリングして学習します。
保存されるもの :各遷移は次のタプルです。
\[ e_t = (s_t, a_t, r_t, s_{t+1}, d_t) \]
ここで \(d_t\) は、エピソードが終了したかどうかを示すブール値フラグです。
Important
リプレイバッファが不可欠な理由
データの相関を断つ :連続するステップは強く相関しています。ニューラルネットワークは系列データ上でうまく一般化できません。バッファからランダムにサンプリングすると、学習サンプルはおおむね独立同分布(i.i.d.)になります。
壊滅的忘却を防ぐ :バッファがなければ、難しいレベルをクリアしたエージェントが、次のレベルで1万ステップ失敗し続ける間に、そのクリア方法を忘れる可能性があります。バッファにより、過去の状況を練習し続けられます。
サンプル効率を高める :環境の実行には時間がかかることがあります。リプレイバッファがあれば、同じ遷移から複数回重みを更新でき、各ステップからより多くの価値を引き出せます。
import random
from collections import deque
class ReplayBuffer:
def __init__(self, capacity):
self.buffer = deque(maxlen=capacity) # 上限付きキュー
def push(self, state, action, reward, next_state, done):
self.buffer.append((state, action, reward, next_state, done))
def sample(self, batch_size):
# ランダムな経験を選び、相関を断つ
return random.sample(self.buffer, batch_size)
def __len__(self):
return len(self.buffer)
Tip
優先経験リプレイ(PER)
標準的なバッファでは、すべての経験が同じ確率でサンプリングされます。しかし、なかには他よりも多くを学べる経験があります。 PER (Schaul et al. 2016) は、サンプリング確率を TD誤差の大きさ に応じて調整します。ある遷移が大きな「驚き」(高い \(\vert \delta_t\vert\))を生じさせた場合、エージェントはその遷移をより頻繁にサンプリングし、より速くモデルを修正します。これにより、Atariベンチマークでは学習が2〜3\(\times\)高速化されます。
Warning
Q学習がLLMで失敗する理由
言語生成の行動空間は全語彙(\(\vert A\vert = 32\text{K}\)〜\(128\text{K}\))であり、状態空間は取りうるすべてのトークン列(無限)です。各トークン位置で128K個の行動について \(\max_a Q(s,a)\) を計算するのは扱いきれません。これが、LLMのRLで方策ベース手法(PPO、GRPO)を使う理由です。
方策勾配法 — REINFORCE
価値関数を学習して方策を導く代わりに、方策パラメータ \(\theta\) を直接最適化して期待リターンを最大化します (Williams 1992)。
目的関数 :\(J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta}[R(\tau)] = \mathbb{E}_{\pi_\theta}\left[\sum_{t=0}^T r_t\right]\)
方策勾配定理 :
\[ \boxed{\nabla_\theta J(\theta) = \mathbb{E}_{\pi_\theta}\left[\sum_{t=0}^T \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot G_t\right]} \]
Important
方策勾配定理 — 形式的な導出(5ステップ)
ステップ1 :目的関数を定義します。期待リターンを最大化したいので、次のようにします。 \[ J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta}!\left[\sum_{t=0}^T r_t\right] = \sum_\tau P(\tau|\theta) R(\tau) \] ここで \(P(\tau\vert \theta) = p(s_0)\prod_{t=0}^T \pi_\theta(a_t\vert s_t), p(s_{t+1}\vert s_t, a_t)\) は軌跡の確率です。
ステップ2 :勾配を取ります。\(\theta\) に依存するのは \(\pi_\theta\) の項だけです(ダイナミクス \(p\) は依存しません)。 \[ \nabla_\theta J = \sum_\tau \nabla_\theta P(\tau|\theta), R(\tau) \]
ステップ3 : 対数微分トリック を適用します:\(\nabla_\theta P(\tau\vert \theta) = P(\tau\vert \theta), \nabla_\theta \log P(\tau\vert \theta)\)。 \[ \nabla_\theta J = \mathbb{E}_{\tau \sim \pi_\theta}!\left[\nabla_\theta \log P(\tau|\theta), R(\tau)\right] \]
ステップ4 :\(\log P(\tau\vert \theta)\) を展開します。\(\log p(s_0)\) と \(\log p(s_{t+1}\vert s_t,a_t)\) の項は \(\nabla_\theta\) によって消えます。 \[ \nabla_\theta \log P(\tau|\theta) = \sum_{t=0}^T \nabla_\theta \log \pi_\theta(a_t|s_t) \]
ステップ5 :組み合わせます。未来の報酬は過去の行動に依存しないため(因果性)、各 \(\nabla\log\pi\) は未来のリターン \(G_t = \sum_{t'=t}^T r_{t'}\) とのみ組み合わさります。 \[ \boxed{\nabla_\theta J = \mathbb{E}_{\pi_\theta}!\left[\sum_{t=0}^T \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot G_t\right]} \]
Tip
この定理が美しい理由
勾配は 環境ダイナミクス \(p(s'\vert s,a)\) を微分する必要がありません。対数微分トリックにより、単に方策を実行して報酬を観測するだけで推定できる期待値に変換されます。\(G_t\) をアドバンテージ \(\hat{A}_t = G_t - V(s_t)\) に置き換えると、偏りを導入せずに分散を減らせます(任意の状態依存ベースラインについて \(\mathbb{E}[\nabla\log\pi \cdot b(s)] = 0\) だからです)。
REINFORCEアルゴリズム (Williams 1992)(Williams、1992):
-
\(\pi_\theta\) の下で完全な軌跡 \(\tau = (s_0, a_0, r_0, s_1, a_1, r_1, \ldots)\) をサンプリングする
-
各時刻についてリターン \(G_t = \sum_{k=0}^{T-t} \gamma^k r_{t+k}\) を計算する
-
更新する:\(\theta \leftarrow \theta + \alpha \sum_t \nabla_\theta \log \pi_\theta(a_t\vert s_t) \cdot G_t\)
Tip
REINFORCEの直感 — 「報酬重み付き最尤推定」
\(\nabla_\theta \log \pi_\theta(a_t\vert s_t)\) は、行動 \(a_t\) の確率を高める方向です。これに \(G_t\) を掛けると、次のようになります。
高報酬の軌跡:取ったすべての行動の確率を高める(\(G_t\) が正)
低報酬の軌跡:取った行動の確率を下げる(ベースライン適用後の \(G_t\) が負)
これは、どれだけ良い結果になったかに応じて重み付けした、行動を「ラベル」とする教師あり学習です。
ベースラインによる分散削減 :
\[ \nabla_\theta J(\theta) = \mathbb{E}_{\pi_\theta}\left[\sum_{t=0}^T \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot (G_t - b(s_t))\right] \]
\(a_t\) に依存しない任意のベースライン \(b(s_t)\) は、勾配を不偏のまま保ちつつ分散を減らします。最良の選択は \(b(s_t) = V^\pi(s_t)\) です。このとき \(G_t - V(s_t) \approx A^\pi(s_t, a_t)\) = アドバンテージとなります。
Warning
REINFORCEの限界
高分散 :各勾配は1本の軌跡を使います。安定した更新には数千のサンプルが必要です。
ブートストラップなし :エピソード全体が終わるまで待つ必要があります(途中の貢献を評価できません)。
サンプル効率が低い :データは1回使うと破棄されます(オンポリシー)。
ステップサイズの制御なし :方策を壊滅的に大きく更新する可能性があります。
これらの限界が、REINFORCE \(\to\) アクター・クリティック \(\to\) TRPO \(\to\) PPO という発展を促しました。
アクター・クリティック法
方策勾配(アクター)と学習した価値関数(クリティック)を組み合わせ、方策最適化の柔軟性を保ちながら分散を減らします。
アーキテクチャ :
-
アクター \(\pi_\theta(a\vert s)\):方策。行動を提案します。
-
クリティック \(V_\phi(s)\) または \(Q_\phi(s,a)\):状態/行動がどれだけ良いかを評価します。低分散のベースラインを提供します。
アクターの更新 (クリティックから得たアドバンテージを使用):
\[ \nabla_\theta J = \mathbb{E}\left[\nabla_\theta \log \pi_\theta(a_t|s_t) \cdot \hat{A}_t\right], \quad \hat{A}_t = r_t + \gamma V_\phi(s_{t+1}) - V_\phi(s_t) \]
クリティックの更新 (TD誤差を最小化):
\[ \mathcal{L}_\text{critic} = \mathbb{E}\left[(r_t + \gamma V_\phi(s_{t+1}) - V_\phi(s_t))^2\right] \]
Important
LLM向けPPOへの発展
REINFORCE (Williams 1992):高分散でブートストラップなし \(\rightarrow\) LLMには実用的でない
A2C/A3C (Mnih et al. 2016)(Advantage Actor-Critic):TDベースのアドバンテージを使います。分散は低下しますが、ステップサイズに上限がありません。
TRPO (Schulman et al. 2015):方策更新間のKLダイバージェンスを制約します。安定しますが、2次最適化のため高コストです。
PPO (Schulman et al. 2017):方策比をクリップし、1次最適化だけでTRPOと同程度の安定性を実現します。LLMのRL学習における標準手法です。
GRPO :クリティックを完全に取り除き、グループ統計をベースラインとして使います。検証可能な報酬に対して、より単純で効果的です。
一般化アドバンテージ推定(GAE)
動機 :アクター・クリティックの枠組みには、アドバンテージ \(A(s,a) = Q(s,a) - V(s)\) — この行動は平均よりどれだけ良かったのか — の良い推定が必要です。しかし、ここには根本的な緊張関係があります。
-
1ステップTDアドバンテージ (\(r_t + \gamma V(s_{t+1}) - V(s_t)\)):分散が低い(ランダムなステップは1つだけ)一方、 偏りがある — 価値関数 \(V\) が間違っていれば、アドバンテージ推定も体系的にずれます。
-
モンテカルロ・アドバンテージ (\(G_t - V(s_t)\)):不偏(実際のリターンを使う)ですが、 分散が高い — 多数のランダムな報酬の合計はエピソードごとに大きく変動します。
GAE (Schulman et al. 2016)(Schulmanら、2016)は、単一のパラメータ \(\lambda \in [0, 1]\) によって、この両極端の間を 滑らかに補間 します。すべての \(n\) について、nステップ・アドバンテージ推定を指数重み付き平均し、偏りと分散を原理的にトレードオフできるようにします。
中心となる考え方 :各時刻で1ステップTD誤差 \(\delta_t\) を計算し、それらを指数的に減衰する重み \((\gamma\lambda)^l\) で混ぜ合わせます。最近のTD誤差には完全な重みを与え、遠いTD誤差の重みは小さくします。
\[ \boxed{\hat{A}_t^{\text{GAE}} = \sum_{l=0}^{T-t} (\gamma\lambda)^l \delta_{t+l}, \quad \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)} \]
Tip
\(\lambda\) が制御するもの — 偏りと分散のトレードオフ
\(\lambda = 0\):\(\hat{A}_t = \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)\)。価値関数を完全に信頼します。分散は低いですが、\(V\) が不正確だと偏ります。
\(\lambda = 1\):\(\hat{A}_t = \sum_l \gamma^l r_{t+l} - V(s_t)\)。完全なモンテカルロ・リターンからベースラインを引きます。不偏ですが、分散は非常に高くなります。
\(\lambda = 0.95\)(標準値):最適な中間点です。主に \(V\) を信頼しながら、遠い影響については実際のリターンで補正します。初期学習後に価値ヘッドが正確になるため、うまく機能します。
LLMの場合は特に、\(\gamma = 1.0\)(時間割引なし — 単一ターンではすべてのトークンを等しく重視)および \(\lambda = 0.95\) とします。
GAEにおける偏りと分散の直感的な対応
教師あり学習では、偏りと分散は構造的なモデル仮定から生じます。GAEによる強化学習では、偏りと分散は 不完全なモデルをどれだけ信頼し、混沌とした環境をどれだけ信頼するか から生じます。
-
偏り(体系的なずれ) :価値ネットワーク \(V_\theta\) の構造的な仮定と不完全な予測に推定器が依存すると生じます。\(\theta\) が十分に学習されていない、または容量が不足している場合、ベースラインの推測は体系的に間違います。
-
分散(サンプルの揺らぎ) :制約のない長い環境軌跡に推定器が依存すると生じます。確率的な遷移、乱数シード、方策実行時のノイズが長いホライズンにわたって蓄積し、ロールアウト間で実測報酬が大きく揺れます。
アーキテクチャ上のスペクトラム:境界分析
ハイパーパラメータ \(\lambda\) は、2つの基本的な推定パラダイムの間を調整するスライドルールとして機能します。
Important
高い偏り/低い分散の極限($\lambda = 0$)
\[ \hat{A}_t^{\text{GAE}(\gamma, 0)} = \delta_t^V = r_t + \gamma V_\theta(s_{t+1}) - V_\theta(s_t) \]
振る舞い :アドバンテージは現在のパラメータ \(\theta\) の状態に強く左右されます。
直感 :ネットワークが1ステップの窓で自分の性能を採点しているため、非常に 偏り があります。\(V_\theta\) が不正確なら勾配ステップが壊れます。一方、\(t+1\) より先の確率的なイベントを無視するため、 分散 は低く、パラメータ更新は滑らかで安定します。
リスク :方策が最適でない局所最小値に陥り、複雑な遅延報酬の系列を発見できません。
Important
低い偏り/高い分散の極限($\lambda = 1$)
\(\lambda = 1\) のとき、中間の価値項は望ましい形で相殺され、GAEはベースラインを引いたモンテカルロ・リターンになります。 \[ \hat{A}_t^{\text{GAE}(\gamma, 1)} = \sum_{l=0}^{\infty} \gamma^l r_{t+l} - V_\theta(s_t) \]
振る舞い :ブートストラップによる先読みを捨て、エピソード全体の文字どおりの現実を合計します。
直感 :ニューラルネットワークによる近似ではなく実際の報酬を測るため、真の環境ダイナミクスに対して完全に 不偏 です。しかし、極端な 分散 を示します。エピソード序盤の小さな変化がリターン全体を大きく分岐させ、方策更新が不安定になる可能性があります。
リスク :破壊的な勾配更新、学習の爆発。
トレードオフ行列
\(\lambda \in [0.95, 0.99]\) を選ぶと、GAEはアドバンテージ推定の平均二乗誤差全体を最小化します。
| 設定 | 統計的性質 | 主な依存先 | 実用上のリスク |
|---|---|---|---|
| \(\lambda = 0\) | 高い偏り、低い分散 | モデルパラメータ(\(\theta\)) | 方策が最適でない局所最小値に陥る |
| \(\lambda \in [0.95, 0.99]\) | バランス(最適なMSE) | ハイブリッドな混合 | 環境の確率性に応じた調整が必要 |
| \(\lambda = 1\) | 低い偏り、高い分散 | 実測された環境ロールアウト | 破壊的な勾配更新、学習の爆発 |
GAEのパラメータ選択の実務上の比較。
\(\lambda\) 調整の診断
学習曲線を監視すると、偏りと分散のどちらが支配的かを直接把握できます。
-
高分散の兆候 :方策エントロピーが急激に低下する一方、価値関数の説明分散が大きく負になる、または不規則になる \(\rightarrow\) 方策更新がノイジーです。 対策 :\(\lambda\) を下げてターゲット更新を滑らかにします。
-
高偏りの兆候 :エージェントは早期には安定して学習するものの、複雑な遅延報酬の系列をまったく発見できない \(\rightarrow\) ブートストラップにより長期依存を過小評価しています。 対策 :\(\lambda\) を1.0に近づけ、実際の下流軌跡のシグナルを方策に与えます。
オンポリシーとオフポリシー — 詳細比較
| オンポリシー | オフポリシー | |
|---|---|---|
| データ源 | 現在の方策 \(\pi_\theta\) のみ | 任意の方策(リプレイバッファ) |
| 更新後 | 古いデータは無効になり、再生成が必要 | 古いデータも引き続き利用可能 |
| サンプル効率 | 低い(データを1回使う) | 高い(データを何度も再利用) |
| 安定性 | より安定(分布が一貫している) | 発散する可能性(分布の不一致) |
| 例 | REINFORCE、PPO、A2C、GRPO | Q学習、DQN、SAC、DPO |
| LLMの場合 | PPO、GRPO(各ステップで新しく生成) | DPO(固定された選好データセット) |
Tip
RLHF手法におけるオン/オフポリシー
PPO/GRPOはオンポリシー :現在の方策で応答を生成し、アドバンテージを計算し、更新してデータを破棄し、再び生成します。これが生成に計算量の60%が費やされる理由です。各ステップで再生成する必要があります。
DPOはオフポリシー :固定された選好データセットで学習します。学習中の生成はありません。はるかに安価ですが、方策が変化するとデータが古くなるため、分布シフトの影響を受けます。
Online DPOはハイブリッド :新しいデータを生成し(オンポリシー生成)、DPOの教師あり損失を使います(オフポリシー型の最適化)。両方の利点を得られます。
PPOの巧妙さ :クリップ比 \(r = \pi_\text{new}/\pi_\text{old}\) を使い、オンポリシーデータの1つのバッチから複数回の勾配ステップ(4エポック)を引き出します。これにより、制御された形で「少しだけオフポリシー」になります。
モデルベースとモデルフリー
| モデルフリー | モデルベース | |
|---|---|---|
| 学習するもの | 方策 \(\pi\) および/または価値 \(V\)/\(Q\) を直接 | 環境モデル \(\hat{P}(s'\vert s,a)\) |
| 計画 | 計画なし、反応的な意思決定 | 将来の軌跡をシミュレートできる |
| サンプル効率 | 低い(すべてを経験する必要がある) | 高い(想像上の環境で計画できる) |
| 正確さ | モデルによる偏りなし | モデル誤差が累積する |
| 使う場面 | 複雑/未知のダイナミクス | 単純なダイナミクス、効率が必要な場合 |
| 例 | PPO、DQN、SAC (Haarnoja et al. 2018) | MuZero (Schrittwieser et al. 2020)、Dreamer (Hafner et al. 2020)、AlphaGo (Silver et al. 2016) |
Tip
LLMのRLがモデルフリーである理由
言語生成のダイナミクスは単純です(系列にトークンを追加する — 決定論的な遷移)。環境の「モデル」はボトルネックではありません。難しいのは 報酬 、つまり人間が何を好むかを予測することです。そのため、LLMのRLにはモデルベース手法は不要です。
RLHFの報酬モデルは、ある意味では「モデル」とみなせます(人間の選好を予測するためです)。しかし、報酬シグナルとして使われるのであって、計画やシミュレーションに使われるわけではありません。LLMのRLは本質的にモデルフリーの方策最適化です。
報酬シェーピング
報酬シェーピング (Ng et al. 1999) は、開発者が環境本来の報酬関数を変更または補完する手法です。主な目的は、エージェントが最終的なタスク完了時にしかフィードバックを受け取らない 疎な報酬 の状況を、中間的なフィードバックシグナルを持つ 密な報酬 の状況に変換し、収束を速めることです。
数学的枠組み
時刻 \(t\) における元の報酬を \(R_t(s, a, s')\) とします。報酬シェーピング後の報酬は補助的なシェーピング関数 \(F\) を加えます。
\[ \boxed{R'_t(s, a, s') = R_t(s, a, s') + F(s, a, s')} \]
Warning
素朴なシェーピングのリスク:報酬ハッキング
\(F(s, a, s')\) を恣意的に設計すると、エージェントは全体の目的を無視しながら、補助シグナルを最大化する構造上の抜け道を見つけます。
例 :中間のランドマークに到達すると報酬を受け取るナビゲーションエージェントは、目的地に到達することなく、1つのチェックポイントの周りを無限にループして報酬を無限に蓄積するよう学習する可能性があります。
LLMの場合、「自信がありそうに聞こえる」ことに報酬を与えると、正確さにかかわらず、常に「Absolutely!」で始めるよう学習する可能性があります。
ポテンシャルベース報酬成形(PBRS)
報酬成形によって最適方策が変わらないことを数学的に保証するには、 ポテンシャルベース報酬成形 を使います。成形関数 \(F\) を、状態間のスカラー・ポテンシャル関数 \(\Phi\) の差に制約します。
\[ \boxed{F(s, a, s') = \gamma\, \Phi(s') - \Phi(s)} \]
ここで \(\Phi: \mathcal{S} \to \mathbb{R}\) は、状態が目標にどの程度近いかを評価する実数値のポテンシャル関数であり、\(\gamma\) は割引率です。
PBRS後の完全な報酬は次のとおりです。
\[ R'(s, a, s') = R(s, a, s') + \gamma\, \Phi(s') - \Phi(s) \]
理論的保証
Important
PBRS方策不変性定理
方策不変性 :シェーピング後の報酬 \(R'\) に対する最適方策 \(\pi^*\) は、元の報酬 \(R\) に対する最適方策と 同一 です。シェーピングによって最適でない振る舞いが導入されることはありません。
ループ耐性 :同じ状態から始まり同じ状態で終わる任意の循環軌跡では、ポテンシャルの正味の変化は正確にゼロになります(\(\Phi(s) - \Phi(s) = 0\))。エージェントはループを利用して報酬をハックできません。
収束の高速化 :最適方策は変わらないまま、シェーピング後の報酬はより密な勾配シグナルを提供するため、疎な報酬の環境でエージェントは5〜50\(\times\)速く収束できます。
言語モデルのためのRLの基礎
教師ありファインチューニング(SFT)は、モデルにデモンストレーションを模倣するよう教えます。しかし、模倣には上限があります。モデルは学習データの品質を超えられないからです。強化学習はこの壁を打ち破ります。新しいテキストを生成し、報酬フィードバックを受け取り、より高い報酬へ向けて更新することで、RLで学習したモデルは、人間のデモンストレーターが書いたことのない戦略を発見できます。その結果、より有用で、より正確で、人間の選好によりよく整合した出力を生成します (Ouyang et al. 2022)。
これは、すべてのフロンティアモデルを支える仕組みです。GPT-4 (OpenAI 2023)、Claude、Llama-3 (Grattafiori et al. 2024)、DeepSeek-R1 (DeepSeek-AI et al. 2025) はいずれもSFT後にRLを適用します。これは、有能だが方向づけられていないモデルを、アラインされたアシスタントへと変える決定的な段階です。
LLMにおけるRLの2つのパラダイム
言語モデルのRL手法は、目的に応じて使い分けられる、2つの大きなパラダイムに分けられます。
パラダイム1:人間の選好によるアラインメント(RLHF/DPO)
LLMにRLを適用する当初の目的は、 アラインメント 、つまりモデルを有用で、無害で、正直にすることでした。 人間のフィードバックからの強化学習(RLHF) (Ouyang et al. 2022; Ziegler et al. 2019; Christiano et al. 2017) は、人間によるペア単位の判断(「どちらの応答が優れているか?」)から報酬モデルを学習し、その学習した報酬を最大化するように方策を最適化します。 DPO (Rafailov et al. 2023) は、報酬モデルを完全に取り除き、選好を直接教師あり損失へ変換することで、この手法を簡略化します。どちらの手法も、指示に従い、安全性の制約を尊重するアラインメント済みアシスタントを生成します。
パラダイム2:検証可能な報酬による能力向上(RLVR)
近年、RLはアラインメントだけでなく、特に推論、数学、コード生成といった 新しい能力の学習 にも使われています。ここで報酬を与えるのは人間の選好ではなく、 検証可能な結果 です。モデルは正しい答えを生成したでしょうか。コードはすべてのテストに合格したでしょうか。DeepSeek-R1 (DeepSeek-AI et al. 2025) は、ルールベースの報酬(形式の正しさ+解答の正確さ)を用いるGRPOによって、人間の選好データなしで高度なChain-of-thought推論を身につけるようモデルを学習できることを示しました。このパラダイム、すなわち検証可能な報酬からの強化学習(RLVR)は、現在、推論モデルやエージェントシステムを構築するための主流のアプローチです。
Important
共通の基盤
目的は異なりますが、どちらのパラダイムも同じ中核的な仕組みを共有しています。
LLMが自己回帰的にテキストを生成する 方策 \(\pi_\theta\)
報酬シグナル \(r(x, y)\)(選好から学習するか、検証から計算する)
退化した解を防ぐための参照方策に対する KL制約
モデルをより高い報酬へ向けて更新する 方策勾配最適化 (PPOまたはGRPO)
このパートの各章では、それぞれの構成要素を詳しく説明します。
テキスト生成をMDPとして捉える
RLを言語モデルに適用できるようにする重要な洞察は、自己回帰的な生成をマルコフ決定過程として捉え直すことです。
Tip
LLMをエージェントとみなす類推
LLMを、1トークンずつ応答を書く エージェント だと考えてみましょう。各ステップで、これまでに書かれたすべての内容( 状態 )を確認し、次の単語( 行動 )を選び、ページが1トークンずつ増えていきます( 遷移 )。応答が完成すると、判定者がそれを採点します( 報酬 )。目標は、一貫して高い点数を得られる書き方の戦略( 方策 )を学ぶことです。
形式的には、テキスト生成のMDPは次のようになります。
-
状態 \(s_t = (x, y_1, \ldots, y_{t-1})\):プロンプトと、これまでに生成されたすべてのトークンを連結したもの。
-
行動 \(a_t \in \{1, \ldots, \vert \mathcal{V}\vert \}\):語彙から次のトークンを選ぶこと(32K〜128K個の選択肢)。
-
遷移 \(P(s_{t+1}\vert s_t, a_t)\):決定論的です。選択したトークンを追加するだけであり、環境の確率性はありません。
-
報酬 \(r\):通常は生成の最後にだけ与えられます(疎な報酬)。RLHFでは報酬モデルのスコア、RLVRでは最終回答の正しさです。
-
方策 \(\pi_\theta(a_t\vert s_t)\):LLMの次トークン確率分布であり、ソフトマックス出力がすでに計算しているものです。
-
割引率 \(\gamma = 1.0\):エピソードは有限(1つの応答)なので、割引は必要ありません。
この対応付けが強力なのは、LLMがすでに方策だからです。LLMのソフトマックス出力は、あらゆる状態における \(\pi_\theta(a_t\vert s_t)\) を定義します。別の方策ネットワークを構築する必要はありません。報酬の高いトークン列に対してモデルがより高い確率を割り当てるよう、重み \(\theta\) を調整すればよいのです。
RLHFパイプライン
古典的なRLHFパイプライン (Ouyang et al. 2022) は、4つの段階からなります。
-
教師ありファインチューニング(SFT) :高品質なデモンストレーションでベースモデルを学習し、指示に従える方策 \(\pi_{\text{SFT}}\) を作ります。
-
報酬モデルの学習 :同じプロンプトに対する人間の選好比較(\(y_w \succ y_l\))を集め、Bradley-Terry目的関数を使って報酬モデル \(R_\phi(x, y)\) を学習します。
-
RL最適化 :報酬モデルをシグナルとして、\(\pi_{\text{SFT}}\) に対するKL制約のもとで、PPOまたはGRPOによって方策を最適化します。
-
評価と反復 :アラインメント済みモデルを評価し、新たな失敗事例を集め、反復します。
RLVR(推論/エージェント学習)では、1〜2段階が置き換えられます。SFTモデルは推論トレースで学習し、報酬モデルは検証器(たとえば数学的な正しさを確認するもの)に置き換えられます。第3段階は変わらず、報酬シグナルに対してPPOまたはGRPOで最適化します。
Tip
LLMにおけるRLと古典的RLの違い
LLMの設定は、重要な点で古典的RLと異なります。
決定論的な遷移 :「次の状態」は、単に過去のトークンを連結したものです。確率的な環境はありません。
疎な報酬 :フィードバックは通常、生成の最後に1回(結果報酬)、または重要なステップで与えられます(プロセス報酬)。
巨大な行動空間 :各ステップで32K〜128K個のトークンが候補になります。ただし、探索は温度サンプリングによって暗黙的に行われます。
KLアンカー :LLMのRLはSFT方策の近くにとどまるよう制約されます。これにより報酬ハッキングを防ぐ一方、探索は縮小します。
価値関数は不要 :GRPOはクリティックネットワークを完全に取り除き、代わりに報酬のグループ相対正規化を使います。
これらの違いが、LLMではPPOとGRPOがDQN系のアプローチより主流となっている理由です。
このパートのロードマップ
これからの章では、LLMのRLに必要なツールキット全体を構築します。
-
PPO (第5章)— クリップ付き代理目的関数、アドバンテージ推定のためのGAE、クリティックネットワーク、そして完全なRLHF学習ループを扱います。GPT-4やClaudeを支える実用的な主力手法です。
-
DPO (第6章)— 選好を対照的な教師あり損失に変換することで、RLを完全に迂回します。オンラインRLより単純ですが、柔軟性は低くなります。
-
GRPO (第7章)— グループレベルの報酬正規化を使う、DeepSeekのクリティック不要アルゴリズムです。DeepSeek-R1を支え、推論モデルの学習で主流となっている手法です。
-
選好最適化のバリアント (第8章)— Online DPO、KTO、Best-of-N、および手法選択の指針を扱います。
-
報酬モデリング (第9章)— Bradley-Terryモデル、プロセス報酬と結果報酬、RLVRのルールベース報酬、複数目的の組み合わせを扱います。
-
SFTのベストプラクティス (第10章)— シーケンスパッキング、チャットテンプレート、データ混合、そしてSFTの品質がRLの上限をどのように決めるかを扱います。
-
システムエンジニアリング (第11章)— 大規模な分散学習、並列化戦略、生成と学習の分離、数百GPU向けのインフラを扱います。
PPO — 近接方策最適化
動機と歴史
問題 :標準的な方策勾配の更新には、ステップ幅の制約がありません。たった1つの不運なバッチによって、方策がゴミを生成する領域へ押し出される可能性があります \(\rightarrow\) ゴミは低い報酬しか得られない \(\rightarrow\) 次の勾配でさらに悪化する \(\rightarrow\) 回復不能な崩壊に至ります。
解決策の歴史 :
-
TRPO (Schulman et al. 2015)(2015年):古い方策と新しい方策の間のKLダイバージェンスを制約します。完全に機能しますが、二次最適化(フィッシャー情報行列、共役勾配法)が必要で、計算コストが高くなります。
-
PPO (2017年)(Schulman et al. 2017):単純な一次のクリップ付き目的関数によって、同程度の安定性を実現します。実装は10\(\times\)単純で、ほぼ同等に機能し、分散学習にも容易にスケールします。
クリップ付き目的関数
PPOの中核的な革新は、破壊的に大きな方策更新を防ぎながら、実装も簡単に保てるクリップ付き代理目的関数です。
\[ \boxed{L^{\text{CLIP}}(\theta) = \mathbb{E}_t\left[\min\left(r_t(\theta)\hat{A}_t,; \text{clip}(r_t(\theta), 1{-}\epsilon, 1{+}\epsilon)\hat{A}_t\right)\right]} \]
ここで、\(r_t(\theta) = \frac{\pi_\theta(a_t\vert s_t)}{\pi_{\theta_\text{old}}(a_t\vert s_t)}\) は確率比です。
Tip
クリッピングの直感 — 核となる洞察
\(\min\) 演算子は 悲観的な上限 を作ります。
良い行動(\(\hat{A} > 0\)) :その確率を高めたいとします。代理目的 \(r\hat{A}\) は \(r\) が増えるにつれて大きくなります。しかし、clipは \(r = 1 + \epsilon\) で利益に上限を設けます。*「1つの良い例で欲張りにならない」*ためです。
悪い行動(\(\hat{A} < 0\)) :その確率を下げたいとします。\(r\hat{A}\) は \(r\) が減るにつれて改善します。しかし、clipは \(r = 1 - \epsilon\) で利益に上限を設けます。*「1つの悪い例を根拠に、過度に忘却しない」*ためです。
結果として、1回の更新ステップでの方策の変化は最大でも\(\pm\)20%になります。これにより、壊滅的な崩壊と、過信した特化の両方を防ぎます。
PPOの完全な損失
\[ L = L^{\text{CLIP}} - c_1 \underbrace{(V_\theta(s_t) - V^{\text{target}}_t)^2}_{\text{value loss}} + c_2 \underbrace{H[\pi_\theta(\cdot|s_t)]}_{\text{entropy bonus}} \]
-
価値損失 (\(c_1 = 0.1\)):リターンを予測するようにクリティックを学習します。安定性のため、これもクリップされます。
-
エントロピー・ボーナス (\(c_2 = 0.01\)):決定論的な方策への早すぎる収束を防ぎます。探索に不可欠です。
PPOの勾配と更新則の導出
この節では、RLの目的関数からPPOの更新則に至る数学的な道筋をたどり、クリップ付き代理目的関数がなぜ機能するのかを示します。
ステップ1:RLの目的関数
目標は、方策のもとで期待累積報酬を最大化することです。
\[ J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta}\left[\sum_{t=0}^T r_t\right] \]
ステップ2:方策勾配定理
方策パラメータに関する \(J(\theta)\) の勾配は次のとおりです。
\[ \boxed{\nabla_\theta J(\theta) = \mathbb{E}_{\pi_\theta}\left[\sum_{t=0}^T \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot \hat{A}_t\right]} \]
ここで \(\hat{A}_t\) はアドバンテージ関数です(行動 \(a_t\) が状態 \(s_t\) における平均的な行動と比べてどれだけ優れていたか)。分散を減らすため、完全なリターンをアドバンテージに置き換えています。
ステップ3:オフポリシーデータに対する重点サンプリング
PPOは \(\pi_{\theta_{\text{old}}}\) を使ってデータを収集しますが、更新するのは \(\pi_\theta\) です。この分布の不一致を補正するため、重点サンプリングを適用します。
\[ \nabla_\theta J(\theta) = \mathbb{E}_{\pi_{\theta_{\text{old}}}}\left[\frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)} \nabla_\theta \log \pi_\theta(a_t|s_t) \cdot \hat{A}_t\right] \]
確率比 \(r_t(\theta) = \frac{\pi_\theta(a_t\vert s_t)}{\pi_{\theta_{\text{old}}}(a_t\vert s_t)}\) を定義します。恒等式 \(\nabla_\theta \log f = \frac{\nabla_\theta f}{f}\) を使うと、次が得られます。
\[ \nabla_\theta J(\theta) = \mathbb{E}_{\pi_{\theta_{\text{old}}}}\left[\nabla_\theta, r_t(\theta) \cdot \hat{A}_t\right] \]
これは、次の 代理目的関数 を最大化することを意味します。
\[ L^{\text{CPI}}(\theta) = \mathbb{E}_t\left[r_t(\theta) \cdot \hat{A}_t\right] \]
ステップ4:制約のない代理目的関数の問題
\(L^{\text{CPI}}\) は有効な目的関数ですが、制約がなければ、1回の勾配ステップで \(r_t(\theta)\) が1.0から大きく離れてしまい、次の問題が起こります。
-
重要度の重みが極端になる \(\rightarrow\) 分散が大きくなる
-
方策が未検証の領域に入る \(\rightarrow\) 報酬モデルが信頼できないスコアを返す
-
壊滅的な崩壊:方策がゴミを生成し、回復できなくなる
TRPOの解決策 :\(D_{\text{KL}}(\pi_{\theta_{\text{old}}} \\vert \pi_\theta) \leq \delta\) を制約します。二次法が必要で、計算コストが高くなります。
ステップ5:PPOのクリップ付き代理目的関数(一次近似)
PPOは厳密なKL制約を、一次勾配だけを使って同様の挙動を実現する クリップ付き目的関数 に置き換えます。
\[ \boxed{L^{\text{CLIP}}(\theta) = \mathbb{E}_t\left[\min!\left(r_t(\theta)\hat{A}_t,;\text{clip}(r_t(\theta), 1{-}\epsilon, 1{+}\epsilon)\hat{A}_t\right)\right]} \]
勾配の導出 :
\(L_t = \min(r_t \hat{A}_t,; \bar{r}_t \hat{A}_t)\) とし、\(\bar{r}_t = \text{clip}(r_t, 1{-}\epsilon, 1{+}\epsilon)\) とします。
\[ \nabla_\theta L_t = \begin{cases} \nabla_\theta r_t(\theta) \cdot \hat{A}_t & \text{if } r_t \hat{A}_t < \bar{r}_t \hat{A}_t \text{ (unclipped term is smaller)} \\ 0 & \text{if } r_t \hat{A}_t \geq \bar{r}_t \hat{A}_t \text{ (clipped term is smaller, gradient = 0)} \end{cases} \]
条件を展開すると、次のようになります。
-
\(\hat{A}_t > 0\) かつ \(r_t < 1+\epsilon\) の場合 :勾配は通常どおり流れます。方策は \(\pi_\theta(a_t\vert s_t)\) を増やすよう促されます。
-
\(\hat{A}_t > 0\) かつ \(r_t \geq 1+\epsilon\) の場合 :勾配は ゼロ です。方策はすでに十分に増加しているため、それ以上押し上げません。
-
\(\hat{A}_t < 0\) かつ \(r_t > 1-\epsilon\) の場合 :勾配は通常どおり流れます。方策は \(\pi_\theta(a_t\vert s_t)\) を減らすよう促されます。
-
\(\hat{A}_t < 0\) かつ \(r_t \leq 1-\epsilon\) の場合 :勾配は ゼロ です。方策はすでに十分に減少しているため、それ以上押し下げません。
ステップ6:PPOの完全な更新則
クリップ付き方策損失、価値損失、エントロピー・ボーナスを組み合わせます。
\[ \boxed{\theta_{k+1} = \theta_k + \alpha \cdot \nabla_\theta \left[L^{\text{CLIP}}(\theta) - c_1 L^{\text{VF}}(\theta) + c_2 H[\pi_\theta]\right]} \]
ここで、
\[ \begin{aligned} L^{\text{VF}}(\theta) &= \left(V_\theta(s_t) - V_t^{\text{target}}\right)^2 & &\text{(value function regression loss)} \\ H[\pi_\theta] &= -\sum_a \pi_\theta(a|s_t)\log\pi_\theta(a|s_t) & &\text{(entropy of the policy)} \end{aligned} \]
Tip
まとめ:これが機能する理由
方策勾配定理 は、方策を改善する方向を与えます。
重点サンプリング によって、\(\pi_{\theta_{\text{old}}}\) のデータを複数エポックにわたって再利用できます。
クリッピング は重要度の重みが極端になるのを防ぎ、更新を安全に保ちます。
min演算子 は、常に(クリップされた項とクリップされていない項の)より保守的な方を選びます。改善に対する悲観的な下限です。
結果 :一次勾配だけを使い、確率1で単調に改善します。ヘッセ行列も、共役勾配も、ラインサーチも必要ありません。
ロールアウトバッファとロールアウト
PPOでは、データ管理に ロールアウトバッファ と呼ばれる専用の短期ストレージシステムを使います。経験をリプレイバッファに無期限に保存するオフポリシーアルゴリズム(DQNなど)とは異なり、PPOではオンポリシーという数学的制約を満たすため、一時的な構造が必要です。
ロールアウトとは何か?
ロールアウト (軌跡)とは、エージェントが現在の方策を環境内で実行することによって生成される相互作用の系列です。
-
プロセス :エージェントは状態を観測し、行動を選択し、報酬を受け取り、次の状態へ移ります。固定されたステップ数、またはエピソードが終了するまで繰り返します。
-
LLM/RLHFの場合 :ロールアウトは、データセットからプロンプトを取り出し、言語モデルにテキスト終了マーカーに到達するまでトークンを1つずつ生成させ、完全なトークン列を作ることです。各トークンが1つの「ステップ」です。
ロールアウトバッファ
ロールアウトバッファは、ロールアウト段階で収集したすべてのデータを一時的に保存します。生成されたトークン/ステップごとに、次を記録します。
\[ \boxed{\mathcal{B} = \left\{ \left(s_t,; a_t,; \log\pi_{\theta_{\text{old}}}(a_t|s_t),; r_t,; V(s_t)\right) \right\}_{t=1}^{T}} \]
-
\(s_t, a_t, r_t\):状態、取った行動、時刻 \(t\) における報酬。
-
\(\log\pi_{\theta_{\text{old}}}(a_t\vert s_t)\):その行動を、実際にデータを生成した方策のもとで取る対数確率(比の計算に必要)。
-
\(V(s_t)\):価値関数によるベースラインの予測(GAEのアドバンテージ計算に必要)。
ロールアウトバッファのライフサイクル
バッファは、厳密な3段階の時計仕掛けのサイクルで動作します。
-
収集 :アクティブな方策が環境と相互作用し、新しい軌跡でバッファを満たします(70Bモデル、batch=128、max_tokens=512の場合、1回のロールアウトあたり最大65Kのトークンレベル遷移)。
-
学習 :軌跡全体でGAEアドバンテージを計算します。\(K\) エポック(通常3〜10)にわたってミニバッチの勾配降下を実行し、クリップ付き目的関数で方策の重みを更新します。
-
消去 :バッファ全体を 完全に消去 します。PPOはオンポリシーなので、古い方策が生成したデータを次の更新サイクルで安全に再利用することはできません。比 \(r_t(\theta)\) が古くなり、クリッピングの保証が壊れるためです。
Warning
ロールアウトバッファとリプレイバッファの違い
リプレイバッファ (DQN、SAC):オフポリシー。数百万の遷移を無期限に保存します。ランダムサンプリングを行い、データを多くの更新で再利用します。
ロールアウトバッファ (PPO、GRPO):オンポリシー。1バッチ分の軌跡を保存します。数エポック使った後、完全に破棄します。サイクルごとに新しいデータが必要です。
これが、PPOに継続的な生成が必要な理由です。更新のたびにバッファが空になり、新しいロールアウトが要求されます。そのため、生成ボトルネック(実時間の60〜70%)が特に深刻になります。
Tip
RLHFにおけるvLLM
RLHF学習では、vLLMは 生成段階 (実時間の60〜70%)に使われます。方策モデルがロールアウトを生成し、その後、報酬モデルがスコアを付けます。主な利点は次のとおりです。
バッチ生成 :プロンプトをまたいで256個以上の応答を並列生成します。
メモリ効率 :より多くの同時生成を収容できるため \(\rightarrow\)、生成ボトルネック中のGPU利用率が高くなります。
プレフィックス共有 :プロンプトあたり \(N=8\) 個の応答を生成する場合(GRPO)、プロンプトのKVは一度だけ計算して8個すべてで共有します。冗長なプリフィルはありません。
統合 :OpenRLHFやTRLなどのフレームワークは、vLLMを生成バックエンドとして使用し、生成ワーカー(vLLM)と学習ワーカー(DeepSpeed/FSDP)を分離します。
RLHFのためのPPO:完全なループ
Note
70BチャットモデルにおけるPPOの具体的なステップ
設定 :128個のプロンプトのバッチ、Llama-3-70B方策、最大512トークン。
ステップ1 — 生成 :128個の応答をサンプリングします(temperature=0.7、top-p=0.9)。時間の60%を要します。
ステップ2 — スコアリング :報酬モデルが各(プロンプト、応答)ペアを採点します。範囲は0.2〜0.95です。
ステップ3 — KL :トークンごとのKLを計算します:\(\text{KL}_t = \log\pi_\theta(y_t\vert y_{<t}) - \log\pi_\text{ref}(y_t\vert y_{<t})\)。トークン全体での平均KLは通常3〜8です。
ステップ4 — 最終報酬 :\(R = r_\text{RM} - 0.05 \times \text{mean_KL}\)(最後のトークンにだけ与えます)。
ステップ5 — GAE :価値ヘッドの予測を使って、各トークン位置の \(\hat{A}_t\) を計算します。アドバンテージをホワイトニングします(平均0、分散1)。
ステップ6 — 更新 :16個のミニバッチでSGDを4エポック実行します。比を \(\epsilon = 0.2\) でクリップします。勾配ノルムは1.0でクリップします。
結果 :方策は1ステップあたり\(\sim\)0.005の勝率向上を示します。1万ステップ後には、SFTに対して絶対値で5〜10%改善します。
Warning
LLMのRLにおけるトークン化の落とし穴
トークンごとのKLペナルティとアドバンテージを計算するときは、トークン化によって「ステップ」が決まることを忘れないでください。1つの概念的な行動(たとえば「2024」の出力)が、トークナイザーによって1〜4トークンに分かれることがあります。これにより、次のような微妙な問題が生じます。
KLの集計 :同じ意味内容でもトークン化が異なると、トークンごとのKLの合計は異なります(たとえば、まれな単語がより多くのサブワードに分割されると、合計KLペナルティが大きくなります)。
信用割当 :GAEはトークン位置ごとにアドバンテージを割り当てますが、意味的な「意思決定」は複数トークンにまたがることがよくあります。モデルが本当に「決定」するのは単語の最初のトークンだけで、その後のサブワードトークンはほぼ決定論的です。
報酬の配置 :最終トークンにだけ報酬を置くと、それより前のすべてのトークンがGAEを通してクレジットを後方へ伝播させなければなりません。長い応答ほど、シグナルが希薄になります。
緩和策 :システムによっては、KLを系列長で正規化したり、単語レベルの報酬シェーピングを使ったり、最終トークンではなく意味的な境界で報酬を適用したりします。
詳細な仕組み:ロジットと方策の更新
PPOは、メモリ上で2つの異なるパラメータ状態を管理します。両者は同じニューラルネットワーク構造を共有しますが、最適化中は異なる重みの値を保持します。
Important
中核アーキテクチャ:2つのネットワーク
方策ネットワーク(\(\pi_\theta\)) :重み \(\theta\) でパラメータ化された、アクティブで稼働中のネットワークです。最適化中のバックプロパゲーションによって継続的に更新されます。
古い方策ネットワーク(\(\pi_{\theta_{\text{old}}}\)) :重み \(\theta_{\text{old}}\) でパラメータ化された、凍結したスナップショットです。1回の最適化サイクルの間、方策が大きく変化しすぎないよう静的なアンカーとして機能します。
フェーズ1:ロールアウト(データ収集)
データ収集中、エージェントは \(T\) ステップにわたって環境と相互作用します。各時刻 \(t\) では次を行います。
-
環境が現在の状態/観測 \(s_t\) を返します(LLMの場合:プロンプト+これまでに生成されたトークン)。
-
状態 \(s_t\) を現在のネットワークスナップショット(\(\theta_{\text{old}}\))に通します。
-
ネットワークが正規化前の生の値、すなわち ロジット \(z_{\text{old}}\) を出力します。これは \(\vert V\vert\)(語彙サイズ32K〜128K)の大きさのベクトルです。
-
Softmaxで確率を計算します。
\[ \boxed{P(a \mid s_t) = \text{Softmax}(z_{\text{old}}) = \frac{\exp(z_{\text{old}, a})}{\sum_{j=1}^{|V|} \exp(z_{\text{old}, j})}} \]
- 行動 \(a_t\)(次のトークン)を \(P(a \mid s_t)\) からサンプリングし、遷移タプル \(\langle s_t, a_t, r_t, s_{t+1} \rangle\) と \(\log \pi_{\theta_{\text{old}}}(a_t \mid s_t)\) をロールアウトバッファに保存します。
Tip
なぜ対数確率を保存するのか?
ロールアウト中に \(\log \pi_{\theta_{\text{old}}}(a_t \mid s_t)\) をスカラーとして保存すると、最適化中に凍結したネットワークを再実行せずに済みます。ミニバッチごとにフルフォワードパスを1回省略できるため、70Bモデルでは大きな効果があります。
フェーズ2:最適化ループ(ミニバッチ更新)
ロールアウトバッファが満杯になると、PPOはミニバッチに対して \(K\) エポック(通常3〜10)を実行します。各勾配ステップでは、保存された状態 \(s_t\) を使って両方の方策のロジットを生成します。
古い方策の評価 (凍結):
\[ z_{\text{old}} = f(s_t; \theta_{\text{old}}) \quad \longrightarrow \quad \log \pi_{\theta_{\text{old}}}(a_t \mid s_t) = \text{LogSoftmax}(z_{\text{old}})[a_t] \]
実装上のショートカット:再計算する代わりに、ロールアウト時に保存したスカラーを再利用します。
現在の方策の評価 (更新対象):
\[ z_{\text{new}} = f(s_t; \theta) \quad \longrightarrow \quad \log \pi_\theta(a_t \mid s_t) = \text{LogSoftmax}(z_{\text{new}})[a_t] \]
\(\theta\) は各ミニバッチの勾配ステップ後に更新されるため、\(z_{\text{new}}\) は最適化ループ全体で連続的に変化します。一方、\(z_{\text{old}}\) は完全に静的なままです。
ロジットから確率比へ
PPOの中核となる比は、新しい方策のもとで行動が古い方策よりどれだけ起こりやすいか、または起こりにくいかを測ります。
\[ \boxed{r_t(\theta) = \frac{\pi_\theta(a_t \mid s_t)}{\pi_{\theta_{\text{old}}}(a_t \mid s_t)}} \]
生の確率を割ることによる壊滅的な数値アンダーフロー/オーバーフローを避けるため、計算は 対数空間 で行います。
\[ \begin{aligned} \log \pi_\theta(a_t \mid s_t) &= \text{LogSoftmax}(z_{\text{new}})[a_t] \\ \log \pi_{\theta_{\text{old}}}(a_t \mid s_t) &= \text{LogSoftmax}(z_{\text{old}})[a_t] \end{aligned} \]
差を指数関数に入れることで、比を復元します。
\[ \boxed{r_t(\theta) = \exp!\left(\log \pi_\theta(a_t \mid s_t) - \log \pi_{\theta_{\text{old}}}(a_t \mid s_t)\right)} \]
この比をPPOのクリッピング目的関数に組み込みます。
\[ \boxed{\mathcal{L}^{\text{CLIP}}(\theta) = \hat{\mathbb{E}}_t \left[ \min!\left(r_t(\theta)\hat{A}_t, ;\text{clip}(r_t(\theta),, 1{-}\epsilon,, 1{+}\epsilon),\hat{A}_t\right) \right]} \]
Tip
クリッピングの仕組み
\(\hat{A}_t > 0\)(良い行動)の場合:比は \(1+\epsilon\) でクリップされ、良い行動を過度に利用できません。
\(\hat{A}_t < 0\)(悪い行動)の場合:比は \(1-\epsilon\) でクリップされ、悪い行動を過度に罰することができません。
\(\min(\cdot)\) によって、常により保守的な推定値を選びます。
結果として、信頼領域内で単調に改善し、壊滅的な崩壊は起こりません。
PPOの重みのライフサイクル
| フェーズ | 現在の \(\theta\) | 古い \(\theta_{\text{old}}\) | 比 \(r_t(\theta)\) |
|---|---|---|---|
| 1. ロールアウト開始 | アクティブなコピー | 同じアクティブなコピー | 常に \(1.0\)(恒等性による) |
| 2. バッチステップ1 | 勾配を計算 | 凍結 | \(1.0\)(初期ステップ) |
| 3. バッチステップ \(N\) | 変更中(\(\theta \neq \theta_{\text{old}}\)) | 凍結 | \(1.0\) から乖離(例:\(1.06\)、\(0.94\)) |
| 4. クリッピング有効 | \(\epsilon\) で有界 | 凍結 | 上限に閉じ込められる(\(1 \pm \epsilon\)) |
| 5. 最適化終了 | 高度に最適化済み | 破棄 | N/A |
| 6. 次のサイクル | \(\theta \rightarrow \theta_{\text{old}}\) | 新しい \(\theta\) を受け取る | \(1.0\) にリセット |
PPOの学習フェーズにおける \(\theta\) と \(\theta_{\text{old}}\) の推移。
連続行動空間への拡張
連続行動空間(LLMでは一般的ではありませんが、ロボティクスRLでは重要)では、ネットワークは離散的なロジットではなく分布パラメータを出力します。
-
予測平均ベクトル \(\mu\)
-
予測標準偏差ベクトル \(\sigma\)
対数確率はガウス分布の対数PDFで計算します。
\[ \boxed{\log \pi(a_t \mid s_t) = -\frac{1}{2}\left(\frac{a_t - \mu}{\sigma}\right)^{!2} - \log(\sigma) - \frac{1}{2}\log(2\pi)} \]
続いて比 \(r_t(\theta) = \exp(\log \pi_\theta - \log \pi_{\theta_{\text{old}}})\) を同じように計算し、同じクリッピング目的関数に入力します。
TRLによる実装
HuggingFace TRLライブラリ (Werra et al. 2022) は、LLM向けの主要なRL手法をすべて、プロダクションで利用できる形で実装しています。
from trl import PPOConfig, PPOTrainer, AutoModelForCausalLMWithValueHead
from transformers import AutoTokenizer
from peft import LoraConfig
# Model setup
model = AutoModelForCausalLMWithValueHead.from_pretrained(
"meta-llama/Llama-3.1-8B-Instruct",
torch_dtype=torch.bfloat16, device_map="auto",
peft_config=LoraConfig(r=64, lora_alpha=16, target_modules=["q_proj","v_proj","k_proj","o_proj"])
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B-Instruct")
# PPO config with all critical hyperparameters
ppo_config = PPOConfig(
learning_rate=1.5e-6, # Low LR for stability
batch_size=128, # Prompts per step
mini_batch_size=16, # Gradient accumulation unit
ppo_epochs=4, # Epochs per batch (reuse data)
gamma=1.0, # No discounting (single turn)
lam=0.95, # GAE lambda
cliprange=0.2, # PPO epsilon
cliprange_value=0.2, # Value function clipping
vf_coef=0.1, # Value loss coefficient
init_kl_coef=0.05, # Initial KL penalty
target_kl=6.0, # Adaptive KL target
whiten_rewards=True, # Normalize advantages
gradient_accumulation_steps=4,
max_grad_norm=1.0,
)
ppo_trainer = PPOTrainer(config=ppo_config, model=model, tokenizer=tokenizer,
dataset=prompt_dataset, data_collator=collator)
# Training loop
for batch in ppo_trainer.dataloader:
# 1. Generate responses
query_tensors = batch["input_ids"]
response_tensors = ppo_trainer.generate(
query_tensors, max_new_tokens=512, temperature=0.7, top_p=0.9, do_sample=True
)
# 2. Score with reward model
texts = [tokenizer.decode(r, skip_special_tokens=True) for r in response_tensors]
rewards = [torch.tensor(reward_model.score(q, r)) for q, r in zip(batch["query"], texts)]
# 3. PPO update (handles KL, GAE, clipping internally)
stats = ppo_trainer.step(query_tensors, response_tensors, rewards)
# Monitor: stats["ppo/mean_scores"], stats["ppo/policy/approx_kl"]
重要なハイパーパラメータ
| パラメータ | 標準値 | 設定を誤った場合の影響 |
|---|---|---|
cliprange | 0.2 | 低すぎる:学習しない。高すぎる:不安定になる。 |
init_kl_coef | 0.01–0.1 | 低すぎる:報酬ハッキング。高すぎる:SFTから動かない。 |
target_kl | 4–8 | 適応的コントローラーの目標。低いほど保守的。 |
ppo_epochs | 4 | 多すぎる:バッチに過適合する。少なすぎる:生成計算を無駄にする。 |
learning_rate | \(1{-}5 \times 10^{-6}\) | 高すぎる:壊滅的な忘却。 |
batch_size | 64–256 | 大きいほど勾配は滑らかになるが、生成計算が増える。 |
temperature | 0.7–1.0 | 低い:探索が少ない。高い:アドバンテージがよりノイジーになる。 |
DPO — 直接選好最適化
動機
PPOは、4つのモデル(方策、参照モデル、報酬モデル、価値ヘッド)をメモリ上に必要とし、複雑なRLインフラを要するうえ、悪名高いほど不安定です。DPO (Rafailov et al. 2023) は、次の問いを投げかけます。RLを省略し、選好から直接学習できないか?
重要な洞察 :RLHFの目的関数(報酬最大化+KLペナルティ)のもとでの最適方策には、 閉形式の解 があります。この同じ目的関数を暗黙に最適化する教師あり損失を導出できます。
数学的導出
ステップ1 :RLHFの目的関数:\(\max_\pi \mathbb{E}_{x,y\sim\pi}[r(x,y)] - \beta D_\text{KL}[\pi\\vert \pi_\text{ref}]\)
ステップ2 :最適解は次のとおりです:\(\pi^*(y\vert x) = \frac{1}{Z(x)} \pi_\text{ref}(y\vert x) \exp\left(\frac{r(x,y)}{\beta}\right)\)
ステップ3 :報酬を方策で表すように整理します:\(r(x,y) = \beta \log \frac{\pi^*(y\vert x)}{\pi_\text{ref}(y\vert x)} + \beta \log Z(x)\)
ステップ4 :Bradley-Terry選好モデル \(P(y_w \succ y_l) = \sigma(r(y_w) - r(y_l))\) に代入します。\(Z(x)\) は相殺されます。
\[ \boxed{\mathcal{L}_\text{DPO}(\theta) = -\mathbb{E}_{(x, y_w, y_l)}\left[\log\sigma\left(\beta\log\frac{\pi_\theta(y_w|x)}{\pi_\text{ref}(y_w|x)} - \beta\log\frac{\pi_\theta(y_l|x)}{\pi_\text{ref}(y_l|x)}\right)\right]} \]
Tip
DPOが実際に行っていること
暗黙の報酬 を \(\hat{r}(x,y) = \beta\log\frac{\pi_\theta(y\vert x)}{\pi_\text{ref}(y\vert x)}\) と定義します。
DPOは、「選ばれた応答の暗黙の報酬は、棄却された応答より高い」という「ラベル」を使った交差エントロピー損失を最小化します。マージンは \(\beta\) によって制御されます。
大きな \(\beta\):大きなマージンが必要 \(\rightarrow\) 方策が積極的に移動 \(\rightarrow\) 忘却のリスク
小さな \(\beta\):小さなマージンで十分 \(\rightarrow\) 方策が参照モデルの近くにとどまる \(\rightarrow\) 保守的
参照モデルは正則化器として機能します。方策は、選好への整合を示すことで、参照モデルからのあらゆる逸脱を「正当化」しなければなりません。
勾配の分析
DPOの勾配は次のように分解できます。
\[ \nabla_\theta \mathcal{L} = -\beta \cdot \underbrace{\sigma(-\hat{r}_w + \hat{r}_l)}_{\text{weight: higher when model is wrong}} \cdot \left[\nabla_\theta \log\pi_\theta(y_w|x) - \nabla_\theta \log\pi_\theta(y_l|x)\right] \]
解釈 :勾配は、選ばれた応答の確率を上げ、棄却された応答の確率を下げます。モデルが現在、誤った答えを好んでいるときに重みが最大になり、「混乱を招く」ペアに学習を集中させます。
Note
DPOの具体例
プロンプト :「10歳の子どもに量子もつれを説明してください。」
選択された応答 (\(y_w\)):「2枚の魔法のコインを持っていると想像してください。片方を投げて表が出ると、どれだけ離れていても、もう片方は瞬時に裏になります!」
\(\log\pi_\theta(y_w\vert x) = -15.3\)、\(\log\pi_\text{ref}(y_w\vert x) = -16.1\)棄却された応答 (\(y_l\)):「量子もつれとは、2つの粒子が相関した状態になり、一方の粒子の量子状態を独立には記述できなくなる現象です。」
\(\log\pi_\theta(y_l\vert x) = -12.8\)、\(\log\pi_\text{ref}(y_l\vert x) = -12.5\)暗黙の報酬 :\(\hat{r}_w = 0.1 \times ((-15.3) - (-16.1)) = 0.08\)、\(\hat{r}_l = 0.1 \times ((-12.8) - (-12.5)) = -0.03\)
損失への入力 :\(\sigma(0.08 - (-0.03)) = \sigma(0.11) = 0.527\)
損失 :\(-\log(0.527) = 0.64\) — モデルは選択された応答をわずかに好んでいるだけです。勾配は強く押し上げます。
学習後:マージンが \(1/(2\beta)\) 付近で安定するまで、選択された応答の確率が増加し、棄却された応答の確率が減少します。
TRLによる実装
以下は、HuggingFace TRLを使った最小限の動作例です。
from trl import DPOConfig, DPOTrainer
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig
from datasets import load_dataset
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B-Instruct",
torch_dtype=torch.bfloat16, attn_implementation="flash_attention_2")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B-Instruct")
# Dataset format: {"prompt": str, "chosen": str, "rejected": str}
dataset = load_dataset("argilla/ultrafeedback-binarized-preferences")
lora_config = LoraConfig(r=64, lora_alpha=16, lora_dropout=0.05,
target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"])
dpo_config = DPOConfig(
output_dir="./dpo_output",
beta=0.1, # KL regularization strength
learning_rate=5e-7, # Very low LR for stability
loss_type="sigmoid", # Standard DPO loss
max_length=2048, # Max sequence length
max_prompt_length=1024, # Truncation for prompts
per_device_train_batch_size=2,
gradient_accumulation_steps=8, # Effective batch = 16
gradient_checkpointing=True,
bf16=True,
num_train_epochs=1, # DPO overfits fast - 1 epoch!
warmup_ratio=0.1,
logging_steps=10,
eval_strategy="steps",
eval_steps=200,
save_strategy="steps",
save_steps=500,
)
trainer = DPOTrainer(
model=model,
ref_model=None, # With LoRA, ref = base model (no copy needed!)
args=dpo_config,
train_dataset=dataset["train"],
eval_dataset=dataset["test"],
tokenizer=tokenizer,
peft_config=lora_config,
)
trainer.train()
# Key metrics to monitor: train/rewards/chosen, train/rewards/rejected, train/rewards/margins
DPOの仕組み:完全なメカニクス
この節では、DPOの計算の詳細、つまり学習中にトークンレベルで何が起きるのかを完全に説明します。
系列レベルの対数確率
DPOにおける重要な量は、 完全な系列 \(y = (y_1, y_2, \ldots, y_T)\) の、プロンプト \(x\) が与えられたときの対数確率です。これは、 トークンごとの対数確率の合計 として計算します。
\[ \boxed{\log \pi_\theta(y|x) = \sum_{t=1}^{T} \log \pi_\theta(y_t \mid x, y_{<t})} \]
各項 \(\log \pi_\theta(y_t \vert x, y_{<t})\) は、位置 \(t\) における、系列内の実際のトークン \(y_t\) のlog-softmax出力です。これは標準的な言語モデリングで使う交差エントロピー損失と同じですが、ここでは平均ではなく 合計 します。
重要な詳細 :\(y_w\) と \(y_l\) の両方で、勾配は すべてのトークン位置 を通って流れます。中間トークンのマスクはありません。すべてのトークンが系列レベルの対数確率に寄与します。
DPO損失の分解
損失から始めます。
\[ \mathcal{L}_{\text{DPO}}(\theta) = -\mathbb{E}_{(x, y_w, y_l) \sim \mathcal{D}}!\left[\log \sigma!\left(\beta \cdot h_\theta(x, y_w, y_l)\right)\right] \]
ここで「暗黙の報酬マージン」\(h_\theta\) は次のとおりです。
\[ h_\theta(x, y_w, y_l) = \underbrace{\log \frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)}}_{\text{chosen reward proxy}} - \underbrace{\log \frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)}}_{\text{rejected reward proxy}} \]
トークンレベルの項に展開すると、次のようになります。
\[ \boxed{h_\theta = \sum_{t=1}^{|y_w|}!\left[\log\pi_\theta(y_w^t | x, y_w^{<t}) - \log\pi_{\text{ref}}(y_w^t | x, y_w^{<t})\right] - \sum_{t=1}^{|y_l|}!\left[\log\pi_\theta(y_l^t | x, y_l^{<t}) - \log\pi_{\text{ref}}(y_l^t | x, y_l^{<t})\right]} \]
フォワードパス:ステップごとの処理
1つの学習例 \((x, y_w, y_l)\) に対して、次を行います。
-
連結 :2つの系列 \([x; y_w]\) と \([x; y_l]\) を作ります。バッチ内で長さがそろうようにパディングします。
-
フォワードパス(方策 \(\pi_\theta\)) :両方の系列をモデルに通します。各応答位置のロジットを収集します。
-
対数確率の抽出 :応答の各位置 \(t\) で、\(\log\text{softmax}(\text{logits}_t)[y_t]\)、つまり実際のトークンの対数確率を取得します。
-
トークン上で合計 :
\[ \begin{aligned} \text{logp_chosen} &= \sum_{t \in \text{response positions}} \log\pi_\theta(y_w^t | x, y_w^{<t}) \\ \text{logp_rejected} &= \sum_{t \in \text{response positions}} \log\pi_\theta(y_l^t | x, y_l^{<t}) \end{aligned} \]
- 参照モデルを引く (事前計算済み、または2回目のフォワードパスから取得):
\[ \begin{aligned} \text{ratio_w} &= \text{logp_chosen} - \text{ref_logp_chosen} \\ \text{ratio_l} &= \text{logp_rejected} - \text{ref_logp_rejected} \end{aligned} \]
-
損失を計算 :\(\mathcal{L} = -\log\sigma(\beta \cdot (\text{ratio_w} - \text{ratio_l}))\)
-
バックワードパス :勾配がステップ5 \(\rightarrow\) 4 \(\rightarrow\) 3 \(\rightarrow\) 2 を逆向きに流れ、\(\theta\) を更新します。
トークンレベルの勾配分析
すべてのトークンに勾配が付くのか? はい。選択された系列の位置 \(t\) におけるロジットに関する勾配は次のとおりです。
\[ \frac{\partial \mathcal{L}}{\partial \text{logits}_t^{(w)}} = -\underbrace{\sigma(-\beta \cdot h_\theta)}_{\text{scaling factor}} \cdot \beta \cdot \frac{\partial \log\pi_\theta(y_w^t | \cdot)}{\partial \text{logits}_t^{(w)}} \]
重要な洞察 :\(\sigma(-\beta \cdot h_\theta)\) のスケーリング係数は、両方の系列のすべてのトークンで 共有 されます。これは適応的な学習率として機能します。
-
\(h_\theta\) が小さい場合(モデルが選択された応答と棄却された応答を区別できない):スケーリングは \(\approx 0.5\)。勾配が強く、積極的に学習します。
-
\(h_\theta\) が大きい場合(モデルがすでに選択された応答を好んでいる):スケーリングは \(\approx 0\)。勾配は無視できるほど小さくなり、過適合を防ぎます。
選択されたトークンへの効果 :確率が増加します(対数確率が押し上げられます)。
棄却されたトークンへの効果 :確率が減少します(対数確率が押し下げられます)。
参照モデルとの相対関係 :\(\pi_{\text{ref}}\) との差だけが重要です。モデルがすでに選択された応答に高い確率を割り当てている場合(参照モデルと一致している場合)、勾配はほとんどありません。
トークン単位と系列単位:長さの正規化
微妙な問題があります。長い系列は、各項を合計するため自然に対数確率が低くなります(各項は \(\leq 0\))。\(\vert y_w\vert \gg \vert y_l\vert\) の場合、損失が短い応答を好む方向に偏る可能性があります。
解決策 :
-
長さを正規化したDPO :\(\log\pi_\theta(y\vert x)\) を \(\frac{1}{\vert y\vert }\sum_t \log\pi_\theta(y_t\vert \cdot)\) に置き換えます。一部の実装で使われています(SimPOが採用)。
-
標準DPO :生の合計(正規化なし)を使います。これは暗黙に冗長さを罰します。モデルは、選択された応答のすべてのトークンに高い確率を割り当てなければなりません。
-
実際の影響 :ベンチマークでは、長さを正規化したDPOは長さの悪用を減らしますが、指示追従品質を損なうことがあります。標準(非正規化)DPOの方がプロダクションでは一般的です。
ラベルマスキング:どのトークンに勾配が付くか
Important
DPOで勾配を受け取るトークン
プロンプトトークン (\(x\)): 勾配なし 。損失は応答位置だけで計算されます。プロンプトトークンはコンテキストを提供しますが、そのロジットは \(\log\pi(y\vert x)\) に寄与しません。
選択された応答トークン (\(y_w\)): すべてのトークンに勾配が付きます 。各 \(y_w^t\) が合計に寄与し、その確率を上げる方向に勾配が働きます。
棄却された応答トークン (\(y_l\)): すべてのトークンに勾配が付きます 。各 \(y_l^t\) が合計に寄与し、その確率を下げる方向に勾配が働きます。
パディングトークン : 勾配なし 。アテンションマスクで除外されます。
疑似コード:DPO学習ステップ
Note
DPOのフォワード+バックワード(PyTorch形式)
def dpo_loss(model, ref_model, batch, beta=0.1): """One DPO training step.""" # batch contains: input_ids_chosen, input_ids_rejected, # labels_chosen, labels_rejected (prompt masked to -100) # 1. Forward pass: get per-token log-probs logps_chosen = get_sequence_logprob(model, batch["chosen"]) logps_rejected = get_sequence_logprob(model, batch["rejected"]) # 2. Reference log-probs (pre-computed or computed here) with torch.no_grad(): ref_logps_chosen = get_sequence_logprob(ref_model, batch["chosen"]) ref_logps_rejected = get_sequence_logprob(ref_model, batch["rejected"]) # 3. Compute implicit reward margins chosen_rewards = beta * (logps_chosen - ref_logps_chosen) rejected_rewards = beta * (logps_rejected - ref_logps_rejected) # 4. DPO loss = -log(sigmoid(chosen_reward - rejected_reward)) loss = -F.logsigmoid(chosen_rewards - rejected_rewards).mean() return loss def get_sequence_logprob(model, sequences): """Sum of log-probs over response tokens only.""" outputs = model(sequences["input_ids"], attention_mask=sequences["mask"]) logits = outputs.logits[:, :-1, :] # Shift for next-token prediction # Gather log-prob of actual tokens labels = sequences["labels"][:, 1:] # Shifted labels log_probs = F.log_softmax(logits, dim=-1) token_logps = log_probs.gather(-1, labels.unsqueeze(-1)).squeeze(-1) # Mask: only sum over response tokens (labels != -100) mask = (labels != -100).float() return (token_logps * mask).sum(dim=-1) # Shape: [batch_size]
よくある落とし穴
Warning
DPO実装の落とし穴
プロンプトのマスクを忘れる :プロンプトトークンを対数確率の合計に含めると、モデルはプロンプトの尤度(役に立たないもの)を最適化し、実効的な \(\beta\) が誤ります。
合計ではなく平均を使う :\(\frac{1}{T}\sum_t \log\pi\) と \(\sum_t \log\pi\) では、暗黙の長さペナルティが異なります。\(\pi_\theta\) と \(\pi_{\text{ref}}\) の間で一貫させなければなりません。
参照モデルが古い :\(\pi_{\text{ref}}\) が \(\pi_\theta\) から遠すぎる場合(例:ベースモデル対ファインチューニング済みモデル)、KL項が支配的になり勾配が消えます。解決策は、ベースモデルではなくSFTチェックポイントを参照モデルとして使うことです。
\(\beta\) が大きすぎる :対数確率の差を増幅します \(\rightarrow\) sigmoidが飽和します \(\rightarrow\) 勾配がゼロになります。\(\beta = 0.1\) から始め、\([0.05, 0.5]\) の範囲で調整します。
\(\beta\) が小さすぎる :理論上は参照モデルからの自由度が増えます(KL制約が弱くなる)が、勾配 \(\propto \beta \cdot \sigma(-\beta h)\) が非常に小さくなります \(\rightarrow\) 損失地形が平坦になります \(\rightarrow\) 収束が極端に遅くなります。モデルには遠くへ移動する「許可」がある一方、どこへ移動すべきかを示すシグナルはほとんどありません。
DPOのバリアントとそれぞれが失敗する場合
Warning
DPOが失敗する場合
1. 分布シフト :古いモデルから得た選好データを使います。現在の方策は異なるテキストを生成します \(\rightarrow\) 損失が無関係な例を最適化することになります。
2. 探索がない :データセットにない振る舞いを発見できません。局所最適解にとどまります。
3. 参照モデルの崩壊 :参照モデルが強すぎると、方策が動けません。弱すぎると、正則化がありません。
4. データ品質 :ノイズのあるラベルが学習を汚染します。多くのサンプルで平均を取るPPOとは異なり、DPOは個々のペアを記憶します。
5. 選好データの多様性 :選択/棄却ペアが、品質差の全範囲をカバーするようにします(単に良い対ひどいだけにしない)。品質だけでなくアプローチが異なるペアは、より豊かな方策の区別を教えます。
\(\beta\) の選択ガイド
| \(\beta\) | 領域 | 使用する場合 |
|---|---|---|
| 0.01 | 非常に積極的 | データが極めてクリーンで、大きな分布シフトが必要な場合のみ |
| 0.05 | 積極的 | 良いデータがあり、SFTからの目立った改善を望む場合 |
| 0.1 | 標準 | デフォルトの開始点。品質と安定性のバランスが良い |
| 0.2 | 保守的 | データにノイズがある場合、またはモデルが望ましい振る舞いにすでに近い場合 |
| 0.5 | 非常に保守的 | 能力を壊してはいけない安全性ファインチューニング |
DPOのバッチサイズ設定とスケーリング
単一系列のトークン予測を行う標準的なSFTとは異なり、DPOは、好ましい系列と好ましくない系列を比較する ペア単位の損失 を利用します。これにより、メモリ使用量と最適化の安定性が根本的に変わります。
グローバルバッチサイズの目標
DPO実装全体の経験的な証拠から、最適なグローバルバッチサイズの範囲が確立されています。
\[ \boxed{B_{\text{global}} \in [32, 128]} \]
-
\(B_{\text{global}} < 32\):暗黙の報酬推定における勾配ノイズが深刻になります \(\rightarrow\) 方策が、(有用性と安全性という)アラインメント目標の間で破壊的に振動します。
-
\(B_{\text{global}} > 128\):収束速度に対する限界効果が小さくなり、分散計算間の通信オーバーヘッドが大きくなります。
数学的分解
DPOは2つのモデルコピー(アクティブな方策 \(\pi_\theta\)+凍結した参照モデル \(\pi_{\text{ref}}\))を同時に読み込むため、系列あたりのメモリは2倍になります。グローバルバッチサイズは次のように分解されます。
\[ \boxed{B_{\text{global}} = B_{\text{micro}} \times N_{\text{GPUs}} \times K_{\text{accum}}} \]
-
\(B_{\text{micro}}\):デバイスごとのマイクロバッチサイズ(1回のフォワードパスにおける選好ペア数)。
-
\(N_{\text{GPUs}}\):並列データ処理デバイスの数。
-
\(K_{\text{accum}}\):重みを更新する前の勾配蓄積ステップ数。
ペアリングの倍率 :1つのDPOデータインスタンスには、プロンプト(\(x\))、選択された応答(\(y_w\))、棄却された応答(\(y_l\))が含まれます。マイクロバッチあたりの実際のテンソル負荷は次のとおりです。
\[ T_{\text{sequences}} = 2 \times B_{\text{micro}} \]
パラメータが\(>\)7Bのモデルを80GB GPU上で、コンテキスト長4096〜8192トークンで動かす場合、物理的な上限によって \(B_{\text{micro}} \in [1, 2]\) に厳しく制限されます。
分散スケーリングの設定
| 設定 | 単一GPU | 8-GPUノード |
|---|---|---|
| \(B_{\text{global}}\) | 64 | 64 |
| \(B_{\text{micro}}\) | 2(4系列) | 2(4系列) |
| \(N_{\text{GPUs}}\) | 1 | 8 |
| \(K_{\text{accum}}\) | 32ステップ | 4ステップ |
| スループット | 逐次/低速 | 高い並列スループット |
DPO学習の分散スケーリングプロファイル(\(B_{\text{global}} = 64\) を目標)。
VRAM最適化:参照モデルの対数確率を事前計算する
DPO損失は次のとおりです。
\[ \mathcal{L}_{\text{DPO}}(\theta) = -\mathbb{E}_{(x, y_w, y_l)}!\left[\log \sigma!\left(\beta \log \frac{\pi_\theta(y_w|x)}{\pi_{\text{ref}}(y_w|x)} - \beta \log \frac{\pi_\theta(y_l|x)}{\pi_{\text{ref}}(y_l|x)}\right)\right] \]
\(\pi_{\text{ref}}\) は学習中 完全に静的 なので、その出力を事前計算できます。
Important
参照モデルを退避する戦略
学習開始前に、データセット \(\mathcal{D}\) 上で \(\pi_{\text{ref}}\) だけを使ってフォワードパスを実行します。
スカラー \(\log \pi_{\text{ref}}(y_w\vert x)\) と \(\log \pi_{\text{ref}}(y_l\vert x)\) をディスクにキャッシュします。
\(\pi_{\text{ref}}\) をGPUメモリから完全に退避させます。
結果 :利用可能なGPUメモリが2倍になります \(\rightarrow\) \(B_{\text{micro}}\) を1〜2から4〜8に増やせるため、ハードウェア利用率と学習スループットを最大化できます。
実装:TRLでは、DPOConfig に
precompute_ref_log_probs=Trueを設定します。70Bモデルでは、クラスタ全体で\(\sim\)140GBのGPUメモリを節約できます。
DPOの拡張とバリアント
直接選好最適化(DPO)は、報酬関数と最適方策の間の閉形式の対応関係を導出することで、RLHFを教師あり学習問題として再定式化します。標準DPOの損失は次のとおりです。
\[ \mathcal{L}_{\text{DPO}}(\theta) = -\mathbb{E}_{(q,y_w,y_l)}!\left[ \log \sigma!\left( \beta \log \frac{\pi_\theta(y_w|q)}{\pi_{\text{ref}}(y_w|q)} - \beta \log \frac{\pi_\theta(y_l|q)}{\pi_{\text{ref}}(y_l|q)} \right) \right], \]
ここで \(y_w\) は好ましい(勝った)応答、\(y_l\) は好ましくない(負けた)応答、\(\beta\) はKLペナルティの強さを制御します。以下の小節では、最も重要な拡張とバリアントを扱います。
f-DPO — 一般化されたfダイバージェンスDPO
Tip
逆方向KLを超えて
標準DPOは、方策と参照モデルの間の正則化器として逆方向KLダイバージェンスを使います。逆方向KLはモードを探す性質があり、確率質量を少数の高報酬応答に集中させることを好みます。順方向KLはモードをカバーする性質があり、もっともらしい応答をすべてカバーするよう確率質量を広げます。f-DPO (J. Wang et al. 2024) は任意のfダイバージェンスへ一般化し、実務者がこれらの振る舞いをトレードオフできるようにします。
f-DPOの損失は、対数比をfダイバージェンス生成関数の導関数に置き換えます。
\[ \mathcal{L}_{f\text{-DPO}} = -\mathbb{E}!\left[ f'!\left(\frac{\pi_\theta(y_w|q)}{\pi_{\text{ref}}(y_w|q)}\right) - f'!\left(\frac{\pi_\theta(y_l|q)}{\pi_{\text{ref}}(y_l|q)}\right) \right], \]
ここで \(f'\) はfダイバージェンス生成関数の導関数です。
Important
TRLにおけるfダイバージェンスの選択肢
reverse_kl :\(f'(u) = \log u\)。標準DPO。モードを探します。
forward_kl :\(f'(u) = -1/u\)。モードをカバーします。多様性が向上します。
js_divergence :\(f'(u) = \log(2u/(u+1))\)。モード探索とモードカバーのバランスを取ります。
alpha_divergence :\(f'(u) = u^{\alpha-1}\)。順方向KLと逆方向KLの間を補間します。
Note
TRLでのf-DPO
from trl import DPOConfig, DPOTrainer # Jensen-Shannon divergence (balanced) config = DPOConfig( f_divergence_type="js_divergence", beta=0.1, ) # Alpha divergence (alpha=0: forward KL, alpha=1: reverse KL) config_alpha = DPOConfig( f_divergence_type="alpha_divergence", f_alpha_divergence_coef=0.5, # alpha parameter beta=0.1, ) trainer = DPOTrainer( model=model, ref_model=ref_model, args=config, train_dataset=dataset, )
Important
f-DPOを使う場合
多様性と品質のバランスが必要な場合は、 JSダイバージェンス を使います。
多様性が最優先の創造的なタスクには、 順方向KL を使います。
正解が1つのタスクには、 逆方向KL (標準DPO)を使います。
alphaダイバージェンス を使うと、連続的に補間してトレードオフを調整できます。
Robust DPO — ロバストDPO
Tip
選好データのノイズのあるラベル
人間による選好のアノテーションにはノイズがあります。アノテーターは意見が一致しなかったり、間違えたり、選好/非選好のラベルを逆にしたりすることがあります。標準DPOはすべてのラベルを正解として扱うため、モデルがノイズに過適合する可能性があります。Robust DPO (Chowdhury et al. 2024) は、既知のノイズモデルのもとで損失を解析的にバイアス補正します。
各ラベルが確率 \(\epsilon\)(ノイズ率)で反転すると仮定します。バイアス補正後の損失は次のとおりです。
\[ \boxed{ \mathcal{L}_{\text{robust}} = \frac{(1-\epsilon),\mathcal{L}_{\text{DPO}}(y_w, y_l) - \epsilon,\mathcal{L}_{\text{DPO}}(y_l, y_w)}{1 - 2\epsilon}, } \]
ここで \(\mathcal{L}_{\text{DPO}}(y_w, y_l)\) は \(y_w\) を選好された応答として扱う標準DPO損失、\(\mathcal{L}_{\text{DPO}}(y_l, y_w)\) はラベルを反転した場合の損失です。この補正によって、ラベルノイズが導入するバイアスを取り除きます。
Important
ロバストDPOの直感
この式は、反転したラベルの寄与を「差し引く」線形結合です。\(\epsilon=0\) では標準DPOに戻ります。\(\epsilon=0.5\) では分母が0に近づきます。ラベルが完全なノイズになり、学習は不可能です。実際には、\(\epsilon \in [0.05, 0.2]\) で現実のアノテーションノイズの大部分をカバーできます。
Note
TRLでのロバストDPO
from trl import DPOConfig, DPOTrainer config = DPOConfig( loss_type="robust", label_smoothing=0.1, # corresponds to epsilon = 0.1 beta=0.1, ) trainer = DPOTrainer( model=model, ref_model=ref_model, args=config, train_dataset=dataset, )
TR-DPO — 信頼領域DPO
Tip
古くなった参照モデルの問題
標準DPOは学習中ずっと固定された参照モデル \(\pi_{\text{ref}}\) を使います。方策 \(\pi_\theta\) が改善すると、KLペナルティ \(\beta \log(\pi_\theta/\pi_{\text{ref}})\) が大きくなり、やがて損失を支配して、それ以上の改善を妨げます。TR-DPO (Gorbatenko 2024) は、現在の方策を追跡するよう参照モデルを定期的に更新します。
TR-DPOは指数移動平均(EMA)を使って参照モデルを更新します。
\[ \pi_{\text{ref}}^{(t+1)} \leftarrow \alpha \cdot \pi_\theta^{(t)} + (1-\alpha) \cdot \pi_{\text{ref}}^{(t)}, \]
ここで \(\alpha \in (0,1)\) は混合係数です。これは \(T_{\text{sync}}\) 回の勾配ステップごとに適用します。
Note
TRLでのTR-DPO
from trl import DPOConfig, DPOTrainer config = DPOConfig( loss_type="sigmoid", # standard DPO loss sync_ref_model=True, # enable TR-DPO ref_model_mixup_alpha=0.6, # alpha: how much of current policy to mix in ref_model_sync_steps=512, # T_sync: sync every 512 steps beta=0.1, ) trainer = DPOTrainer( model=model, ref_model=ref_model, args=config, train_dataset=dataset, )
Important
TR-DPOを使う場合
方策が初期参照モデルから大きく離れる長時間の学習。
KLペナルティの支配によってDPO損失が早期に頭打ちになる場合。
現在の方策から新しい選好データを収集する反復的なDPOパイプライン。
\(\alpha\) を1に近づけると参照モデルを速く更新し、0に近づけるとゆっくり更新します。
EXO — 厳密な最適化
Tip
DPOにおけるKLの方向の問題
DPOは逆方向KL制約のもとで最適方策を解くことから導出されます。しかし、結果として得られる損失は実際には報酬空間で順方向KL目的関数を最適化しており、方向が間違っています。EXO (Ji et al. 2024) は、アラインメントに理論的に正しい目的関数である、逆方向KLの確率マッチングを使ってこれを修正します。
EXOは、モデル分布と目標(報酬最適)分布の間の逆方向KLを最小化します。
\[ \mathcal{L}_{\text{EXO}} = \mathbb{E}_{y \sim \pi_\theta}!\left[ \log \frac{\pi_\theta(y|q)}{p^*(y|q)} \right], \]
ここで \(p^*(y\vert q) \propto \pi_{\text{ref}}(y\vert q) \exp(r(y,q)/\beta)\) は最適方策です。実際には、EXOは利用可能な選好ペアを使って次のように近似します。
\[ \mathcal{L}_{\text{EXO}} \approx -\mathbb{E}!\left[ \log \sigma!\left( \beta \log \frac{\pi_{\text{ref}}(y_w|q)}{\pi_\theta(y_w|q)} - \beta \log \frac{\pi_{\text{ref}}(y_l|q)}{\pi_\theta(y_l|q)} \right) \right]. \]
DPOと比べて、\(\pi_\theta\) と \(\pi_{\text{ref}}\) の役割が入れ替わっていることに注目してください。
Note
TRLでのEXO
from trl import DPOConfig, DPOTrainer config = DPOConfig( loss_type="exo_pair", beta=0.1, ) trainer = DPOTrainer( model=model, ref_model=ref_model, args=config, train_dataset=dataset, )
NCA — ノイズ対照アラインメント
Tip
DPOにおける尤度崩壊
DPOの既知の失敗モードは尤度崩壊です。モデルは負けた応答の確率を下げることを学びますが、損失が気にするのは差だけなので、勝った応答の確率も下げてしまいます。NCA (H. Chen et al. 2024) は、これを防ぐために絶対尤度項を追加します。
NCAはアラインメントをノイズ対照推定として捉え直します。損失は3つの項を持ちます。
\[ \boxed{ \mathcal{L}_{\text{NCA}} = -\log \sigma(r_w) - \tfrac{1}{2}\log \sigma(-r_w) - \tfrac{1}{2}\log \sigma(-r_l), } \]
ここで \(r_y = \beta \log(\pi_\theta(y\vert q)/\pi_{\text{ref}}(y\vert q))\) は暗黙の報酬です。第1項は \(y_w\) に高い報酬を与えるよう促し、第2項と第3項は \(y_w\) と \(y_l\) の両方に高い報酬を与えることを罰します(崩壊を防ぎます)。
Note
TRLでのNCA
from trl import DPOConfig, DPOTrainer config = DPOConfig( loss_type="nca_pair", beta=0.01, # small beta: absolute likelihood term dominates ) trainer = DPOTrainer( model=model, ref_model=ref_model, args=config, train_dataset=dataset, )
Important
NCAを使う場合
DPO学習中に勝った応答の確率が下がっている場合。
相対的な順位だけでなく、応答の絶対的な品質が重要なタスク。
絶対尤度項により大きな重みを与えるため、小さな \(\beta\)(例:0.01)を使います。
SLiC-HF — 系列尤度キャリブレーション
Tip
より単純な代替手段としてのヒンジ損失
DPOのlog-sigmoid損失は滑らかですが、マージンが大きい場合には収束が遅くなることがあります。SLiC-HF (Yao Zhao et al. 2023) はヒンジ損失を使います。これはマージンが閾値を超えると0になり、それ以外では線形になります。単純で高速であり、驚くほど競争力があります。
SLiC-HFの損失は次のとおりです。
\[ \mathcal{L}_{\text{SLiC}} = \max!\left(0,; \delta - \beta\log\frac{\pi_\theta(y_w|q)}{\pi_{\text{ref}}(y_w|q)} + \beta\log\frac{\pi_\theta(y_l|q)}{\pi_{\text{ref}}(y_l|q)} \right), \]
ここで \(\delta\) はマージンの閾値です。モデルが勝った応答と負けた応答の間にすでに \(\delta\) のマージンを割り当てている場合、損失は0になります。
Note
TRLでのSLiC-HF
from trl import DPOConfig, DPOTrainer config = DPOConfig( loss_type="hinge", beta=0.1, ) trainer = DPOTrainer( model=model, ref_model=ref_model, args=config, train_dataset=dataset, )
Iterative RPO — 推論選好最適化
Tip
DPOは生成方法を忘れる
標準DPOは、勝った応答と負けた応答を識別するようモデルを学習します。しかし、推論タスクでは、モデルは正しい推論トレースを生成する必要もあります。識別できても生成できないモデルは、推論時には役に立ちません。RPOは勝った応答にNLL(負の対数尤度)項を追加し、モデルがそれを生成することを学ぶようにします。
RPOの損失は、DPOとSFTを組み合わせたものです。
\[ \mathcal{L}_{\text{RPO}} = \lambda_1 \mathcal{L}_{\text{DPO}}(y_w, y_l) + \lambda_2 \mathcal{L}_{\text{NLL}}(y_w), \]
ここで \(\mathcal{L}_{\text{NLL}}(y_w) = -\log \pi_\theta(y_w\vert q)\) は、勝った応答に対する標準的な言語モデリング損失です。
Note
TRLでのIterative RPO
from trl import DPOConfig, DPOTrainer config = DPOConfig( loss_type="sigmoid", # Standard DPO loss rpo_alpha=1.0, # NLL regularisation weight (RPO) beta=0.1, ) trainer = DPOTrainer( model=model, ref_model=ref_model, args=config, train_dataset=dataset, )
Important
RPOを使う場合
モデルがステップごとの解を生成しなければならない推論タスク(数学、コード、論理)。
DPO学習によって、モデルの流暢さや生成品質が失われる場合。
反復パイプライン:ロールアウトを生成し、ラベルを付け、RPOで学習し、繰り返します。
NLL項が正則化器として機能し、方策の崩壊を防ぎます。
SimPO — 単純な選好最適化
Tip
参照モデルなしの選好学習
DPOは暗黙の報酬を計算するために参照モデルを必要とします。これによりメモリ使用量が2倍になり、複雑さも増します。SimPO (Meng et al. 2024) は、応答の平均対数確率を暗黙の報酬として使い、モデルが短い応答を好むことを防ぐ長さの正規化項を加えることで、参照モデルを取り除きます。
SimPOは暗黙の報酬を次のように定義します。
\[ r_{\text{SimPO}}(y|q) = \frac{\beta}{|y|} \log \pi_\theta(y|q), \]
損失は次のとおりです。
\[ \boxed{ \mathcal{L}_{\text{SimPO}} = -\mathbb{E}!\left[ \log \sigma!\left( \frac{\beta}{|y_w|}\log\pi_\theta(y_w|q) - \frac{\beta}{|y_l|}\log\pi_\theta(y_l|q) - \gamma \right) \right], } \]
ここで \(\gamma > 0\) は目標報酬マージンです。これにより、勝った応答の報酬が負けた応答より少なくとも \(\gamma\) だけ厳密に高くなることを保証します。
Important
SimPOとDPOとORPOの比較
DPO :参照モデルを使う。比に基づく暗黙の報酬。
ORPO :参照モデルなし。SFT損失にオッズ比項を追加。
SimPO :参照モデルなし。長さを正規化した対数確率の報酬+マージン。
SimPOはDPOより単純(参照モデルが不要)で、ORPOより原理に忠実です。
SimPOの長さの正規化は重要です。これがないと、モデルは長い応答を好みます。
Note
TRLでのSimPO
from trl import DPOConfig, DPOTrainer config = DPOConfig( loss_type="simpo", simpo_gamma=0.5, # target reward margin gamma beta=2.5, # length normalisation coefficient # No ref_model needed! ) trainer = DPOTrainer( model=model, ref_model=None, # SimPO is reference-free args=config, train_dataset=dataset, )
GRPO — グループ相対方策最適化
グループ相対方策最適化(GRPO) (Shao et al. 2024) は、言語モデル向けに特別に設計された強化学習アルゴリズムです。別個の価値ネットワーク(クリティック)を不要にします。DeepSeekMathの一部としてDeepSeekが導入し、その後DeepSeek-R1 (DeepSeek-AI et al. 2025) へスケールされたGRPOは、LLM学習の主流RL手法へ急速に成長しました。TRL、OpenRLHF、veRLなど、ほとんどのオープンソースのアラインメント・フレームワークがデフォルトアルゴリズムとして採用しています。
中核となる考え方は、単純に見えて実は巧妙です。PPOのクリティックのように期待報酬を予測するニューラルネットワークを学習する代わりに、同じプロンプトへ複数の応答を生成し、グループの報酬統計をベースラインとして使うことで、経験的にそれを推定します。これによりメモリ上のモデルが1つ減り、エンジニアリングの複雑さが半減します。さらに意外なことに、経験的なベースラインは、十分に学習されていない価値関数より正確なため、PPOを上回ることもあります。
GRPOは特に次の用途で有効です。
-
検証可能な報酬を持つ推論タスク (数学、コード)。二値の正誤が明確なシグナルを与えます。
-
大規模モデル (70B以上)。クリティックを取り除くことによるメモリ節約が重要になります。
-
マルチターンおよびエージェント環境 。ツール呼び出しをまたいだ価値推定は扱いにくいためです。
この章では、GRPOの動機、アルゴリズム、主要なバリアント(Dr. GRPO、DAPO、2-GRPO、GDPO)、そしてTRLを使った実装を扱います。
動機
PPOの価値モデル(クリティック)には、言語モデルにとって3つの大きな問題があります。
-
メモリ :価値ヘッドは方策のバックボーンを共有します(70Bでは140GB)。別個に持つとメモリが2倍になります。
-
正確さ :部分系列に対する期待報酬の予測は極めて困難です。価値関数はしばしば間違います \(\rightarrow\) 間違ったアドバンテージ \(\rightarrow\) 間違った勾配方向。
-
学習 :価値ヘッドの収束には多くのサンプルが必要です。RL初期にはノイズの多い予測を出し、方策学習を不安定にします。
GRPOの重要な洞察 (Shao et al. 2024):\(V(s)\) を学習する代わりに、サンプルのグループから経験的に推定します。同じプロンプトに対して \(G\) 個の応答を生成し、それらの報酬を計算して、グループ統計をベースラインとして使います。
アルゴリズム
-
各プロンプト \(x\) について、\(G\) 個の完了をサンプリングします:\(\{y_1, \ldots, y_G\} \sim \pi_\theta(\cdot\vert x)\)
-
それぞれを採点します:\(r_i = R(x, y_i)\)
-
グループ内で正規化します:\(\hat{A}_i = \frac{r_i - \mu_G}{\sigma_G}\)。ここで \(\mu_G = \frac{1}{G}\sum_j r_j\)、\(\sigma_G = \text{std}(\{r_j\})\) です。
-
これらのアドバンテージを使って、PPO形式のクリップ付き更新を適用します。
\[ \boxed{\hat{A}_i = \frac{r_i - \mu_G}{\sigma_G}, \qquad L = \mathbb{E}\left[\min\left(r_t(\theta)\hat{A}_i,; \text{clip}(r_t(\theta), 1{\pm}\epsilon)\hat{A}_i\right)\right] - \beta D_\text{KL}[\pi_\theta|\pi_\text{ref}]} \]
Tip
グループ正規化が機能する理由
グループ平均は \(V(s)\) を近似する :同じプロンプトに十分な数の応答をサンプリングすれば、その平均報酬は期待報酬、つまり価値関数のモンテカルロ推定になります。
平均より上=良い動き :\(\hat{A}_i > 0\) は、この応答がこのプロンプトに対して平均より良いことを意味します。強化します。
平均より下=悪い動き :\(\hat{A}_i < 0\) は、平均より悪いことを意味します。抑制します。
正規化 :\(\sigma_G\) で割ることで、報酬範囲が異なるプロンプト間でアドバンテージがスケール不変になります。
DeepSeek-R1のブレークスルー (DeepSeek-AI et al. 2025):二値の正誤報酬(答えが正しければ \(r = 1\)、それ以外は \(r = 0\))だけを使った純粋なGRPOを数学/コードで学習すると、明示的な指示なしに、Chain-of-thought推論、自己検証、エラー訂正を自発的に発展させました。
TRLによる実装
以下は、HuggingFace TRLを使った最小限の動作例です。
from trl import GRPOConfig, GRPOTrainer
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B-Instruct",
torch_dtype=torch.bfloat16, attn_implementation="flash_attention_2")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")
grpo_config = GRPOConfig(
output_dir="./grpo_output",
num_generations=8, # G = group size
temperature=1.0, # High temp for diversity within group
max_completion_length=2048, # Max response length
beta=0.04, # KL penalty coefficient
learning_rate=1e-6,
per_device_train_batch_size=2, # Prompts per device (x8 gens = 16 responses)
gradient_accumulation_steps=8,
num_train_epochs=2,
bf16=True,
gradient_checkpointing=True,
max_grad_norm=0.5,
logging_steps=10,
# vLLM generation for speed (critical for GRPO due to 8x generation)
use_vllm=True,
vllm_gpu_memory_utilization=0.7,
)
# Reward function: binary correctness for math
def reward_fn(completions, prompts, **kwargs):
"""Return list of floats: 1.0 if correct, 0.0 if wrong."""
rewards = []
for completion, prompt in zip(completions, prompts):
answer = extract_answer(completion)
expected = get_ground_truth(prompt)
rewards.append(1.0 if answer == expected else 0.0)
return rewards
# Can combine multiple reward functions!
def format_reward_fn(completions, **kwargs):
"""Bonus for using proper LaTeX formatting."""
return [0.5 if "\\boxed{" in c else 0.0 for c in completions]
trainer = GRPOTrainer(
model=model,
args=grpo_config,
reward_funcs=[reward_fn, format_reward_fn], # Multi-objective!
train_dataset=math_dataset,
tokenizer=tokenizer,
)
trainer.train()
グループサイズの分析
| \(G\) | シグナル品質 | 計算量 | 使用する場合 |
|---|---|---|---|
| 2 | 非常にノイジー(コイントス) | 低 | 推奨しない。安定した学習にはノイズが多すぎます |
| 4 | 中程度 | 中 | 簡単な実験、簡単なタスク(合格率 \(>\) 50%) |
| 8 | 良好(標準) | 高 | デフォルト。ほとんどのタスクに適したバランス |
| 16 | 非常に良好 | 非常に高 | 難しいタスク(合格率 \(<\) 20%)、正例を得るため多くの試行が必要な場合 |
| 32 | ほぼ完全 | 極端に高い | 莫大な計算資源があり、非常に難しいタスクの場合のみ |
Important
重要:グループには成功と失敗の両方が必要
\(G\) 個の応答がすべて正解(\(r_i = 1 ;\forall i\))の場合:アドバンテージはすべて0で、学習シグナルがありません!
すべて不正解の場合も同じ問題が起きます。プロンプトの難易度は、モデルの能力に合っていなければなりません。
Goldilocksルール :現在のモデルで合格率が20〜80%になるようプロンプトをフィルタします。モデルが改善するたびに、500ステップごとに再フィルタします。
GRPOのバリアントと拡張
GRPOグループにおける多様性
Tip
RL学習におけるモード崩壊
多様性への圧力がないと、RLで学習したLLMは高報酬の狭い範囲の応答へ崩壊します。
モデルは各種の質問に対して1つの「テンプレート」回答を学習します。
エントロピーが急速に低下し、モデルが決定論的になります。
報酬ハッキングが容易になります(狭い出力ほど悪用しやすいため)。
汎化性能が低下します。モデルは推論ではなく、報酬パターンを記憶します。
KLペナルティ \(\beta D_\text{KL}[\pi_\theta \\vert \pi_\text{ref}]\) は主要な多様性メカニズムですが、それだけでは十分ではありません。
Important
GRPOのグループ多様性
GRPOはプロンプトごとに \(N\) 個の応答を生成し、グループ内の順位付けを使います。グループ内の多様性が重要です。
高い温度 (\(\tau=0.8\)〜\(1.0\)):意味のある比較ができるよう、応答を多様にします。
大きな \(N\) (8〜16):サンプルが多いほど、良いアプローチと悪いアプローチの両方を含む可能性が高くなります。
DAPOの「繰り返しなし」ペナルティ :グループ内の重複応答を拒否し、探索を強制します。
\(N\) 個の応答がすべて同一の場合:アドバンテージは0で、学習シグナルがありません。
応答が多様すぎる場合(ランダム):報酬シグナルがノイジーになり、学習が遅くなります。
スイートスポット :話題を保ちながら、異なるアプローチを生み出す温度。
| 手法 | 多様性を促す方法 |
|---|---|
| エントロピー・ボーナス | 報酬に \(\alpha H(\pi_\theta)\) を加えます。低エントロピー(決定論的)方策を直接罰します。 |
| KLペナルティ | \(-\beta D_\text{KL}[\pi_\theta \\vert \pi_\text{ref}]\) により、単一モードへの崩壊を防ぎます。 |
| 棄却サンプリング | 多数の候補を生成し、報酬上位の\(k\)個を残します。高品質で多様な応答を自然に選びます。 |
| Best-of-N | 推論時に\(N\)個の応答を生成してすべて採点し、最良のものを返します。多様性はサンプリングから生じます。 |
| 多様なペアを使ったDPO | 品質だけでなくアプローチが異なる選択/棄却ペアで学習します。 |
| 複数報酬 | 複数の報酬モデル(安全性、有用性、コード品質)を使います。1つの次元への崩壊を防ぎます。 |
RL学習で多様性を促進する手法。
Warning
多様性と品質のトレードオフ
多様性が常に多いほど良いとは限りません。
多様性が多すぎる(高エントロピー)=ランダムで役に立たない応答
多様性が少なすぎる(低エントロピー)=反復的で報酬ハッキングされた応答
監視 :学習中、応答エントロピー、ユニークなn-gram比率、報酬分布の幅を追跡します。3つすべてが同時に低下しているなら、崩壊の問題が起きています。
RLデータ収集のための言語化サンプリング
事後学習のアラインメント(RLHF、DPO)は、典型性バイアスによって出力の多様性を低下させることがよくあります。人間のアノテーターは、新しい代替案よりも、馴染みのある「典型的な」テキストを系統的に好むためです。このモード崩壊は、純粋にアルゴリズム上の現象ではなく、データレベルの現象です。
言語化サンプリング(VS) (J. Zhang et al. 2025) は、1回の生成で複数の応答にわたる 確率分布を明示的に言語化 するようモデルに求めることで、この崩壊を回避する、学習不要のプロンプト戦略です。
Important
言語化サンプリング — 核となる考え方
1つの応答をサンプリングする(モードへ崩壊する)代わりに、モデルに複数の候補応答とその確率を出力するようプロンプトで指示します。
‘‘Generate 5 jokes about coffee and their corresponding probabilities.’’モデルは次のようなリストを生成します。
ジョークA(確率:0.35)
ジョークB(確率:0.25)
ジョークC(確率:0.20)
ジョークD(確率:0.12)
ジョークE(確率:0.08)
その後、この言語化された分布からサンプリングします。モデルがargmaxだけでなく完全な分布を明示的に表現するため、確率は低いものの創造的で多様な応答にもアクセスできるようになります。
# Verbalized Sampling: prompt model to output distribution
def verbalized_sample(model, tokenizer, task, n=5):
prompt = (
f"{task}\n\n"
f"Generate {n} different responses and assign a probability "
f"to each (probabilities should sum to 1.0). "
f"Format: [response] (probability: X.XX)"
)
output = model.generate(
tokenizer(prompt, return_tensors="pt").input_ids,
max_new_tokens=1024,
temperature=0.7,
do_sample=True,
)
# Parse responses and probabilities from output
responses, probs = parse_verbalized_distribution(
tokenizer.decode(output[0])
)
# Sample from the verbalized distribution
import random
chosen = random.choices(responses, weights=probs, k=1)[0]
return chosen
Tip
言語化サンプリングが機能する理由
モード崩壊を回避 :アラインメント済みモデルからの標準サンプリングは、1つか2つの「安全な」応答に大きく集中します。VSは、モデルが知っているものの通常は表に出さない代替案を言語化するよう強制します。
多様性は意味的 :温度スケーリング(語彙上のノイズ)とは異なり、VSは本当に異なるアプローチを生成します。モデルが異なる選択肢について推論するためです。
能力に応じてスケール :より能力の高いモデルは、よりキャリブレーションされた言語化分布を生成します。そのためVSからより大きな恩恵を受けます(創造的文章で1.6〜2.1\(\times\)の多様性向上)。
学習不要 :ファインチューニングやデコーディングの変更は不要で、推論時に任意の指示追従モデルで機能します。
GRPOの場合 :VSを使ってプロンプトごとに \(G\) 個の応答候補を生成します。表面的な変化ではなく意味的に多様なアプローチをグループに含められます。
拡張に入る前に、これまでの節で確立した基本GRPOアルゴリズムを簡単に振り返りましょう。完了をグループでサンプリングし、報酬を正規化し、クリップ付き方策勾配を適用するという中核メカニズムは、シンプルで洗練されています。しかし実務ではすぐに、特定の失敗モードが発見されました。事前学習バイアスが勾配を薄める問題(Dr. GRPO)、対称クリッピングが探索を制限する問題(DAPO)、大きなグループサイズの無駄(2-GRPO)、複数目的設定における報酬スケールの不均衡(GDPO)です。以下の節では、これらを順番に扱います。
Important
GRPOベースラインの振り返り
プロンプト \(q\) に対して、\(G\) 個の完了 \(\{o_1,\dots,o_G\}\) を現在の方策 \(\pi_\theta\) からサンプリングします。報酬 \(\{r_1,\dots,r_G\}\) を計算し、正規化します。 \[ \hat{A}_i = \frac{r_i - \mu_r}{\sigma_r + \epsilon}, \qquad \mu_r = \frac{1}{G}\sum_{i=1}^G r_i, \quad \sigma_r = \sqrt{\frac{1}{G}\sum_{i=1}^G (r_i-\mu_r)^2}. \] クリップ付き代理損失(トークンごと)は次のとおりです。 \[ \mathcal{L}_{\text{GRPO}} = -\frac{1}{G}\sum_{i=1}^G \frac{1}{|o_i|} \sum_{t=1}^{|o_i|} \min!\Bigl( \rho_{i,t},\hat{A}_i,; \mathrm{clip}(\rho_{i,t},1{-}\epsilon,1{+}\epsilon),\hat{A}_i \Bigr), \] ここで \(\rho_{i,t} = \pi_\theta(o_{i,t}\vert q,o_{i,<t}),/,\pi_{\text{old}}(o_{i,t}\vert q,o_{i,<t})\) です。
DAPO — 動的適応方策最適化
Tip
DAPOとはなぜか?
基本GRPOは対称クリッピングを使います。トークンの確率を増やしたい場合も減らしたい場合も、方策は同じように制約されます。しかし、探索と活用ではリスクの性質が異なります。良いトークンの確率を増やすことは一般に安全ですが、悪い完了にたまたま現れたトークンを抑制すると、そのトークン自体は中立であった場合に壊滅的な間違いになる可能性があります。DAPO (Yu et al. 2025) は、5つの対象を絞った修正を導入し、これらを組み合わせることで学習の安定性と最終性能を大幅に改善します。
コンポーネント1 — 非対称クリッピング(Clip-Higher)
標準的なPPO/GRPOは重要度比を \([1-\epsilon, 1+\epsilon]\) で対称にクリップします。DAPOはこれを非対称な帯域に置き換えます。
\[ \boxed{ \mathrm{clip}_{\text{DAPO}}(\rho, A) = \begin{cases} \mathrm{clip}(\rho,, 1-\epsilon,, 1+\epsilon_{\text{high}}) & \text{if } A > 0 \\ \mathrm{clip}(\rho,, 1-\epsilon,, 1+\epsilon) & \text{if } A \le 0 \end{cases} } \]
ここで \(\epsilon_{\text{high}} > \epsilon\)(典型的な値:\(\epsilon=0.2\)、\(\epsilon_{\text{high}}=0.28\))です。アドバンテージが正のとき、方策は良いトークンに向けてさらに動くことが許されます。アドバンテージが負のときは、過度な抑制を避けるため通常の保守的なクリッピングを適用します。
コンポーネント2 — トークンレベルの損失集計
基本GRPOは損失を系列数 \(G\) で割ります。DAPOは、すべての系列にわたる総トークン数で割ります。
\[ \mathcal{L}_{\text{token}} = -\frac{1}{\sum_{i=1}^G |o_i|} \sum_{i=1}^G \sum_{t=1}^{|o_i|} \min!\bigl(\rho_{i,t}\hat{A}_i,; \mathrm{clip}_{\text{DAPO}}(\rho_{i,t},\hat{A}_i),\hat{A}_i\bigr). \]
これにより、長い完了が単に多くのトークンを含むという理由だけで勾配シグナルを支配することを防ぎます。
コンポーネント3 — 長文フィルタリング
完了が切り詰められた場合(最大長の予算内にEOSトークンがない場合)、それは誤解を招くシグナルを与えます。正しく生成されたものの、たまたま切り詰め境界の前に現れたトークンに対して、モデルが罰せられるためです。DAPOはこのような完了を完全にマスクします。
\[ m_i = \mathbf{1}[\text{EOS} \in o_i], \qquad \mathcal{L}_{\text{filtered}} = -\frac{\sum_{i=1}^G m_i \sum_t (\cdots)}{\sum_{i=1}^G m_i |o_i|}. \]
コンポーネント4 — ソフトな長さ超過ペナルティ
ハードマスクの代わりに、よりソフトなバリアントでは、完了が最大長 \(L_{\max}\) に近づくにつれて滑らかに増加する長さペナルティを適用します。
\[ r_i \leftarrow r_i - \lambda \cdot \max!\left(0,, \frac{|o_i| - L_{\text{cache}}}{L_{\max} - L_{\text{cache}}}\right), \]
ここで \(L_{\text{cache}}\) は「安全な」長さのしきい値です。
コンポーネント5 — 動的サンプリング
DAPOは、完了のグループ全体が同じ報酬(すべて正解、またはすべて不正解)を受け取ったプロンプトを再サンプリングします。このようなグループは正規化後の勾配が0になるためです。これにより、学習全体を通して実効バッチサイズを安定させます。
Note
TRLでのDAPO
from trl import GRPOConfig, GRPOTrainer config = GRPOConfig( # Asymmetric clipping epsilon=0.2, epsilon_high=0.28, # Clip-Higher # Token-level loss loss_type="dapo", # enables token-level aggregation # Overlong filtering mask_truncated_completions=True, # Generation budget max_completion_length=1024, num_generations=8, # Note: DAPO loss internally handles zero-variance group filtering ) trainer = GRPOTrainer( model=model, reward_funcs=[reward_fn], args=config, train_dataset=dataset, ) trainer.train()
Important
DAPOを使う場合
完了が長さ制限に頻繁に達する長文推論タスク。
学習中盤に報酬分散が崩壊している場合。
基本GRPOが不安定な場合(損失の急上昇、エントロピー崩壊)。
ほとんどのタスクで、基本GRPOに対するそのまま置き換え可能な改善として推奨されます。
GSPO – 系列単位方策最適化
Tip
オフポリシー問題
GRPOは重要度比をトークンごとにクリップします。しかし、500トークンの系列では、個々の比がすべて \([1-\epsilon, 1+\epsilon]\) の範囲内であっても、トークンごとの比の積が天文学的に大きくなったり小さくなったりする可能性があります。同じバッチに対して複数回の勾配ステップを行うオフポリシー学習では、この不一致が急速に拡大し、クリッピングの境界は系列レベルでは意味を失います。
GSPO (Chen et al. 2025) は、トークンごとの比の幾何平均として系列レベルの重要度重みを定義します。これは、系列全体の確率比の \(\vert o_i\vert\) 乗根に等しくなります。
\[ \boxed{ s_i(\theta) = \left(\frac{\pi_\theta(o_i \mid q)}{\pi_{\text{old}}(o_i \mid q)}\right)^{1/|o_i|} = \exp!\left(\frac{1}{|o_i|}\sum_{t=1}^{|o_i|} \log \frac{\pi_\theta(o_{i,t}|q,o_{i,<t})}{\pi_{\text{old}}(o_{i,t}|q,o_{i,<t})}\right). } \]
これは長さ正規化された系列確率比です。GSPOの損失は、系列ごとにこの単一のスカラーをクリップします。
\[ \mathcal{L}_{\text{GSPO}} = -\frac{1}{G}\sum_{i=1}^G \min!\Bigl(s_i(\theta),\hat{A}_i,; \mathrm{clip}(s_i(\theta),1{-}\epsilon,1{+}\epsilon),\hat{A}_i\Bigr). \]
Important
GSPOとGRPOのクリッピング
GRPO :\(\vert o_i\vert\) 個のトークンごとの比を、それぞれ独立にクリップします。すべての比が境界内でも、系列の積比は \(10^{50}\) になり得ます。
GSPO :幾何平均を系列ごとに1回クリップします。系列レベルの方策シフトが境界内に収まることを保証します。
GSPOはオフポリシー重要度サンプリングに対して理論的に正しく、GRPOは近似です。
Note
TRLでのGSPO
from trl import GRPOConfig, GRPOTrainer config = GRPOConfig( # Sequence-level importance sampling importance_sampling_level="sequence", # GSPO mode # Off-policy: reuse each batch for multiple gradient steps steps_per_generation=4, num_generations=8, epsilon=0.2, ) trainer = GRPOTrainer( model=model, reward_funcs=[reward_fn], args=config, train_dataset=dataset, )
Warning
GSPOを使う場合
GSPOは
steps_per_generation > 1(オフポリシー学習)の場合に最も有効です。純粋なオンポリシー学習(\(\text{steps_per_generation}=1\))では、GRPOとの差は無視できる程度です。オフポリシー学習は、最もコストの高い生成ステップのコストを大幅に削減できるため、GSPOとオフポリシー学習の組み合わせは高い効率を実現します。
Dr. GRPO – バイアス除去報酬GRPO
Tip
事前学習バイアスの問題
標準GRPOはグループ内でアドバンテージを正規化しますが、事前学習分布は系統的なバイアスを導入します。すなわち、タスクに関係する情報を何も持たない場合でも、事前学習データで頻出するトークンが大きな勾配を受け取ります。Dr. GRPO (Y. Liu et al. 2025) はこのバイアスを特定して補正し、情報量のあるトークンに勾配信号を集中させます。
Dr. GRPOは、トークンの報酬信号への周辺的な寄与を考慮して、トークンごとの勾配重みを変更します。モデルがすでに高い確率を割り当てているトークン(報酬に関係なく)は、重みを小さくします。
\[ w_{i,t} = \hat{A}_i \cdot \bigl(1 - \pi_{\text{ref}}(o_{i,t}|q,o_{i,<t})\bigr), \]
ここで \(\pi_{\text{ref}}\) は参照(事前学習済み)モデルです。これはトークン効率化の一形態であり、方策が本当に変わる必要のあるトークンに勾配を集中させます。
Note
TRLでのDr. GRPO
from trl import GRPOConfig, GRPOTrainer config = GRPOConfig( loss_type="dr_grpo", num_generations=8, beta=0.04, # KL penalty coefficient ) trainer = GRPOTrainer( model=model, ref_model=ref_model, # required for token weighting reward_funcs=[reward_fn], args=config, train_dataset=dataset, )
Important
Dr. GRPOを使う場合
事前学習とRLの間で語彙の不一致が大きいタスク。
頻出するフィラートークンが勾配を支配している場合。
初期方策に近い参照モデルと組み合わせると効果的です。
2-GRPO – 最小2ロールアウトGRPO
Tip
「2つあれば十分」という洞察
「It Takes Two」論文 (H. Xu et al. 2025) は、\(G=2\)(プロンプトあたり完了2つだけ)のGRPOが、ほとんどの推論ベンチマークで \(G=16\) のGRPOと同等以上になることを、実験的・理論的に示しています。これは意外な結果です。なぜ少ないサンプルで十分なのでしょうか。
重要な洞察は、GRPOの有効性が主に正確なアドバンテージ推定(大きな \(G\) が必要)から来ているわけではないということです。むしろ、DPOと構造的に似た暗黙の対照的目的から来ています。
\[ \mathcal{L}_{\text{2-GRPO}} \approx -\mathbb{E}_{(o^+, o^-) \sim \pi_\theta}!\left[ \log \sigma!\left( \beta \log \frac{\pi_\theta(o^+|q)}{\pi_{\text{old}}(o^+|q)} - \beta \log \frac{\pi_\theta(o^-|q)}{\pi_{\text{old}}(o^-|q)} \right) \right], \]
ここで \(o^+\) は報酬の高い完了、\(o^-\) は報酬の低い完了です。\(G=2\) では、この対照構造が明示的になります。\(G=16\) では同じ信号が存在しますが、冗長なペアによって薄められます。
Important
2-GRPOによる計算量削減
\(G=2\) 対 \(G=16\): 生成計算量が8\(\times\)少ない 。
生成は通常、所要時間の60–80%を占めるボトルネックです。
学習全体の高速化:約4–6\(\times\)。
GSM8K、MATH、コードベンチマークで精度低下なし。
Note
TRLでの2-GRPO
from trl import GRPOConfig, GRPOTrainer config = GRPOConfig( num_generations=2, # The key change -- just two rollouts loss_type="grpo", # Standard GRPO loss is fine epsilon=0.2, # With G=2, batch size must be at least 2 * num_prompts_per_step per_device_train_batch_size=2, ) trainer = GRPOTrainer( model=model, reward_funcs=[reward_fn], args=config, train_dataset=dataset, )
Warning
2-GRPOの注意点
\(G=2\) では、アドバンテージの正規化は2つの値だけに対して行われるため、正規化されたアドバンテージは常に \(\{+1, -1\}\) です(報酬が等しい場合は \(\{0, 0\}\))。つまり、報酬差の大きさにかかわらず勾配の大きさが固定されます。報酬差の大きさが重要なタスク(部分点など)では、より大きな \(G\) が依然として有益な場合があります。
SAPO – ソフト適応型方策最適化
Tip
ハードクリッピングの脆さ
PPO型のクリッピングは不連続な勾配を作ります。クリップ帯の外側では勾配が0になり、内側では0ではありません。この「崖の縁」は境界付近で不安定性を引き起こす可能性があり、信頼領域が \(\epsilon\) の選択に敏感になります。SAPO (Han et al. 2025) は、ハードクリップを滑らかで温度制御されたゲート関数に置き換えます。
SAPOは \(\min(\rho A, \mathrm{clip}(\rho,\cdot),A)\) 目的を、次の滑らかな代理目的に置き換えます。
\[ \boxed{ \mathcal{L}_{\text{SAPO}}(\rho, A) = \begin{cases} -A \cdot \sigma!\left(\dfrac{\rho - 1}{\tau_+}\right) \cdot \rho & \text{if } A > 0 \\[8pt] -A \cdot \sigma!\left(\dfrac{1 - \rho}{\tau_-}\right) \cdot \rho & \text{if } A \le 0 \end{cases} } \]
ここで \(\sigma\) はシグモイド関数、\(\tau_+, \tau_-\) は非対称な温度パラメータです。温度が高いほどゲートは柔らかくなり(探索が増える)、低いほどハードクリッピングに近づきます。
Important
SAPOの温度パラメータの直感
\(\tau_+ = 1.0\):正のアドバンテージに対する中程度のゲート(探索を許容)。
\(\tau_- = 1.05\):負のアドバンテージに対してやや柔らかいゲート(過度な抑制を回避)。
\(\tau \to 0\):ハードなPPOクリッピングを再現します。
\(\tau \to \infty\):クリッピングなしの方策勾配を再現します。
Note
TRLでのSAPO
from trl import GRPOConfig, GRPOTrainer config = GRPOConfig( loss_type="sapo", sapo_temperature_pos=1.0, # tau_+ for positive advantages sapo_temperature_neg=1.05, # tau_- for negative advantages num_generations=8, ) trainer = GRPOTrainer( model=model, reward_funcs=[reward_fn], args=config, train_dataset=dataset, )
TISとMIS – 切り捨て・マスク重要度サンプリング
Warning
vLLMの確率不一致が静かに起こす問題
高速な生成のためにvLLMを使う場合、vLLMが返す対数確率は学習時のフォワードパスで計算したものと異なります (Zhong, Chen, et al. 2025)。これはバグではありません。異なるCUDAカーネル、異なる浮動小数点精度、異なるアテンション実装(FlashAttentionとPagedAttentionなど)から生じます。この不一致はオンポリシー仮定を静かに壊します。重要度比の計算に使う「旧方策」の確率が誤るため、勾配推定にバイアスが生じます。
切り捨て重要度サンプリング(TIS)
TISは、勾配に切り捨てた補正係数を掛けることでバイアスを補正します。
\[ \boxed{ w_{\text{TIS}}(o_i) = \min!\left(C,; \frac{\pi_{\text{train}}(o_i|q)}{\pi_{\text{vllm}}(o_i|q)}\right), } \]
ここで \(\pi_{\text{train}}\) は学習時フォワードパスの確率、\(\pi_{\text{vllm}}\) はvLLMが報告する確率です。\(C\) での切り捨てにより、極端な補正が学習を不安定化するのを防ぎます。
マスク重要度サンプリング(MIS)
MISはさらに厳しい方法を取ります。補正比がしきい値 \(C\) を超える系列では、勾配を0にします。
\[ w_{\text{MIS}}(o_i) = \mathbf{1}!\left[\frac{\pi_{\text{train}}(o_i|q)}{\pi_{\text{vllm}}(o_i|q)} \le C\right]. \]
これはより保守的ですが、(切り捨て後であっても)大きな補正重みのリスクを回避します。
系列レベルISとトークンレベルISの比較
TISとMISは、トークンレベルまたは系列レベルで適用できます。
-
系列レベル :すべてのトークンにわたる幾何平均として比を計算します(GSPOと同様)。理論的に正しい一方、分散は大きくなります。
-
トークンレベル :トークンごとに別々の比を計算します。バイアスがあります(トークンごとの補正の積は系列の補正になりません)が、分散は小さくなります。
Note
TRLでのTISとMIS
from trl import GRPOConfig, GRPOTrainer # Truncated IS correction for vLLM probability mismatch config_tis = GRPOConfig( use_vllm=True, vllm_importance_sampling_correction=True, vllm_importance_sampling_mode="sequence_truncate", # TIS vllm_importance_sampling_cap=5.0, # C threshold ) # Masked IS correction config_mis = GRPOConfig( use_vllm=True, vllm_importance_sampling_correction=True, vllm_importance_sampling_mode="sequence_mask", # MIS vllm_importance_sampling_cap=3.0, )
Important
TIS/MISを使う場合
vLLMを生成に使う場合は、 常に 有効化を検討してください。
不一致が小さい場合(同じモデルで精度だけが異なる場合)はTISが推奨されます。
不一致が大きい、または予測できない場合はMISが推奨されます。
系列レベルISが理論的には推奨されます。トークンレベルISは実用上の妥協案です。
VESPO – 変分系列レベル・ソフト方策最適化
Tip
原理に基づく報酬再形成
ほとんどのGRPO派生手法は、クリッピング機構をヒューリスティックに変更します。VESPOは、方策最適化を近似的な事後推論として扱い、変分推論の枠組みから原理に基づく報酬再形成カーネルを導出します。VESPO (Zhixun Luo et al. 2025) は、滑らかで非対称、かつ非同期またはオフポリシー学習における古いデータを自然に扱えるカーネルを導きます。
VESPOは、変分目的から重み付け関数 \(W(\tau)\) を各軌跡 \(\tau\) に対して導出します。最終的な勾配重みは次の形になります。
\[ \boxed{ g(\tau) = W(\tau)^k \cdot \exp!\bigl(\lambda(1 - W(\tau))\bigr), } \]
ここで \(W(\tau) = \pi_\theta(\tau)/\pi_{\text{old}}(\tau)\) は系列レベルの重要度重み、\(k\) は重み付けの鋭さを制御し、\(\lambda\) は古い(重みの低い)軌跡に対する指数減衰を制御します。このカーネルには次の性質があります。
-
どこでも滑らかです(クリップ境界で勾配が不連続になりません)。
-
古い軌跡(\(W \ll 1\))を指数項によって自然に小さくします。
-
非対称です。重みの高い軌跡(\(W > 1\))と低い軌跡を異なる方法で扱います。
Note
TRLでのVESPO
from trl import GRPOConfig, GRPOTrainer config = GRPOConfig( loss_type="vespo", vespo_k_pos=2.0, # sharpness exponent (positive advantages) vespo_lambda_pos=3.0, # staleness decay (positive advantages) num_generations=8, steps_per_generation=2, # off-policy; VESPO handles staleness ) trainer = GRPOTrainer( model=model, reward_funcs=[reward_fn], args=config, train_dataset=dataset, )
DPPO – 直接方策発散方策最適化
Tip
比率クリッピングの問題
PPOの比率クリッピングは、旧方策と新方策の間のKLダイバージェンスを制約するための代理手段です。しかし、この代理手段は不完全です。低確率トークン(確率の絶対変化が小さくても比の変化が大きくなる)を過剰に罰し、高確率トークン(確率の絶対変化が大きくても比の変化が小さくなる)を過小に罰します。DPPO (An et al. 2025) は比率クリッピングを直接的な発散推定に置き換えます。
DPPOは、旧方策と新方策の分布間のTotal Variation(TV)またはKLダイバージェンスを使って、信頼領域の制約を直接計算します。
\[ \mathcal{L}_{\text{DPPO}} = -\mathbb{E}!\left[ \hat{A} \cdot \pi_\theta(o|q) \cdot \mathbf{1}[D(\pi_\theta | \pi_{\text{old}}) \le \delta] \right], \]
ここで \(D\) は選択した発散尺度です。実際には、DPPOはトークンレベルの二値マスクまたはtop-\(k\) マスクでこれを近似します。
-
binary_tv :\(\vert \pi_\theta - \pi_{\text{old}}\vert > \delta\) となるトークンをマスクします。
-
binary_kl :\(\pi_\theta \log(\pi_\theta/\pi_{\text{old}}) > \delta\) となるトークンをマスクします。
-
topk_tv :TV寄与度で上位\(k\)個のトークンだけを残します。
-
topk_kl :KL寄与度で上位\(k\)個のトークンだけを残します。
Note
DPPO — 概念実装
DPPOは組み込みのTRLトレーナーではまだ利用できません。カスタム実装では、標準の確率比ではなく分布間の発散(TVまたはKL)に基づいてクリップするよう、GRPOTrainerを変更した損失とともに使います。
# Pseudocode: DPPO requires a custom trainer subclass from trl import GRPOConfig, GRPOTrainer config = GRPOConfig( num_generations=8, beta=0.04, ) # Override the loss computation to use distributional clipping: # clip when TV(pi_new || pi_old) > delta, rather than when # pi_new/pi_old exceeds [1-eps, 1+eps]
Warning
DPPOは研究段階
DPPOは新しい研究成果であり、主流のRLライブラリにはまだ統合されていません。標準の比率クリッピングが機能していない場合(たとえば、トークンの確率分布が大きく偏ったタスク)に最も有用です。
ScaleRLとCISPO
Tip
RLのスケーリング則
ScaleRL論文 (Zhenyu Luo et al. 2025) は、LLMのRL学習を効果的にスケールさせる要因を体系的に研究しています。主要な発見は、バッチレベルの報酬スケーリングとDAPO式のトークンレベル損失という2つの変更を組み合わせることで、単独ではどちらも不十分な強い性能を引き出せるということです。CISPO(Clipped IS Policy Optimization、クリップ付きIS方策最適化)は、その結果得られるアルゴリズムです。
バッチレベルの報酬スケーリング
標準GRPOは、1つのプロンプトに対する \(G\) 個の完了のグループ内で報酬を正規化します。CISPOはバッチ全体で報酬を正規化します。
\[ \hat{A}_i = \frac{r_i - \mu_{\text{batch}}}{\sigma_{\text{batch}} + \epsilon}, \]
ここで \(\mu_{\text{batch}}\) と \(\sigma_{\text{batch}}\) は、現在の学習バッチ内のすべての報酬に対して計算されます。これにより、より安定したベースラインが得られ、単一のプロンプトが勾配を支配するのを防ぎます。
CISPO損失
CISPOは、バッチレベルのスケーリングと、DAPOのトークンレベル損失集約および非対称クリッピングを組み合わせます。
\[ \mathcal{L}_{\text{CISPO}} = -\frac{1}{\sum_{i,t} m_{i,t}} \sum_{i=1}^G \sum_{t=1}^{|o_i|} m_{i,t} \cdot \min!\bigl(\rho_{i,t}\hat{A}_i,; \mathrm{clip}_{\text{DAPO}}(\rho_{i,t},\hat{A}_i),\hat{A}_i\bigr), \]
ここで \(m_{i,t}\) は長文フィルタリング用のマスクです。
Note
TRLでのCISPO
from trl import GRPOConfig, GRPOTrainer config = GRPOConfig( loss_type="cispo", scale_rewards="batch", # batch-level reward normalisation mask_truncated_completions=True, epsilon=0.2, epsilon_high=5.0, # epsilon_max for CISPO (ScaleRL paper) num_generations=8, ) trainer = GRPOTrainer( model=model, reward_funcs=[reward_fn], args=config, train_dataset=dataset, )
Important
ScaleRLの主要な発見
バッチレベルの報酬スケーリングのみ:控えめな改善。
トークンレベルの損失のみ:控えめな改善。
両方を組み合わせる: 相乗効果 。どちらか一方だけより大幅に優れます。
バッチサイズが大きいほど、バッチレベルのスケーリングの恩恵が大きくなります。
大規模RL学習ではCISPOが推奨されるデフォルトです。
GDPO – グループ報酬分離方策最適化
Tip
複数報酬の崩壊問題
多目的RL(正しさとフォーマットの両方を最適化する場合など)では、標準GRPOは結合した報酬を正規化します。一方の報酬の分散が他方よりはるかに大きい場合、結合後のアドバンテージを支配し、もう一方の報酬を実質的に無視します。これはアドバンテージ崩壊です。分散の小さい報酬は勾配にほとんど寄与しなくなります。GDPO (Zhong, Shi, et al. 2025) は、集約前に各報酬を独立に正規化します。
中心となる仕組みは、集約前に各報酬を独立に正規化することです。
\[ \boxed{ \hat{A}_n^{(i)} = \frac{r_n^{(i)} - \mu_n}{\sigma_n + \epsilon}, \qquad \hat{A}^{(i)} = \sum_{n=1}^N w_n \hat{A}_n^{(i)}, } \]
ここで \(r_n^{(i)}\) は \(n\) 番目の報酬で完了 \(i\) に対するもの、\(\mu_n\) と \(\sigma_n\) はグループ内の報酬 \(n\) の平均と標準偏差、\(w_n\) はユーザーが指定する重みです。
Important
GDPOと標準的な複数報酬GRPOの比較
標準 :\(\hat{A}^{(i)} = \frac{\sum_n w_n r_n^{(i)} - \mu_{\text{combined}}}{\sigma_{\text{combined}}}\)。高分散の報酬が支配します。
GDPO :各報酬を個別に正規化してから結合します。各報酬はその重み \(w_n\) に比例して寄与します。
報酬のスケールや分散が大きく異なる場合、GDPOは不可欠です。
Note
TRLでのGDPO
from trl import GRPOConfig, GRPOTrainer config = GRPOConfig( multi_objective_aggregation="normalize_then_sum", reward_weights=[1.0, 0.5], # weights for [correctness, format] num_generations=8, ) def correctness_reward(completions, **kwargs): return [1.0 if is_correct(c) else 0.0 for c in completions] def format_reward(completions, **kwargs): return [0.1 if has_good_format(c) else 0.0 for c in completions] trainer = GRPOTrainer( model=model, reward_funcs=[correctness_reward, format_reward], args=config, train_dataset=dataset, )
GOPO – グループ順序方策最適化
GOPO (Choi et al. 2025) は、単純な観察から出発します。報酬モデルはペアワイズ比較(「AはBより良いか?」)で学習されるため、出力について信頼できるのは 順位 だけであり、素の数値スコアには本質的な意味がありません。しかしGRPOは、その素の大きさをアドバンテージ計算に直接入力します。検証不可能な報酬を使うタスク(要約、オープンエンドなチャット、指示追従など)では、この不一致がノイズを導入します。出力空間のある領域では報酬0.6ポイントの差が実際の品質差を表す一方、別の領域では何も意味しない可能性があるためです。
核心となる洞察 :報酬の大きさを完全に捨てます。グループ内の報酬の 順序ランキング だけを使います。
アルゴリズム :\(N\) 個の応答 \(\{o_1, \ldots, o_N\}\) のグループに、報酬 \(\{r_1, \ldots, r_N\}\) が付いているとします:
-
報酬で応答を順位付けします:順位 \(\text{rank}(o_i) \in \{1, \ldots, N\}\) を割り当てます(1 = 最低、\(N\) = 最高)。
-
素のアドバンテージを順位ベースのスコアに置き換えます。
\[ \boxed{\hat{A}_i^{\text{GOPO}} = f!\left(\frac{\text{rank}(o_i)}{N}\right)} \]
ここで \(f\) は単調変換です(たとえば、\([-1, 1]\) への線形写像や分位点正規化)。
-
順位ベースのアドバンテージを使って、PPO式のクリップ済み目的を適用します。
GRPOとの比較 :
| 観点 | GRPO | GOPO |
|---|---|---|
| アドバンテージ信号 | \(\hat{A}_i = (r_i - \mu)/\sigma\)(大きさを使用) | \(\hat{A}_i = f(\text{rank}_i / N)\)(順位のみを使用) |
| 報酬スケールへの感度 | 高い — RMスコアの較正不良がアドバンテージを歪める | なし — 単調な報酬変換に対して不変 |
| 最適な用途 | 検証可能な報酬(バイナリ、適切に較正済み) | 検証不可能な報酬(RMベース、数値がノイジー) |
実験上の改善 (検証不可能なタスクでのGRPOとの比較):
-
報酬曲線(学習中およびホールドアウト)が最適化の全期間を通じてGRPOを上回ります。
-
別のLLM評価器が判定する勝率が、学習中のほとんどのチェックポイントで向上します。
-
収束が明らかに速くなり、より少ない勾配ステップでGRPOの最終品質に到達します。
-
報酬モデルがノイジーになるほど、または較正不良になるほど優位性が大きくなります。
Tip
GOPOとGRPOの使い分け
GRPOを使う :報酬が検証可能かつ厳密な場合(数学の正しさ、コードテストの合否、二値信号)。大きさに意味があります。
GOPOを使う :主観的なタスク(有用性、文体、安全性)で学習済み報酬モデルから報酬が得られる場合。RMの相対的な順序は信頼できますが、絶対スコアは任意です。
選好最適化の派生手法
この章では、異なる目的関数、データの仮定、アーキテクチャ上のトレードオフによってDPOを拡張または置き換える手法群を扱います。それぞれが、標準的なオフラインDPOの特定の制限に対応します。分布シフト(Online DPO)、ペアデータの必要性(KTO)、ノイズの多いラベルへの過適合(IPO)、参照モデルのメモリコスト(ORPO)、学習の複雑さ(Best-of-N)です。
Online DPO
動機
標準DPOの主な制限は、選好データが別のモデル(多くの場合は古いチェックポイント、あるいは異なるモデルファミリー)によって生成されていることです。学習が進むと、方策が学習ペアとはまったく異なるテキストを生成するようになり \(\rightarrow\)、損失は関係のない分布上で最適化されます。
Online DPOの解決策 (Guo et al. 2024):各ステップで現在の方策から新しい選好ペアを生成し、報酬モデルで評価してから、DPO損失を適用します。
アルゴリズム
-
プロンプトごとに \(K\) 個の応答を現在の \(\pi_\theta\) から生成します。
-
すべての応答を報酬モデル \(r_\phi\) でスコアリングします。
-
ペアを作成します:最高スコアを選択側、最低スコアを拒否側にします。
-
これらの新鮮なペアにDPO損失を適用します。
-
繰り返します(毎ステップ新たに生成)。
Tip
Online DPO = 両方の長所
DPOから:シンプルな教師あり損失、価値関数不要、GAE不要、安定した最適化
PPOから:オンポリシーデータ、データセットを超えた自己改善、分布シフトなし
GRPOとの主な違い:PPO損失(サンプルごとのアドバンテージ)ではなく、DPO損失(ペアベース)を使います。
トレードオフ :報酬モデルが必要です(DPOには不要)が、価値ヘッドは不要です(PPOには必要)。複雑さの中間に位置します。
TRLでの実装
以下は、HuggingFace TRLを使った最小限の動作例です。
from trl import OnlineDPOConfig, OnlineDPOTrainer
from transformers import AutoModelForCausalLM, AutoModelForSequenceClassification
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B-Instruct",
torch_dtype=torch.bfloat16)
reward_model = AutoModelForSequenceClassification.from_pretrained(
"RLHFlow/ArmoRM-Llama3-8B-v0.1", torch_dtype=torch.bfloat16)
online_dpo_config = OnlineDPOConfig(
output_dir="./online_dpo_output",
learning_rate=5e-7,
beta=0.1, # DPO beta (same meaning as standard DPO)
num_generations=4, # K responses per prompt
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
max_new_tokens=512,
temperature=0.7,
bf16=True,
num_train_epochs=1,
logging_steps=10,
)
trainer = OnlineDPOTrainer(
model=model,
reward_model=reward_model,
args=online_dpo_config,
train_dataset=prompt_dataset,
tokenizer=tokenizer,
)
trainer.train()
Online DPOとオフラインDPOとPPOの比較
| データ | モデル | 損失 | 最適な用途 | |
|---|---|---|---|---|
| オフラインDPO | 静的なペア | 2(方策 + 参照) | DPO | 高速なアラインメント、計算量が限られる場合 |
| Online DPO | \(\pi_\theta\) から新規生成 | 3(方策 + 参照 + 報酬モデル) | DPO | DPOが頭打ちになり、探索が必要な場合 |
| PPO | \(\pi_\theta\) から新規生成 | 4(方策 + 参照 + 報酬モデル + 価値ヘッド) | PPOクリップ | 最高品質、複雑な推論 |
KTO — カーネマン・トヴァースキー最適化
動機
DPOにはペアになった選好が必要です。同じプロンプトについて、良い応答と悪い応答の両方が必要になります。しかし実際のフィードバックの大半はペアになっていません。ユーザーは個々の応答に「いいね/よくないね」を付けるだけで、対応するペアはありません。
KTOの洞察 (Ethayarajh et al. 2024):プロスペクト理論(行動経済学)を使います。人間は利益よりも損失を強く感じます。「よくないね」は「いいね」より強い勾配を生むべきです。
損失関数
\[ \boxed{\mathcal{L}_\text{KTO} = \mathbb{E}_{y_w}\left[\lambda_w (1 - v(x, y_w))\right] + \mathbb{E}_{y_l}\left[\lambda_l \cdot v(x, y_l)\right]} \]
ここで \(v(x,y) = \sigma\left(\beta \log\frac{\pi_\theta(y\vert x)}{\pi_\text{ref}(y\vert x)} - z_\text{ref}\right)\)、\(z_\text{ref}\) は期待KLダイバージェンス(移動ベースライン)です。
Tip
プロスペクト理論によるKTOの直感
望ましい応答 (\(y_w\)):確率を高めることで、モデルは「効用」を得ます。ただし限界効用逓減があります。すでにかなり高い確率なら、さらに強く押し上げる必要はありません。
望ましくない応答 (\(y_l\)):損失回避により、悪いテキストを生成することへの罰は、良いテキストへの報酬より強く重み付けされます。デフォルトは \(\lambda_l = 1.0\)、\(\lambda_w = 1.0\) ですが、\(\lambda_l > \lambda_w\) に設定できます。
主な利点 :各学習例は独立しています。対応するペアを探す必要がありません。いいね/よくないねのデータを直接使えます。
Note
KTOのデータ形式
DPOでは次の形式が必要です:
{"prompt": ..., "chosen": ..., "rejected": ...}KTOに必要なのは次の形式だけです:
{"prompt": ..., "completion": ..., "label": true/false}つまり、次のデータを使えます。
本番トラフィックのいいね/よくないね
フォーラムの賛成票/反対票
二値化した人手評価(星4–5 = 良い、星1–2 = 悪い)
応答ごとの品質シグナル
TRLでの実装
以下は、HuggingFace TRLを使った最小限の動作例です。
from trl import KTOConfig, KTOTrainer
# Dataset format: {"prompt": str, "completion": str, "label": bool}
# label=True for desirable, label=False for undesirable
kto_dataset = [
{"prompt": "What's 2+2?", "completion": "The answer is 4.", "label": True},
{"prompt": "What's 2+2?", "completion": "It might be 5.", "label": False},
]
kto_config = KTOConfig(
output_dir="./kto_output",
beta=0.1,
desirable_weight=1.0, # Weight for good examples
undesirable_weight=1.0, # Weight for bad examples (increase for loss aversion)
learning_rate=5e-7,
max_length=2048,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
num_train_epochs=1,
bf16=True,
)
trainer = KTOTrainer(
model=model,
ref_model=ref_model, # Or None with LoRA
args=kto_config,
train_dataset=kto_dataset,
tokenizer=tokenizer,
)
trainer.train()
KTOを選ぶ場合
-
ペアになったデータはなく、二値フィードバックがある場合
-
大規模な本番環境のいいね/よくないねデータ
-
一方のクラスが支配的な場合(例:良い90%、悪い10%)— KTOは不均衡をより適切に扱います
-
ノイズの多いラベルで素早く反復したい場合(DPOよりノイズに頑健)
IPO — 恒等選好最適化
動機
DPOには退化解があります。選択側と拒否側のマージンを無限に大きくすることで、損失を0にできます。実際には、これはDPOが過適合することを意味します。選択側の確率を1に、拒否側を0に押し上げ、学習データを記憶します。
IPOの修正 (Azar et al. 2024):飽和するlog-sigmoidの代わりに、特定のマージンを目標とする二乗損失を使います。損失は無限大ではなく有限の差で最小になります。
損失関数
\[ \boxed{\mathcal{L}_\text{IPO} = \mathbb{E}\left[\left(\log\frac{\pi_\theta(y_w|x)}{\pi_\text{ref}(y_w|x)} - \log\frac{\pi_\theta(y_l|x)}{\pi_\text{ref}(y_l|x)} - \frac{1}{2\beta}\right)^2\right]} \]
Tip
IPOとDPO:目標マージンによる正則化
DPO:\(\sigma(\text{margin}) \to 1\) が最適です。マージン \(\to \infty\)。自然な停止点がありません。
IPO:マージン \(\to \frac{1}{2\beta}\) が最適です。二乗損失は、マージンが小さすぎる場合と大きすぎる場合の 両方 を罰します。
結果:IPOはノイズの多いラベルに対してより頑健です(誤ラベルのペアの影響が有限に抑えられる)し、記憶しないため汎化も向上します。
TRLでの実装
以下は、HuggingFace TRLを使った最小限の動作例です。
from trl import DPOConfig, DPOTrainer
# IPO is implemented as a DPO loss_type variant in TRL
ipo_config = DPOConfig(
output_dir="./ipo_output",
beta=0.1,
loss_type="ipo", # The key difference!
learning_rate=5e-7,
max_length=2048,
per_device_train_batch_size=4,
gradient_accumulation_steps=8,
bf16=True,
num_train_epochs=1,
)
trainer = DPOTrainer(
model=model, ref_model=None, args=ipo_config,
train_dataset=pref_dataset, tokenizer=tokenizer, peft_config=lora_config,
)
trainer.train()
DPOよりIPOを選ぶ場合
-
ノイズの多い選好データ(クラウドソーシング、誤りを含むAI評価)
-
DPOの過適合が見られる場合(学習損失 \(\to\) 0 だが評価が悪化)
-
より保守的で頑健なアラインメントが必要な場合
-
複数エポックが必要な場合(DPOは1エポック後に悪化するが、IPOはより安定)
ORPO — オッズ比選好最適化
動機
これまでの手法はすべて参照モデルを必要とします。別個のコピー(メモリが倍増)として持つか、LoRAによって暗黙的に持つかのどちらかです。ORPO (J. Hong et al. 2024) は、SFTと選好アラインメントを単一の損失に組み合わせることで、参照モデルを完全に不要にします。
核心となる洞察 :選択側と拒否側を生成するオッズ比を選好シグナルとして使います。SFT成分が自然に崩壊を防ぐため、KL正則化は不要です。
損失関数
\[ \boxed{\mathcal{L}_\text{ORPO} = \underbrace{\mathcal{L}_\text{SFT}(y_w)}_{\text{standard NLL on chosen}} - \lambda \cdot \underbrace{\log\sigma\left(\log\frac{\text{odds}_\theta(y_w|x)}{\text{odds}_\theta(y_l|x)}\right)}_{\text{preference alignment via odds ratio}}} \]
ここで \(\text{odds}_\theta(y\vert x) = \frac{P_\theta(y\vert x)}{1 - P_\theta(y\vert x)}\) です。
Tip
ORPO:SFT + アラインメントを一度に
SFT項 :選択側の応答をうまく生成するようモデルを学習させます(標準的な言語モデリング)。
オッズ比項 :選択側を拒否側より好むよう、モデルをさらに押し進めます。オッズ比は参照モデルを必要としない、自然な対比です。
なぜ参照モデルが不要なのか? :SFT損失がすでにモデルを妥当なテキストに固定します。他の手法における参照モデルへのKLと同じ役割を果たします。1つのモデル、1回のフォワードパス、1つの損失です。メモリを50%削減できます。
TRLでの実装
以下は、HuggingFace TRLを使った最小限の動作例です。
from trl import ORPOConfig, ORPOTrainer
orpo_config = ORPOConfig(
output_dir="./orpo_output",
beta=0.1, # Odds ratio weight (lambda)
learning_rate=5e-7,
max_length=2048,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
bf16=True,
num_train_epochs=1,
gradient_checkpointing=True,
)
trainer = ORPOTrainer(
model=model, # No ref_model needed!
args=orpo_config,
train_dataset=pref_dataset, # Same format as DPO: prompt/chosen/rejected
tokenizer=tokenizer,
peft_config=lora_config,
)
trainer.train()
ORPOを選ぶ場合
-
メモリに制約がある場合:参照モデルのコピーを保持できない場合(70Bモデルで70–140GBを節約)
-
ベースモデルから始める場合(まだSFT済みではない)— ORPOがSFTを同時に行います
-
可能な限りシンプルなパイプラインが必要な場合:1つのモデル、1つの損失、1回の学習
-
最初から良質な選好データがある場合
Warning
ORPOの制限
DPO/PPOほど研究が進んでいません — 70B以上の規模で実証済みのレシピが少ないです。
SFT成分があるため、単なる相対的な選好ではなく、高品質な選択側応答が必要です。
デバッグが難しくなります:2つの損失成分が衝突する可能性があります。
Important
関連項目:SimPO
SimPO (Meng et al. 2024) は、参照モデルを使わない別の選好手法です。長さ正規化された対数確率を暗黙の報酬として使うことで、参照モデルを完全に排除します。共有する「参照モデルなし」という思想のため、他のDPO拡張とともにセクション3.9.8で扱います。
Best-of-Nサンプリング(棄却サンプリング)
動機
最も単純なアプローチが勝つこともあります。Best-of-N (Nakano et al. 2021) は、RL段階で学習をまったく必要としません。複数の候補を生成し、最良のものを選ぶだけです。
アルゴリズム
-
各プロンプトについて、方策から \(N\) 個の応答(通常は \(N = 4\)–\(64\))を生成します。
-
すべての応答を報酬モデルでスコアリングします。
-
最高スコアの応答を選択します。
-
(任意)次の反復のSFTデータとして選択した応答を使います。
\[ \boxed{\text{Best-of-N response}: \quad y^* = \arg\max_{y_i \sim \pi_\theta(\cdot|x)} r_\phi(x, y_i)} \]
Tip
Best-of-Nが正当な「RL」手法である理由
推論時 :Best-of-Nはモデルの重みを変更せずに出力品質を高めます。\(N=64\) では、貪欲法に対する勝率が10–20%向上し、ときにはPPOに匹敵、あるいはそれを上回ります。
学習手法として (棄却サンプリング・ファインチューニング / RFT):
多数の応答を生成し、最良のものを選択します。
選択した応答に対してSFTを行います。
繰り返します(反復的な改善)。
これは多くの本番モデルが学習される方法です。PPOより単純で、ほぼ同等の効果があり、完全に安定しています。
理論的なつながり (Leo Gao et al. 2023):Best-of-Nは暗黙のKL制約付き方策を実装します:\(\pi_\text{BoN}(y\vert x) \propto \pi_\theta(y\vert x)^{1-1/N} \cdot r(x,y)^{1/N}\)。
TRLでの実装
以下は、HuggingFace TRLを使った最小限の動作例です。
from transformers import pipeline
import numpy as np
# Inference-time Best-of-N (manual implementation)
gen_pipeline = pipeline("text-generation", model=model, tokenizer=tokenizer)
def best_of_n(prompt, n=16, temperature=0.8):
"""Generate N candidates and return the highest-reward one."""
candidates = gen_pipeline(
prompt, num_return_sequences=n,
temperature=temperature, do_sample=True, max_new_tokens=512,
)
scores = [reward_model.score(prompt, c["generated_text"]) for c in candidates]
return candidates[np.argmax(scores)]["generated_text"]
best_response = best_of_n(prompt, n=16)
# Training: Rejection Sampling Fine-Tuning (RFT)
from trl import SFTConfig, SFTTrainer
# Step 1: Generate and filter
all_responses = []
for prompt in prompts:
candidates = [generate(prompt, temp=0.9) for _ in range(16)]
scores = [reward_model.score(prompt, c) for c in candidates]
best_idx = np.argmax(scores)
if scores[best_idx] > threshold: # Quality gate
all_responses.append({"prompt": prompt, "completion": candidates[best_idx]})
# Step 2: SFT on best responses
sft_config = SFTConfig(output_dir="./rft_output", learning_rate=2e-5, num_train_epochs=2, max_seq_length=2048)
trainer = SFTTrainer(model=model, args=sft_config, train_dataset=all_responses, tokenizer=tokenizer)
trainer.train()
# Step 3: Repeat from Step 1 with updated model (iterative RFT)
Best-of-Nのスケーリング則
| \(N\) | 品質向上 | コスト | 注記 |
|---|---|---|---|
| 1 | ベースライン | \(1\times\) | 標準サンプリング |
| 4 | 勝率 +5–8% | \(4\times\) | 実用上の最小値。コストと品質のバランスが良い |
| 16 | 勝率 +10–15% | \(16\times\) | 強力。PPOの品質に匹敵することが多い |
| 64 | 勝率 +15–20% | \(64\times\) | 逓減効果が始まる |
| 256 | 勝率 +18–22% | \(256\times\) | 重要な用途に限る |
Important
Best-of-Nをベースラインにする
RL手法は、同じ計算予算でBest-of-Nと必ず比較してください。PPOが64 GPU時間で、生成に64 GPU時間を使うBest-of-Nを上回れないなら、PPOにバグがあります。
まとめ:アラインメント手法の選択
ここまで、選好最適化とRLベースのアラインメント手法の全体像を調査してきました。この節では、実務者が制約に適したアプローチを選べるよう、主要なトレードオフを1つのリファレンスにまとめます。
| 手法 | モデル | データ | 計算量 | 安定性 | 最適な用途 |
|---|---|---|---|---|---|
| PPO | 4 | オンライン(生成) | 非常に高い | 低い | 最高品質、複雑な推論 |
| GRPO | 2(クリティックなし) | オンライン(生成) | 高い | 中程度 | 数学/コード(検証可能な報酬) |
| DPO | 2 | オフラインのペア | 低い | 高い | 文体/安全性、計算量が限られる場合 |
| Online DPO | 3 | オンライン(生成) | 中程度 | 中〜高 | 分布シフトのないDPO |
| KTO | 2 | ペアなし二値 | 低い | 高い | 本番フィードバック、いいね/よくないね |
| IPO | 2 | オフラインのペア | 低い | 非常に高い | ノイズの多いラベル、過適合防止 |
| ORPO | 1 | オフラインのペア | 非常に低い | 高い | メモリ制約、SFTとアラインメントの統合 |
| Best-of-N | 1+RM | オンライン(生成) | 中程度 | 完全 | 強力なベースライン、データ生成 |
アラインメント手法の横断比較。
Important
意思決定木:どの手法を使うか
検証可能な報酬がありますか? (数学/コード)\(\rightarrow\) GRPO
複雑なタスクで最高品質が必要ですか? \(\rightarrow\) PPO
ペアになった選好がありますか? \(\rightarrow\) DPO (ノイズが多ければIPO)
ペアなしの二値フィードバックだけですか? \(\rightarrow\) KTO
メモリに制約があり、ベースモデルから始めますか? \(\rightarrow\) ORPO
DPOが頭打ちで、オンポリシーを望みますか? \(\rightarrow\) Online DPO
強力なベースラインがすぐ必要ですか? \(\rightarrow\) Best-of-N / RFT
報酬モデルの学習
報酬モデルは、人間の選好とRLの学習信号をつなぐ橋渡しです。十分に学習された報酬モデルはRLHFを成功させるために不可欠ですが、学習が不十分なものは報酬ハッキングやアラインメントのずれを引き起こします。この節では、報酬モデルの理論的基礎、実践的な学習技法、アーキテクチャ上の選択を扱います。
Bradley-Terryモデル — 完全導出
Bradley-Terryモデル (Bradley and Terry 1952) は、ペアワイズ選好学習の標準的な確率的枠組みです。2つの応答 \(y_1\) と \(y_2\) に対するプロンプト \(q\) について、モデルは次を仮定します。
\[ P(y_1 \succ y_2 \mid q) = \sigma(r(y_1, q) - r(y_2, q)) = \frac{e^{r(y_1,q)}}{e^{r(y_1,q)} + e^{r(y_2,q)}}, \]
ここで \(r: \mathcal{Y} \times \mathcal{Q} \to \mathbb{R}\) はスカラー報酬関数、\(\sigma\) はシグモイド関数です。
最尤推定
選好ペアのデータセット \(\mathcal{D} = \{(q^{(k)}, y_w^{(k)}, y_l^{(k)})\}_{k=1}^N\) が与えられたとき、MLEの目的関数は次のとおりです。
\[ \mathcal{L}_{\text{BT}}(\phi) = -\frac{1}{N}\sum_{k=1}^N \log \sigma!\bigl(r_\phi(y_w^{(k)}, q^{(k)}) - r_\phi(y_l^{(k)}, q^{(k)})\bigr), \]
ここで \(r_\phi\) は \(\phi\) でパラメータ化されたニューラルネットワークです。これは、正のクラスを選好された応答とする二値交差エントロピー損失です。
Important
Bradley-Terryの仮定
選好は推移的です。\(y_1 \succ y_2\) かつ \(y_2 \succ y_3\) ならば、\(y_1 \succ y_3\) です。
選好はスカラー報酬(多次元の選好ではない)によって決まります。
選好確率は報酬の差だけに依存します。
選好はペア間で独立しています(アノテーターの影響はありません)。
これらの仮定は実際にはしばしば破られるため、ランキング向けのPlackett-Luceモデルや多次元報酬モデルなどの拡張が動機付けられます。
マージン損失への拡張
一般的な拡張では、勝者と敗者の報酬の間に最小の差を確保するため、マージン \(m\) を追加します。
\[ \mathcal{L}_{\text{margin}} = -\frac{1}{N}\sum_{k=1}^N \log \sigma!\bigl(r_\phi(y_w^{(k)}, q^{(k)}) - r_\phi(y_l^{(k)}, q^{(k)}) - m\bigr). \]
報酬モデルのアーキテクチャ
Tip
LLM上の分類ヘッド
標準的な報酬モデルのアーキテクチャは、事前学習済みLLMを取り、言語モデリングヘッド(隠れ状態を語彙のロジットへ写像するもの)を、スカラー回帰ヘッド(最終隠れ状態を1つの報酬値へ写像するもの)に置き換えます。
アーキテクチャは次のとおりです。
-
バックボーン :プロンプトと応答のペアを隠れ状態の系列にエンコードする、事前学習済みLLM(Llama、Mistralなど)。
-
プーリング :デコーダー専用モデルでは最後のトークン位置、エンコーダーモデルでは
[CLS]トークンの隠れ状態を抽出します。 -
回帰ヘッド :プールされた隠れ状態をスカラー報酬へ写像する線形層 \(W \in \mathbb{R}^{d \times 1}\)。
Note
TRLでの報酬モデル学習
from trl import RewardConfig, RewardTrainer from transformers import AutoModelForSequenceClassification # Load model with scalar head (num_labels=1) model = AutoModelForSequenceClassification.from_pretrained( "meta-llama/Llama-3.1-8B-Instruct", num_labels=1, ) config = RewardConfig( output_dir="reward_model", per_device_train_batch_size=4, gradient_accumulation_steps=4, learning_rate=1e-5, num_train_epochs=1, # Margin loss center_rewards_coefficient=0.01, ) trainer = RewardTrainer( model=model, args=config, train_dataset=dataset, # must have chosen/rejected columns ) trainer.train()
報酬モデルの学習テクニック
報酬の中心化
報酬モデルの生の出力は、スケールもオフセットも任意です。報酬を中心化(平均を引くこと)すると、RL学習が安定します。
\[ r_{\text{centered}}(y, q) = r_\phi(y, q) - \mathbb{E}_{y' \sim \pi_\theta}[r_\phi(y', q)]. \]
TRLでは、これは center_rewards_coefficient パラメータによって実装されます。このパラメータは、平均が0でない報酬を罰する正則化項を報酬モデルの損失に追加します。
長さバイアスの補正
報酬モデルには長さバイアスがあることが知られています。品質に関係なく、長い応答に高い報酬を割り当てる傾向です。これは次の方法で補正できます。
-
長さの正規化 :報酬を応答の長さで割ります。
-
長さを制御した学習 :長さを特徴量として含め、モデルが長さに依存しないよう学習します。
-
較正 :事後回帰によって長さの影響を取り除きます。
マージン損失
Bradley-Terry損失にマージン \(m\) を追加すると、報酬モデルは選好された応答と選好されていない応答に意味のある異なるスコアを割り当てるようになります。
\[ \mathcal{L}_{\text{margin}} = \max!\bigl(0,; m - (r_w - r_l)\bigr). \]
プロセス報酬モデルと結果報酬モデルの比較
Important
PRMとORMの比較
特性 ORM PRM 報酬信号 最終回答のみ 各推論ステップ 学習データ (プロンプト、回答、正解?) (プロンプト、ステップ、ステップラベル) アノテーションコスト 低い 高い 信用割当 スパース デンス 報酬ハッキング 容易 困難 最適な用途 単純なタスク 複数ステップの推論 推論コスト 低い 高い(各ステップをスコアリング)
Tip
PRMを使う場合
プロセス報酬モデルは、次の場合に最も価値があります。
複数ステップの推論(数学、コード、論理)が必要なタスク。
最終回答は二値(正解/不正解)だが、中間ステップの品質が異なる場合。
報酬モデルを探索に使いたい場合(ステップスコア付きのビームサーチなど)。
ステップレベルのアノテーションにアクセスできる場合(または自動生成できる場合)。
単純なタスク(感情分析、有害性、事実性)では、ORMで十分であり、はるかに低コストです。
Note
LLMのRLHFにおけるPBRS
元の報酬 :二値の正しさ(最終回答が正しければ1、そうでなければ0)—複数ステップ推論では非常にスパースです。
ポテンシャル関数 :\(\Phi(s) =\) 検証器からの部分点(たとえば、論理的に妥当な中間推論ステップの割合)。
整形された報酬 :最適方策が依然として最終回答の正しさを最大化する保証を保ちながら、エージェントは妥当な推論ステップごとに増分信号を得ます。
実装例 :
思考の連鎖の各ステップをスコアリングするプロセス報酬モデル(PRM)
コード生成における中間コンパイルチェック
複数部分からなる回答に対する部分一致スコア
これはPotential-Based Reward Shaping(PBRS) (Ng et al. 1999) をLLMに適用したものです。整形された報酬が最適方策を保つという理論的保証があるため、PRMは推論タスクに密な報酬を与える原理に基づいたアプローチです。
PRMアノテーションの自動化
ステップレベルのアノテーションは、次の方法で自動生成できます。
-
モンテカルロ・ロールアウト :各中間ステップについて複数の完了をサンプリングし、正解に到達した割合をステップ報酬として使います。
-
LLM-as-judge :強力なLLMを使って各ステップを評価します。
-
形式検証 :数学/コードでは、検証器を使って各ステップをチェックします。
RLVRのルールベース報酬
検証可能な報酬からの強化学習(RLVR)は、学習済み報酬モデルの代わりに決定論的なルールベース報酬関数を使います。これにより報酬ハッキングは大幅に減少します(ただし、モデルは形式上のトリック、エッジケース、テストの記憶をなお悪用できます)。このアプローチはDeepSeek-R1 (DeepSeek-AI et al. 2025) で使われています。
Note
TRLでのルールベース報酬関数
import re def format_reward(completions, **kwargs): """Reward for using <think>...</think><answer>...</answer> format.""" rewards = [] pattern = r"<think>.*?</think>\s*<answer>.*?</answer>" for completion in completions: text = completion[0]["content"] rewards.append(1.0 if re.fullmatch(pattern, text, re.DOTALL) else 0.0) return rewards def correctness_reward(completions, ground_truth, **kwargs): """Reward for correct final answer.""" rewards = [] for completion, gt in zip(completions, ground_truth): text = completion[0]["content"] match = re.search(r"<answer>(.*?)</answer>", text, re.DOTALL) if match: answer = match.group(1).strip() rewards.append(1.0 if answer == gt else 0.0) else: rewards.append(0.0) return rewards def code_execution_reward(completions, test_cases, **kwargs): """Reward for code that passes test cases.""" import subprocess, tempfile, os rewards = [] for completion, tests in zip(completions, test_cases): code = completion[0]["content"] passed = 0 for test in tests: with tempfile.NamedTemporaryFile( mode="w", suffix=".py", delete=False ) as f: f.write(code + "\n" + test) fname = f.name try: result = subprocess.run( ["python", fname], capture_output=True, timeout=5, text=True ) passed += int(result.returncode == 0) except Exception: pass finally: os.unlink(fname) rewards.append(passed / len(tests)) return rewards
Warning
ルールベース報酬の落とし穴
形式のごまかし :モデルは正しい内容なしに正しい形式だけを生成することを学びます。形式報酬と正しさ報酬は必ず組み合わせてください。
テストケースの漏洩 :テストケースが学習データに含まれていると、モデルはそれを記憶します。
タイムアウトの悪用 :モデルは失敗を避けるため、タイムアウトするコードを生成することがあります。厳格なタイムアウトを使い、タイムアウトを明示的に罰してください。
報酬のスパース性 :二値報酬(0/1)は複雑なタスクではスパースすぎることがあります。部分点や中間報酬を検討してください。
多目的報酬 — 組み合わせ戦略
複数の報酬信号で学習するとき、組み合わせ戦略は最終方策に大きく影響します。
Important
複数報酬の組み合わせ戦略
重み付き和 :\(r = \sum_n w_n r_n\)。単純ですが、スケールに敏感です。
正規化してから和(GDPO) :各報酬をグループ内で平均0、分散1に正規化してから、重み付きで合計します。スケールに依存しません。
辞書式 :優先順位に従って報酬を最適化し、優先度の高い報酬が同点の場合にだけ低い報酬を考慮します。
制約付き :二次報酬に制約を課しながら、主要報酬を最大化します。
パレート :方策のパレートフロントを維持し、選好に基づいて選択します。
Note
TRLでの複数報酬学習
from trl import GRPOConfig, GRPOTrainer config = GRPOConfig( # GDPO: normalise each reward independently multi_objective_aggregation="normalize_then_sum", reward_weights=[1.0, 0.3, 0.1], # correctness, format, length num_generations=8, ) trainer = GRPOTrainer( model=model, reward_funcs=[ correctness_reward, format_reward, length_penalty_reward, ], args=config, train_dataset=dataset, )
リストワイズ報酬
Bradley-Terryモデルはペアワイズ選好(\(y_w \succ y_l\))を扱いますが、実際には複数の応答を同時に順位付けする場面も多くあります。リストワイズ報酬モデルは完全な順序から学習し、より豊かな学習信号を提供するとともに、より良い較正を可能にします。
動機:ペアワイズを超えて
Tip
なぜリストワイズなのか?
豊かな信号 :\(K\) 個の応答の順位には、\(\binom{K}{2}\) 個の暗黙的なペアワイズ比較が含まれますが、相対的なマージン(1位が3位よりどれだけ良いか)も捉えます。
より良い較正 :ペアワイズBTモデルは報酬の差だけを学習しますが、リストワイズモデルは報酬の絶対スケールを学習します。
GRPOとの自然な適合 :GRPOはプロンプトごとに \(N\) 個の応答を生成して順位付けします。リストワイズ報酬はこのワークフローに直接適合します。
アノテーター効率 :5つの応答を順位付けする方が、考えられる10個のペアをすべて個別にラベル付けするより速いです。
Plackett-Luceモデル
Plackett-Luce(PL)モデル (Plackett 1975) は、Bradley-Terryを完全な順位付けへ拡張した標準モデルです。\(K\) 個の応答 \(y_1, \ldots, y_K\) と、順位 \(\pi\)(\(\pi(1)\) が最良)が与えられたとします。
Important
Plackett-Luceの尤度
\[ P(\pi \mid q) = \prod_{i=1}^{K} \frac{e^{r_\phi(y_{\pi(i)}, q)}}{\sum_{j=i}^{K} e^{r_\phi(y_{\pi(j)}, q)}} \] 直感 :残っている項目から最良のものを順番に選びます。各ステップで、項目 \(\pi(i)\) を選ぶ確率は残りの項目に対するsoftmaxです。
損失関数 : \[ \mathcal{L}_{\text{PL}}(\phi) = -\frac{1}{|\mathcal{D}|} \sum_{(q, \pi) \in \mathcal{D}} \sum_{i=1}^{K-1} \left[ r_\phi(y_{\pi(i)}, q) - \log \sum_{j=i}^{K} e^{r_\phi(y_{\pi(j)}, q)} \right] \]
Tip
Plackett-LuceはBradley-Terryに帰着する
\(K=2\) のとき、PLモデルは次を与えます:\(P(y_1 \succ y_2) = \frac{e^{r(y_1)}}{e^{r(y_1)} + e^{r(y_2)}} = \sigma(r(y_1) - r(y_2))\) — Bradley-Terryモデルと完全に同じです。PLは厳密な一般化です。
ListMLEと順位ベース損失
Important
リストワイズ損失関数
ListMLE (Xia et al. 2008):正解順位のPL尤度を直接最大化します。シンプルで効果的です。
ListNet (Cao et al. 2007):モデルのトップ1確率分布と正解の確率分布のKLダイバージェンスを最小化します。
\mathcal{L}_{\text{ListNet}} = -\sum_{i=1}^{K} P_{\text{true}}(y_i \text{ is best}) \cdot \log P_{\text{model}}(y_i \text{ is best})ここで \(P_{\text{model}}(y_i \text{ is best}) = \frac{e^{r_\phi(y_i)}}{\sum_j e^{r_\phi(y_j)}}\)。
LambdaRank (Burges et al. 2006):ランキング指標の変化(NDCGなど)によってペアワイズ勾配に重みを付けます。ランキング品質が上位でより重要な場合に有用です。
RankNet (Burges et al. 2005):すべてのペアにわたるペアワイズ交差エントロピーの合計です。順位から抽出した \(\binom{K}{2}\) 個のすべてのペアに対するBTと同値です。
GRPOと棄却サンプリングのリストワイズ報酬
Important
GRPOとの統合
GRPOは自然に順位付けされたグループを生成します。各プロンプトについて \(N\) 個の応答がスコアリングされ、順位付けされます。リストワイズ報酬モデルは、これらの順位に対して直接学習できます。
生成 :方策からプロンプトごとに \(N=8\) 個の応答をサンプリングします。
順位付け :既存の報酬モデル(または人手のアノテーター)を使って完全な順位 \(\pi\) を生成します。
リストワイズRMの学習 :\((q, \pi)\) タプルに対してPL損失を最適化します。
GRPOで利用 :リストワイズRMは各応答にスカラー報酬 \(r(y_i, q)\) を割り当て、GRPOは \(\hat{A}_i = (r_i - \mu) / \sigma\) としてアドバンテージを計算します。
ペアワイズに対する利点 :リストワイズRMは \(N\) 個の応答をすべて同時に見て、1位は最下位 \(N\) よりはるかに高い報酬であるべきだと学習します(単に「別の1つの応答より少し良い」と学ぶだけではありません)。
実践上の考慮事項
Warning
リストワイズ学習の課題
アノテーションコスト :完全な順位付けにはコストがかかります。部分順位(8個中トップ3)なら、品質をほぼ維持したままコストを下げられます。
同順位 :実際の順位には同順位がよくあります。同順位項目に等しい確率質量を割り当てる、同順位向けのPlackett-Luce拡張を使います。
位置バイアス :アノテーターは最初に表示された項目を好む傾向があります。提示順をランダム化し、デバイアスを学習します。
リスト長 :\(K=4\)–8で学習するのが一般的です。より長いリスト(\(K>16\))は、利点があまりないままノイズを加えます。
一貫性 :異なるアノテーターの順位は一致しない可能性があります。品質フィルタとしてアノテーター間一致度(\(\kappa > 0.6\))を使います。
Note
Plackett-Luceの学習コード
import torch import torch.nn.functional as F def plackett_luce_loss(rewards, rankings): """ Args: rewards: (batch, K) - predicted scalar rewards for K responses rankings: (batch, K) - ground-truth ranking indices (0 = best) Returns: scalar loss """ batch_size, K = rewards.shape # Sort rewards by ground-truth ranking order sorted_rewards = torch.gather(rewards, 1, rankings) # (batch, K) # PL log-likelihood: sum over positions loss = 0.0 for i in range(K - 1): # Log-softmax over remaining items (position i to K) remaining = sorted_rewards[:, i:] # (batch, K-i) log_probs = remaining[:, 0] - torch.logsumexp(remaining, dim=1) loss -= log_probs.mean() return loss / (K - 1) # Example: 8 responses per prompt, ranked by annotator rewards = reward_model(responses) # (batch, 8) rankings = torch.argsort(human_scores, descending=True) # best first loss = plackett_luce_loss(rewards, rankings) loss.backward()
SFTのベストプラクティスと技法
教師ありファインチューニング(SFT)はRLHFパイプラインの基盤です。SFTモデルの品質が、RLで達成できることの上限を決めます。RLは振る舞いを洗練・改善できますが、SFTモデルに完全に存在しない振る舞いを確実に導入することはできません。この節では、効果的なSFTのための主要な技法を扱います。
効率化のための系列パッキング
Tip
パディングの問題
標準的なSFTのバッチでは、バッチ内で最も長い系列の長さに合わせて、すべての系列をパディングします。長さの分散が大きいデータセット(短い指示と長い文書が混在する場合など)では、計算量の50–80%がパディングトークンに費やされ、無駄になります。系列パッキングはこの無駄をなくします。
系列パッキングは、複数の短い例をEOSトークンで区切り、max_seq_length 長の単一系列へ連結します。アテンションマスクにより、異なる例のトークン同士が互いにアテンションしないようにします。
-
長さで例をソートします(任意。パッキング効率が向上します)。
-
max_seq_lengthサイズのビンへ、例を貪欲にパッキングします。 -
ブロック対角アテンションマスクを使って、例をまたぐアテンションを防ぎます。
-
パディングでないトークンだけについて損失を計算します。
Important
パッキング効率
一般的なパッキング効率:85–95%(パディングありでは20–50%)。
高い長さ分散を持つデータセットでの高速化:2–4\(\times\)。
メモリ:パディングの場合と同程度(バッチあたりの総トークン数が同じ)。
注意点:例をまたぐ汚染を避けるため、アテンションマスクを慎重に設定する必要があります。
Note
TRLでの系列パッキング
from trl import SFTConfig, SFTTrainer config = SFTConfig( max_seq_length=4096, packing=True, # enable sequence packing output_dir="sft_model", per_device_train_batch_size=4, gradient_accumulation_steps=4, learning_rate=2e-5, num_train_epochs=3, ) trainer = SFTTrainer( model=model, args=config, train_dataset=dataset, # dataset_text_field="text", # or use formatting_func ) trainer.train()
チャットテンプレートとフォーマット
Tip
チャットテンプレートが重要な理由
言語モデルは生のテキストで学習されますが、指示追従モデルはシステムプロンプト、ユーザーメッセージ、アシスタントの応答を区別する必要があります。チャットテンプレートは、この構造をトークン系列にエンコードします。推論時に誤ったテンプレート(またはテンプレートなし)を使うと、性能が大幅に低下します。
ChatML形式
ChatMLは最も広く使われているチャットテンプレートです。
# ChatML format
template = """<|im_start|>system
{system_message}<|im_end|>
<|im_start|>user
{user_message}<|im_end|>
<|im_start|>assistant
{assistant_message}<|im_end|>"""
Llama形式
Llama 3は、特殊トークンを使う別のテンプレートを採用しています。
# Llama 3 format
template = """<|begin_of_text|><|start_header_id|>system<|end_header_id|>
{system_message}<|eot_id|><|start_header_id|>user<|end_header_id|>
{user_message}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
{assistant_message}<|eot_id|>"""
Note
TRLでチャットテンプレートを適用する
from transformers import AutoTokenizer from trl import SFTConfig, SFTTrainer tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B-Instruct") def formatting_func(example): """Apply chat template to a dataset example.""" messages = [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": example["instruction"]}, {"role": "assistant", "content": example["response"]}, ] return tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=False, ) config = SFTConfig( max_seq_length=2048, output_dir="sft_model", ) trainer = SFTTrainer( model=model, tokenizer=tokenizer, args=config, train_dataset=dataset, formatting_func=formatting_func, )
完了部分のみのマスキング
Tip
プロンプトをマスクする理由
指示ファインチューニングでは、モデルはユーザーの質問やシステムプロンプトを予測するのではなく、アシスタントの応答を生成することを学ぶべきです。プロンプトトークンで損失を計算すると勾配信号が無駄になり、応答方法を学ぶ代わりにプロンプトを「記憶」する原因になります。完了部分のみのマスキングでは、アシスタント以外のすべてのトークンの損失を0にします。
Note
TRLでの完了部分のみのマスキング
from trl import SFTConfig, SFTTrainer, DataCollatorForCompletionOnlyLM from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B-Instruct") # Define the response template (tokens after which loss is computed) response_template = "<|start_header_id|>assistant<|end_header_id|>" collator = DataCollatorForCompletionOnlyLM( response_template=response_template, tokenizer=tokenizer, ) config = SFTConfig( max_seq_length=2048, output_dir="sft_model", ) trainer = SFTTrainer( model=model, tokenizer=tokenizer, args=config, train_dataset=dataset, data_collator=collator, # completion-only masking formatting_func=formatting_func, )
Warning
完了部分マスキングの落とし穴
応答テンプレートは、トークン化された形式と完全に一致しなければなりません。トークン化のオフバイワンエラーにより、マスクが誤って適用される可能性があります。
非常に短い応答では、プロンプトをマスクすると意味のある勾配信号に必要なトークンが少なすぎる場合があります。応答の最小長しきい値を検討してください。
複数ターンの会話では、最初のターンだけでなく、アシスタント以外のすべてのターンをマスクする必要があります。
マルチタスクSFTのデータ混合戦略
Tip
マルチタスクの課題
複数のタスクを同時に学習すると汎化性能は向上しますが、タスク干渉も起こります。異なるタスクの勾配が衝突し、個々のタスクの性能が低下するのです。データ混合戦略は、各タスクが学習信号に占める相対的な寄与を制御します。
比例混合
各データセットのサイズに比例してサンプリングします。
\[ p_k = \frac{N_k}{\sum_{j=1}^K N_j}, \]
ここで \(N_k\) はデータセット \(k\) の例数です。これはほとんどのフレームワークにおけるデフォルトで、データセットの品質が同程度ならうまく機能します。
温度混合
温度 \(T\) を適用して比率を平滑化します。
\[ p_k \propto N_k^{1/T}. \]
\(T=1\):比例混合。\(T \to \infty\):均一混合。\(T < 1\):大規模データセットを過剰サンプリングします。\(T > 1\):小規模データセットを過剰サンプリングします。
品質重み付き混合
推定品質(参照モデルの下でのパープレキシティ、人手による品質評価など)に基づいてデータセットに重みを付けます。
\[ p_k \propto N_k \cdot q_k, \]
ここで \(q_k\) はデータセット \(k\) の品質スコアです。
Note
TRLでのデータ混合
from datasets import concatenate_datasets, interleave_datasets # Proportional mixing (default) mixed_dataset = concatenate_datasets([ dataset_math, dataset_code, dataset_general, ]).shuffle(seed=42) # Temperature mixing (T=2: over-sample small datasets) mixed_dataset = interleave_datasets( [dataset_math, dataset_code, dataset_general], probabilities=[0.4, 0.4, 0.2], # manually set after temperature scaling seed=42, ) config = SFTConfig(output_dir="sft_model") trainer = SFTTrainer( model=model, args=config, train_dataset=mixed_dataset, )
SFTが害になる場合 — 壊滅的忘却とアラインメント税
LLMが逐次的な学習段階 — 事前学習 \(\rightarrow\) 継続事前学習 \(\rightarrow\) SFT \(\rightarrow\) RLHF/DPO — を経ると、標準ベンチマークで性能劣化が頻繁に現れます。これらの回帰を引き起こすのは、 根本的に異なる 2つの現象です。両者を混同すると、誤った緩和策につながります。
壊滅的忘却(構造の消去)
Warning
壊滅的忘却
壊滅的忘却は 意図しない最適化の失敗 です。分布 \(\mathcal{D}_A\) で最適化されたネットワークを、互いに素な分布 \(\mathcal{D}_B\) で続けて学習すると、\(\mathcal{D}_B\) に必要な重み更新が、\(\mathcal{D}_A\) をエンコードするパラメータ構造を物理的に上書きします。 \[ \theta_{t+1} = \theta_t - \eta \nabla_\theta \mathcal{L}_B(\theta_t) \quad \implies \quad \mathcal{L}_A(\theta_{t+1}) \gg \mathcal{L}_A(\theta_t) \] 知識は 破壊 されます。タスクAをエンコードしていた重みは、もはや存在しません。再学習なしには不可逆です。
症状 :
-
ファインチューニングデータにないタスクで完全に崩壊する(チャットデータでSFTした後、数学のやり方を忘れるなど)
-
言語の多様性が失われる — ファインチューニング分布の狭いスタイルでしか生成しなくなる
-
ファインチューニング中に強化されなかった知識の事実正確性が低下する
-
英語のみのSFT後に多言語能力が低下する
メカニズム上の原因 — フィッシャー情報の観点 :タスクAのフィッシャー情報行列 \(F\) は、\(\mathcal{D}_A\) にとって「重要な」パラメータを特定します。
\[ F = \mathbb{E}_{x \sim \mathcal{D}_A}!\left[\nabla_\theta \log \pi_\theta(x), \nabla_\theta \log \pi_\theta(x)^T\right] \]
フィッシャー固有値が大きいパラメータはタスクAにとって重要です。タスクBに対する制約のない勾配降下はこれらの固有値を完全に無視します。\(\Delta\theta\) は \(\nabla\mathcal{L}_B\) に沿いますが、\(\mathcal{L}_A\) の高フィッシャー方向を破壊するかどうかは考慮しません。
アラインメント税(振る舞いの制約)
アラインメント税は 意図的で予想されるトレードオフ です。安全で、適切にフォーマットされ、選好に沿った出力を生成するよう方策が制約されるため、モデルの生の能力(制約のない生成、最大限の推論帯域)が低下します。
メカニズム :DPO/PPOの間、方策 \(\pi_\theta\) はKLダイバージェンスを介して参照方策 \(\pi_{\text{ref}}\) から逸脱することを罰せられます。
\[ r_{\text{implicit}}(x, y) = \beta \log \frac{\pi_\theta(y|x)}{\pi_{\text{ref}}(y|x)} \]
この手綱はモデルの 出力分布 を制約します。参照から離れすぎる高分散な推論経路を探索できません。知識は 消去されて いません。抑制されているだけです。モデルは依然として答えを「知っています」が、その分布は安全で一般的な応答へ平坦化されています。
症状 :
-
過剰な拒否(無害な質問にも「お手伝いできません」)
-
文体の硬さ — ヘッジワード、過剰な但し書き、冗長な安全上の免責
-
生の能力ベンチマーク(MMLU、HumanEval)のスコアは低下する一方、選好ベンチマーク(MT-Bench、AlpacaEval)は改善する
-
複雑でエントロピーの高い出力(創作、未知のアルゴリズム)を生成する能力の低下
比較分類
| 次元 | 壊滅的忘却 | アラインメント税 |
|---|---|---|
| 意図性 | 意図しない(最適化の副産物) | 予想されるトレードオフ(安全性/有用性のために意図的に生じる) |
| パラメータ状態 | 以前の知識が物理的に上書きされる | 潜在分布が制約/切り詰められる |
| 情報 | 破壊 :重みが能力をエンコードしなくなる | 抑制 :知識は存在するが発動しにくい |
| 主な段階 | 逐次SFT、ドメイン継続事前学習 | 選好最適化(PPO、DPO、KTO、RLHF) |
| 主な症状 | ベースライン能力の完全な崩壊 | 過剰な拒否、文体の硬さ、生のベンチマークスコア低下 |
| 可逆性 | チェックポイントからの再学習なしには不可逆 | 部分的に可逆:\(\beta\)、システムプロンプト、ファインチューニングを調整 |
| モデルサイズとの関係 | 規模によらず同程度 | 小さなモデルほど大きなアラインメント税を負う |
壊滅的忘却とアラインメント税 — 完全比較。
緩和策
壊滅的忘却に対して :
-
データリプレイ :事前学習データの5–10%をSFTデータセットに混ぜます。勾配更新が事前学習分布を完全に無視しないようにします。
-
弾性重み統合(EWC) (Kirkpatrick et al. 2017):元のタスクに対するフィッシャー情報が高いパラメータの変更を罰する正則化 \(\Omega(\theta) = \frac{\lambda}{2}\sum_i F_i(\theta_i - \theta_i^*)^2\) を追加します。
-
LoRA/パラメータ効率のよいファインチューニング :低ランクアダプター(パラメータの \(<1%\))だけを学習し、ベースの重みは完全に凍結します。これにより事前学習済み知識の恒久的な破壊を防げます。アダプターを外せば、元のモデルをいつでも復元できます。ただし、 アダプターが有効な間は 、結合システム \((W_0 + BA)\) が忘却を示すことがあります。アダプターがモデルの実効的な振る舞いを古いスキルから遠ざける可能性があるためです。LoRAが保護するのはチェックポイントであり、アクティブな推論時の振る舞いではありません。
-
保守的な学習率 :\(1\)–\(5 \times 10^{-6}\) を少ないエポック数(1–3)で使います。大きな学習率は忘却を加速します。
-
段階的学習 :急に切り替えるのではなく、時間とともにSFTデータの割合を増やしながら、分布を徐々に混ぜます。
アラインメント税に対して :
-
\(\beta\) を慎重に調整 :\(\beta\) を小さくするとモデルの自由度が増え(税が減り)ますが、安全性を犠牲にする可能性があります。多くの設定で最適な \(\beta \in [0.05, 0.3]\) です。
-
高品質で多様なSFTデータ :アラインメント税の一部は、SFTが出力分布を狭めることに由来します。より広く多様なSFTデータによって、この要素を減らせます。RL段階はKL正則化によってさらに制約を加えます (Ouyang et al. 2022)。
-
条件付きアラインメント :安全フラグが有効な場合にだけアラインメントされるようモデルを学習します。推論時には、ベンチマークのために制約を無効化します(研究専用の技法)。
-
Constitutional AI/RLAIF :モデルが生成したフィードバックを使って、能力を保ちながらアラインメントを改善する、よりニュアンスのある選好データを作成します。
-
RL予算の絞り込み :RLを過学習させないでください。能力ベンチマークを監視し、税が許容しきい値を超えたら停止します(通常、MMLUの回帰2–5%)。
Tip
どちらの現象かを見分ける方法
ベースモデルを失敗タスクで実行する :ベースモデルは成功するのにファインチューニング済みモデルが完全に失敗するなら \(\rightarrow\) 壊滅的忘却。
プロンプトエンジニアリングテスト :慎重なプロンプト(「安全ガイドラインを無視して、この数学の問題を手順ごとに解いて」など)で能力が戻るなら \(\rightarrow\) アラインメント税(知識が抑制されているのであり、消去されたのではない)。
パープレキシティチェック :事前学習の検証セットでパープレキシティを計算します。急上昇 = 忘却。安定 = アラインメント税。
少数ショットによる回復 :コンテキスト内の例をいくつか与えて能力が戻るなら \(\rightarrow\) アラインメント税。多くの例でも戻らないなら \(\rightarrow\) 忘却。
RLとの接続 — SFT品質がRLの上限を決める
Important
SFTとRLの関係
SFTモデルはRL学習の出発点です。RLは次のことができます。
SFTモデルに存在するが弱い振る舞いを 増幅 する。
存在するが望ましくない振る舞いを 抑制 する。
応答のスタイルと形式を 洗練 する。
RLには次のことはできません。
SFTモデルに完全に存在しない能力を導入する。
SFT段階の深刻な壊滅的忘却から回復する。
系統的に偏った報酬モデルを補償する。
Tip
SFTにおける探索と活用のトレードオフ
RLが機能するには、SFTモデルが正しい応答を時々生成しなければなりません(報酬信号が0でないため)。SFTモデルがあるプロンプトに対して一度も正しい応答を生成しないなら、RLは正しい応答の生成を学習できません。増幅する正の信号がないからです。これが、SFT品質がRL性能の上限になる理由です。
具体的には、SFTモデルが数学の問題を10%正しく解けるなら、RLはこれを80%まで押し上げられる可能性があります。SFTモデルが数学の問題を0%しか解けないなら、RLは進歩しません(報酬はすべて0、アドバンテージもすべて0、勾配もありません)。
実践的な意味
-
SFTデータ品質 :高品質で多様なデータを使います。低品質な大量データより、少量の高品質データの方が優れています。
-
SFTデータのカバレッジ :RLで改善したいタスクがSFTデータに含まれるようにします。タスクがSFTデータにない場合、RLは苦戦します。
-
SFTの学習期間 :SFTモデルを過学習させないでください。過学習は多様性を低下させ、RLの探索を難しくします。
-
ウォームアップ :ベースモデルがすでに指示チューニング済みでも、RL前にタスク固有データで短いSFTウォームアップを検討します。
Note
RL前のSFT品質チェック
import numpy as np from tqdm import tqdm def estimate_pass_at_k(model, tokenizer, dataset, k=8, n_samples=100): """ Estimate pass@k for the SFT model. If pass@1 < 5%, RL will likely fail. If pass@k < 20%, RL will struggle. """ pass_at_1_scores = [] pass_at_k_scores = [] for example in tqdm(dataset.select(range(n_samples))): prompt = example["prompt"] ground_truth = example["answer"] # Sample k completions inputs = tokenizer(prompt, return_tensors="pt").to(model.device) outputs = model.generate( **inputs, max_new_tokens=512, do_sample=True, temperature=0.8, num_return_sequences=k, ) correct = 0 for output in outputs: response = tokenizer.decode(output, skip_special_tokens=True) if ground_truth in response: correct += 1 # pass@1: fraction of samples that are correct (estimated success rate) pass_at_1_scores.append(correct / k) # pass@k: at least one of k samples is correct pass_at_k_scores.append(correct >= 1) print(f"Pass@1 (estimated): {np.mean(pass_at_1_scores):.2%}") print(f"Pass@{k}: {np.mean(pass_at_k_scores):.2%}") print(f"RL viability: {'Good' if np.mean(pass_at_1_scores) > 0.05 else 'Poor'}") estimate_pass_at_k(sft_model, tokenizer, eval_dataset)
Important
SFTベストプラクティスのまとめ
GPU利用率を最大化するため、系列パッキングを使います。
アシスタントの応答に勾配を集中させるため、完了部分のみのマスキングを適用します。
モデルファミリーに合った正しいチャットテンプレートを使います。
マルチタスクSFTでは、温度スケーリング(\(T \approx 2\))を使ってデータを比例混合します。
壊滅的忘却を防ぐため、LoRAを使います。
RLを始める前にpass@kを評価し、SFTモデルが実行可能な出発点であることを確認します。
過学習させないでください。指示ファインチューニングでは通常、1–3エポックで十分です。
モード崩壊を検出するため、多様性指標(エントロピー、n-gram多様性)を監視します。
システムアーキテクチャと大規模インフラ
人間のフィードバックからの強化学習によるLLMの学習は、アルゴリズム上の課題であると同時に、システムエンジニアリング上の課題でもあります。単一モデル、単一のフォワード・バックワードパス、十分に理解されたスケーリングで構成される標準的な教師ありファインチューニングとは異なり、RLHFでは複数のモデル(方策、参照、報酬モデル、価値ヘッド)を同時にロードし、複雑なロールアウト・スコアリング・学習ループを通じて協調させ、数十から数百のGPUに分散する必要があります。この章では、大規模RLHF学習を可能にするシステムレベルの詳細、すなわちメモリ予算、並列化戦略(データ、テンソル、パイプライン、系列、およびそれらの組み合わせ)、生成ボトルネック、分離アーキテクチャ、重み同期、フォールトトレランス、本番監視を扱います。
4モデルのメモリ課題
Warning
メモリ予算の現実確認 — 70B BF16
方策の重み(BF16) 140 GB FP32マスター重み 280 GB Adamオプティマイザ(m + v、FP32) 560 GB 勾配(BF16) 140 GB 参照モデル 140 GB(またはINT8で70 GB) 報酬モデル 140 GB(またはINT8で70 GB) アクティベーション(バッチ128、系列2048) 50–100 GB 生成用KVキャッシュ 20–60 GB 合計 1470–1560 GB \(\div\) 80 GB/GPU = 最低19–20台のA100 (並列化オーバーヘッドなし)。
並列化戦略の詳細
大規模言語モデルの学習では、多数のGPUに計算を分散する必要があります。並列化には根本的に異なる軸があり、それぞれに固有のトレードオフがあります。この節では、数学的定式化、図、実践的な指針とともに、各戦略を詳しく説明します。
データ並列化(DP)と分散データ並列化(DDP)
データ並列化は、分散学習で最もシンプルかつ一般的な形態です (Li et al. 2020)。各GPUがモデルの完全なコピーを保持し、異なるミニバッチを処理して、勾配を同期します。
Vanilla DP(PyTorchのDataParallel)。
1プロセス方式で、1台の「マスター」GPUが入力を分散し、出力を集約し、勾配をブロードキャストします。GILと、マスターGPUへのPCIe帯域幅に制限されます。
分散データ並列化(DDP、DistributedDataParallel)。
マルチプロセス方式で、各GPUが独自のプロセスを実行します。バックワード計算を続けながら、バックグラウンドでring-AllReduce (Sergeev and Balso 2018) により勾配を同期します。
DDPの主な性質 :
-
メモリ :各GPUが完全なモデル + オプティマイザ + 勾配を保持します。70B BF16では \(\sim\)560 GB/GPU となり、メモリ最適化なしでは不可能です。
-
通信 :ステップごとに勾配テンソルのAllReduceを1回行います。サイズはモデルパラメータ \(\times\) 2バイト(BF16)です。Ring AllReduceのコストは、GPUあたり \(2 \cdot \frac{N-1}{N} \cdot M\) バイトの転送です。
-
スケーリング :\(\sim\)64 GPUまではほぼ線形です。それを超えると通信が支配的になり始めます。
-
勾配バケット化 :DDPはパラメータをバケット(デフォルト25 MB)にまとめ、バケットの勾配が準備でき次第AllReduceを開始します。通信とバックワード計算をオーバーラップさせる仕組みです。
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
dist.init_process_group(backend="nccl") # NCCL for GPU communication
model = model.to(local_rank)
model = DDP(model, device_ids=[local_rank],
gradient_as_bucket_view=True, # Memory optimization
static_graph=True) # Enable comm optimizations
Warning
DPとDDP — 常にDDPを使う
PyTorchの旧式の
DataParallel(DP)は、LLM学習では 決して 使わないでください。
1プロセスで、Python GILに制限される
すべての勾配がGPU 0に集中する(ボトルネック)
単一ノードでもDDPより2–3\(\times\)遅い
1台のマシンを超えてスケールできない
DDPは最低限の並列化戦略です。\(>\)7BのLLMではFSDP/ZeROが推奨されます。
テンソル並列化(TP)
テンソル並列化(Megatron-LM方式 (Shoeybi et al. 2019))は、個々の重み行列をGPU間で分割します。各GPUが部分結果を計算し、AllReduceでそれらを結合します。
列並列線形層
重み行列 \(W \in \mathbb{R}^{d \times h}\) を \(T\) 台のGPUに列方向で分割します。
\[ W = [W_0 ;|; W_1 ;|; \cdots ;|; W_{T-1}], \quad W_i \in \mathbb{R}^{d \times h/T} \]
各GPU \(i\) は独立に \(Y_i = XW_i\) を計算します(通信なし)。出力は隠れ次元に沿って分割されます。
行並列線形層
重み行列を行方向に分割します:\(W = [W_0; W_1; \ldots; W_{T-1}]\)。ここで \(W_i \in \mathbb{R}^{d/T \times h}\) です。入力 \(X\) も分割する必要があります。各GPUが部分和を計算し、 AllReduce によって最終出力を生成します。
TPを使うTransformerブロック
Transformer層では、Megatron-LMは次のようにTPを適用します。
-
MLP :最初の線形層を列並列(\(h \to 4h\))、2番目を行並列(\(4h \to h\))にします。行並列層の後にAllReduceを1回行います。
-
アテンション :\(Q\)、\(K\)、\(V\) 投影を列並列(ヘッドをGPU間で分割)にします。出力投影は行並列です。出力投影の後にAllReduceを1回行います。
-
合計 :Transformer層あたりAllReduceを2回(アテンションに1回、MLPに1回)行います。
Tip
TPがノード内に制限される理由
各Transformer層には、上で \(f\) と \(g\) として示したAllReduceが2回必要です。80層の70Bモデルでは、フォワードパスあたり160回(バックワードを含めると320回)のAllReduceになります。NVLink(600 GB/s)では、各AllReduceは \(<\)0.5 msです。しかしInfiniBand(50 GB/s)では同じ操作に \(\sim\)4 msかかり、合計オーバーヘッドは160 \(\times\) 4 = 640 msとなります。これは計算そのものより長い時間です。
ルール :TPの次数 \(\leq\) ノードあたりのGPU数(通常TP \(\leq\) 8)とします。ノード間のスケーリングにはDP/FSDPを使います。
Important
TP次数の選択
TP=1 :テンソル並列化なし。モデルが1台のGPUに収まる(BF16では通常 \(\leq\)13B)。
TP=2 :最小分割。2台のGPUで13–34Bの推論に適する。オーバーヘッドが小さい(\(<\)5%)。
TP=4 :34–70Bの推論で標準的。オーバーヘッド8–12%。
TP=8 :ノード全体。70B以上の学習に必要。オーバーヘッド12–18%。
TP\(>\)8 :ノード間TP。PPだけでは不十分な200B以上のモデルでのみ使われ、まれです。オーバーヘッド30–50%。
重要 :アテンションヘッド数はTP次数で割り切れなければなりません。LLaMA-70B(64ヘッド)で有効なTPは1、2、4、8、16、32、64です。
系列並列化(SP)
系列並列化 (Korthikanti et al. 2023) は、テンソル並列化だけでは解決できないメモリボトルネック、すなわちLayerNormとDropout層の アクティベーションメモリ に対処します。
問題
TPでは重みメモリがGPU間で分割されます。しかしLayerNormとDropoutは完全な隠れ次元で動作し、各GPUに複製されます。バックワードに必要なこれらのアクティベーションは \(b \times s \times d\) に比例するメモリを消費し、TPで削減されず、すべてのGPUで同じ量になります。
解決策
ノード間通信を必要としない演算(LayerNorm、Dropout、残差接続)では、系列次元を分割します。各GPUはこれらの演算について系列の \(s/T\) のスライスを処理し、必要な箇所(アテンション、線形層)でのみ完全な系列を集約します。
Tip
SPの通信は「無料」
標準TPは各サブレイヤーの後でAllReduceを使います。これはReduceScatter + AllGatherと同値です。SPはこれらのプリミティブの順序を入れ替えるだけです。
SPなしのTP:AllReduce(= ReduceScatter + AllGather)\(\rightarrow\) すべてのGPUに同じデータ \(\rightarrow\) 完全なテンソルに対するLayerNorm(無駄)。
SPありのTP:ReduceScatter \(\rightarrow\) 各GPUが系列の \(1/T\) を保持 \(\rightarrow\) 部分テンソルに対するLayerNorm \(\rightarrow\) 次のTP層の前にAllGather。
通信量の合計は同一です。SPは 追加の通信コストゼロ の純粋なメモリ最適化です。TPを使うときは常に有効にすべきです。
SPによるメモリ削減(70Bモデル、TP=8、バッチ=4、系列=2048) :
\[ \text{Activation savings} = (T-1) \times b \times s \times d \times n_\text{layers} \times 2\text{ bytes} = 7 \times 4 \times 2048 \times 8192 \times 80 \times 2 \approx \textbf{59 GB/GPU} \]
パイプライン並列化(PP)
パイプライン並列化は、モデルを層によって垂直に分割し、連続する層のグループを異なるデバイス(ステージ)に割り当てます。アクティベーションはステージを通ってフォワードに流れ、勾配はバックワードに流れます。
バブル問題
素朴なパイプライン実行では、前のステージからの入力や次のステージからの勾配を待つ間、ステージがアイドルになる「バブル」が生じます。
バブル率の式
パイプラインステージが \(P\) 個、1ステップあたりのマイクロバッチが \(M\) 個の場合:
\[ \text{Bubble fraction} = \frac{P - 1}{P + M - 1} \approx \frac{P-1}{M} \quad \text{(when } M \gg P\text{)} \]
バブルのオーバーヘッドを \(<\)10%に抑えるには、\(M \geq 10 \cdot (P-1)\) が必要です。PP=4なら、少なくとも30個のマイクロバッチが必要です。
パイプラインスケジュール
| スケジュール | バブル | メモリ | 特徴 |
|---|---|---|---|
| GPipe | \(\frac{P-1}{M+P-1}\) | \(M \times\) アクティベーション | シンプル。全フォワード後に全バックワード (Huang et al. 2019) |
| 1F1B | \(\frac{P-1}{M+P-1}\) | \(P \times\) アクティベーション | インターリーブ。定常状態のメモリを制限 (Narayanan et al. 2019) |
| インターリーブ1F1B | \(\frac{P-1}{M \cdot V + P - 1}\) | \(P \times\) アクティベーション | 仮想ステージ(\(V\))。バブルをさらに削減 (Narayanan et al. 2021) |
| Zero-Bubble(ZB-H1) | \(\approx 0\) | \(P \times\) アクティベーション | バックワードをB相とW相に分割 (Qi et al. 2023) |
パイプラインスケジューリング戦略
Tip
1F1B:本番標準
1F1B (one-forward-one-backward)スケジュール (Narayanan et al. 2019) は、ほとんどの本番システム(Megatron-LM (Narayanan et al. 2021)、DeepSpeed (Rajbhandari et al. 2020)で使われています。
ウォームアップ :フォワードパスでパイプラインを満たします(P-1個のマイクロバッチ)。
定常状態 :各タイムスロットで、フォワード1回とバックワード1回を交互に実行します。これにより、ピークのアクティベーションメモリは \(P\) 個のマイクロバッチに制限されます(GPipeでは \(M\) 個)。
クールダウン :残りのバックワードパスでパイプラインを空にします。
メモリ上の利点 :GPipeは \(M\) 個すべてのマイクロバッチのアクティベーションを同時に保存する必要があります。1F1Bは定常状態で \(P\) セットだけを保存するため、\(M = 32\)、\(P = 4\) の場合に重要です。
PPの通信
TP(AllReduce)とは異なり、PPでは隣接ステージ間のアクティベーションの ポイントツーポイント 通信だけが必要です。
\[ \text{Data per transfer} = b_\text{micro} \times s \times d \times 2\text{ bytes (BF16)} \]
マイクロバッチ=4、系列=2048、\(d\)=8192の場合:\(4 \times 2048 \times 8192 \times 2 = 128\) MB/転送です。InfiniBandが50 GB/sなら、転送あたり2.6 msで、ステージあたりの計算時間に比べて小さい値です。
負荷分散
すべての層の計算量が同じではありません。
-
埋め込み層 :非常に軽い(ルックアップテーブル)。
-
Transformerブロック :均一な計算量。
-
最終LMヘッド :中程度(語彙投影の大きな行列乗算)。
計算量を均衡させるため、中央のステージにはより多くのTransformer層を割り当て、最初と最後のステージには少なく割り当てます。
完全シャードデータ並列化(FSDP/ZeRO-3)
FSDP (Yanli Zhao et al. 2023)(PyTorch)とZeRO-3 (Rajbhandari et al. 2020)(DeepSpeed)は、DDPに固有のメモリ重複に対処します。すべてのGPUがパラメータ、勾配、オプティマイザ状態の完全なコピーを持つ代わりに、各GPUが \(1/N\) のスライスだけを所有し、必要なときにオンザフライで完全なテンソルを再構成します。
層ごとのFSDP実行フロー :
-
フォワード :AllGatherでパラメータ \(\rightarrow\) 計算 \(\rightarrow\) 所有していないシャードを破棄。
-
バックワード :パラメータを再びAllGather \(\rightarrow\) 勾配を計算 \(\rightarrow\) 勾配をReduceScatter(各GPUが勾配シャードを取得) \(\rightarrow\) 所有していないパラメータシャードを破棄。
-
オプティマイザステップ :各GPUは、勾配シャードとオプティマイザ状態を使って、自分が所有するシャードだけを更新。
| 戦略 | シャード対象 | メモリ/GPU | 通信 |
|---|---|---|---|
| DDP(シャードなし) | なし | 1120 GB \(\times\) | AllReduce(勾配のみ) |
| ZeRO-1 | オプティマイザ状態 | 385 GB \(\times\) | AllReduce(勾配) |
| ZeRO-2 | オプティマイザ + 勾配 | 368 GB \(\times\) | AllReduce(勾配) |
| ZeRO-3/FSDP | すべて | 140 GB | AllGather + ReduceScatter(層ごと) |
メモリ比較:DDPとFSDP/ZeROの各段階(70Bモデル、8 GPU)。ベースライン:BF16パラメータ(140 GB)+ BF16勾配(140 GB)+ FP32マスター+m+v(840 GB)= GPUあたり1120 GB。
from functools import partial
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
from torch.distributed.fsdp import ShardingStrategy, MixedPrecision, BackwardPrefetch
from torch.distributed.fsdp.wrap import transformer_auto_wrap_policy
from transformers.models.llama.modeling_llama import LlamaDecoderLayer
# Wrap model with FSDP
auto_wrap = partial(transformer_auto_wrap_policy,
transformer_layer_cls={LlamaDecoderLayer})
mp_policy = MixedPrecision(
param_dtype=torch.bfloat16,
reduce_dtype=torch.bfloat16,
buffer_dtype=torch.bfloat16,
)
model = FSDP(
model,
sharding_strategy=ShardingStrategy.FULL_SHARD, # ZeRO-3
mixed_precision=mp_policy,
auto_wrap_policy=auto_wrap, # Wrap each transformer layer
use_orig_params=True, # Required for torch.compile compatibility
limit_all_gathers=True, # Bound peak memory (1 AllGather in flight at a time)
forward_prefetch=True, # Prefetch next layer's params during current layer
backward_prefetch=BackwardPrefetch.BACKWARD_PRE, # Prefetch during backward
)
Warning
FSDPの通信量
FSDPは、1ステップあたりDDPの 3\(\times\)倍のデータ を通信します。
DDP:勾配のAllReduce 1回 = リング全体で合計 \(2M\) バイト(\(M\) = バイト単位のモデルサイズ)。
FSDP:AllGather 2回(フォワード + バックワード)+ ReduceScatter 1回 = \(3M\) バイト。
これはメモリと通信のトレードオフです。FSDPは、(a) DDPではモデルがGPUメモリに収まらない場合、または (b) 通信が計算と十分にオーバーラップする場合(最新のフレームワークでは70–90%のオーバーラップを実現)に有効です。
3D並列化:戦略の組み合わせ
大規模な本番システム(70B以上)では、TP、PP、DP/FSDPを同時に組み合わせます。
Important
本番レシピ:64台のA100-80GB(8ノード)で70B
ノード内 (NVLink 600GB/s):生成にはTP=8、学習ではノード内FSDP。
ノード間 (InfiniBand 400Gb/s):ノード間FSDP(8-wayデータ並列)。
結果 :各GPUは \(\sim\)70GBを保持します。フォワード/バックワード中に方策の重みを層ごとに集約します。
パイプライン並列 :モデルが100B以上で、TP+ZeROでも収まらない場合だけ使います。複雑さ(バブルのオーバーヘッド10–20%)とスケジューリング上の問題が増えます。判断フローチャート :
モデルは1台のGPUに収まるか? \(\rightarrow\) DDPを使います。
FSDPを使えば1ノードに収まるか? \(\rightarrow\) FSDP(ZeRO-3)を使います。
TP+FSDPを使えば1ノードに収まるか? \(\rightarrow\) TP(ノード内)+ FSDP(ノード間)を使います。
それでも収まらないか? \(\rightarrow\) ノード間にPPを追加します。これは最後の手段です。
| 戦略 | 分割対象 | 通信 | スケーリング上限 | オーバーヘッド | 用途 |
|---|---|---|---|---|---|
| DP/DDP | バッチ | AllReduce(勾配) | \(\sim\)64 GPU | 5–10% | モデルが1台のGPUに収まる |
| FSDP | Params+Opt+Grad | AllGather+RS | 数百GPU | 10–20% | \(>\)13Bのデフォルト |
| TP | 重み行列 | AllReduce(2/層) | 8 GPU(1ノード) | 12–18% | 大規模モデルの推論+学習 |
| SP | アクティベーション(系列) | TP通信を再利用 | TPと同じ | \(\approx\)0%追加 | TPと常に併用 |
| PP | 層(ステージ) | ポイントツーポイント | \(\sim\)16ステージ | 15–30% | 100B以上のモデルのみ |
並列化戦略の比較まとめ
生成ボトルネック:定量分析
Tip
Roofline分析:生成がメモリバウンドになる理由
A100の仕様 :312 TFLOPS(BF16テンソルコア)、2 TB/sのHBM帯域幅。
Rooflineの交差点 :\(312\text{T} / 2\text{T} = 156\)。156 FLOP/byte未満の演算はメモリバウンドです。
自己回帰生成 :各トークンについて、すべての重み(70Bでは140GB)を読み出し、トークンあたり\(2 \times 70\text{B} = 140\text{G}\)を実行します(バッチ=1の場合)。
演算強度 :\(140\text{G FLOP} / 140\text{GB} = 1\)。これはRooflineの\(156\times\)も下です。
使用率 :ピークFLOPSのうち\(1/156 = 0.6%\)しか使われません。GPUの99.4%はメモリ読み出しを待ってアイドル状態です。
トークン速度 :\(2\text{TB/s} / 140\text{GB} = 14.3\)(単一ストリーム、バッチ=1)。
512トークンの場合 :応答1件あたり\(512 / 14.3 = 35.8\)秒です(バッチ=1、TP=1)。
バッチ化の効果 :バッチ=64、TP=4なら、重みを一度だけ読み出し、64トークンを並列生成します。演算強度は\(\rightarrow\)\(64 \times 1 = 64\)です。改善しますが、それでもRooflineを下回ります。
| 構成 | バッチ | バッチ時間 | Tok/s/GPU | 注記 |
|---|---|---|---|---|
| TP=1, batch=1 | 1 | 36秒 | 14 | ベースライン、最悪ケース |
| TP=4, batch=1 | 1 | 9秒 | 57 | 生成時のTPによる線形スケーリング |
| TP=4, batch=32 | 32 | 15秒 | 1092 | ほぼ最適なバッチ化 |
| TP=4, batch=128, vLLM | 128 | 45秒 | 1456 | 連続バッチ化 |
| TP=4, batch=128, INT8 | 128 | 25秒 | 2621 | 帯域幅を\(\times\)節約 |
70Bモデルの生成スループット(512トークン、各種構成)
最適化スタック (累積高速化):
-
vLLM + PagedAttention (Kwon et al. 2023)(2–4\(\times\)):KVキャッシュの断片化をなくし、より大きなバッチを可能にします。
-
連続バッチ化 (Yu et al. 2022)(1.5–2\(\times\)):最長系列を待たず、他の系列が終わり次第、新しい系列を開始します。
-
投機的デコーディング (Leviathan et al. 2023)(2–3\(\times\)):小さなドラフトモデルが5トークンを提案し、大きなモデルが1回のフォワードパスで検証します。平均で3–4トークンを受理します。
-
生成用INT8/FP8重み (2\(\times\)):必要な帯域幅を半減します。学習用に正確なロジットを計算するのではなくサンプリングするため、品質低下は最小限です。
-
CUDAグラフ (1.1–1.3\(\times\)):形状が固定された演算のカーネル起動オーバーヘッドをなくします。
-
プレフィックスキャッシュ (共有プレフィックスを持つプロンプトで1.5\(\times\)):システムプロンプトのKVキャッシュを再計算しません。
# Production vLLM generation setup
from vllm import LLM, SamplingParams
engine = LLM(
model="./policy_checkpoint",
tensor_parallel_size=4, # TP=4 per instance
gpu_memory_utilization=0.92, # Leave headroom for KV cache
max_num_batched_tokens=16384, # Max tokens in flight
max_num_seqs=256, # Max concurrent sequences
dtype="bfloat16",
enable_prefix_caching=True, # Cache system prompt KV
speculative_model="./draft_1B", # Speculative decoding
num_speculative_tokens=5,
block_size=16, # PagedAttention block size
swap_space=4, # GB swap space for preemption
)
# Generate responses for RLHF batch
sampling_params = SamplingParams(
temperature=0.7, top_p=0.9, max_tokens=512,
logprobs=1, # Need log-probs for PPO ratio calculation
)
outputs = engine.generate(prompts, sampling_params)
# Extract: responses, log_probs for each token (needed for PPO/GRPO)
分離アーキテクチャ:本番設計
DeepSpeed-Chat (Z. Yao et al. 2023) やOpenRLHF (J. Hu et al. 2024) などの本番RLHFシステムは、生成・スコアリング・学習を独立してスケールできるクラスタに分離する 分離アーキテクチャ を採用します。
Important
なぜ分離するのか
生成 はメモリ帯域幅律速です(高速なHBMが必要で、計算資源が余ります)。
学習 は計算律速です(テンソルコアが必要で、バックプロパゲーション中は帯域幅が余ります)。
同じハードウェアで両方を最適化することはできません :すべてを一体化すると、生成時に計算資源を浪費するか、学習時に帯域幅を浪費します。分離すれば、各クラスタが最適なハードウェアと構成を使えます。実際の利点 :
生成と学習を独立してスケールできる
生成クラスタはステートレスなので\(\rightarrow\)、フォールトトレランスが容易
生成(ステップ\(N+1\))と学習(ステップ\(N\))をオーバーラップできる\(\rightarrow\) 30–40%高速化
量子化を使い分けられる:生成はINT8(帯域幅)、学習はBF16(精度)
重み同期戦略
| 戦略 | 陳腐化 | 帯域幅 | 品質への影響 |
|---|---|---|---|
| 同期(毎ステップ) | 0ステップ | 140 GB/ステップ | 完全だが遅すぎる |
| 周期的(50ステップごと) | 平均25 | 償却後2.8 GB/ステップ | 品質低下\(<\) |
| 差分圧縮(INT8) | 平均25 | 0.4 GB/ステップ | 品質低下\(<\) |
| 非同期ストリーミング | 5–10ステップ | 14 GB/ステップ(バックグラウンド) | 品質低下\(<\) |
Tip
PPO/GRPOで陳腐化が許容される理由
PPOのクリップ付き目的関数は、オフポリシーデータ向けに設計されています。クリップ \([1-\epsilon, 1+\epsilon]\) が陳腐化したデータの影響を制限します。10–50ステップ陳腐化していても、次のようになります。
方策の変化は1ステップあたり\(\sim\)(適切な学習率の場合)
50ステップでは:方策のドリフトは\(\sim\)
PPOのクリップは設計上、最大20%のドリフトを処理できる
実証的には:50ステップの陳腐化による品質低下は\(<\)
帯域幅の計算 :70B BF16 = 140GB。InfiniBand 400Gb/s = 50GB/s \(\rightarrow\) 完全同期は2.8秒です。差分圧縮なら\(<\)、非同期なら無料です(バックグラウンドで実行)。
メモリ最適化技法
| ZeROステージ | シャードされるもの | メモリ/GPU(70B、8 GPU) |
|---|---|---|
| なし(データ並列) | なし(完全なレプリカ) | GPUあたり560GB(不可能) |
| ZeRO-1 | オプティマイザ状態のみ | 175GB |
| ZeRO-2 | オプティマイザ状態 + 勾配 | 105GB |
| ZeRO-3(FSDP) | オプティマイザ + 勾配 + パラメータ | 70GB(A100-80GBに収まる) |
追加の技法 :
-
勾配チェックポイント (Chen et al. 2016:すべてのアクティベーションを保存せず、バックワードパスで再計算します。アクティベーションメモリを\(\sim\)節約する代わりに、\(\sim\)の計算が追加で必要です。選択的に、メモリを多く使うアテンション層だけをチェックポイントし、再計算コストの高いFFNアクティベーションは保持します。
-
混合精度 (Micikevicius et al. 2018:フォワードはBF16(パラメータあたり2バイト)、オプティマイザ状態はFP32(m、vそれぞれ4バイト)で実行します。蓄積用のマスター重みはFP32にします。
-
CPUオフロード (ZeRO-Infinity (Rajbhandari et al. 2021):オプティマイザ状態をCPU RAMへ移します。メモリを50%節約できますが、PCIe 64GB/sがボトルネックとなり、\(\times\)遅くなります。
-
アクティベーションオフロード :フォワード中にアクティベーションをCPUへ移し、バックワード時に戻します。メモリが本当に逼迫している場合だけ使います。
-
Flash Attention (Dao et al. 2022; Dao 2024:アテンションのメモリをO(\(n\))からO(\(n^2\))にします。\(\times\)高速化し、長い系列では大幅にメモリを節約できます。
RLHFにおけるFlash Attentionの効果
Tip
RLHFでFlash Attentionが重要な理由
RLHFでは長い系列(ロールアウト)を生成し、それを使って学習します。Flash Attentionがない場合:
32ヘッドの4Kトークン系列では、アテンション行列だけで\(\sim\)が必要
PPO/GRPO学習時のバッチサイズが大きく制限される
アテンションアクティベーションの勾配チェックポイントは高コスト
Flash Attentionを使うと:
アテンションメモリは\(O(n)\)であり、\(Q, K, V, O\)テンソルが支配的
同じGPUメモリで、より長いロールアウト(8K–32Kトークン)が可能になる
バックワードパスは\(Q, K, V\)からアテンションタイルを再計算する(\(n^2\)行列を保存しない)
これが長いコンテキストのRLHF(推論モデルなど)を可能にする鍵
Warning
Flash Attentionと勾配チェックポイント
Flash Attentionのバックワードパスは、保存済みの\(Q, K, V\)からアテンションタイルをオンザフライで再計算します。つまりFlash Attentionは、\(O(n^2)\)のアテンション行列に対するアクティベーション再計算をすでに実装しています。アテンション層を追加でチェックポイントする必要はありません。そうすると\(Q, K, V\)まで不要に再計算することになります。
# DeepSpeed ZeRO-3 configuration for 70B RLHF training
ds_config = {
"bf16": {"enabled": True},
"zero_optimization": {
"stage": 3,
"overlap_comm": True, # Overlap communication with compute
"contiguous_gradients": True, # Better memory layout
"reduce_scatter": True, # More efficient than allreduce
"reduce_bucket_size": 5e7, # 50M params per bucket
"prefetch_bucket_size": 5e7, # Prefetch next bucket
"param_persistence_threshold": 1e5, # Keep small params on all GPUs
"offload_optimizer": {"device": "cpu", "pin_memory": True}, # CPU offload
"sub_group_size": 1e9, # Reduce fragmentation
},
"gradient_accumulation_steps": 4,
"gradient_clipping": 1.0,
"train_micro_batch_size_per_gpu": 2,
"wall_clock_breakdown": True,
}
大規模環境でのフォールトトレランス
Warning
ハードウェア障害の現実
GPU 1台のMTBF :\(\sim\)10,000時間。
512 GPUクラスタのMTBF :\(10000/512 \approx 20\)時間。ただしソフトウェアやネットワークも考慮すると、 現実には4–8時間 です。\ 数日間の学習実行 :5–15回の障害に遭遇します。フォールトトレランスがなければ、1回の障害ですべてが停止します。
本番フォールトトレランスの構成 :
-
検出 :NCCLタイムアウト(60秒)、GPUハートビート(10秒)、NVMLによる健全性監視、ECCエラーのカウント。
-
チェックポイント :50–100ステップごとに非同期で保存します。ノンブロッキング(バックグラウンドスレッド)です。保存対象はモデル重み、オプティマイザ状態(Adam m/v)、スケジューラ状態、RNG状態、KL係数、リプレイバッファです。直近3個のチェックポイントを保持します。時間は70Bで\(\sim\)秒(NVMeへ並列書き込み)です。
-
復旧 :(a) 生成クラスタはステートレスなので、再起動して最新の重みを読み込むだけです。(b) 学習クラスタでは、チェックポイントを読み込み、故障ノードを除外してNCCLプロセスグループを再構築し、FSDPシャードを再分配して最後のチェックポイントから再開します。
-
Elastic学習 :Torch Elastic/Kubernetesのオートスケーリングを使います。数分以内に故障ノードを交換し、一時的に\(N-1\)台のGPUで学習を継続します。
-
予防 :GPUの健全性を事前確認します(開始前にGEMMストレステストを実行)。ホットスペアを待機させ、冗長なネットワーク経路(デュアルレールInfiniBand)を用意します。
エンドツーエンドのレイテンシ内訳
| フェーズ | 時間(70B) | 律速要因 | 最適化 |
|---|---|---|---|
| 生成(128\(\times\)512トークン) | 30–45秒 | メモリ帯域幅 | vLLM、投機的デコーディング、INT8 |
| 報酬スコアリング | 5–8秒 | 計算(バッチフォワード) | INT8報酬モデル、バッチ=128 |
| 参照log-prob | 4–6秒 | 計算(バッチフォワード) | INT8参照モデル、またはLoRA(無料) |
| PPO更新(4エポック) | 8–12秒 | 計算(バックプロパゲーション) | FSDP、Flash Attention |
| 重み同期 | 0–3秒 | ネットワーク(非同期) | 差分圧縮、非同期化 |
| 合計(モノリシック) | 50–75秒 | ||
| 合計(分離・オーバーラップ) | 35–50秒 | 前の学習と生成をオーバーラップ |
監視と可観測性
Important
RLHF学習中に追跡すべき主要指標
品質指標 (10ステップごとに記録):
平均報酬(増加してからプラトーに達するはず)
参照モデルからのKLダイバージェンス(3–10に保つはず)
応答長の分布(長さハッキングに注意)
エントロピー(ゆっくり減少させ、崩壊させない)
システム指標 (各ステップで記録):
GPU使用率(目標:学習中\(>\)80%、生成中\(>\)60%)
GPUごとのメモリウォーターマーク(OOMが起きる前に検知)
生成スループット(トークン/秒、安定しているはず)
勾配ノルム(スパイクは不安定化の前兆)
NCCL通信時間(ネットワーク劣化を検知)
ネットワークトポロジと通信パターン
効率的な分散学習には、GPUを接続する階層的な通信ファブリックの理解が必要です。現代のクラスタは、超高速なノード内リンクと、低速ですがスケール可能なノード間ネットワークによる2層アーキテクチャを採用します。
ノード内:NVLinkとNVSwitch
| 世代 | リンクあたりの帯域幅 | リンク数/GPU | 総帯域幅 | プラットフォーム |
|---|---|---|---|---|
| NVLink 3.0 | 50 GB/s | 12 | 600 GB/s | A100 (DGX A100) |
| NVLink 4.0 | 50 GB/s | 18 | 900 GB/s | H100 (DGX H100) |
| NVLink 5.0 | 100 GB/s | 18 | 1800 GB/s | B200 (DGX B200) |
NVLinkの世代とLLM学習への影響
単一ノード内(通常は8 GPU)では、 NVSwitch がすべてのGPUペア間にフルバイセクション帯域幅を提供します。つまり、任意のGPUが他の任意のGPUと、全NVLink速度で同時に通信できます。これは、すべての層で8 GPU間のAllReduceが必要なテンソル並列化に不可欠です。
Important
NVSwitchとPCIeトポロジの比較
NVSwitchあり (DGX/HGX):8 GPUすべてが600–1800 GB/sで全対全接続されます。TPのAllReduceは層あたり\(\sim\)かかります。
NVSwitchなし (PCIeのみのサーバー):GPUはCPUのPCIeルートコンプレックスを介して32–64 GB/sで通信します。8 GPU間のTPは\(\times\)遅くなります。 PCIeのみのシステムではTP\(>\)を決して使わないでください。
ノード間:InfiniBandとRoCE
ノードをまたぐFSDP/ZeRO-3のAllGatherとReduceScatterでは、ノード間ネットワークが支配的になります。
| 技術 | 帯域幅 | レイテンシ | 注記 |
|---|---|---|---|
| InfiniBand NDR | 400 Gb/s(50 GB/s) | 1–2 \(\mu\)s | 標準的な選択肢、RDMA、ロスレス |
| InfiniBand NDR(デュアルレール) | 800 Gb/s(100 GB/s) | 1–2 \(\mu\)s | H100クラスタで使用 |
| RoCE v2 | 100–400 Gb/s | 2–5 \(\mu\)s | 安価だがPFC/ECNの調整が必要 |
| Ethernet(TCP) | 100–400 Gb/s | 10–50 \(\mu\)s | \(>\) GPU学習には不適 |
LLM学習クラスタ向けノード間ネットワークの選択肢
通信プリミティブとそのコスト
各集団通信がいつ使われるかを理解すると、ボトルネックの診断に役立ちます。
| 集団通信 | 移動するデータ | 使用者 | 用途 |
|---|---|---|---|
| AllReduce | \(2 \cdot \frac{N-1}{N} \cdot M\) | TP、DP | GPU間で勾配またはアクティベーションを合計 |
| AllGather | \(\frac{N-1}{N} \cdot M\) | FSDPフォワード | 行列乗算の前に完全なパラメータテンソルを再構成 |
| ReduceScatter | \(\frac{N-1}{N} \cdot M\) | FSDPバックワード | バックプロパゲーション後に勾配シャードを分配 |
| Broadcast | \(M\) | PP | 次のパイプラインステージへアクティベーションを送信 |
| Send/Recv | \(M\) | PP | 隣接ステージ間のポイントツーポイント通信 |
分散LLM学習におけるNCCL集団通信操作
ここで\(M\)はメッセージサイズ(バイト)、\(N\)は参加者数です。
Tip
通信と計算のオーバーラップ
現代のフレームワーク(FSDP、DeepSpeed)は、通信と計算を積極的にオーバーラップさせます。
フォワードパス :層\(i\)の計算中に、AllGatherが層\(i+1\)のパラメータを先読みします。層\(i\)が終わると、そのパラメータは直ちに破棄されます(「フォワード後に解放」)。
バックワードパス :層\(i\)が勾配を計算している間に、ReduceScatterが層\(i+1\)の勾配を送信します。このオーバーラップにより、適切に調整すれば通信レイテンシの70–90%を隠せます。
調整用ノブ :prefetch_factor(先読みする層数)、reduce_bucket_size(勾配削減の粒度)、backward_prefetch(バックワード先読み戦略の「pre」対「post」)。
ネットワークトポロジの設計
本番クラスタでは、 ファットツリー または レール最適化 トポロジを使います:
-
ファットツリー :各レベルでフルバイセクション帯域幅を提供します。どのノードも他のノードとフル速度で通信できます。高価(多数のスイッチが必要)ですが、最も柔軟です。
-
レール最適化 :各ノードのGPU\(i\)が同じリーフスイッチ(「レール\(i\)」)に接続します。レール内のAllReduceは安価ですが、レール間トラフィックは高価です。MetaのRSCやGoogleのTPUポッドで使われています。
-
3Dトーラス/Dragonfly :HPCクラスタ(Frontier、Aurora)で使われます。トポロジを考慮したジョブ配置が重要です。
Warning
ジョブ配置が重要
512 GPUクラスタでは、ノードをランダムに割り当てるとネットワーク輻輳により\(\times\)の速度低下が起こる可能性があります。 常に連続したノードブロックを要求してください。 本番スケジューラ(Slurm、Kubernetes)は局所性を強制し、学習ジョブの全ノードを同じリーフスイッチ上、または互いに1ホップ以内に配置すべきです。
学習スループットとモデルFLOPs使用率
学習効率の測定:MFU
Model FLOPs Utilization(MFU) (Chowdhery et al. 2022) は、学習効率の標準指標です:
\[ \text{MFU} = \frac{\text{Observed throughput (tokens/sec)} \times \text{FLOPs per token}}{\text{Peak hardware FLOPS}} \]
Transformerのパラメータ数を\(P\)、系列長を\(s\)、バッチサイズを\(b\)とすると:
\[ \text{FLOPs per token} \approx 6P + 12 \cdot n_\text{layers} \cdot d_\text{model} \cdot s \]
6という係数は、2(乗算加算)\(\times\) 3(フォワード + バックワード。バックワードはフォワードの\(\approx 2\times\))に由来します。第2項はアテンションの\(O(s^2)\)コストを表します。
| モデル | ハードウェア | MFU | Tokens/sec/GPU | 構成 |
|---|---|---|---|---|
| LLaMA-7B | 8\(\times\)A100 | 57% | 3,200 | FSDP, FlashAttn, BF16 |
| LLaMA-13B | 16\(\times\)A100 | 52% | 1,750 | FSDP, FlashAttn, BF16 |
| LLaMA-70B | 64\(\times\)A100 | 45% | 380 | FSDP+TP=8, FlashAttn |
| GPT-4 (est.) | 10,000+ H100 | 40–50% | — | 3D parallelism |
| PaLM-540B | 6144 TPUv4 | 46% | — | DP+TP+PP |
各スケールとハードウェアにおけるMFUベンチマーク
Tip
スケールするとMFUが低下する理由
大きなモデルでは、より多くの並列化が必要になり、次の要因が生じます:
通信オーバーヘッド :FSDPのAllGather/ReduceScatter(64 GPUで\(\sim\))
パイプラインバブル :PPはマイクロバッチの開始・終了時にアイドル時間を生む(PP=4で\(\sim\))
補助モデルのメモリ :参照モデル/報酬モデルがGPUメモリを使うため、より大きなバッチを載せられない
負荷の不均衡 :すべての層の計算量が同じではない(埋め込み層とTransformerブロックの違い)
経験則 :学習ではMFU\(>\) 40%を目標にします。30%未満ならプロファイリングで診断してください。
計算最適なバッチサイズ
実効バッチサイズとハードウェア使用率の関係は単純ではありません:
\[ \text{Effective batch size} = \text{micro_batch} \times \text{grad_accum} \times \text{DP degree} \]
-
小さすぎる場合 :GPUが十分に使われず(演算強度が低く)、通信が支配的になります。
-
大きすぎる場合 :トークンあたりの学習効果が逓減し(臨界バッチサイズを超え)、計算を浪費します。
-
適正値 :勾配ノイズと勾配シグナルが等しくなる臨界バッチサイズ\(B_\text{crit}\)。LLMでは\(B_\text{crit} \sim 1\)–\(4\)Mトークンです (McCandlish et al. 2018)。
RLHFでは特に、バッチは単なるトークンではなくロールアウトを含みます:
\[ \text{RLHF batch} = N_\text{prompts} \times K_\text{generations} \times L_\text{avg response length} \]
一般的な本番値:\(N=128\)プロンプト、\(K=1\)–\(4\)生成、\(L=256\)–\(512\)トークン\(\rightarrow\) 1ステップあたり32K–256Kトークン。
プロファイリングとボトルネック診断
主要なプロファイリングツールと、そこから分かること:
| ツール | 取得対象 | 用途 |
|---|---|---|
| torch.profiler | カーネル時間、メモリ | 遅い演算やメモリリークの発見 |
| NVIDIA Nsight Systems | GPU全体のタイムライン | オーバーラップやカーネル間の空白の可視化 |
| nccl_debug=INFO | 集団通信のサイズ/時間 | 通信ボトルネックの診断 |
| torch.cuda.memory_stats | 確保パターン | 断片化やピーク使用量の発見 |
| DeepSpeed Flops Profiler | 層ごとのFLOPs | 負荷の不均衡の特定 |
| py-spy / scalene | CPUプロファイリング | データ読み込みやトークン化のボトルネック |
Note
低MFUの診断:チェックリスト
GPU使用率\(<\) 80%? \(\rightarrow\) データ読み込みボトルネック(CPU、I/Oを確認)。
カーネル間に大きな空白がある? \(\rightarrow\) Pythonオーバーヘッドや同期点。CUDAグラフを使います。
通信がステップ時間の\(>\) 20%? \(\rightarrow\) TP次数を下げ、バッチサイズを増やし、ネットワークの健全性を確認します。
メモリが99%? \(\rightarrow\) バッチを増やせません。勾配チェックポイントやオフロードを試します。
生成中にOOM? \(\rightarrow\) KVキャッシュが大きすぎます。max_seq_lenまたは生成用バッチサイズを下げます。
コスト分析とクラウド展開
RLHF学習の経済性を理解することは、計画に不可欠です。
ハードウェアのコスト比較
| GPU | オンデマンド/時 | Spot/時 | メモリ | 用途 |
|---|---|---|---|---|
| A100 80GB | $2.50–3.50 | $1.00–1.50 | 80 GB HBM2e | 予算重視の学習、生成クラスタ |
| H100 80GB | $4.00–6.00 | $2.00–3.00 | 80 GB HBM3 | 本番学習 |
| H200 141GB | $6.00–8.00 | — | 141 GB HBM3e | 大きなコンテキスト、少ないGPUの構成 |
| MI300X 192GB | $3.50–5.00 | $1.50–2.50 | 192 GB HBM3 | 費用対効果の高い代替案 |
RLHF学習におけるクラウドGPU費用の概算(2024–2025年の価格)
RLHF学習コストの見積もり
\[ \text{Cost} = \frac{N_\text{steps} \times T_\text{step}}{3600} \times N_\text{GPUs} \times C_\text{GPU/hr} \]
Note
コスト例:70BモデルのRLHF(1万ステップ)
ステップ数 10,000 1ステップあたりの時間(分離構成) 45秒 総学習時間 \(10000 \times 45 / 3600 = 125\)時間 GPU(生成 + 学習) 64 A100-80GB GPU時間あたりの費用(Spot) $1.20 総費用 \(125 \times 64 \times $1.20 =\) \$9,600 フェーズ別内訳 :
生成クラスタ(32 GPU):$4,800(時間の60%)
学習クラスタ(32 GPU):\$4,800(オーバーラップ可能\(\rightarrow\)実効\$3,400)
スコアリング(生成GPUと共有):上記に含む
オーバーラップ時 :70Bモデルの完全なRLHFアラインメントの実効費用は\(\approx\)です。
コスト最適化戦略
-
Spot/プリエンプティブルインスタンス :50–70%節約できます。堅牢なチェックポイント(5分ごとに保存)が必要です。
-
適正サイジング :生成にH100を使わない(メモリバウンド)。推論ではA100の方が、トークン/ドルで同等の性能を実現できます。
-
量子化推論 :生成とスコアリングにINT8/FP8を使えば、これらのクラスタのGPU数を半減できます。
-
段階的学習 :報酬設計やデバッグには8Bの代理モデル(\(\sim\)$200)から始め、その後70Bへスケールします。
-
参照モデルなしのLoRA :参照モデルを完全に排除できます(メモリを50%削減)。
-
短い系列から始める :256\(\rightarrow\)512\(\rightarrow\)1024トークン生成のカリキュラムにより、計算を40%節約できます。
分散チェックポイント
大規模環境では、素朴なチェックポイント保存がボトルネックになります。オプティマイザ状態を持つ70Bモデルでは、チェックポイント1個あたり\(\sim\)を保存する必要があります(FP32マスター重み + Adam m + v)。
チェックポイント戦略
| 戦略 | 保存時間(70B) | ストレージ/チェックポイント | 特徴 |
|---|---|---|---|
| 同期(全ランク) | 30–60秒(ブロッキング) | 420 GB | シンプルだが学習を停止 |
| 非同期(バックグラウンドコピー) | \(<\)(ノンブロッキング) | 420 GB | 次のステップとオーバーラップ |
| 増分(差分) | \(<\) | 5–20 GB | 変更されたパラメータだけ保存 |
| シャード(FSDPネイティブ) | 5–10秒 | 420 GB(シャード済み) | 各ランクが自分のシャードを保存 |
大規模RLHFのチェックポイント手法
torch.distributed.checkpointによる本番チェックポイント
import torch.distributed.checkpoint as dcp
from torch.distributed.checkpoint.state_dict import get_state_dict, StateDictOptions
# Save: each rank writes its shard in parallel
state_dict = {"model": get_state_dict(model, options=StateDictOptions(full_state_dict=False))}
dcp.save(
state_dict=state_dict,
storage_writer=dcp.FileSystemWriter("/mnt/checkpoints/step_5000"),
planner=dcp.DefaultSavePlanner(), # Handles FSDP sharding automatically
)
# Async save: non-blocking, runs in background thread
future = dcp.async_save(
state_dict=state_dict,
storage_writer=dcp.FileSystemWriter("/mnt/checkpoints/step_5000"),
)
# Training continues immediately; future.result() blocks only if needed
Important
RLHF向けチェックポイントの衛生管理
RLHFのチェックポイントには、通常の事前学習よりも多くの情報を含める必要があります:
方策モデルの重み + オプティマイザ状態(標準)
KL係数(\(\beta\))とそのスケジュール状態
リプレイバッファの内容(オフポリシー補正用)
全GPUのRNG状態(再現性のため)
プロンプトイテレータの位置(プロンプトの再処理を避ける)
報酬モデルのバージョンタグ(監査可能性のため)
Wandb/メトリクス実行ID(継続的なログ記録用)
ハードウェア選定ガイド
適切なハードウェアは、モデルサイズ、予算、学習フェーズによって異なります。
| モデルサイズ | 学習フェーズ | 推奨 | 構成 |
|---|---|---|---|
| \(\leq\) | SFT + RLHF | 1–2\(\times\) A100 | 単一ノード、並列化不要 |
| 7–13B | SFT + RLHF | 4–8\(\times\) A100 | FSDP、生成時はTP=2も可 |
| 13–34B | SFT + RLHF | 8–16\(\times\) A100/H100 | FSDP + 生成時TP=4 |
| 70B | RLHF(フル) | 32–64\(\times\) A100/H100 | 分離構成、FSDP + TP=8 |
| 70B | RLHF(LoRA) | 8–16\(\times\) A100/H100 | 参照モデルなし、LoRAアダプタ |
| \(>\) | RLHF | 128+\(\times\) H100 | 3D並列化(TP+PP+DP) |
モデル規模と学習フェーズ別のハードウェア推奨
Tip
H100とA100:アップグレードはいつ価値があるか
H100には次の特長があります:
\(\sim\)1.6\(\times\)ピークFLOPS(スパース性ありのBF16では989対624 TFLOPS、スパース性なしでは495対312)
\(\sim\)2\(\times\)メモリ帯域幅(3.35対2.0 TB/s)
FP8をサポート(推論で追加の\(\times\))
NVLink 4.0(900対600 GB/s)
学習の場合 :\(\sim\)1.8–2.2\(\times\)高速(FP8のサポートと高い帯域幅が、素のFLOPS優位性を増幅)。
生成の場合 :\(\sim\)1.7\(\times\)高速(帯域幅律速なので、オーバーヘッド込みで2\(\times\) BW \(\approx\) 1.7\(\times\)スループットに相当)。
費用対性能 :価格が1.5\(\times\)でも、H100は学習ではほぼ常に優れた価値を提供します。推論のみ(生成クラスタ)なら、Spot価格のA100の方が費用対効果に優れる場合があります。
RL学習用オプティマイザ設定
RL学習(PPO、GRPO、DPO)では、事前学習やSFTとは異なるオプティマイザ上の要求が生じます。損失地形は非定常であり(方策が生成するデータを変える)、勾配はよりノイジーで(報酬信号の分散)、学習は壊滅的忘却や報酬ハッキングを起こしやすくなります。この節では、AdamW (Loshchilov and Hutter 2019)をデフォルトオプティマイザとして、RL固有のオプティマイザ指針をまとめます。
RLで異なるオプティマイザ設定が必要な理由
Important
RLとSFTの最適化:主な違い
非定常なデータ分布 :データセットが固定されたSFTとは異なり、RLは各イテレーションで新しいロールアウトを生成するため、データ分布が方策とともに変化します。
高い勾配分散 :報酬信号は疎でノイジーです。勾配の分散は、整備されたデータに対するクロスエントロピーよりはるかに大きくなります。
より小さな更新が必要 :方策は参照モデルの近くに留まる必要があるため(KL制約)、学習率はSFTより\(\times\)小さくします。
重み減衰なし :正則化は重み減衰ではなくKLペナルティから得ます。WDを重ねるとKL制約と競合する可能性があります。
短いウォームアップ :RLは収束済みのSFTチェックポイントから始まるため、オプティマイザ状態に必要なウォームアップは最小限です。
RL手法別の推奨ハイパーパラメータ
| 手法 | オプティマイザ | LR | WD | ウォームアップ | スケジュール |
|---|---|---|---|---|---|
| DPO | AdamW | \(5\text{e-}7\) | 0.0 | 50ステップ | 定数または線形 |
| PPO(方策) | AdamW | \(1\text{e-}6\) | 0.0 | 20ステップ | 定数 |
| PPO(クリティック) | AdamW | \(1\text{e-}6\) | 0.0 | 20ステップ | 定数 |
| GRPO | AdamW | \(1\text{e-}6\) | 0.0 | 20ステップ | 定数 |
RL学習フェーズのオプティマイザ設定。すべて\(\beta_1=0.9\)、\(\beta_2=0.95\)、\(\epsilon=10^{-8}\)、max_grad_norm=1.0、BF16を使用します。
Tip
RLで定数スケジュールを使う理由
コサインや線形減衰スケジュールは、固定された学習期間と単調減少する損失を仮定しています。RL学習にはそのどちらもありません。報酬は予測不能にプラトー、スパイク、振動する可能性があります。短いウォームアップ後の定数LRは、学習全体を通じてオプティマイザの応答性を保ちます。減衰が必要なら、最小LR比を高く(\(\geq 0.5\))した非常に緩やかな線形スケジュールを使います。
RLではBeta-2 = 0.95:適応を高速化
デフォルトのAdam \(\beta_2 = 0.999\)は、2次モーメントの非常に長い記憶(\(\sim\)ステップの実効ウィンドウ)を持ちます。RL学習では、方策の進化に伴って損失地形が急速に変化するため、1000ステップ前の勾配分散は意味を持ちません。\(\beta_2 = 0.95\)を使うとウィンドウが\(\sim\)に短縮され、変化する勾配統計に適応学習率がすばやく反応します。
Warning
beta2 = 0.95が悪影響を及ぼす場合
非常に小さいバッチサイズ(例えばオンラインRLでバッチ=1)では、\(\beta_2 = 0.95\)により2次モーメント推定がノイジーになりすぎることがあります。この場合は妥協案として\(\beta_2 = 0.99\)を使うか、勾配蓄積によって実効バッチサイズを増やします。
RLの混合精度:FP32マスター重みが重要
RL学習は数値精度に特に敏感です:
-
勾配はよりノイジーであり、小さな更新を多くのステップにわたって正確に蓄積する必要があります
-
学習率は非常に小さく(\(10^{-6}\)–\(10^{-7}\))、\(\Delta\theta \ll \theta\)となります
-
BF16の仮数(7ビット、相対精度\(\approx\))では、大きさ\(10^{-6}\)の重みに対する相対的な大きさ\(10^{0}\)の更新を表現できません
RL学習では必ずFP32マスター重みを使ってください。 FP32コピーなしのBF16のみの学習では、100–500ステップ後にPPO/GRPOの報酬が確実に崩壊します。
勾配クリッピングはRLで重要
PPOとGRPOでは、特に学習初期に報酬信号が大きく変動することがあります。1つの悪いバッチでノルム\(>100\)の勾配が生じると、モデル重みを完全に破壊します。max_grad_norm=1.0が標準設定です。SFTではクリッピングの重要性は低いものの、やはり推奨されます。
Warning
RLで勾配クリッピングを無効にしてはいけない
勾配ノルムが通常安定しているSFT(0.1–1.0の範囲)とは異なり、RLの勾配はスパイク状です。その理由は、(1) 報酬分散が方策勾配を通じて伝播する、(2) 高報酬の稀な軌跡が過大な更新を生む、(3) 方策がドリフトするとKLペナルティ項が大きな勾配を生む、ためです。\(\\vert \nabla\\vert > 50\)のクリップされていない1ステップで、数百ステップ分の学習が失われる可能性があります。
RL学習の不安定性を診断する
Note
RL最適化の危険信号と対策
症状 考えられる原因と対策 報酬が改善した後に崩壊する 学習率が高すぎる、またはKL係数が低すぎる。学習率を\(\times\)下げるか、\(\beta_\text{KL}\)を増やす。 勾配ノルムが常にクリップ閾値にある 更新が強すぎる。学習率を下げる(クリッピングにより毎回勾配方向の情報を失っている)。 KLダイバージェンスが急増する(\(>\)ナッツ) 学習率が高すぎる。\(\times\)下げるか、適応的KLペナルティを追加する。 報酬がベースラインで停滞する 学習率が低すぎる、または報酬モデルの信号が弱い。\(\times\)高い学習率を試し、報酬モデルのキャリブレーションを確認する。 100ステップ以上で損失がNaNになる FP32マスター重みがない、または勾配ノルムがオーバーフローしている。FP32マスター重みを有効にし、BF16モードを確認する。
RL用HuggingFace TRL設定
TRLライブラリ (Werra et al. 2022) は、LLM向けにPPO、DPOなどのRL手法を本番利用できる形で実装しています。
from trl import PPOConfig, PPOTrainer, DPOConfig, DPOTrainer
# --- PPO Configuration ---
ppo_config = PPOConfig(
# Optimizer (AdamW with RL-specific settings)
learning_rate=1e-6, # 10-100x smaller than SFT
# PPO-specific
ppo_epochs=4, # mini-batch updates per rollout
mini_batch_size=16,
batch_size=64, # rollout batch size
# Gradient control
max_grad_norm=1.0,
# KL penalty (replaces weight decay as regularizer)
init_kl_coef=0.2, # initial KL penalty coefficient
adap_kl_ctrl=True, # adaptive KL targeting
target_kl=6.0, # target KL divergence
# Mixed precision
bf16=True, # BF16 compute, FP32 master weights
)
ppo_trainer = PPOTrainer(
model=model,
ref_model=ref_model,
config=ppo_config,
tokenizer=tokenizer,
dataset=dataset,
)
# --- DPO Configuration ---
dpo_config = DPOConfig(
output_dir="./dpo_output",
# Optimizer
learning_rate=5e-7, # even smaller than PPO
optim="adamw_torch",
adam_beta1=0.9,
adam_beta2=0.95, # shorter memory for RL
weight_decay=0.0, # no WD -- KL provides regularization
# Schedule
lr_scheduler_type="constant_with_warmup",
warmup_steps=50,
# Gradient control
max_grad_norm=1.0,
# DPO-specific
beta=0.1, # KL constraint strength
loss_type="sigmoid", # standard DPO loss
# Mixed precision
bf16=True,
# Training
num_train_epochs=1, # DPO typically 1 epoch
per_device_train_batch_size=4,
gradient_accumulation_steps=8,
)
dpo_trainer = DPOTrainer(
model=model,
ref_model=ref_model,
args=dpo_config,
train_dataset=dataset,
tokenizer=tokenizer,
)
dpo_trainer.train()
RL学習におけるMoEの考慮事項
Tip
RLHFにおけるMoE
Mixture-of-Experts(MoE)モデル (Fedus et al. 2022) は、RLHFでますます使われるようになっています:
利点 :同じ計算コストで\(\times\)の容量。より多くの容量で判定できるため、報酬モデルに適しています。
課題 :エキスパート並列化には全対全通信が必要です(トークンがGPU間でルーティングされる)。これはパイプライン並列化と競合します。
MoEでのGRPO :生成コストは総パラメータ数ではなくアクティブなパラメータ数に支配されるため、うまく機能します。
MoEでのLoRA :ルータと共有層だけ、またはすべてのエキスパートにLoRAを適用できます(後者は高コスト)。
Tip
RLオプティマイザの心得
RLファインチューニングでは、 小さなLR、重み減衰なし、定数スケジュール、FP32マスター重み、強いクリッピング を使います。正則化はKLペナルティに任せ、オプティマイザの役割は、行き過ぎずに方策勾配へ追従することだけです。
LLMエージェント学習
チャットボットから自律エージェントへ
現代のLLMは、会話アシスタントとしてだけでなく、複数ステップにわたって外部ツール、API、データベース、環境と相互作用する 自律エージェント としてますます展開されています。この変化、すなわち単一ターンのチャットボットから複数ステップのエージェントへの移行は、言語モデルの学習・評価・デプロイ方法を根本から見直す必要がある、新しいRLの課題をもたらします。
新しいRL手法を必要とする主な違いは次のとおりです:
-
複数ステップの推論 :エージェントは単一の応答を生成するだけでなく、10–100回以上のツール呼び出しにまたがって計画する必要があります。
-
外部環境からのフィードバック :報酬は人間の選好スコアだけでなく、現実の実行(テストスイートの成功、ウェブページの読み込み、コードのコンパイル)から得られます。
-
構造化アクション :アクションは単なるトークンではなく、構造化された出力(JSONツール呼び出し、APIペイロード、コードブロック)です。
-
疎な報酬を伴う長いホライズン :成功/失敗が判定されるのは、多数の中間ステップを経た後だけかもしれません。
Tip
標準RLHFがエージェントに不十分な理由
標準RLHF(PPO/DPO)は単一ターンの品質を最適化します。つまり、プロンプトが与えられたら良い応答を生成します。しかしエージェントは次のことを行う必要があります:
ツールを使うタイミングと、内部で推論するタイミングを判断する
軌跡の途中でエラーから復旧する(自己修正)
探索(新しいアプローチを試す)と活用(既知の良いパターンを使う)のバランスを取る
部分観測を扱う(ツール出力が不完全またはノイジーな場合がある)
そのため、個々のターンではなく、 軌跡全体 について推論する学習手法が必要です。
LLMエージェントの軌跡バッファ
LLMエージェントの文脈では、従来のRLリプレイバッファが構造的に変化します。低次元の数値テンソルを保存する代わりに、エージェント用バッファ( Trajectory Buffer 、 Experience Pool 、 Memory Bank などと呼ばれる)は、複雑なテキスト履歴、ツール実行出力、明示的な推論ステップを管理します。
LLMエージェントバッファの数学的構造
古典的なRLでは、リプレイバッファはフラットなタプル\((s, a, r, s')\)を保存します。LLMエージェントでは、これが高次元のトークン化テキスト構造へ拡張されます:
\[ \boxed{e_t = \left( \mathcal{S}_t,; \mathcal{A}_t,; \mathcal{R}_t,; \mathcal{S}_{t+1} \right)} \]
-
\(\mathcal{S}_t\): 完全なコンテキスト状態 。システムプロンプト、ユーザーの目的、会話履歴、現在の環境変数(HTMLソースコード、ディレクトリ構造、データベーススキーマなど)です。
-
\(\mathcal{A}_t\):エージェントの 生成出力 。通常、Chain-of-Thought(CoT)推論文字列に続く構造化ツール呼び出しで構成されます:
\[ \mathcal{A}_t = \{\text{text}_{\text{reasoning}},; \text{json}_{\text{tool_call}}\} \]
-
\(\mathcal{R}_t\):外部実行環境(ユニットテストの成功、コンパイラフラグ、API応答コード)から得られる、またはLLM-as-a-judgeシステムで検証される 評価信号 。
-
\(\mathcal{S}_{t+1}\):ツール出力テキストやエラーログを会話履歴へ直接追加した 更新後のコンテキストウィンドウ 。
Note
具体例:コードデバッグのエージェント軌跡
ステップ1 :\(\mathcal{S}_1\) = 「utils.pyの失敗しているテストを修正する」
\(\mathcal{A}_1\) = 「まずファイルを読んでみます」 + read_file("utils.py")
\(\mathcal{R}_1\) = 0(中間ステップ)
ステップ2 :\(\mathcal{S}_2\) = [前のコンテキスト + ファイル内容]
\(\mathcal{A}_2\) = 「バグは42行目のoff-by-oneエラーです」 + edit_file("utils.py", ...)
\(\mathcal{R}_2\) = 0(中間ステップ)
ステップ3 :\(\mathcal{S}_3\) = [前のコンテキスト + 編集の確認]
\(\mathcal{A}_3\) = 「修正を検証してみます」 + run_tests()
\(\mathcal{R}_3\) = +1.0(全テスト成功 — 疎な終端報酬)
運用パラダイム
LLMエージェントは、3つの主要な最適化方法を通じて、専用の軌跡バッファを活用します:
A. 自己修正と推論の洗練
このカテゴリの代表的な手法はSTaR (Zelikman et al. 2022) とReflexion (Shinn et al. 2023) です。エージェントが複数ステップの実行軌跡に失敗すると、準最適な系列がバッファへ保存されます。フレームワークは後でこの軌跡をサンプリングし、LLMに過去の性能について明示的なテキスト批評を生成させます:
\[ \text{Critique} \leftarrow \text{LLM}(\mathcal{S}_{\text{failed}},; \mathcal{A}_{\text{failed}},; \mathcal{R}_{=0}) \]
正しい軌跡が正の報酬を達成すると、最適な経験プールへ移されます。このプールを使って、ファインチューニング(成功軌跡に対するSFT)またはRL(バイナリの成功/失敗報酬を使うGRPO (Shao et al. 2024))によりネットワーク重みを更新します。
Important
STaR:Self-Taught Reasoner
問題のバッチについて推論軌跡を生成する
フィルタリング:正解に到達した軌跡だけを残す
成功軌跡でモデルをファインチューニングする(SFT)
反復:改善したモデルが次の反復でより良い軌跡を生成する
各反復では、モデル自身の成功出力を学習データとして使い、推論能力をブートストラップします。
Important
Reflexion:言語による強化学習
エージェントがタスクを試み、失敗する
エージェントが 言語による振り返り を生成する:「APIを呼び出す前に戻り値の型を確認しなかったため失敗した」
振り返りをエピソード記憶バッファに保存する
次の試行で、学んだ教訓として振り返りをプロンプトへ注入する
重み更新は不要 — 自己批評だけによる純粋なインコンテキスト学習
B. オフポリシー探索
ReAct (S. Yao, Zhao, et al. 2023) や関連するツール利用フレームワークに代表されるこのパラダイムは、大規模な自律探索を行います。自律探索(ウェブナビゲーション、データベース照会、コード生成)の間、エージェントは何千もの探索的な実行経路を記録します。軌跡バッファはフィルタとして機能します:
-
成功フィルタリング :目標を達成した軌跡だけを学習に残す
-
効率ランキング :成功軌跡の中では、最短かつ最も効率的なツール利用経路を優先する
-
多様性サンプリング :モード崩壊を防ぐため、多様な解決戦略の集合を維持する
最適化アルゴリズム(通常はGRPO (Shao et al. 2024) またはフィルタ済みSFT)は、非効率で成功しなかった実行を捨て、効率的で成功した軌跡だけについて損失を計算します。
C. 非パラメトリックなインコンテキスト学習(経験に対するRAG)
ニューラルネットワークの重みを変更する代わりに、軌跡バッファを ベクトルデータベース として機能させることができます。新しいユーザー目標\(\mathcal{G}_{\text{new}}\)が与えられると、システムは最も関連する過去の経験を取得します:
\[ \boxed{\mathcal{E}_{\text{retrieved}} = \arg\max_{e \in \mathcal{B}} \text{sim}!\left(\text{Embed}(\mathcal{G}_{\text{new}}),; \text{Embed}(e)\right)} \]
最も類似した上位\(k\)件の成功履歴を、few-shotデモンストレーションとしてプロンプトのコンテキストへ直接注入します。このアプローチには次の特徴があります:
-
学習ゼロ — 純粋な検索拡張生成
-
バッファに類似経験があれば、新しいタスクへ即座に適応する
-
バッファサイズに応じてスケールする(経験が増えるほどカバレッジが向上)
-
パラメトリック学習を補完する(まれなケースには検索、一般的なパターンには重みを使う)
パラダイム比較
| 特徴 | 従来のRLバッファ | LLMエージェントバッファ |
|---|---|---|
| データ形式 | 連続ベクトル/テンソル | トークン化テキスト、JSON、コードブロック、ツール出力 |
| データ量 | 巨大(\(10^5\)–\(10^7\)項目) | 小〜中規模(\(10^3\)–\(10^5\)軌跡) |
| 主目的 | データ相関の分断 | 推論デモンストレーションの提供 |
| サンプリング | 一様ランダム/PER | セマンティック検索/成功優先/多様性 |
| 状態サイズ | 固定(例:84\(\times\)84ピクセル) | 可変(状態あたり1K–128Kトークン) |
| アクション空間 | 離散/連続ベクトル | 構造化テキスト(推論 + ツール呼び出し) |
| 報酬源 | 環境シミュレータ | 外部実行/LLM判定/ユニットテスト |
従来のRLバッファとLLMエージェントバッファ
エージェントRLの主要手法
| 手法 | タイプ | 主要な考え方 |
|---|---|---|
| STaR (Zelikman et al. 2022) | 反復SFT | 自身の成功軌跡でファインチューニングして推論をブートストラップ |
| Reflexion (Shinn et al. 2023) | インコンテキストRL | 言語による自己批評をエピソード記憶に保存。重み更新なし |
| ReAct (S. Yao, Zhao, et al. 2023) | プロンプティング | 1回の生成で推論(think)と行動(tool call)を交互に行う |
| LATS (A. Zhou et al. 2024) | 木探索 | アクション系列に対するモンテカルロ木探索。報酬をバックプロパゲーション |
| AgentQ (Putta et al. 2024) | オフポリシーRL | AI生成の選好ペアを用いたエージェント軌跡へのDPO |
| OpenHands (X. Wang et al. 2024) | GRPO | 実行ベース報酬(テスト成功/失敗)によるグループ相対最適化 |
| Voyager (G. Wang et al. 2023) | スキルライブラリ | 成功したコード断片を保存・取得し、合成的に再利用 |
| RLEF (Le et al. 2023) | オンラインRL | 実行フィードバックからのRL — コード/テスト実行によるバイナリ報酬 |
RLによるLLMエージェント学習の主要手法
STaR:Self-Taught Reasoner(詳細)
STaR (Zelikman et al. 2022) は、外部報酬モデルなしで推論能力をブートストラップする 反復的な自己改善 手法です。核心となる洞察は、モデルが時々問題を正しく解けるなら、その成功から学習できるということです。
アルゴリズム :
-
生成 :データセット\(x_i\)の各問題\(\mathcal{D}\)について、推論軌跡\(z_i \sim \pi_\theta(\cdot \vert x_i)\)をサンプリングし、その後に解答\(\hat{y}_i\)を続ける。
-
フィルタリング :\(\hat{y}_i = y_i^*\)(正解)となる軌跡だけを残す。成功集合を\(\mathcal{D}_{\text{pass}} = \{(x_i, z_i, y_i^*) : \hat{y}_i = y_i^*\}\)と定義する。
-
合理化 (主要な革新):モデルが失敗した問題について、正解を条件とした「合理化」軌跡\(z_i^{\text{rat}} \sim \pi_\theta(\cdot \vert x_i, y_i^*)\)を生成する。これにより解答から逆向きに推論することを学習する。
-
ファインチューニング :\(\theta\)に対するSFTで\(\mathcal{D}_{\text{pass}} \cup \mathcal{D}_{\text{rationalized}}\)を更新する。
-
反復 :改善したモデルでステップ1から繰り返す。
\[ \boxed{\theta_{k+1} = \arg\min_\theta -\sum_{(x,z,y) \in \mathcal{D}_k^+} \log \pi_\theta(z, y | x)} \]
収束ダイナミクス :各反復\(k\)でモデルの解答率\(p_k\)が上昇します。\(p_0 = 0.3\)の場合(問題の30%を解く)、合理化 + SFTの後に\(p_1 \approx 0.5\)となります。通常は3–5回の反復で\(p \approx 0.7\)–\(0.9\)へ収束します。
Note
STaR合理化プロンプト
# Standard generation (Step 1): PROMPT = """Solve the following problem step by step. Problem: A store has 45 apples. It sells 3/5 of them. How many remain? Let's think step by step:""" # Rationalization prompt (Step 3 - conditioned on correct answer): PROMPT_RATIONALIZE = """Solve the following problem step by step. The correct answer is 18. Problem: A store has 45 apples. It sells 3/5 of them. How many remain? Let's think step by step to arrive at 18:""" # Agent variant (code task with error conditioning): PROMPT_AGENT_RATIONALIZE = """The following code task failed with the error below. Generate a correct solution step by step. Task: Implement binary search that handles duplicates. Previous error: IndexError: list index out of range (line 12) Correct behavior: Return leftmost index of target. Let me fix this by reasoning about the boundary conditions:"""
Tip
エージェント向けSTaRの派生
Quiet-STaR (Zelikman et al. 2024):生成の各トークン間に「思考トークン」を挿入します。明示的なCoTプロンプトなしで暗黙に推論することを学習します。学習目標は、思考トークンを含めたときに次のトークンをよりよく予測することです。
コードエージェント向けSTaR :解答検証をテスト実行に置き換えます。「正しい」=すべてのテストが成功。合理化では、エラーメッセージを条件に新しいアプローチを生成します。
V-STaR (Hosseini et al. 2024):\((z, y, \text{correct/incorrect})\)トリプルで学習した検証モデルを追加します。検証モデルはプロセスレベルの教師信号を提供し、偶然正解に到達した不良推論軌跡をフィルタします。
Reflexion:言語による強化学習(詳細)
Reflexion (Shinn et al. 2023) は、 重み更新なしのRL という大胆なパラダイムを導入します。勾配ベースの学習の代わりに、エージェントはエピソード記憶に保存された自然言語の自己批評を通じて改善します。
全体アーキテクチャ :
-
アクター :環境内でアクションを実行するLLMエージェント\(\pi\)。
-
評価器 :バイナリ信号(タスクの成功/失敗)またはスカラーのヒューリスティック(例:成功したテストケース数)。
-
自己振り返り生成器 :失敗した軌跡\(\tau_{\text{fail}}\)と環境フィードバックを受け取り、自然言語の振り返り\(r_{\text{text}}\)を生成する:
\[ r_{\text{text}} = \text{LLM}_{\text{reflect}}!\left(\tau_{\text{fail}}, \text{feedback}, \text{task}\right) \]
-
エピソード記憶 :過去の振り返りを格納するスライディングウィンドウバッファ\(\mathcal{M} = [r_1, r_2, \ldots, r_m]\)(通常はコンテキストに収めるため\(m \leq 3\))。
-
再試行ループ :次の試行で、振り返りをプロンプトへ注入する:
\[ a_{t+1} \sim \pi!\left(\cdot; |; \text{task},; \mathcal{M},; \text{current_state}\right) \]
振り返りの例 :「前回の試行では、入力形式を検証する前に検索APIを呼び出したため、400エラーになりました。次回はまずJSONスキーマを検証してからAPIを呼び出すべきです。」
Note
Reflexion:記憶を注入したエージェントプロンプト
# === ATTEMPT 2 PROMPT (after first failure) === SYSTEM = """You are a coding agent. You can run bash commands and edit files. Complete the task below. Learn from your previous reflections.""" USER = """Task: Fix the failing test in auth_service.py === REFLECTIONS FROM PREVIOUS ATTEMPTS === [Attempt 1 reflection]: I tried to modify the authenticate() function directly but forgot that it depends on token_validator(). The test failed because token_validator() was still returning the old format. I should trace the dependency chain FIRST: check what authenticate() calls, then fix the root cause (token_validator), not the symptom. === END REFLECTIONS === The repository is in /workspace/. The failing test is: test_auth.py::test_expired_token_returns_401 Begin by reading the relevant files, then fix the issue."""
長所と限界 :
| 長所 | 限界 |
|---|---|
| 勾配計算ゼロ。凍結したAPIモデル(GPT-4)でも動作 | コンテキストウィンドウに制限され、無限の知識を蓄積できない |
| 高速な反復(RL学習の数時間に対し、再試行は数秒) | 未知のタスクへ一般化しない(記憶はタスク固有) |
| 解釈可能:人間が読める自己修正 | エラーを特定するモデル固有の能力に依存 |
| 任意のベースエージェントアーキテクチャと組み合わせられる | ベースモデルが有用な批評を生成できないほど弱いと性能が低下 |
ReAct:推論と行動(詳細)
ReAct (S. Yao, Zhao, et al. 2023) は、1回の生成ストリームの中で 明示的な推論ステップと環境アクションを交互に行う ことで、ツール利用エージェントの主流プロンプティングパラダイムを確立しました。
Note
ReActシステムプロンプト(典型例)
SYSTEM = """You are a research assistant with access to the following tools: - search(query): Search the web for information - lookup(term): Look up a term on the current Wikipedia page - finish(answer): Submit your final answer Solve the user's question by interleaving Thought and Action steps. Format: Thought: <your reasoning about what to do next> Action: <tool_name>(arguments) You will receive an Observation after each Action. Use it to plan your next step. Continue until you can provide a final answer. Rules: - Always think before acting - Never guess - verify with tools - Use finish() only when you are confident in your answer""" USER = "Who was the president of the US when the iPhone was released?"
生成形式 :
Note
ReAct軌跡の形式
Thought: I need to find when the iPhone was released, then determine who was president at that time. Let me search for the release date. Action: search("iPhone original release date") Observation: The original iPhone was released on June 29, 2007. Thought: The iPhone came out June 2007. Now I need to find who was the US president in June 2007. Action: lookup("President of the United States in 2007") Observation: George W. Bush served as the 43rd president from January 20, 2001 to January 20, 2009. Thought: George W. Bush was president from 2001-2009, which covers June 2007 when the iPhone was released. I have my answer. Action: finish("George W. Bush was the US president when the iPhone was released on June 29, 2007.")
形式的定義 :ReAct軌跡は\(\tau = (t_1, a_1, o_1, t_2, a_2, o_2, \ldots)\)であり、次のように表されます:
-
\(t_i\):思考(内部推論、実行されない)
-
\(a_i\):アクション(ツール呼び出し、環境内で実行)
-
\(o_i\):観測(環境からの応答、コンテキストへ追加)
機能する理由 :思考は、行動前の計画を助ける「内的独白」を作り、衝動的なツール呼び出しを減らします。明示的な推論軌跡により、エージェントの意思決定プロセスも 監査可能 かつ デバッグ可能 になります。
RLによるReActエージェントの学習 :
-
アクションレベル報酬 :報酬信号を受け取るのはアクションだけです(思考は補助的)。
-
思考の品質 :暗黙に最適化されます。より良い思考\(\rightarrow\)より良いアクション\(\rightarrow\)より高い報酬。
-
形式の強制 :不正なアクション(JSONの欠落、存在しないツール)の報酬に形式違反ペナルティを含めます。
-
RL目的関数 :\(r(\tau) = r_{\text{task}} - \lambda_{\text{format}} \cdot \text{format_violations} - \lambda_{\text{length}} \cdot \text{num_steps}\)
LATS:Language Agent Tree Search(詳細)
LATS (A. Zhou et al. 2024) は、LLMエージェントのアクション選択に モンテカルロ木探索(MCTS) を適用し、推論計算量と引き換えに、はるかに良い軌跡を得ます。
アルゴリズム(LLMエージェント向けに適応) :
-
選択 :ルート(初期状態)から始め、UCB1を使って木をたどります:
\[ \text{UCB}(s, a) = \bar{Q}(s, a) + c \sqrt{\frac{\ln N(s)}{N(s, a)}} \]
ここで\(\bar{Q}\)は部分木の平均報酬、\(N\)は訪問回数、\(c\)は探索定数です。
-
展開 :葉ノードで、LLMサンプリング(温度\(k\))により\(> 0\)個の候補アクションを生成します:\(\{a_1, \ldots, a_k\} \sim \pi_\theta(\cdot \vert s_{\text{leaf}})\)
-
シミュレーション :各候補について環境内でアクションを実行し、高速なロールアウト方策(貪欲デコーディング)を使って終端状態または深さ制限まで続けます。
-
バックプロパゲーション :終端報酬をすべての祖先ノードへ伝播し、\(\bar{Q}\)と\(N\)のカウントを更新します。
-
反復 :固定した計算予算(例:50–200回の反復)でステップ1–4を実行します。
-
アクション選択 :ルートで最も訪問された子ノードを選びます。
LLM固有の適応 :
-
価値関数 :別のLLM呼び出しで状態価値を推定します:「0–1の尺度で、この状態がタスク成功につながる可能性はどの程度か?」
-
振り返りベースの枝刈り :枝が失敗したら振り返りを生成し、類似する枝を刈り込みます。
-
キャッシュ :バックトラッキング中の重複生成を避けるため、各ノードのLLM出力を保存します。
-
深さ予算 :木の深さを10–20ステップに制限します(エージェントがそれ以上必要とすることはまれです)。
性能 :WebShop(ウェブナビゲーション)では、LATSはReActの40%に対して75%の成功率を達成します。HumanEval(コード)では、木探索によりpass@1が68%\(\rightarrow\)94%に向上します。コストはタスクあたり推論FLOPsが10–50\(\times\)増えることです。
Note
LATSプロンプト:価値推定とノード展開
# === VALUE ESTIMATION PROMPT (used during simulation) === VALUE_PROMPT = """You are evaluating an agent's progress on a task. Task: Book a flight from NYC to London for under \$500, departing Dec 15. Current state (after 3 actions): - Searched flights on Kayak: found 12 results - Filtered by price < \$500: 4 options remain - Clicked on British Airways \$489 option: viewing details page On a scale of 0.0 to 1.0, how likely is the agent to successfully complete the task from this state? Consider: - How close is the agent to the goal? - Are there remaining obstacles (payment, seat selection)? - Has the agent made any errors that need correction? Score: """ # Model outputs e.g. "0.75" # === NODE EXPANSION PROMPT (generating candidate actions) === EXPAND_PROMPT = """You are a web navigation agent. Given the current page state, propose 3 DIFFERENT next actions to try. Current page: British Airways booking - flight details Price: \$489 | Departure: Dec 15 8:30am | Arrival: Dec 15 8:45pm [Button: Select] [Button: Back to results] [Link: Fare rules] Generate 3 diverse candidate actions (explore different strategies): Action 1:""" # Model generates 3 options for tree expansion
AgentQ:エージェント軌跡へのDPO(詳細)
AgentQ (Putta et al. 2024) は、軌跡の結果から選好ペアを自動生成することで、 オフライン選好学習(DPO) と オンラインエージェント実行 を橋渡しします。
パイプライン :
-
ロールアウト :現在の方策\(N\)を使い、タスクごとに\(\pi_\theta\)本の軌跡を実行します。
-
評価 :実行ベースの報酬(バイナリの成功/失敗またはスカラー指標)で各軌跡を採点します。
-
ペア構築 :各タスクについて選好ペアを構築します:
\[ (\tau_w, \tau_l) \text{ where } r(\tau_w) > r(\tau_l) \]
同じタスクの軌跡では、報酬が最大のものをchosen、最小のものをrejectedとします。
-
DPO更新 :軌跡レベルの対数確率に対して標準DPO損失を適用します:
\[ \mathcal{L}_{\text{AgentQ}} = -\log \sigma!\left(\beta \left[\log\frac{\pi_\theta(\tau_w)}{\pi_{\text{ref}}(\tau_w)} - \log\frac{\pi_\theta(\tau_l)}{\pi_{\text{ref}}(\tau_l)}\right]\right) \]
- 反復 :更新された\(\pi_\theta\)が、次のラウンドで新しい(より良い)軌跡を生成します。
主要な設計上の選択 :
-
MCTS誘導探索 :ロールアウト段階でLATSを使い、多様で高品質な軌跡(より良い学習データ)を生成します。
-
ステップレベルDPO :完全な軌跡を比較する代わりに、アクションレベルで比較します。同じプレフィックスから、どの次のアクションが成功につながるかを問います。
-
自己対戦による改善 :各DPO反復がより良い方策を生み、その方策がより良い軌跡を生成し、それがより良い学習ペアを生むという好循環です。
結果 :WebShopでは、AgentQは3回のDPO反復でベース方策に対する成功率を絶対値で50%\(\rightarrow\)82%改善します。
Voyager:スキルライブラリによる生涯学習(詳細)
Voyager (G. Wang et al. 2023) は 構成的なスキル蓄積 を導入します。エージェントが高レベルのアクションとして使える、再利用可能なコード関数のライブラリを成長させます。
アーキテクチャ :
-
自動カリキュラム :LLMがエージェントの現在のスキル一覧に基づいて、段階的に難しいタスクを提案します:「木を伐採して板材を作れるようになりました。次の挑戦は作業台を作ることです。」
-
スキル生成 :各タスクについて、エージェントは解決用のJavaScript関数(実行可能コード)を書きます:
\[ \text{skill}_i = \text{LLM}(\text{task}_i, \text{environment_docs}, \text{error_feedback}) \]
-
検証 :環境内でコードを実行します。成功すればスキルライブラリに追加し、失敗すればエラーフィードバックを使って(最大5回)反復します。
-
スキルライブラリ (ベクトルDB):検証済みスキルを次の情報とともに保存します:
-
関数シグネチャ + docstring(検索用)
-
タスク記述の埋め込み(セマンティック検索用)
-
依存関係(呼び出す他のスキル)
-
-
検索 + 合成 :新しいタスクでは、最も関連する上位\(k\)件のスキルを取得して合成します:
\[ \text{solution} = \text{LLM}(\text{new_task}, \text{retrieve}(\text{skill_library}, k{=}5)) \]
主要な洞察 :スキルは 構成的 です。単純で検証済みの関数を組み合わせることで、複雑な振る舞いが生まれます。ライブラリは永続的なのでエージェントは決して忘れず、検証済みスキルだけを追加するため単調に改善します。
Note
Voyager:カリキュラムとスキル生成プロンプト
# === AUTOMATIC CURRICULUM PROMPT === CURRICULUM_PROMPT = """You are a curriculum designer for an AI agent. Agent's current skill inventory: - mine_wood(): Mines nearby oak/birch trees - craft_planks(): Converts logs to planks - craft_sticks(): Converts planks to sticks - mine_stone(): Mines stone with wooden pickaxe Propose the next task that: 1. Builds on existing skills (reachable from current abilities) 2. Introduces exactly ONE new concept or challenge 3. Is concrete and verifiable (clear success condition) Next task proposal:""" # Output: "Craft a furnace (requires 8 cobblestone blocks arranged # in a square). You already know mine_stone()." # === SKILL GENERATION PROMPT === SKILL_GEN_PROMPT = """Write a JavaScript function to accomplish this task in Minecraft. Use the bot API (bot.dig, bot.craft, bot.equip, etc.) Task: Smelt 5 iron ingots using a furnace. Prerequisites available: mine_stone(), craft_furnace(), mine_iron_ore() Error from previous attempt: "Cannot smelt without fuel in furnace" Write the corrected function: async function smeltIronIngots(bot, count=5) {"""
RLEF:実行フィードバックからのRL(詳細)
RLEF (Le et al. 2023) は、 決定論的な実行ベース報酬によるオンラインRL をコード生成エージェントに適用し、エージェント学習の最も単純で効果的なパラダイムを確立します。
学習ループ :
-
タスクのサンプリング :学習セットからテストケース\((x, \text{tests})\)を持つコーディング問題を取り出します。
-
生成 :現在の方策\(\pi_\theta\)を使い、エージェントが解決軌跡(ファイル読み込み、コード記述、テスト実行)を生成します。
-
実行 :サンドボックス環境でテストスイートを実行します。報酬は次のとおりです:
\[ r = \frac{\text{# tests passed}}{\text{# total tests}} \in [0, 1] \]
-
更新 :報酬信号として\(r\)を使い、GRPO/PPOを適用します。
-
反復 :新しいタスクで何千回も反復します。
実行フィードバックがRLに適している理由 :
-
ノイズゼロ :人間の選好と異なり、テスト結果は決定論的です。同じコード\(\rightarrow\)同じ報酬が毎回得られるため、RL学習を不安定化する報酬ノイズを排除できます。
-
無限のスケール :プログラムで無制限のタスク(ランダムアルゴリズム、API統合テスト、データ変換)を生成できます。
-
報酬ハッキングなし :学習済み報酬モデルと異なり、テストスイートは(テストが適切に書かれていれば)「だます」ことができません。エージェントは実際に問題を解く必要があります。
-
密な信号 :部分的なテスト通過\(r = 0.6\)は、バイナリの成功/失敗より豊かな勾配を提供します。
OpenHands/SWE-Agent:ソフトウェア工学のためのGRPO
OpenHands (X. Wang et al. 2024) とSWE-Agent (J. Yang et al. 2024) は、コードを読み、パッチを書き、テストスイートを実行してGitHub issueを自律的に解決するエージェントの学習にGRPOを適用します。
学習の詳細 :
-
環境 :完全なリポジトリ、テストスイート、開発者ツール(git、grep、lint)を備えたDockerコンテナ。
-
アクション空間 :Bashコマンド、ファイル編集、git操作、テスト実行。
-
軌跡長 :GitHub issueの解決では通常15–50アクション。
-
報酬 :バイナリ。生成したパッチがissueの回帰テストに合格するかどうか。
-
グループサイズ :GRPO正規化のため、issueあたり\(N = 8\)–\(16\)本の軌跡。
-
カリキュラム :「good first issue」とラベル付けされたissueから始め、複雑な複数ファイルのリファクタリングへ進みます。
最先端の結果 :SWE-bench Verifiedでは、RL学習後に解決率が30%\(\rightarrow\)55%(SFTのみのベースラインとの比較)になります。
Note
OpenHands/SWE-Agent:システムプロンプト
SYSTEM = """You are an autonomous software engineer. You are given a GitHub issue to resolve. You have access to the full repository in /workspace/ and can execute any bash command. AVAILABLE COMMANDS: - bash(command): Execute a shell command - edit(file, start_line, end_line, new_content): Edit a file - search(pattern, path): Search for text in files - submit(): Submit your patch when done WORKFLOW: 1. Read the issue carefully and understand the expected behavior 2. Explore the codebase to find relevant files 3. Reproduce the bug (write/run a test that fails) 4. Implement the fix 5. Verify the fix (run the test again - must pass) 6. Run the full test suite to check for regressions 7. Submit when all tests pass RULES: - Do NOT modify test files unless the issue explicitly asks for it - Prefer minimal, targeted changes over large refactors - Always verify your fix before submitting""" USER = """GitHub Issue #4521: `DataFrame.merge()` silently drops rows when `on` column contains NaN values. Expected: NaN keys should be preserved (matched with other NaN rows) Actual: Rows with NaN keys are dropped entirely Repository: /workspace/pandas-dev/pandas/"""
Tip
未来:RL + エージェント
この分野は、複数ステップのエージェント軌跡に実行ベース報酬を適用する、 実行時報酬によるオンラインRL というパターンへ収束しつつあります。主な傾向は次のとおりです:
コード実行やツール成功によるバイナリの成功/失敗報酬を使うGRPO/PPO
カリキュラム学習:簡単なタスクから始め、段階的に難易度を上げる
軌跡レベル最適化(トークンレベルではない) — 複数ステップ系列の最後だけで報酬を与える
ハイブリッド手法:まれなタスクには検索(非パラメトリック)、一般的なタスクにはRL(パラメトリック)を使う
スケーリング則:推論時の計算(検索/再試行)を増やす方が、学習時の計算を増やすより有効なことが多い
用例:生産性コパイロットのエージェントRL
この節では、文書、スプレッドシート、プレゼンテーション、メール、メッセージング、クラウドストレージを横断して動作するLLMベースのコパイロットを、エージェントRLで学習するための完全な設計図を示します。
アーキテクチャの概要
生産性コパイロットの形式的MDP定義
生産性コパイロットの環境は、部分観測マルコフ決定過程(POMDP)として形式化されます:
\[ \boxed{\mathcal{M} = \langle \mathcal{S}, \mathcal{A}, \mathcal{T}, \mathcal{R}, \Omega, \mathcal{O}, \gamma \rangle} \]
-
\(\mathcal{S}\): 状態空間 。完全なワークスペース環境の状態、すなわち文書内容、メールスレッド、カレンダーイベント、ファイルシステム、ユーザー権限です。完全には観測できません。エージェントが見られるのはAPIクエリが返すものだけです。
-
\(\mathcal{A}\): アクション空間 。構造化されたAPI呼び出し(後述)。各アクションは、対象アプリ、操作、パラメータを指定するJSONオブジェクトです。
-
\(\mathcal{T}\): 遷移関数 。ほとんどの操作では決定論的(文書への書き込み\(\rightarrow\)文書が更新)ですが、ネットワーク依存のアクション(メール配送時間、Teamsの利用可能性)では確率的です。
-
\(\mathcal{R}\): 報酬関数 。複数の成分からなる(報酬設計の節を参照)。
-
\(\Omega\): 観測空間 。API応答、描画された文書ビュー、エラーメッセージ。
-
\(\mathcal{O}\): 観測関数 。状態を観測へ写像します(API応答の整形、コンテキストウィンドウ制限による切り詰め)。
-
\(\gamma = 0.99\):割引率(長いホライズンで、通常は10–50ステップ)。
Note
具体例:「先週のProject Alphaのメールを要約し、ステータススライドを作成する」
以下では、POMDPの各形式要素を具体的な実現へ対応付けながら、エピソード全体を追跡します。
ユーザー要求 :「先週のProject Alphaのメールから重要な決定事項を要約し、チームのデッキにステータス更新スライドを追加してください。」
記号 具体的な実現 \(s_0\) 真の状態:過去7日間に「Project Alpha」に言及したメールが受信箱に47件。SharePointに「Q3_Status.pptx」という12枚のPowerPointファイルがあり、ユーザーには編集権限がある。 \(o_0\) エージェントが観測するもの:ユーザー要求のテキスト + システムプロンプト。エージェントはまだメール件数やデッキ内容を知らない。 \(a_1\) outlook.search({query: "Project Alpha", last_7_days: true}) \(\mathcal{T}(s_0, a_1)\) \(s_1\):システムが一致するメール47件を取得(決定論的)。 \(o_1\) APIが上位10件のメール件名 + 送信者 + タイムスタンプを返す(コンテキスト制限により切り詰められる — アクション内の\(\mathcal{O}\))。 \(a_2\) outlook.read({ids: [top_5_thread_ids]}) — エージェントが最も関連するスレッドを選ぶ。 \(o_2\) 5つのメールスレッドの本文全体(\(\sim\)8Kトークン、\(\mathcal{O}\)による切り詰め後)。 \(a_3\) 内部推論アクション:エージェントが重要な決定事項を統合する:(1) 締切が11月15日に変更、(2) 予算承認、(3) ベンダー決定。 \(a_4\) sharepoint.read({file: "Q3_Status.pptx", slides: "last"}) — 現在の最後のスライドを確認。 \(o_4\) 最後のスライドは「Q2 Summary」(12枚目)。エージェントは13枚目を追加する。 \(a_5\) powerpoint.add_slide({file: "Q3_Status.pptx", position: 13, layout: "Title and Content", title: "Project Alpha --- Week 42 Status", content: "Key decisions: 1) Deadline: Nov 15..."}) \(\mathcal{T}(s_4, a_5)\) \(s_5\):デッキにスライドを追加(決定論的)。 \(o_5\) APIが {success: true, slide_id: 13} を返す。 \(R(s_5)\) 報酬の成分: +0.4 タスク完了(スライド作成)、 +0.3 情報品質(正しい決定事項の抽出)、 +0.2 形式準拠(適切なレイアウト)、 +0.05 効率(5アクション、エラーなし)、 -0.0 安全性ペナルティ。 合計:0.95 。 図から分かるPOMDPの要点 :
部分観測 :\(t=0\)では、エージェントはメール件数やデッキ内容を知らず、状態を発見するためにクエリを実行する必要がある。
観測関数\(\mathcal{O}\) :コンテキストウィンドウ制限のため、APIは47件中上位10件だけを返す。エージェントが見るのは真の状態の射影です。
確率的遷移 :代わりに teams.send_message() を試した場合、配送時間は不確実(受信者がオンライン/オフライン)になる。
複数ステップ計画 :エージェントは2つのアプリケーションにまたがって5つのアクションを連鎖させ、メール要約とスライド内容の整合性を保つ必要がある。
割引\(\gamma=0.99\) :5ステップなら割引は小さい(\(0.99^5 = 0.95\))ですが、50ステップのタスクでは重要になり、効率的な解決を促す。
アクション空間の設計
アクション空間は 構造化され、型安全で、合成可能 でなければなりません:
Note
生産性コパイロットのアクションスキーマ
{ "action_type": "api_call", "target_app": "outlook | excel | word | powerpoint | teams | sharepoint", "operation": "read | write | search | create | delete | modify", "parameters": { "endpoint": "/me/messages?$filter=subject eq 'Project X'", "body": { ... }, // For write operations "options": { "top": 10 } // Pagination, filtering }, "reasoning": "I need to find relevant emails before summarizing" }
アプリケーション別アクション分類 :
| アプリ | 複雑さ | 主要アクション |
|---|---|---|
| Outlook | Medium | search, read, draft, send, move, flag, create_rule |
| Excel | High | read_range, write_range, insert_formula, create_chart, pivot_table, run_macro |
| Word | Medium | read_paragraphs, insert_text, format_section, find_replace, insert_table |
| PowerPoint | Medium | add_slide, insert_shape, set_text, set_layout, add_image, apply_theme |
| Teams | Low | send_message, create_meeting, search_chat, add_members, post_to_channel |
| SharePoint | Medium | list_files, upload, download, search, create_page, set_permissions |
状態表現
各ステップにおけるエージェントの観測(コンテキストウィンドウ):
\[ o_t = [\text{system_prompt};; \text{user_intent};; \text{tool_history}_{1:t-1};; \text{current_result}_t] \]
コンテキスト予算の管理 (128Kウィンドウでは重要):
-
システムプロンプト :2Kトークン(能力、安全規則、出力形式)
-
ユーザー意図 + 会話 :4Kトークン
-
ツール履歴 (スライディングウィンドウ):直近8–12個のアクション + 観測。古いものは要約。合計最大80Kトークン。
-
現在の観測 :最大32Kトークン(大きなスプレッドシート、メールスレッド)
-
予約 :エージェントの推論 + 次のアクション生成用に10Kトークン
状態圧縮戦略 :
-
選択的包含 :現在のサブゴールに関連するAPI応答だけを含める(補助的な「関連性スコアラー」を使う)。
-
構造化要約 :大きなスプレッドシートは全データではなく、スキーマ + サンプル行として表現する。
-
階層的メモリ :完全な軌跡を外部に保存し、圧縮した要約をコンテキストへ注入する。
報酬設計:多目的信号
生産性コパイロットの報酬関数は、複数の目的のバランスを取る必要があります:
\[ \boxed{R(\tau) = \alpha_1 R_{\text{task}} + \alpha_2 R_{\text{quality}} + \alpha_3 R_{\text{efficiency}} + \alpha_4 R_{\text{safety}} + \alpha_5 R_{\text{user}}} \]
| 成分 | 重み | 信号タイプ | 定義 |
|---|---|---|---|
| \(R_{\text{task}}\) | 0.40 | バイナリ/スカラー | タスクが正常に完了(メール送信、文書作成、式が正しい) |
| \(R_{\text{quality}}\) | 0.25 | LLM判定 | 出力品質:書式、明瞭さ、内容の正確さ |
| \(R_{\text{efficiency}}\) | 0.15 | スカラー | 過剰なステップへのペナルティ:\(-0.02 \times (\text{num_steps} - \text{optimal_steps})\) |
| \(R_{\text{safety}}\) | 0.15 | バイナリ | 安全でないアクションがない(確認なしの削除、誤った宛先への送信、権限違反)。違反があれば\(R_{\text{safety}} = 0\)。 |
| \(R_{\text{user}}\) | 0.05 | 疎 | 利用可能な場合の明示的なユーザーフィードバック(サムズアップ/ダウン) |
生産性コパイロット学習における報酬成分
中間報酬(密な信号) :
-
成功したAPI呼び出し(200応答):+0.05
-
正しい情報取得(後続利用で検証):+0.10
-
エラーから適切に復旧(修正したパラメータで再試行):+0.08
-
APIエラー(4xx/5xx):–0.03
-
同一アクションの反復(ループ検出):–0.10
-
意図が本当に曖昧なときに確認質問をする:+0.05
エンドツーエンド学習パイプライン
Important
生産性コパイロットのRL学習パイプライン
フェーズ1:教師ありファインチューニング(基盤)
生産性タスクの人間実演軌跡を5万–20万件収集(テレメトリ、アノテータ、または合成生成)。
ReAct形式の(命令、軌跡)ペアでベースLLMをSFTする。
検証:保留タスクでエージェントが40–60%のタスク完了率を達成するはず。
フェーズ2:シミュレーション環境の構築
モックAPIエンドポイント、合成メールボックス、文書、カレンダーを備えた サンドボックス環境 を構築する。
各「ユーザー」に現実的なプロファイルを持たせる:500通以上のメール、20件以上の文書、カレンダーイベント、Teamsチャンネル。
タスク生成器:多様な命令–検証ペアを生成する:「AliceからのQ4予算に関するメールをすべてFinanceフォルダへ移動」 + 検証関数。
フェーズ3:オンラインRL学習(GRPO)
タスクバッチをサンプリング(反復あたり256タスク)。
サンドボックス環境で方策\(N=8\)を使い、タスクごとに\(\pi_\theta\)本の軌跡を生成する。
軌跡を実行し、検証関数から報酬を収集する。
GRPOアドバンテージを計算(タスクごとに8軌跡をグループ正規化)。
クリップ付き目的関数 + SFTモデルに対するKLペナルティで方策を更新する。
500反復ごとに保留ベンチマーク(200タスク、5難易度)で評価する。
フェーズ4:Human-in-the-Loopの改善
社内のドッグフードユーザー(1000人以上、2週間)へ展開する。
サムズアップ/ダウン信号 + 自由記述の修正を収集する。
A/B展開(旧方策対新方策)からDPO選好ペアを構築する。
人間の選好に対してDPOファインチューニングを1–2ラウンド適用する。
シミュレーション環境のアーキテクチャ
Note
サンドボックス環境(簡略版)
class ProductivityEnvironment: def __init__(self, user_profile: UserProfile): self.mailbox = SyntheticMailbox(user_profile.emails) self.drive = SyntheticOneDrive(user_profile.files) self.calendar = SyntheticCalendar(user_profile.events) self.teams = SyntheticTeams(user_profile.channels) self.step_count = 0 self.max_steps = 50 def step(self, action: dict) -> Tuple[Observation, float, bool]: """Execute action, return (observation, reward, done).""" self.step_count += 1 # Route to appropriate app handler handler = self.get_handler(action["target_app"]) try: result = handler.execute(action["operation"], action["parameters"]) obs = Observation(status=200, body=result) reward = 0.05 # Successful API call except APIError as e: obs = Observation(status=e.code, body=str(e)) reward = -0.03 # Check terminal condition done = self.step_count >= self.max_steps return obs, reward, done def evaluate(self, task: Task) -> float: """Check if task objective is achieved (terminal reward).""" return task.verification_fn(self) # 0.0 or 1.0
タスクカリキュラムの設計
学習の有効性は、タスク難易度を段階的に上げることに大きく左右されます:
| レベル | ステップ | アプリ | タスク例 |
|---|---|---|---|
| L1:単一ステップ | 1–2 | 1 | 「Bobからの最新メールを読んで」、「セルA1には何がある?」 |
| L2:単一アプリ | 3–5 | 1 | 「要点をまとめた予算メールへの返信を下書きして」 |
| L3:複数ステップ | 5–10 | 1 | 「売上データからピボットテーブルを作り、上位者を太字にする」 |
| L4:アプリ横断 | 5–15 | 2–3 | 「Q4予算メールを探し、数値を抽出し、新しいExcelシートに入れる」 |
| L5:複雑なワークフロー | 10–30 | 3+ | 「週次レポートを作成:Excelから指標を取得し、メール更新を要約し、PowerPointスライドを作成してTeamsで共有する」 |
生産性コパイロットのカリキュラムレベル
カリキュラム戦略 :
-
学習初期はL1–L2タスクを80%、L3を20%から始める。
-
現在のレベルで成功率が70%を超えたら次のレベルへ進む。
-
壊滅的忘却を防ぐため、常に簡単なタスクを10–20%維持する。
-
収束後の最終混合:L1 10%、L2 15%、L3 25%、L4 30%、L5 20%。
安全性とガードレール
Important
生産性コパイロットの安全性フレームワーク
ハード制約 (アクションを即時拒否、報酬 = –1.0):
ユーザー確認なしで外部の宛先へメール/メッセージを送信する
ファイル/メールを恒久的に削除する(ソフト削除のみ許可)
共有リソースの権限を変更する
許可された範囲を超えて他ユーザーのメールボックスやファイルへアクセスする
1バッチで100件を超えるアイテムにアクションを実行する(大量削除/移動事故を防止)
ソフト制約 (報酬にペナルティ、エージェントは避けるべき):
ユーザーにプレビューを見せず下書きを送信:–0.2
意図を先に述べず不可逆な変更を行う:–0.15
機密ラベル(confidential、attorney-client)へアクセス:–0.3
明示的な委任なしに「代理送信」を使う:–0.25
確認プロトコル :「高インパクト」(送信、削除、外部共有)に分類されるアクションでは、エージェントは次を行う必要があります:
意図するアクションを自然言語で述べる
送信/変更される内容のプレビューを表示する
実行前に明示的なユーザー確認を待つ
これは環境レベル(サンドボックスが未確認の高インパクトアクションを拒否)と報酬関数(確認を省略した場合のペナルティ)の両方で強制されます。
複数アプリワークフローでの信用割当
主な課題は、20ステップのアプリ横断ワークフローで、どのステップが成功または失敗に寄与したかを特定することです。
アプローチ:階層的な報酬分解
-
サブゴール検出 :ユーザー命令を検証可能なサブゴールに分解する:
-
「Q4予算メールを探す」\(\rightarrow\)サブゴール1(検証:関連メールを取得)
-
「数値を抽出する」\(\rightarrow\)サブゴール2(検証:正しい値を解析)
-
「Excelシートを作る」\(\rightarrow\)サブゴール3(検証:シートが正しいデータで存在)
-
-
サブゴール報酬 :各サブゴールの完了時に中間報酬\(r = +0.2\)を割り当てる。
-
軌跡の分割 :最終タスクが失敗したら、最初に失敗したサブゴールを特定する。そのサブゴールの範囲内のアクションだけに負の報酬を適用する。
-
反実仮想推定 :「この特定のアクションが違っていたらタスクは成功したか?」を価値関数で推定する。
\[ R_{\text{step}}(t) = \underbrace{R_{\text{sub-goal}}(t)}_{\text{did current sub-goal succeed?}} + \underbrace{\gamma^{T-t} R_{\text{terminal}}}_{\text{discounted final reward}} + \underbrace{r_{\text{intermediate}}(t)}_{\text{per-step API success/failure}} \]
スケーリングとインフラ
計算資源要件 (70Bパラメータモデルの推定):
| コンポーネント | リソース | 注記 |
|---|---|---|
| 方策モデル(70B) | 8\(\times\) A100 80GB(TP=8) | BF16、軌跡を生成 |
| 参照モデル(70B) | 8\(\times\) A100 80GB(TP=8) | 凍結、KL計算用 |
| 環境ワーカー | 128 CPUワーカー | 各ワーカーがサンドボックスインスタンスを実行 |
| 報酬モデル/判定器 | 4\(\times\) A100(LLM判定器の場合) | 実行ベース報酬ならゼロ |
| 学習(GRPO更新) | 16\(\times\) A100(FSDP) | 軌跡バッチに対する勾配蓄積 |
| 合計 | A100 GPU 40台 + CPU 128台 | 5,000 GPU時間で完全な学習実行 |
スループット最適化 :
-
非同期ロールアウト :軌跡生成と勾配更新を分離する。前のバッチを学習している間も継続的に生成する。
-
バッチ化環境 :128個のサンドボックス環境を並列実行し、それぞれ異なるタスクを処理する。
-
KVキャッシュ共有 :タスクあたり\(N=8\)本の軌跡は同じプロンプトプレフィックスを共有するため、プレフィックスキャッシュで重複計算を避ける。
-
選択的バックプロパゲーション :アクショントークン(観測/システムプロンプトではない)だけで勾配を計算する。バックワードFLOPSを40–60%削減する。
評価フレームワーク
| 指標 | 目標 | 測定方法 |
|---|---|---|
| タスク完了率 | \(>85%\)(L1–L3)、\(>60%\)(L4–L5) | サンドボックスでの自動検証 |
| 安全性違反率 | \(<0.1%\) | 1000タスクあたりのハード制約違反数 |
| 完了までの平均ステップ数 | 最適値の\(1.5\times\)以内 | 既知の最短成功軌跡と比較 |
| ユーザー満足度(ドッグフード) | \(>4.2/5.0\) | 社内ユーザーのタスク後アンケート |
| アプリ横断成功率 | \(>55%\) | 2つ以上のアプリを必要とするタスク |
| 復旧率 | \(>70%\) | 失敗したAPI呼び出しのうち、エージェントが再試行に成功した割合 |
| レイテンシ(最初のアクションまで) | \(<3\)秒未満 | モデル推論 + アクション計画時間 |
生産性コパイロットの評価軸
ベンチマークスイート (提案):
-
ProdBench-Easy (200タスク):単一アプリ、1–3ステップ。ベースライン確立。
-
ProdBench-Hard (200タスク):アプリ横断ワークフロー、10–30ステップ。エンドツーエンド能力。
-
ProdBench-Safety (100タスク):安全でないアクションを誘発しようとする敵対的プロンプト。\(<0.1%\)未満の違反率を維持する必要がある。
-
ProdBench-Robustness (100タスク):曖昧な命令、注入されたAPIエラー、権限不足を含むタスク。段階的な機能縮退を検証する。
本番展開から得られる教訓
Tip
生産性エージェントRLの実践的洞察
SFT品質が下限 :RLはSFTが提供するものを改善できるだけです。SFTモデルが有効なGraph API呼び出しを整形できなければ、RLがそれを発見することはありません。フェーズ1のデータ品質に大きく投資してください。
報酬ハッキングは避けられない :エージェントは必ず近道を見つけます。よくある例:
スプレッドシートタスクを「完了」するため空のExcelファイルを作成する(存在チェックを通過)
実際にはアクションを実行せず「Done」と返信する
曖昧な検証関数を悪用する
対策 :多層検証(形式 + 内容 + 意味的な正しさ)。
APIレート制限が重要 :本番ではワークスペースAPIにスロットリング(429応答)があります。並列リクエストを大量送信する方策を避けるため、現実的なレート制限で学習します。
コンテキストウィンドウがボトルネック :豊富なAPI応答を含む20ステップの軌跡は、容易に80K以上のトークンを消費します。技法は観測要約、履歴の選択、階層的コンテキスト管理です。
ユーザー意図は曖昧なことが多い :「受信箱を整理して」はユーザーによって意味が異なります。不確実性が高いときに確認質問をするよう学習させます(適切な確認には報酬、過剰な確認にはペナルティ)。
簡単に始め、段階的にスケールする :Outlookだけのタスク(量とテレメトリデータが最も多い)から始め、Excel、アプリ横断へ広げます。各アプリには固有の失敗モードがあります。
完全な学習レシピ
| パラメータ | 値 | 根拠 |
|---|---|---|
| ベースモデル | 70B Llama/Mistral | 複雑な複数ステップ推論に十分な容量 |
| RLアルゴリズム | GRPO | クリティック不要。長い軌跡に対してメモリ効率がよい |
| グループサイズ\(N\) | 8 | 分散削減と計算コストのバランス |
| クリップ\(\epsilon\) | 0.1 | 長い軌跡に敏感なため標準(0.2)より厳しい |
| KL係数\(\beta\) | 0.04 | SFT方策への中程度の制約 |
| 学習率 | \(5 \times 10^{-7}\) | 保守的。エージェントタスクは大きな更新に敏感 |
| バッチサイズ | 256タスク\(\times\)8軌跡 = 2048 | 安定したGRPO正規化のための大きなバッチ |
| 最大軌跡長 | 50ステップ | 生産性タスクの95%をカバー |
| コンテキストウィンドウ | 128Kトークン | 長いアプリ横断ワークフローに必要 |
| 学習反復数 | 3000–5000 | 評価指標を監視し、安全性が悪化したら早期停止 |
| カリキュラムウォームアップ | 500反復(L1–L2のみ) | 複雑なタスクの前に基本API利用を確立 |
生産性コパイロットRL学習の推奨ハイパーパラメータ
用例:研究エージェントをゼロから構築する
この用例では、本稿全体で説明した技法を使い、仮説を立て、文献を検索し、データを分析し、コードを書き、実験を実行し、最終報告書を作成できる完全自律の 研究エージェント (LLM)を構築する方法を示します。
問題定義
Important
研究エージェントの要件
入力 :研究上の問い(例:「7BモデルのGRPO収束に対する学習率ウォームアップ期間の効果は何か?」)
出力 :方法論、実験、結果、結論を含む完全な研究報告書。
必要な能力 :
文献検索 :arXiv、Semantic Scholarを検索し、関連論文を見つける
仮説生成 :背景知識から検証可能な仮説を立てる
実験設計 :適切な統制を備えた学習スクリプトを書く
コード実行 :実験を実行し、指標を収集する
データ分析 :ログを解析し、統計量を計算し、プロットを生成する
科学的執筆 :発見を一貫した報告書に統合する
自己修正 :失敗した実験を検出し、パラメータを変更して再試行する
MDPの定式化
Important
研究エージェントのMDP
状態\(s_t\):システムプロンプト + 研究上の問い + アクション/観測の完全な履歴(ツール出力、コード結果、検索結果)。コンテキストウィンドウ:128Kトークン。
アクション\(a_t\):アクション空間からの構造化ツール呼び出し(後述) + 推論軌跡(CoT)。
遷移\(T(s_{t+1}\vert s_t, a_t)\):決定論的。アクション + ツール出力をコンテキストへ追加する。
報酬\(R\):報告書の品質に基づく疎な終端報酬(下記の報酬設計を参照)。
ホライズン :20–100ステップ(典型的な研究軌跡)。
割引\(\gamma = 1.0\)(エピソード型。有限タスクでは割引なし)。
アクション空間
| ツール | カテゴリ | 説明 |
|---|---|---|
| search_papers | 文献 | Semantic Scholar/arXivを検索。タイトル、要旨、引用を返す。 |
| read_paper | 文献 | 論文の全文または特定の節を取得。 |
| write_code | 実験 | ワークスペースにPython/学習スクリプトを書く。 |
| execute_code | 実験 | サンドボックス環境でスクリプトを実行。stdout/stderrを返す。 |
| read_file | 分析 | ログ、CSV、中間結果を読む。 |
| plot_data | 分析 | matplotlib/seabornの可視化を生成。 |
| compute_stats | 分析 | 統計検定(t検定、信頼区間)を実行。 |
| write_report | 出力 | 最終研究報告書の節(LaTeX/Markdown)を書く。 |
| think | 推論 | 内部推論ステップ(外部ツール呼び出しなし)。 |
| submit | 終端 | 最終報告書を提出。エピソードを終了する。 |
研究エージェントのツール/アクション空間
アーキテクチャ:モデルとインフラの選択
Important
アーキテクチャ決定 — 本稿の概念を適用
ベースモデル :Qwen-2.5 72B(強い推論 + コード)。QLoRAファインチューニング(\(r=32\)、全線形層)— LoRAの節を参照。
推論 :TP=4のvLLM、プレフィックスキャッシュを有効化(ロールアウト間でシステムプロンプトを共有)— vLLMの節を参照。
学習 :研究上の問いごとに\(N=4\)本の軌跡を使うGRPO — 価値モデルは不要(GRPOの節を参照)。
ハードウェア :\(\times\) H100ノード。QLoRAアダプタは48 GBに収まり、vLLM生成は残りの容量を使う。
コンテキスト管理 :Flash Attentionを使う128Kコンテキスト(Flash Attentionの節を参照)。コンテキストを超える軌跡にはスライディングウィンドウ要約を使う。
投機的デコーディング :長い研究軌跡の高速生成にEagleヘッドを使う(投機的デコーディングの節を参照)。
報酬設計
Important
多成分の研究報酬
終端報酬はエージェントがsubmitを呼び出した時点で計算します: \[ R = w_1 R_{\text{quality}} + w_2 R_{\text{correctness}} + w_3 R_{\text{novelty}} + w_4 R_{\text{efficiency}} + w_5 R_{\text{format}} \]
成分 重み 測定方法 \(R_{\text{quality}}\) 0.30 LLM-as-a-judge(GPT-4が明瞭さ、深さ、厳密さについて報告書を1–10で評価) \(R_{\text{correctness}}\) 0.30 コードがエラーなく実行され、結果が再現可能 \(R_{\text{novelty}}\) 0.15 LLM判定:報告書は論文の要約を超える洞察を提供しているか \(R_{\text{efficiency}}\) 0.15 少ないステップへのボーナス:\(R_{\text{eff}} = \max(0, 1 - \text{steps}/100)\) \(R_{\text{format}}\) 0.10 報告書に必須の全節(導入、方法、結果、結論)がある 中間シェーピング :コード実行の成功ごとに+0.1、ランタイムエラーごとに\(-\)0.05(最初から正しいコードを書くことを促す)。
Warning
報酬ハッキングのリスク
偽の結果 :エージェントが実験出力を捏造する。対策:実行ログと報告された数値を照合し、コードが実際に実行されたことを検証する。
浅い報告書 :エージェントが論文の要旨をそのままコピーする。対策:新規性報酬 + 盗用検出。
長さの悪用 :長い報告書ほど高得点になる。対策:効率報酬 + 長さペナルティ。
簡単な問い :エージェントが難しい研究上の問いを避ける。対策:難易度レベルを持つカリキュラム。
学習パイプライン
-
フェーズ1 — SFTウォームアップ (500ステップ):
-
ツールを使う人間の研究者による専門家研究軌跡を200件収集
-
完了部分のみのマスキング(ツール出力をマスク)を使い、成功軌跡でSFTする
-
これにより、ツール利用構文と基本的な研究ワークフローを教える
-
-
フェーズ2 — GRPO学習 (3000ステップ):
-
プロンプトプール:10領域(ML、NLP、CV、システムなど)にまたがる500の研究上の問い
-
問いごとに\(N=4\)本の完全な研究軌跡を生成
-
多成分報酬で各軌跡をスコアリング
-
GRPOアドバンテージ:\(\hat{A}_i = (R_i - \mu_G) / \sigma_G\)
-
クリップ付き目的関数(クリップ\(\epsilon=0.2\)、KL\(\beta=0.05\))で方策を更新
-
カリキュラム:「Xについての発見を要約」する簡単なタスクから始め、「Xについて実験を設計して実行」へ進む
-
-
フェーズ3 — 棄却サンプリング・ファインチューニング (200ステップ):
-
難しい問いごとに16軌跡を生成し、報酬上位2件を残す
-
これらの高品質軌跡でSFTする
-
最も難しい研究タスクでの性能を安定させる
-
具体例:完全なMDP軌跡
MDPの全要素が実際にどう連携するかを示すため、問いから提出までの研究エピソード全体を追跡し、各ステップに形式的なMDP要素を付記します。
Note
研究エージェントの軌跡 — 完全なMDPトレース
研究上の問い :「GRPOのグループサイズを\(N=16\)以上に増やすと、数学推論性能は向上するか?」
以下は、各ステップの状態\(s_t\)、アクション\(a_t\)、遷移\(T\)、観測\(o_t\)、累積報酬を明示した完全な軌跡です。
\(t\) 要素 説明 \(a_0\) think:「GRPOグループサイズのアブレーション論文を探し、統制実験を行う必要がある。」 \(o_0\) 内部推論(ツール出力なし)。シェーピング報酬:\(r_0 = 0\)。 1 \(s_1\) \(s_0 \oplus (a_0, o_0)\) — コンテキストに推論計画が含まれる。 \(a_1\) search_papers(query="GRPO group size ablation math") \(o_1\) 5本の論文を返す:[DAPO、DeepSeek-Math、Dr. GRPO、...]. \(r_1 = 0\)。 2 \(s_2\) \(s_1 \oplus (a_1, o_1)\) — コンテキストに検索結果。 \(a_2\) read_paper(id="DAPO", sections=["experiments", "ablations"]) \(o_2\) 主要な発見:「コードタスクでは\(N=16\)以上で限界効用が逓減。数学では未検証。」\(r_2 = 0\)。 3 \(s_3\) \(s_2 \oplus (a_2, o_2)\) — 文献上のギャップを特定。 \(a_3\) think:「DAPOはコードだけを検証した。Llama-3 8BでGSM8Kに対して\(N \in \{4, 8, 16, 32, 64\}\)をテストする。統制:総学習トークン数を同じにする。」 \(o_3\) 仮説を形成。\(r_3 = 0\)。 4 \(s_4\) \(s_3 \oplus (a_3, o_3)\) — コンテキストに実験計画。 \(a_4\) write_code("train_grpo_gsm8k.py") — group_size引数、適切なシード、GSM8Kテスト分割での評価を備えた85行のスクリプト。 \(o_4\) ファイルの書き込みに成功。\(r_4 = 0\)。 5 \(s_5\) \(s_4 \oplus (a_4, o_4)\) — コードがワークスペースで利用可能。 \(a_5\) execute_code("python train_grpo_gsm8k.py --N 4 --seed 42") \(o_5\) Accuracy: 67.2% (CI: 65.1--69.3)。\(r_5 = +0.1\)(実行成功)。 6–8 \(a_{6\text{--}8}\) \(N=8, 16, 32\)、\(\to\)、\(r_t = +0.1\)を実行undefined結果:71.4%、74.8%、75.9%。各undefined。 9 \(s_9\) 4つのデータ点をコンテキストに含む。 \(a_9\) execute_code("python train_grpo_gsm8k.py --N 64 --seed 42") \(o_9\) Accuracy: 75.6% (CI: 73.4--77.8) — \(N=32\)からわずかに低下。\(r_9 = +0.1\)。 10 \(s_{10}\) 5つの結果を利用可能。 \(a_{10}\) compute_stats(test="paired_t_test", data=[67.2, 71.4, 74.8, 75.9, 75.6], groups=[4,8,16,32,64]) \(o_{10}\) \(p < 0.01\)について\(N=4\)、\(N=16\)について\(p = 0.72\)、\(N=32\)について\(N=64\)(有意ではない)。\(r_{10} = 0\)。 11 \(s_{11}\) 限界効用の逓減を示す統計的証拠。 \(a_{11}\) plot_data(x=[4,8,16,32,64], y=[67.2,71.4,74.8,75.9,75.6], xlabel="Group Size N", ylabel="GSM8K Accuracy (%)", title="GRPO Group Size vs Math Reasoning") \(o_{11}\) プロットをresults/group_size_curve.pngに保存。\(r_{11} = 0\)。 12 \(s_{12}\) 完全な証拠を収集。 \(a_{12}\) write_report:4節の報告書(導入、方法、図付き結果、結論:「\(N=16\)が最適。\(N=64\)は改善せず、計算量を4\(\times\)増やす」)。 \(o_{12}\) 報告書を書き込み(2,100語)。\(r_{12} = 0\)。 13 \(a_{13}\) submit — エピソード終了。 \(R_{\text{terminal}}\) LLM判定スコア:品質=8/10、コード正解、新規性あり(DAPOを数学へ拡張)、13ステップ、全節あり。 終端報酬の計算 : \[ R = \underbrace{0.30 \times \tfrac{8}{10}}_{\text{quality}} + \underbrace{0.30 \times 1.0}_{\text{correct}} + \underbrace{0.15 \times \tfrac{7}{10}}_{\text{novelty}} + \underbrace{0.15 \times (1 - \tfrac{13}{100})}_{\text{efficiency}} + \underbrace{0.10 \times 1.0}_{\text{format}} \\ = 0.24 + 0.30 + 0.105 + 0.13 + 0.10 = \mathbf{0.875} \]
中間シェーピングの合計 :\(5 \times (+0.1) = +0.5\)(コード実行成功5回)。
GRPOの文脈 :この軌跡は\(N=4\)グループで最高得点(他は0.61、0.72、0.53)でした。GRPOアドバンテージ: \[ \hat{A} = \frac{0.875 - \bar{R}}{\sigma_R} = \frac{0.875 - 0.684}{0.129} = +1.48 \quad \text{(strongly reinforced)} \]
図から分かるMDPの主要な性質 :
決定論的\(T\) :各ツール呼び出しは予測可能な状態拡張\(s_{t+1} = s_t \oplus (a_t, o_t)\)を生成する。
疎な終端報酬 :本当の品質信号はsubmit時にだけ得られ、中間シェーピングは小さい。
長いホライズン :\(\gamma = 1.0\)ステップ、undefined(エピソードタスクでは割引なし)。
自己修正の機会 :ステップ9で\(N=64\)が改善しないことを観測し、都合の良い結果だけを選ぶのではなく結論を調整する。
アクションの多様性 :推論(think)、情報収集(search、read)、実行(write_code、execute)、分析(compute_stats、plot)、出力(write_report、submit)を混ぜる。
主要な設計決定とトレードオフ
| 決定 | 本稿の節 | 根拠 |
|---|---|---|
| QLoRA(\(r=32\)) | LoRAの節 | 72Bモデル。完全ファインチューニングは高コスト。複雑な推論のため\(r=32\)。 |
| GRPO(PPOではない) | GRPOの節 | 価値モデル不要。研究品質は軌跡途中で予測しにくい。 |
| 疎な終端報酬 | 報酬シェーピング | 研究品質は完了時だけ測定可能。中間シェーピングは最小限。 |
| \(N=4\)軌跡 | GRPOグループサイズ | バランス:ランキングには十分な多様性があり、コストも過大でない(100ステップの軌跡)。 |
| 128Kコンテキスト | Flash Attention | 論文内容 + コード + 結果を含む長い軌跡。 |
| vLLM + プレフィックスキャッシュ | vLLMの節 | システムプロンプト + 研究上の問いを4回のロールアウトで共有。 |
| カリキュラム学習 | エージェントRL | 簡単な文献レビューから始め、\(\to\)難しい実験の設計 + 実行へ進む。 |
| LLM-as-a-judge報酬 | 報酬モデル | 研究品質は主観的。LLM判定器はルールベースより柔軟。 |
研究エージェントの設計決定と、本稿の節との対応
評価
Important
研究エージェントの評価フレームワーク
保留された問い (50件):学習中には見ていない、多様な領域の研究上の問い。
人間評価 :領域専門家が品質、正確さ、実用性を1–5で評価する。
再現性 :報告書からエージェントのコードを再実行し、結果が一致することを確認する。
比較ベースライン :(1) ゼロショットGPT-4 + ツール(RL学習なし)、(2) SFTのみのエージェント、(3) 人間の研究者。
効率指標 :タスク難易度で正規化した完了までのステップ数。
期待される結果 (同様のエージェントRL研究に基づく):
エージェント 報告品質(1–5) 平均ステップ ゼロショットGPT-4 + ツール 2.8 25 SFTのみ 3.4 18 GRPO学習済み(本手法) 4.1 14 人間の研究者 4.5 N/A
教訓と失敗モード
Warning
研究エージェント学習でよくある失敗
無限ループ :エージェントが進展せず論文検索を繰り返す。対策:ステップ予算 + 同じ引数のツール呼び出し反復へのペナルティ。
コードデバッグの迷走 :1つのバグの修正に20ステップ以上費やす。対策:再試行を3回に制限し、3回失敗したら方針を捨てて別案を試す。
幻覚した引用 :論文タイトル/結果を捏造する。対策:ツール出力で全引用の存在を検証し、検証できない主張にペナルティを与える。
早すぎる提出 :長い軌跡へのペナルティを避けるため、不完全な報告書を提出する。対策:有効な提出とみなす最低品質閾値を\(R > 0.4\)にし、それ未満は失敗として扱う。
判定器への報酬ハッキング :LLM判定器では高得点だが科学的には浅い文章を生成するよう学習する。対策:判定モデルをローテーションし、定期的に人間評価を報酬へ含める。
LLMエージェントRLの最先端
LLMエージェント向けRL技法は、 オンポリシー方策勾配 と きめ細かな信用割当 を組み合わせます。エージェントはツール操作、APIクエリ、コード実行を含む複雑な複数ターン軌跡を実行するため、標準的な単一ターンのアラインメントアルゴリズムには大幅な変更が必要です。
主要ベースライン:エージェント向けGRPO
DeepSeek-R1 (DeepSeek-AI et al. 2025) によって広く知られるようになった GRPO (Shao et al. 2024) は、エージェント学習の標準になりつつあります。タスクごとに\(N\)本の完全な軌跡をサンプリングし、メモリを大量に消費するクリティックネットワークを不要にします。
タスクプロンプト\(q\)に対して、GRPOは\(N\)本のエージェント軌跡\(\{o_1, o_2, \dots, o_N\}\)から\(\pi_{\theta_{\text{old}}}\)をサンプリングします。各軌跡のアドバンテージは、グループに対する報酬を正規化して計算します:
\[ \boxed{A_i = \frac{r(o_i) - \frac{1}{N}\sum_{j=1}^N r(o_j)}{\text{std}(r(o_1), \dots, r(o_N))}} \]
KL正則化を伴うGRPO目的関数:
\[ L_{\text{GRPO}}(\theta) = \frac{1}{N} \sum_{i=1}^N \min!\left( \frac{\pi_\theta(o_i|q)}{\pi_{\theta_{\text{old}}}(o_i|q)} A_i,; \text{clip}!\left(\frac{\pi_\theta(o_i|q)}{\pi_{\theta_{\text{old}}}(o_i|q)}, 1{-}\epsilon, 1{+}\epsilon\right) A_i \right) - \beta, D_{\text{KL}}(\pi_\theta | \pi_{\text{ref}}) \]
Tip
GRPOがエージェント学習を支配する理由
クリティックなし :GPUメモリを50%節約します。エージェント軌跡がすでに32K–128Kトークンの巨大なコンテキストウィンドウを消費する場合に重要です。
自然な適合 :エージェントタスクは検証可能なバイナリ報酬(テスト成功/失敗、目標達成/未達)を持つことが多く、グループ相対正規化に最適です。
探索 :タスクごとに\(N\)本の多様な軌跡をサンプリングすることで、異なるツール利用戦略を自然に探索します。
インタラクティブエージェント向けPPO
PPO (Schulman et al. 2017) は、ステップレベルの価値推定が役立つ非常に確率的な環境で動作するエージェントにとって、依然として有用です。クリティックはステップごとのアドバンテージ信号を提供し、ツール出力が予測不能な場合に、より細かな信用割当を可能にします:
-
GAEによるステップレベルのアドバンテージ推定が、可変長のツール出力を扱う
-
価値ヘッドが「この時点からこの軌跡が成功する可能性はどれくらいか」を予測する
-
外部ツールが報酬分散を急増させる壊滅的エラーを返す場合に、より安定する
-
トレードオフ:\(2\times\)のメモリが必要(クリティック)が、より密な学習信号を提供する
きめ細かなターンレベルの信用割当
エージェントRLの核心的課題は 疎な報酬問題 です。エージェントが20個のツールアクションを実行して最後にユニットテストに失敗すると、終端報酬\(0\)が20個すべてのアクションを等しく罰します。現代的な解決策は次のとおりです:
Important
検証可能な報酬からの強化学習(RLVR)
決定論的な中間チェックポイント でモデルに報酬を与える:
Bashコマンドが正常にコンパイル\(\rightarrow\) +0.1
ブラウザエージェントが正しいHTML要素を対象\(\rightarrow\) +0.2
SQLクエリが空でない結果を返す\(\rightarrow\) +0.1
最終テストスイートが成功\(\rightarrow\) +1.0(終端)
中間報酬は最終ステップだけでなくすべてのステップに勾配信号を与えます。これにより、疎な報酬だけの場合と比べて学習が3–5\(\times\)高速化します。
Important
複数ターン軌跡の分割
フレームワークは複数ターンのエージェント実行を、個別の独立したステップに分割します。信用割当モジュールが、 軌跡を壊した正確なサブステップ を特定します:
成功したプレフィックス(ステップ1–\(k\))を再生
最初の分岐点(誤ったステップ\(k+1\))を特定
その特定のステップだけに負の報酬を割り当てる
正しいプレフィックスのステップには中立/正の報酬を割り当てる
これにより、すでに正しい振る舞いを損なわず、外科的な方策更新が可能になります。
代替パラダイム
-
反復STaR(Self-Taught Reasoner) (Zelikman et al. 2022):連続RLではなく、反復的なオフラインループを使います。軌跡を生成\(\rightarrow\)失敗をフィルタ\(\rightarrow\)成功例でSFT\(\rightarrow\)反復。スケールしやすく、RLの不安定性を避けます。各反復で推論能力をブートストラップします。
-
強化学習世界モデル学習(RWML) (Yu et al. 2026):報酬ハッキングに対抗するため、エージェントにアクションの意味的帰結を予測させます。環境状態がどう変わるかを正確に予測すると補助報酬を受け取ります(例:SQL実行前にデータベーステーブルの変化を予測)。表面的な報酬稼ぎではなく本当の理解を促します。
-
LATS(Language Agent Tree Search) (A. Zhou et al. 2024):エージェントのアクション系列にモンテカルロ木探索を適用します。各ステップで複数の候補アクションを展開し、結果をシミュレートし、木を通じて報酬をバックプロパゲーションします。RLの価値推定と推論時の計算スケーリングを組み合わせます。
主要手法の比較
| 手法 | 報酬の密度 | メモリコスト | 主な利点 |
|---|---|---|---|
| GRPO (Shao et al. 2024) | 系列/最終指標 | 低(クリティックなし) | GPUメモリを大幅削減。実装が簡単 |
| PPO (Schulman et al. 2017) | ステップごと(GAE) | 高(クリティック必要) | きめ細かな信用割当。ノイジーな環境で安定 |
| 反復STaR (Zelikman et al. 2022) | 疎(フィルタ済みバイナリ) | 最小(SFTのみ) | スケールしやすく、RL最適化の不安定性を回避 |
| RWML (Yu et al. 2026) | 密(予測型) | 中 | 世界モデルにより報酬ハッキングを緩和 |
| LATS (A. Zhou et al. 2024) | バックプロパゲーション | 高(木の展開) | タスクあたりの品質が最良。推論計算量に応じてスケール |
LLMエージェント向けRLパラダイムの比較
大規模推論モデルのためのRL
大規模推論モデルの登場は、現代AIにおける最も重要な進展の一つです。次トークン予測を最適化する標準的な言語モデル学習とは異なり、推論に焦点を当てたRLは、モデルに「答える前に考える」ことを教えます。つまり、推論時に追加の計算を割り当て、中間ステップを探索・検証・改良します。本節では、このパラダイムを支える手法、アーキテクチャ、スケーリング則を包括的かつ技術的に扱います。
動機と背景
なぜ推論には異なるRLアプローチが必要か
標準的なRLHF(節 1.3)は、完全な応答に対する単一のスカラー報酬を最適化します。しかし、数学、形式検証、競技プログラミング、科学的導出など、多段階の推論を必要とするタスクでは、この定式化は次の理由から不十分です。
-
疎な報酬 :数学の問題では20個の中間ステップが必要になることがありますが、結果に対する単一の報酬からは、誤りにつながった中間ステップへの勾配信号が得られません。
-
長いホライズン :推論チェーンは数百から数千トークンに及ぶことがあり、深刻な信用割当問題を生みます。
-
組合せ探索 :有効な推論経路の空間は指数関数的に大きく、モデルはこの空間を効率よく探索する方法を学習しなければなりません。
-
検証可能性 :主観的なテキスト品質とは異なり、数学的・論理的な正しさは客観的に検証できます。そのため、人手によるアノテーションなしで報酬を自動計算できます。
Important
重要な洞察:推論を探索問題として捉える
多段階の推論は、部分解の木に対する 探索問題 として定式化できます。木の各ノードは推論状態(思考の連鎖の接頭辞)、各エッジは推論ステップ(トークンまたは文)、葉は最終回答です。推論のためのRLは、モデルにこの木を効率よくたどる方法を教えます。つまり、有望な枝を探索し、行き止まりからバックトラックし、最も重要な場所に計算資源を割り当てます。
思考の連鎖:創発的な振る舞いと学習された能力
思考の連鎖(CoT)推論は、十分に大規模な言語モデルにおける創発的な能力として最初に観測されました (Wei et al. 2022): ステップごとの例をプロンプトで与えると、大規模モデル (通常は \(\geq\)100Bパラメータ)が精度を向上させる中間推論ステップを自発的に生成しました。 ここから、「CoTはスケールの創発的性質なのか、それとも明示的に学習できるのか」という根本的な問いが生まれました。
DeepSeek-R1や関連研究が示した答えは、 その両方 です。ただし、重要な違いがあります。
-
創発的CoT はインコンテキスト学習から生じ、大規模なベースモデルを必要とします。脆弱でプロンプトに敏感であり、頑健には汎化しません。
-
RLで学習したCoT は、プロンプトの形式によらず、生成プロセスの一部として推論チェーンを内在的に生成するモデルを作ります。こうしたチェーンはより長く探索的で、質的に異なる振る舞い(自己修正、バックトラック、検証)を示します。
Tip
「ひらめきの瞬間」現象(DeepSeek-AI et al. 2025)
推論モデルのRL学習中、DeepSeekの研究者は印象的な創発的振る舞いを観測しました。学習のある時点で、モデルは「待って、考え直させてください…」「実は、間違えたと思います…」のような表現を使いながら、チェーンの途中で初期のアプローチを自発的に再検討し始めたのです。この自己修正の振る舞いは明示的には学習されていませんでしたが、最終回答の精度を最大化するというRLの目的だけから生じました。これは、RLが難しい問題の解決に実用的なメタ認知戦略を発見できることを示唆します。
テスト時計算量のスケーリング則
推論モデル研究の動機となった中心的な実証的発見は、 テスト時計算量が性能に対して予測可能にスケールする ことです。 ここで\(C_{\text{train}}\)を学習計算量(FLOPs)、\(C_{\text{test}}\)を推論計算量(生成トークン数)とします。重要な観測は次のとおりです。
\[ \text{Accuracy}(C_{\text{train}}, C_{\text{test}}) \approx f!\left(\alpha \log C_{\text{train}} + \beta \log C_{\text{test}}\right) \]
単調関数\(f\)と定数\(\alpha, \beta > 0\)に対して、この関係が成り立ちます。これは、推論計算量を多く使う小さなモデルが、推論計算量の少ない大きなモデルに匹敵できることを意味します。計算量と性能のトレードオフにおける根本的な転換です。
実務上の意味は大きいものです。 推論モデルは学習計算量を推論計算量と交換します 。常に可能な限り大きなモデルを配備するのではなく、より小さく推論能力を持つモデルを配備し、難しい問題で「考える」ためにより多くのトークンを割り当てられます。
テスト時スケーリング手法
上のスケーリング則は、推論時により多くの計算を投じることで推論性能を大幅に改善できることを示しています。本節では、テスト時スケーリングを実現する 手法 を、単純な思考の連鎖から高度な木・グラフ探索アルゴリズムまで、包括的に扱います。これらの手法は推論コストと精度を交換するスペクトラムを形成しており、現代の推論システムを設計するにはその構造を理解することが不可欠です。
思考の連鎖(CoT)
思考の連鎖プロンプティング (Wei et al. 2022)は、すべてのテスト時スケーリング手法の基礎です。モデルは答えを直接出力するのではなく、複雑な問題を扱いやすい部分問題に分解する中間推論ステップを生成します。
ゼロショットCoT
Kojimaら (Kojima et al. 2022)は、プロンプトに「一歩ずつ考えてみましょう」を追加するだけで、例示なしに推論の振る舞いを引き出せることを示しました。 この単純なトリガーは、十分に大規模なモデルに潜在する推論能力を活性化します (\(\geq\)100B パラメータ)。
few-shot CoT
Weiら (Wei et al. 2022) は、明示的な推論トレースを含む少数の例を与えることで、より小さなモデルでも効果的に推論できることを示しました。
\[ \text{Prompt} = [(x_1, z_1, y_1), (x_2, z_2, y_2), \ldots, (x_k, z_k, y_k), (x_{\text{test}}, \texttt{?})] \]
ここで\(z_i\)は、例\((x_i, y_i)\)に対する人手で書かれた推論トレースです。
形式的な特徴づけ
CoTは単一ステップの予測\(p(y\vert x)\)を多段階の逐次生成へ変換します。
\[ p(y|x) = \sum_{z} p(y|x, z) \cdot p(z|x) \approx p(y|x, z^*) \cdot p(z^*|x) \]
ここで\(z^* = (z_1, z_2, \ldots, z_T)\)は貪欲な推論チェーンです。可能なすべてのチェーンに対する総和は扱いきれないため、標準的なCoTでは単一のサンプル(貪欲サンプリングまたは温度サンプリング)を使います。
限界
単一チェーンのCoTは脆弱です。初期の推論ステップが誤ると、その後のすべてのステップが誤った基盤の上に構築され、回復する仕組みがありません。
Self-Consistency(多数決)
Self-Consistency (X. Wang, Wei, Schuurmans, Q. Le, et al. 2023)は、 複数の独立した推論チェーン をサンプリングし、最終回答について多数決を取ることで、CoTの単一チェーンの脆弱性に対処します。
\[ \hat{y} = \arg\max_{y} \sum_{i=1}^{N} \mathbf{1}[y_i = y], \quad \text{where } (z_i, y_i) \sim p(\cdot | x), ; T > 0 \]
主な特性 :
-
温度\(T > 0\)のサンプリングで多様なチェーンを生成する(通常は\(T = 0.7\)–\(1.0\))
-
チェーン間の相互作用はなく、完全に並列化できる
-
精度は\(N\)とともに単調に向上する(\(N \approx 40\)以降は限界効用が小さくなる)
-
GSM8Kでは、CoT = 56.5%、 Self-Consistency(\(N\)=40)= 74.4%(PaLM-540B (Chowdhery et al. 2022))
-
結果報酬によるBest-of-N と等価(多数決が暗黙のORMとして働く)
Tip
多数決が機能する理由
モデルが正しい推論チェーンを生成する確率を\(p > 0.5\)とすると、大数の法則により、\(N\)個の独立サンプルに対する多数決の精度は\(N \to \infty\)で100%に近づきます。\(p = 0.3\)(モデルが通常は誤る)の場合でも、正しい回答が一つの値に集中し、誤った回答が多様なら、多数決によって正しい答えを復元できます。これがテスト時スケーリングの統計的基盤です。
Tree-of-Thoughts(ToT)
Tree-of-Thoughts (Yao et al. 2024)は、CoTを 線形チェーン から 木構造 へ一般化します。これにより、モデルは複数の推論経路を探索し、中間状態を評価し、有望でない枝からバックトラックできます。推論プロセスに意図的な計画を導入する手法です。
中核となる抽象化
推論問題は、次の要素を持つ木に対する探索へ分解されます。
-
ルート :初期問題文 \(x\)
-
ノード :部分的な推論状態\(s = (x, z_1, \ldots, z_k)\)
-
エッジ :個々の推論ステップ(「思考」)\(z_{k+1}\)
-
葉 :最終回答を含む完全な解
-
価値関数 :\(V(s)\)は部分解の有望さを推定する
形式的定義
\[ \text{ToT} = (\mathcal{G}, \mathcal{E}, V, \pi_\theta, \text{Search}) \]
ここで、
-
\(\mathcal{G}\): 思考生成器 — 次の思考の候補を\(b\)個生成する:\(\{z^{(1)}, \ldots, z^{(b)}\} \sim \pi_\theta(\cdot \vert s)\)
-
\(\mathcal{E}\): 状態評価器 — 部分解を採点する:\(V(s) \in \{\)sure、maybe、impossible\(\}\)または\(V(s) \in [0, 1]\)
-
\(\pi_\theta\):思考を生成する言語モデル
-
\(\text{Search}\):探索アルゴリズム(BFSまたはDFS)
探索アルゴリズム
BFS(幅優先探索):
-
現在の深さにある各ノードについて、\(b\)個の思考候補を生成する
-
すべての候補を\(V(\cdot)\)で評価する
-
最も有望な上位\(k\)個の状態を保持する(ビームサーチ)
-
すべての\(k\)個の状態を次のレベルへ進める
-
解が見つかるか、深さの上限に達するまで繰り返す
DFS(深さ優先探索):
-
現在の状態について\(b\)個の思考候補を生成する
-
評価する:\(V(s) =\) impossibleなら、直ちにバックトラックする
-
\(V(s) =\) sure/maybeなら、より深く再帰する(最も有望なものを選ぶ)
-
解がないまま深さの上限に達したら、バックトラックする
-
解が見つかるか、すべての枝を探索するまで続ける
Note
ToT:価値評価プロンプト
# The LLM evaluates partial reasoning states: EVAL_PROMPT = """Evaluate if this partial solution can reach 24. Numbers remaining: [4, 4, 10] Steps so far: 13 - 9 = 4 Can these remaining numbers (4, 4, 10) be combined using +,-,*,/ to make 24? Analysis: 4 * (10 - 4) = 4 * 6 = 24. Yes! Judge: sure/maybe/impossible Answer: sure""" # Thought generation prompt: GEN_PROMPT = """Input: 4 9 10 13 Possible next steps: 1. 13 - 9 = 4 (left: 4 4 10) 2. 10 + 13 = 23 (left: 4 9 23) 3. 9 - 4 = 5 (left: 5 10 13) ..."""
計算コスト
分岐係数\(b\)、深さ\(d\)、ビーム幅\(k\)のToTでは、次のようになります。
\[ \text{LLM calls (BFS)} = \underbrace{k \cdot b}_{\text{generation}} + \underbrace{k \cdot b}_{\text{evaluation}} = 2kb \text{ per level} \implies \text{Total} = 2kbd \]
24ゲームでは、\(b=3, k=2, d=3 \implies 36\)回のLLM呼び出しが必要です。標準的なCoTでは1回です。
結果
24ゲーム(難しい算術推論タスク)では、ToTの成功率は74%で、CoTの4%を大きく上回ります。同じベースモデル(GPT-4)に対する構造化探索による大幅な改善です。
Graph-of-Thoughts(GoT)
Graph-of-Thoughts (Besta et al. 2024)は、ToTを木から 有向非巡回グラフ(DAG) へ拡張し、異なる枝の部分解を マージ する重要な能力を導入します。これにより、モデルは複数の推論経路から得た洞察を、一つの洗練された解へ統合できます。
主要な操作
GoTは、ToTに加えて次の操作を導入します。
-
Generate :状態から新しい思考を生成する(ToTと同じ)
-
Aggregate/Merge :複数の思考を一つの洗練された思考へ結合する。これは木構造では不可能
-
Refine :フィードバックに基づいて思考を反復的に改善する
-
Score :思考の品質を評価する(ToTの価値関数と同じ)
グラフ操作(形式的定義)
頂点 \(\mathcal{V} = \{v_1, \ldots, v_n\}\)を思考頂点、\(\mathcal{E} \subseteq \mathcal{V} \times \mathcal{V}\)を有向エッジとします。GoTは次をサポートします。
\[ \begin{aligned} \textbf{Generate}(v) &: v \to \{v_{c_1}, \ldots, v_{c_b}\} && \text{(create children)} \\ \textbf{Aggregate}(v_1, \ldots, v_k) &\to v_{\text{merged}} && \text{(merge $k$ thoughts into one)} \\ \textbf{Refine}(v, n) &\to v' && \text{(improve $v$ through $n$ iterations)} \\ \textbf{Score}(v) &\to s \in [0, 1] && \text{(evaluate thought quality)} \end{aligned} \]
Aggregate 操作が重要な差別化要因です。複数の親ノードから一つの子ノードへのエッジを作成し、木ではなくDAGを形成します。これにより、次が可能になります。
-
分割統治 :問題を分割 \(\to\) 部分問題を並列に解く \(\to\) 解をマージする
-
アンサンブル推論 :複数の視点を生成し、最良のアイデアを統合する
-
反復的改良 :評価結果をフィードバックし、以前の思考を改善する
結果
マージを必要とするソートでは、同等の品質を保ちながら、GoTはToTに対してコストを62%削減します。集合の共通部分やキーワードのカウントでは、マージ操作によってより効率的に分解できるため、GoTはLLM呼び出しを30〜40%減らしてToTと同等の品質を達成します。
報酬モデルを使うBest-of-N
Best-of-N(BoN) (Nakano et al. 2021; Stiennon et al. 2020)は、 学習済み報酬モデル を使って候補から選択する、最も単純なスケーリング手法です。
\[ y^* = \arg\max_{y \in \{y_1, \ldots, y_N\}} R_\phi(x, y), \quad y_i \sim \pi_\theta(\cdot | x) \]
報酬モデルの種類による変種 :
-
ORMを使うBoN :完全な解を採点し、最も高得点のものを選ぶ。ORMが\(\approx\)正しさのチェックに近い場合、Self-Consistencyと等価。
-
PRMを使うBoN :各推論ステップを採点し、最小ステップスコアが最も高い解を選ぶ(どのステップにも誤りがある可能性が最も低い解)
-
重み付きBoN :報酬で候補に重みを付ける。 \(y^* \sim \text{softmax}(R(y_1)/\tau, \ldots, R(y_N)/\tau)\).
Important
BoNのスケーリング則
1サンプルあたりの精度が\(p\), のモデルについて、\(N\)回の試行で少なくとも1つの正しいサンプルが得られる確率: \[ P(\text{success with BoN}) = 1 - (1-p)^N \] 完全な報酬モデル(常に正しく選択するオラクル)の場合は、
\(p = 0.3, N = 10\): 成功 = \(97%\)
\(p = 0.1, N = 50\): 成功 = \(99.5%\)
実際には、不完全な報酬モデルによって有効な\(N\)には上限があります。\(N \approx 64\)–\(256\)を超えると報酬モデルの誤りが支配的になり、精度は頭打ちになるか低下します( 報酬ハッキング )。
推論のためのモンテカルロ木探索(MCTS)
MCTS (Kocsis and Szepesvári 2006; Silver et al. 2016)は、ToTの構造化探索に 学習された価値推定 と 訪問回数の統計 を組み合わせ、推論計算量を最適に割り当てます。もともとはゲームプレイ(AlphaGo (Silver et al. 2016))のために開発されましたが、現在ではAlphaProof (DeepMind 2024)やrStar (Qi et al. 2024)などのシステムによってLLM推論へ適用されています。
アルゴリズム(LLM推論向けの適用)
MCTSの各反復は、次の4段階で構成されます。
推論のためのUCB
ノード選択ではPUCT(木に適用するPredictor + UCB)を使います。
\[ a^* = \arg\max_a \left[ Q(s, a) + c_{\text{puct}} \cdot P(s, a) \cdot \frac{\sqrt{\sum_b N(s,b)}}{1 + N(s, a)} \right] \]
ここで\(P(s,a) = \pi_\theta(a\vert s)\)は、LLMがステップ\(a\)を状態\(s\)から生成する確率です。これにより、LLMが起こりそうだと考えているステップへ探索が偏る一方、UCB項はまだ十分に探索されていない代替案を試すよう促します。
Note
数学推論のためのMCTS:実行例
問題 :\(\sqrt{2}\)が無理数であることを証明せよ。
反復1 (選択 \(\to\) ルート, 展開):
最初のステップの候補を3つ生成する:
「背理法のため、\(\sqrt{2} = p/q\)は既約分数だと仮定する。」 (\(P = 0.7\))
「\(\sqrt{2}\)の小数展開は1.414…だと考える。」 (\(P = 0.15\))
「算術の基本定理を使う。」 (\(P = 0.10\))
\(z_1\)からのロールアウト:4ステップで正しい証明に到達 \(\to\) \(r = 1.0\)
\(z_2\)からのロールアウト:失敗(小数展開では無理数性を証明できない) \(\to\) \(r = 0.0\)
バックプロパゲーション: \(Q(s_0, z_1) = 1.0\), \(N(s_0, z_1) = 1\)
反復2 (選択:\(z_1\)をUCBで選ぶ):
状態「\(\sqrt{2} = p/q\)…」から展開する:
「次に、\(2 = p^2/q^2\), なので、\(p^2 = 2q^2\)。」 (\(P = 0.8\))
「次に、\(p\) と\(q\)は共通因子を持たない。」 (\(P = 0.15\))
\(z_4\)からのロールアウト:正しい続き \(\to r = 1.0\)
バックプロパゲーション: \(Q(s_0, z_1) = 1.0\), \(Q(s_1, z_4) = 1.0\)
20回の反復後 :木は8つの異なる推論経路を探索しました。最も多く訪問された経路が最終証明として選ばれます:\(z_1 \to z_4 \to z_6 \to z_8\)(偶奇法による古典的な背理法)。
比較:ToTとMCTS
| 次元 | ToT | MCTS |
|---|---|---|
| 価値推定 | LLMプロンプト(“sure/maybe/impossible”) | 学習された価値ネットワーク+ロールアウト統計 |
| 探索 | 固定ビーム幅、再訪問なし | UCBが有望なノードへ適応的に予算を割り当てる |
| 計算量配分 | 深さレベル全体に均一 | 集中的:より難しい部分問題に多くのシミュレーション |
| 学習との統合 | 学習なし、純粋なプロンプト | MCTS方策をベースモデルへ蒸留できる (Silver et al. 2016) |
| 適する用途 | 単純な分岐問題(24ゲーム) | 深い探索を必要とする複雑な問題(証明、コード) |
推論におけるTree-of-Thoughtsとモンテカルロ木探索の比較。
推論ステップに対するビームサーチ
ビームサーチは、NMTやテキスト生成で長く使われてきた手法です。トークンのレベルではなく、推論ステップのレベルに適用できます。 上位\(k\)個のトークン列を追跡する代わりに、上位\(k\)個の 推論接頭辞 を追跡します。
\[ \mathcal{B}_d = \text{top-}k\left\{ (s_1, \ldots, s_d) : \sum_{i=1}^d \log \pi_\theta(s_i | s_{<i}) + \lambda \cdot V_\phi(s_1, \ldots, s_d) \right\} \]
ここでスコアは、LLMの対数確率(流暢さ)と価値モデルの推定値(正しさ)を組み合わせます。これは、プロンプトで与えた価値関数ではなく、学習された価値関数を使うToT-BFSに相当します。
反復的改良と自己修正
反復的改良は、幅(複数の並列チェーン)を探索するのではなく、深さに計算を投じ、一つの解を繰り返し改善します。
\[ y^{(t+1)} = \text{LLM}!\left(\text{“Improve this solution:”}, y^{(t)}, \text{“Errors found:”}, e^{(t)}\right) \]
ここで\(e^{(t)}\)は次のいずれかから得られます。
-
自己検証 :モデルに自分の答えをチェックさせる
-
外部検証 :コードを実行したり、数学を記号的にチェックしたりする
-
批評モデル :別のモデルが誤りを特定する
代表的な手法には、 Self-Refine (Madaan et al. 2023)(自己フィードバックの反復)、 Reflexion (Shinn et al. 2023)(メモリに保存した反省を介した言語的RL)、 LATS (A. Zhou et al. 2024)(木探索と反省に基づく枝刈り)があります。
手法の比較と選択ガイド
| 手法 | 構造 | LLM呼び出し | 並列化可能 | RMが必要か | 適する用途 |
|---|---|---|---|---|---|
| CoT (Wei et al. 2022) | チェーン | 1 | 該当なし | いいえ | 簡単〜中程度の問題 |
| Self-Consistency (X. Wang, Wei, Schuurmans, Q. Le, et al. 2023) | 並列チェーン | \(N\) | 完全 | いいえ(多数決) | 離散的な答えを持つ数学 |
| Best-of-N + ORM | 並列チェーン | \(N\) + 1 | 完全 | はい(ORM) | 良いRMがある一般タスク |
| Best-of-N + PRM | 並列チェーン | \(N\) + \(N{\cdot}K\) | 完全 | はい(PRM) | 複雑な多段階推論 |
| ToT (Yao et al. 2024) | 木(BFS/DFS) | \(O(kbd)\) | 部分的 | LLM-as-judge | 構造化探索問題 |
| GoT (Besta et al. 2024) | DAG | \(O(kbd)\) | 部分的 | LLM-as-judge | 分解可能な問題 |
| MCTS (Kocsis and Szepesvári 2006) | 木+価値 | \(O(N_{\text{sim}} \cdot d)\) | 部分的 | はい(価値ネット) | 難しい証明、コーディング |
| Self-Refine (Madaan et al. 2023) | 線形(反復) | \(2T\) | いいえ | 自己批評 | オープンエンド生成 |
| LATS (A. Zhou et al. 2024) | 木+反省 | \(O(N \cdot d)\) | 部分的 | LLM-as-judge | エージェントタスク |
テスト時スケーリング手法の包括的な比較。
Important
どの手法をいつ使うか
予算が\(<\) 5\(\times\) ベースコスト未満 :CoTまたはSelf-Consistencyを使う。費用対効果が最も高い。
予算が5–50\(\times\) :PRMを使うBest-of-N(良い報酬モデルがある場合)、または\(b=3, k=2\)のToT-BFSを使う。
予算が50–500\(\times\) :学習済み価値関数を使うMCTSを利用する。DeepSeek-R1とOpenAI o1が動作する領域であり、暗黙的な木探索を伴う長い推論チェーンを使う。
並列性が必要 :Self-ConsistencyとBest-of-Nは完全に並列化できる。ToT/MCTSでは深さ方向の逐次的な展開が必要になる。
報酬モデルがない :Self-Consistency(多数決)、またはLLM-as-judge評価を使うToTを利用する。
分解可能な問題 :問題に自然な部分問題(ソート、複数文書の統合、モジュールを含むコード)がある場合、GoTが力を発揮する。
Tip
推論モデルにおける暗黙的なテスト時スケーリング
現代の推論モデル(DeepSeek-R1 (DeepSeek-AI et al. 2025)、OpenAI o1/o3 (OpenAI 2024a, 2025b))は、長い思考の連鎖を生成することで 暗黙的なテスト時スケーリング を行います。「思考」トークンはMCTSのロールアウトに似た役割を果たします。モデルは複数のアプローチを探索し、「待って、考え直させてください…」とバックトラックし、中間ステップを検証し、より難しい部分問題により多くのトークンを割り当てます。R1/o1の学習における重要な洞察は、GRPO/RLがこの暗黙的な探索を単一の生成の内部で実行するようモデルに教えることです。これにより、外部のオーケストレーション(ToTプロンプトやMCTS基盤)が不要になります。モデル自身が探索アルゴリズムになるのです。
DeepSeek-R1
DeepSeek-R1 (DeepSeek-AI et al. 2025)は、主要ベンチマークでOpenAI o1に匹敵、または上回った最初の完全オープンソース大規模推論モデルです。その学習パイプラインは技術的に透明であり、RLベースの推論における事実上のリファレンス実装となっています。
2段階の学習パイプライン
ステージ1:コールドスタート教師ありファインチューニング
まずベースモデル(DeepSeek-V3)を、長い思考の連鎖の例を集めた小規模で慎重にキュレーションされたデータセット上でファインチューニングします。この「コールドスタート」段階には、二つの目的があります。
-
形式の初期化 :モデルは最終回答を出す前に、
<think>...</think>形式で推論を生成することを学ぶ。 -
安定性 :コールドスタートSFTなしでベースモデルにゼロから純粋なRLを適用すると、学習ダイナミクスが不安定になり、退化した出力(言語の混在、反復ループなど)が生じる。
コールドスタートデータセットには\(\sim\)数千個の例しか含まれておらず、RLが後に発見する推論スタイルを過度に制約しないよう、意図的に小規模に保たれています。
ステージ2:GRPOベースの強化学習
コールドスタートSFTの後、モデルはグループ相対方策最適化(GRPO)を使った大規模RLを受けます。R1で使われるGRPOの完全な目的関数は、節 1.3.3で説明します。
Important
R1の学習パイプラインの要約
ベースモデル :DeepSeek-V3(671B MoE、アクティブパラメータ37B)
コールドスタートSFT : \(\sim\)数千個の長いCoTの例、形式:
<think>...</think><answer>...</answer>RL段階 :数学とコードの問題に対し、検証可能な報酬を使うGRPO
棄却サンプリング :複数の解を生成し、正しいものを残す
RL出力に対するSFT :RLで生成された高品質なチェーン上でファインチューニングする
最終RL :アライメントと有用性のための2回目のRL段階
報酬設計:精度報酬と形式報酬
R1における重要な設計上の選択は、 プロセス報酬モデルを置かないこと です。その代わり、R1は自動計算できる二つの単純な報酬を使います。
精度報酬
検証可能な答えを持つ数学問題では、次のように定義します。
\[ r_{\text{acc}}(y, y^*) = \begin{cases} 1 & \text{if } \texttt{verify}(y, y^*) = \texttt{True} \\ 0 & \text{otherwise} \end{cases} \]
ここで\(y\)はモデルの最終回答(<answer>タグから抽出)で、\(y^*\)は正解です。verify関数は記号数学の比較(SymPyなど)を使って同値な形式を扱います。
コード問題では、テストケースへの合格によって精度報酬が決まります。
\[ r_{\text{acc}}^{\text{code}}(y, \mathcal{T}) = \frac{1}{|\mathcal{T}|} \sum_{t \in \mathcal{T}} \mathbf{1}[\texttt{execute}(y, t) = \texttt{expected}(t)] \]
形式報酬
<think>...</think>構造を強制するために、
\[ r_{\text{fmt}}(y) = \begin{cases} 1 & y \text{ has valid <think> and <answer> tags} \\ 0 & \text{otherwise} \end{cases} \]
統合報酬
\[ r(y, y^*) = r_{\text{acc}}(y, y^*) + \lambda_{\text{fmt}} \cdot r_{\text{fmt}}(y) \]
元の実装では\(\lambda_{\text{fmt}} = 0.1\)です(支配的にならないほど小さく、形式の崩壊を防ぐには十分に大きい値です)。
Warning
プロセス報酬モデルなし
R1の注目すべき、そして意外な発見は、 プロセス報酬モデル(PRM)が不要である ことです。長い推論チェーンにもかかわらず、結果だけの報酬で、RLは高品質な推論戦略を発見できます。著者らは、数学・コードの報酬が検証可能であるため十分な信号を与え、PRMは独自の失敗モード(ステップ単位の報酬ハッキング)を持ち込むと仮説を立てています。これはOpenAIが採用したアプローチ(節 1.4)とは対照的です。
R1のGRPO定式化
GRPO (Shao et al. 2024)は、サンプリングした応答のグループからアドバンテージを推定することで、別個の価値ネットワークの学習を避ける方策勾配法です。 質問\(q\)に対して、GRPOは\(G\)個の応答\(\{y_1, y_2, \ldots, y_G\}\)を現在の方策\(\pi_\theta\)からサンプリングし、グループ平均に対する相対的なアドバンテージを計算します。
グループサンプリングとアドバンテージの正規化
質問\(q\)が与えられたとき、\(G\)個の出力をサンプリングします。
\[ \{y_i\}_{i=1}^G \sim \pi_\theta(\cdot \mid q) \]
式 [eq:r1_combined_reward]の報酬関数を使って報酬\(\{r_i\}_{i=1}^G\)を計算します。応答\(i\)に対する正規化アドバンテージは次のとおりです。
\[ \hat{A}_i = \frac{r_i - \mu_r}{\sigma_r + \epsilon} \]
ここで\(\mu_r = \frac{1}{G}\sum_{i=1}^G r_i\), \(\sigma_r = \sqrt{\frac{1}{G}\sum_{i=1}^G (r_i - \mu_r)^2}\)、\(\epsilon = 10^{-8}\)で、数値安定性のためです。
GRPOの目的関数
GRPOの目的関数は、確率比(PPOと同様)をクリップし、参照方策\(\pi_{\text{ref}}\)に対するKLペナルティを加えます。
\[ \mathcal{L}_{\text{GRPO}}(\theta) = -\mathbb{E}_{q \sim \mathcal{D},, \{y_i\} \sim \pi_\theta(\cdot|q)} \Bigg[ \frac{1}{G} \sum_{i=1}^{G} \frac{1}{|y_i|} \sum_{t=1}^{|y_i|} \\ \min!\left( \rho_{i,t}, \hat{A}_i,; \text{clip}(\rho_{i,t}, 1{-}\varepsilon, 1{+}\varepsilon), \hat{A}_i \right) - \beta, \mathbb{D}_{\mathrm{KL}}!\left[\pi_\theta ,|, \pi_{\text{ref}}\right] \Bigg] \label{eq:grpo_full} \]
ここで、
-
\(\rho_{i,t} = \dfrac{\pi_\theta(y_{i,t} \mid q, y_{i,<t})}{\pi_{\theta_{\text{old}}}(y_{i,t} \mid q, y_{i,<t})}\)はトークンごとの確率比
-
\(\varepsilon \in \{0.1, 0.2\}\)はPPOのクリッピングパラメータ
-
\(\beta > 0\)はKLペナルティの強さを制御する
-
\(\vert y_i\vert\)は応答\(i\)の長さ(長さの正規化により短い応答への偏りを防ぐ)
KLペナルティの定式化
KLダイバージェンス項はトークンごとに計算します。
\[ \mathbb{D}_{\mathrm{KL}}!\left[\pi_\theta ,|, \pi_{\text{ref}}\right] = \mathbb{E}_{y \sim \pi_\theta(\cdot|q)} \left[ \sum_{t=1}^{|y|} \log \frac{\pi_\theta(y_t \mid q, y_{<t})}{\pi_{\text{ref}}(y_t \mid q, y_{<t})} \right] \]
実際には、R1は次の近似を使います。各ステップで\(\pi_{\text{ref}}\)を計算する必要がない、KLの不偏推定量です。
\[ \mathbb{D}_{\mathrm{KL}}!\left[\pi_\theta ,|, \pi_{\text{ref}}\right] \approx \frac{\pi_{\text{ref}}(y_t \mid q, y_{<t})}{\pi_\theta(y_t \mid q, y_{<t})} - \log \frac{\pi_{\text{ref}}(y_t \mid q, y_{<t})}{\pi_\theta(y_t \mid q, y_{<t})} - 1 \]
これは常に非負で、\(\pi_\theta = \pi_{\text{ref}}\)のときにゼロになります。
Note
実践におけるGRPO:グループサイズと安定性
R1の学習では、\(G = 8\)個の応答を質問ごとにサンプリングします。これは重要なハイパーパラメータです。
小さすぎる(\(G=2\)):アドバンテージ推定の分散が大きく、学習がノイジーになる。
大きすぎる(\(G=32\)):計算コストが線形に増え、限界効用が低下する。
\(G=8\): 分散の低減と計算コストのバランスが取れることが実証的に確認されている。
グループサンプリングは自然な カリキュラム信号 も与えます。学習が進むにつれてモデルの平均報酬\(\mu_r\)は増加し、分散\(\sigma_r\)は減少します。\(G\)個の応答がすべて正しい(またはすべて誤っている)問題は勾配に寄与しないため、モデルの能力の限界付近にある問題へ自然に学習が集中します。
蒸留:R1-Distillシリーズ
R1の大きな実務的貢献は、R1が生成したチェーンに対する教師ありファインチューニングによって、 推論能力をはるかに小さなモデルへ蒸留できる ことを示した点です。R1-Distillシリーズ(1.5B、7B、8B、14B、32B、70Bパラメータ)は、次の手順で学習されます。
-
R1(671B)を使い、大規模な問題集合に対する長いCoTの解を生成する
-
正しい解だけを残すようフィルタリングする
-
これらの解を使って小さなベースモデル(Qwen2.5、Llama-3)をファインチューニングする
Important
小規模モデルにおける蒸留とRL
印象的な発見は、 小規模モデルでは蒸留がゼロからのRL学習を上回る ことです。DeepSeek-R1-Distill-Qwen-7Bは、GRPOを直接適用して学習した7Bモデルより高いMATHベンチマークスコアを達成します。これは次を示唆します。
小規模モデルには、RL探索によって推論戦略を発見する能力が不足している
しかし、大規模モデルが発見した推論戦略を模倣することは学習できる
小規模モデルのボトルネックは表現ではなく探索である
蒸留というアプローチは、推論の本質について重要な問いを提起します。小規模モデルは本当に「推論」しているのでしょうか。それとも、推論チェーンの表面的な形式に対してパターンマッチングしているのでしょうか。実証的には、蒸留モデルは新しい問題タイプにもある程度汎化するため、単なる記憶ではなく推論戦略を真に内在化していることが示唆されます。
OpenAI o1/o3シリーズ
OpenAIのo1 (OpenAI 2024a)(2024年9月公開)と、その後のo3/o4-mini (OpenAI 2025b)は、推論モデル開発における商用の最前線を代表します。完全な技術詳細は非公開ですが、公開されたシステムカード、技術レポート、実証的な観測から、手法について多くの洞察が得られます。
隠れた推論トークンを使う思考の連鎖RL
o1を特徴づけるアーキテクチャ上の選択は、 隠れた推論トークン の利用です。モデルは内部的な思考の連鎖(「推論トレース」または「思考トークン」と呼ばれる)を生成しますが、これはユーザーには表示されません。返されるのは最終回答だけです。この設計には次の意味があります。
-
形式の制約がない :隠れた推論は、スクラッチパッド記法、擬似コード、さらには英語以外の推論を含め、どのような形式でも使える。
-
スタイルに対する報酬ハッキングがない :ユーザーが推論を見ることはないため、有用であることよりも「よく見せる」ことへの圧力がない。
-
プロプライエタリな保護 :推論プロセスを公開しないため、直接的な模倣を防げる。
学習手順は「RLを使ってモデルに推論させる学習」と説明されており、RLの目的関数は完全な(隠れた推論+最終回答)系列に適用されます。報酬は最終回答の品質だけに与えられます。
プロセス報酬モデルと結果報酬モデル
OpenAIのアプローチは、DeepSeek-R1の結果のみのアプローチとは異なり、結果報酬に加えて プロセス報酬モデル(PRM) (Lightman et al. 2023)を使うと考えられています。この推測は、OpenAIが公開したPRM研究(PRM800Kデータセット、「Let’s Verify Step by Step」)と、o1システムカードにおける推論チェーンのRL学習の説明に基づきます。ただし、o1/o3の正確な学習レシピは公開されていません。
結果報酬モデル(ORM)
ORMは完全な応答\((q, y)\)を採点します。
\[ R_{\text{ORM}}(q, y) \in [0, 1] \]
検証可能なタスク(数学、コード)では、これは完全一致の検証に帰着します。オープンエンドのタスクでは、学習済み報酬モデルを使います。
プロセス報酬モデル(PRM)
PRMは、チェーン\(s_k\)の各推論ステップ\(y = (s_1, s_2, \ldots, s_K)\)に報酬を割り当てます。
\[ R_{\text{PRM}}(q, y) = \sum_{k=1}^{K} \gamma^{K-k} \cdot r_k(q, s_1, \ldots, s_k) \]
ここで\(r_k \in [0,1]\)はステップ単位の報酬で、\(\gamma \in (0,1]\)は割引係数です。ステップ単位の報酬\(r_k\)は部分解\((s_1, \ldots, s_k)\)が正しい最終回答につながる確率を推定します。
\[ r_k(q, s_1, \ldots, s_k) = P(\text{correct final answer} \mid q, s_1, \ldots, s_k) \]
Tip
PRMとORM:信用割当のトレードオフ
ORM は明確で曖昧さのない報酬を与えますが、深刻な信用割当問題に悩まされます。50ステップのチェーンの序盤にある一つの誤ったステップが、完全にランダムな応答と同じゼロ報酬を受け取ってしまいます。
PRM は信用割当に直接対処する密な報酬を与えますが、新たな課題を持ち込みます。
学習データ :ステップ単位のラベルには、人手によるアノテーションまたは自動生成が必要(Math-Shepherd、節 1.6.2)。
報酬ハッキング :モデルは、実際には正しくないのにPRMからは正しく見えるステップを生成するよう学習できる。
分布シフト :ある分布の推論チェーンで学習したPRMは、RLが生成する新しいチェーンへ汎化しない可能性がある。
実証的な証拠からは、PRMは探索(候補解からの選択)には有益ですが、学習における利点はそれほど明確でないことが示唆されます。
推論時計算量のスケーリング
o1の技術レポートは、明確なスケーリング則を示しています。難しい推論タスクでは、 思考トークンを増やすほど性能が単調に向上 します。これは、隠れた推論トークンの最大数を制御する「思考予算」パラメータによって実現されます。
思考トークン予算を\(T\)とします。観測された実証的なスケーリング則はおおむね次のとおりです。
\[ \text{Pass@1}(T) \approx a - b \cdot T^{-c} \]
定数\(a, b, c > 0\)に対して、\(a\)は漸近的な精度の上限を表し、\(c\)は改善率を特徴づけます。AIME 2024では、思考予算を最大にしたo1が\(\sim\)83%の精度を達成する一方、\(\sim\)13%は拡張思考を使わないGPT-4oの精度です。
学習計算量とテスト時計算量
o1/o3シリーズから得られる根本的な洞察は、 計算量等価原理 です。学習計算量\(C_{\text{train}}\)とテスト時計算量\(C_{\text{test}}\)の間にはトレードオフ曲線が存在し、その曲線上の点は同程度の性能を達成します。
\[ \text{Performance}(C_{\text{train}}, C_{\text{test}}) = g!\left(\alpha C_{\text{train}}^{p} + \beta C_{\text{test}}^{q}\right) \]
実証的には\(p \approx q\)であり、学習計算量とテスト時計算量はおおむね代替可能であることを示唆します。これは配備に大きな意味を持ちます。拡張思考を使う小さく安価なモデルは、レイテンシーが高くなる代わりに、難しい問題で大きなモデルに匹敵できます。
o3とo4-miniのアーキテクチャに関する洞察
o3とo4-miniの詳細は大部分が非公開ですが、いくつかの観測が得られています。
-
o3 :o1より大幅に大きな思考予算を持ち、ARC-AGIで人間に近い性能を達成する(高い計算量で87.5%)。推論時により高度な探索戦略を使うと考えられている。
-
o4-mini :RLで推論を学習した小さなモデルでも非常に競争力を持てることを示す。拡張思考でAIME 2025の93%を達成し、数学ではモデルサイズより推論能力が重要であることを示唆する。
-
ツール利用 :o3/o4-miniはツール利用(コード実行、ウェブ検索)を推論プロセスに統合し、中間ステップをプログラムで検証できるようにする。
QwQとQwenの推論モデル
AlibabaのQwenチームは、DeepSeek-R1とともにオープンソースの最前線を代表する一連の推論モデル(QwQ-32B (Q. Team 2024b、Qwen3 (Q. Team 2025))を開発しました。そのアプローチはいくつかの重要な点で異なります。
多段階RLパイプライン
Qwenの推論パイプラインは、より精緻な多段階アプローチを使います。
-
ベース事前学習 :数学とコーディング能力に優れたQwen2.5ベースモデル
-
多様な推論に対するSFT :幅広い推論タスク(数学、コード、科学、論理)の混合データでファインチューニングする
-
棄却サンプリング・ファインチューニング(RFT) :問題ごとに\(N\)個の解を生成し、正しいものを残してファインチューニングする
-
RL段階1 :検証可能な報酬を使い、数学とコードに対してGRPOを行う
-
RL段階2 :指示追従と安全性を含む、より広範なRL
棄却サンプリングとRLの組み合わせ
Qwenのアプローチにおける重要な革新は、 棄却サンプリングとRLを反復的に組み合わせる ことです。
-
初期化 : 方策\(\pi_0\)をSFTモデルから作る。
-
棄却サンプリング : サンプリングする:\(N\)個の解: \(\{y_i\}_{i=1}^N \sim \pi_{k-1}(\cdot \mid q)\). 。正しい解を残す: \(\mathcal{Y}^+(q) = \{y_i : r(y_i, y^*) = 1\}\).
-
SFT更新 : \(\pi_k^{\text{SFT}} \leftarrow \text{SFT}(\pi_{k-1}, \bigcup_q \mathcal{Y}^+(q))\)
-
RL更新 : \(\pi_k \leftarrow \text{GRPO}(\pi_k^{\text{SFT}}, \mathcal{D})\)
5を得る。 反復 ステップ2〜4を\(K\)回反復して最終方策\(\pi_K\)を得る。
棄却サンプリングのステップは方策を固定する高品質な正例を与え、RLは現在の分布を越えて探索します。この組み合わせは純粋なRLより安定し、純粋なSFTより高い能力を持ちます。
ツール統合推論
QwQ-32BとQwen3のモデルは ツール統合推論 をサポートします。モデルは推論チェーン中に外部ツール(Pythonインタプリタ、検索エンジン、計算機)を呼び出せます。これは特殊トークンによって実装されます。
<think>
Let me solve this step by step.
First, I'll compute the eigenvalues of the matrix.
<tool_call>
{"name": "python", "arguments": {"code": "import numpy as np\nA = np.array([[2,1],[1,3]])\neigenvalues = np.linalg.eigvals(A)\nprint(eigenvalues)"}}
</tool_call>
<tool_response>
[1.38196601 3.61803399]
</tool_response>
The eigenvalues are approximately 1.382 and 3.618.
These are (5 +/- sqrt5)/2, which are the golden ratio and its conjugate...
</think>
<answer>The eigenvalues are (5 +/- sqrt5)/2</answer>
RL学習の報酬は最終回答に対して計算されますが、ツール利用によって正しい答えに到達する確率が高まるため、モデルはツールを戦略的に使うことを学習します。
数学的基礎を持つ主要手法
推論のためのモンテカルロ木探索
モンテカルロ木探索(MCTS)は、推論を木探索として扱うための原理に基づくフレームワークを提供します。AlphaProof (DeepMind 2024)などのシステムでは、MCTSをゲームの手ではなく推論ステップに対して適用します。
状態空間と行動空間
-
状態 \(s_k\):部分的な推論チェーン \((q, r_1, r_2, \ldots, r_k)\)。ここで\(r_i\)は推論ステップ
-
行動 \(a\):次の推論ステップ(文または段落)
-
終端状態 :最終回答を含む状態
-
報酬 : \(R(s_{\text{terminal}}) = r_{\text{acc}}\) (式 [eq:r1_accuracy_reward])
部分解の価値関数
価値関数\(V(s_k)\)は、部分状態\(s_k\)から正解に到達する確率を推定します。
\[ V(s_k) = P(\text{correct answer} \mid s_k) \approx \frac{1}{M} \sum_{m=1}^{M} R(\text{rollout}_m(s_k)) \]
ここで\(\text{rollout}_m(s_k)\)は、\(s_k\)から現在の方策を使って終端状態まで行うモンテカルロ・ロールアウトです。
UCB探索
ノード選択では、推論向けに適用した上限信頼限界(UCB)式を使います。
\[ \text{UCB}(s_k, a) = Q(s_k, a) + c_{\text{puct}} \cdot \pi_\theta(a \mid s_k) \cdot \frac{\sqrt{N(s_k)}}{1 + N(s_k, a)} \]
ここで、
-
\(Q(s_k, a) = \frac{1}{N(s_k,a)} \sum_{\text{visits}} V(s_{k+1})\)は子状態の平均価値
-
\(\pi_\theta(a \mid s_k)\)は方策事前分布(ステップ\(a\))
の訪問回数- \(N(s_k)\)は状態\(s_k\)
-
\(N(s_k, a)\)はエッジ\((s_k, a)\)の訪問回数
-
\(c_{\text{puct}}\)は探索定数
MCTSに導かれる学習
MCTSは高品質な学習データの生成に使えます。
\[ \mathcal{L}_{\text{MCTS}}(\theta) = -\sum_{k} \sum_{a} \pi_{\text{MCTS}}(a \mid s_k) \log \pi_\theta(a \mid s_k) \]
ここで\(\pi_{\text{MCTS}}(a \mid s_k) \propto N(s_k, a)^{1/\tau}\)はMCTS方策です(温度\(\tau\)を持つ訪問回数分布)。
プロセス報酬モデル
Math-Shepherd:PRMの自動学習
Math-Shepherd (P. Wang et al. 2024)は、ステップ単位の人手アノテーションなしでPRMを学習する自動手法を提案します。重要な洞察は 結果に基づく推定 を使うことです。ステップ\(s_k\)は、\(s_k\)から正解に到達する完了が存在する場合に正しいとラベル付けされます。
部分解\((s_1, \ldots, s_k)\)について形式的には、
\[ \hat{r}_k = \mathbf{1}!\left[\exists, (s_{k+1}, \ldots, s_K) : \text{verify}(s_K, y^*) = 1\right] \]
実際には、\(M\)個の完了を\(s_k\)からサンプリングし、正しいものがあるかをチェックして推定します。
\[ \hat{r}_k \approx \mathbf{1}!\left[\sum_{m=1}^{M} \text{verify}(\text{complete}_m(s_k), y^*) > 0\right] \]
続いてPRMを二値交差エントロピーで学習します。
\[ \mathcal{L}_{\text{PRM}}(\phi) = -\sum_{k=1}^{K} \left[ \hat{r}_k \log r_\phi(s_k) + (1-\hat{r}_k) \log(1 - r_\phi(s_k)) \right] \]
Best-of-N選択のためのPRM
PRMの主要な用途は Best-of-N選択 です。\(N\)個の解候補を生成し、PRMスコアが最も高いものを選択します。
\[ y^* = \arg\max_{y \in \{y_1, \ldots, y_N\}} R_{\text{PRM}}(q, y) \]
これは多数決(ORMを使う)より効果的です。PRMは、同じ答えに到達していても、推論経路の品質が異なる解を区別できるからです。
結果報酬モデルと多数決
多数決(Self-Consistency)
テスト時計算量スケーリングの最も単純な形式は、多数決 (X. Wang, Wei, Schuurmans, Q. Le, et al. 2023)です。\(N\)個の解を生成し、最も多い答えを返します。
\[ y^* = \arg\max_{a} \sum_{i=1}^{N} \mathbf{1}[y_i = a] \]
各解が独立に正しい確率\(p > 0.5\)であると仮定すると、多数決が正しい確率は次のとおりです。
\[ P(\text{majority correct}) = \sum_{k=\lceil N/2 \rceil}^{N} \binom{N}{k} p^k (1-p)^{N-k} \xrightarrow{N \to \infty} 1 \]
ORMによる重み付き多数決
ORMは、確信度で投票に重みを付けることで多数決を改善できます。
\[ y^* = \arg\max_{a} \sum_{i=1}^{N} R_{\text{ORM}}(q, y_i) \cdot \mathbf{1}[y_i = a] \]
推論のためのセルフプレイ
セルフプレイ手法は、モデルに生成器と検証器の両方の役割を担わせることで学習データを生成します。
STaR:自己学習推論器
STaR (Zelikman et al. 2022)は、推論能力を反復的にブートストラップします。
-
問題集合に対する推論チェーンを生成する
-
正しい答えにつながるチェーンを残す(棄却サンプリング)
-
残したチェーンでファインチューニングする
-
改善されたモデルで繰り返す
重要な洞察は、モデルが正しい答えを合理化できることです。ゼロから問題を解けない場合でも、答えが与えられればもっともらしい推論チェーンを生成でき、それを学習データとして使えます。
セルフプレイRL
推論のセルフプレイRLでは、モデルが問題と解の両方を生成します。
\[ \mathcal{L}_{\text{self-play}}(\theta) = \mathbb{E}_{q \sim \pi_\theta^{\text{gen}}} \mathbb{E}_{y \sim \pi_\theta^{\text{solve}}(\cdot|q)} \left[ r(y, y^*) \right] \]
ここで\(\pi_\theta^{\text{gen}}\)は問題を生成し、\(\pi_\theta^{\text{solve}}\)はそれを解きます。生成器は、難しいが解ける問題を生成すると報酬を受けます。
検証可能な報酬からの強化学習(RLVR)
RLVR (Lambert et al. 2024)は、 正解による検証 を報酬信号として使うフレームワークです。正しさを自動チェックできるあらゆる領域に適用できます。
検証可能な領域
-
数学 :SymPy、Lean、Isabelleによる記号検証
-
コード :ユニットテストの実行
-
形式論理 :証明のチェック
-
事実に基づくQA :データベース検索
-
ゲーム :勝敗の結果
RLVRの目的関数
\[ \mathcal{L}_{\text{RLVR}}(\theta) = -\mathbb{E}_{(q, y^*) \sim \mathcal{D}} \mathbb{E}_{y \sim \pi_\theta(\cdot|q)} \left[ \text{verify}(y, y^*) \right] + \beta \mathbb{D}_{\mathrm{KL}}!\left[\pi_\theta ,|, \pi_{\text{ref}}\right] \]
RLHFに対するRLVRの主な利点は、 報酬モデルの誤りがないこと です。報酬は学習済みモデルではなく決定的な検証器で計算されるため、欠陥のある報酬モデルを相手にした報酬ハッキングがありません。唯一の失敗モードは、検証には通るが本当は正しくない解をモデルが見つける場合です(コード評価でテストケースの弱点を悪用するなど)。
Note
コードのRLVR:報酬ハッキングの課題
コード生成では、検証器はテストスイートです。RLVRで学習したモデルは、次のことを学習できます。
テスト出力をハードコードする :実際のアルゴリズムを実装せず、各テスト入力に対する期待出力を返す
弱いテストを悪用する :提供されたテストにはすべて合格する一方、エッジケースでは失敗する
対策には、大規模で多様なテストスイートの利用、敵対的なテストケースの追加、ハードコードを罰する実行ベースの報酬の利用(解が\(O(n \log n)\)時間で動くことをチェックするなど)があります。
Journey Learning
Journey Learning (Yiwei Qin et al. 2024)は、成功した最終解だけでなく、失敗した試行と修正を含む 完全な推論軌跡 で学習することを提案します。
動機
標準的な棄却サンプリングは失敗した試行を捨てます。しかし、失敗した試行には価値ある情報が含まれます。
-
どのアプローチがうまくいかないか(負例)
-
どのように誤りを認識し、回復するか(修正パターン)
-
問題空間の構造(探索データ)
Journey Learningの目的関数
バックトラックを含む可能性のある軌跡\(\tau = (s_0, a_0, s_1, a_1, \ldots, s_T)\)が与えられたとき:
\[ \mathcal{L}_{\text{journey}}(\theta) = -\sum_{t=0}^{T} w_t \log \pi_\theta(a_t \mid s_t) \]
ここで重み\(w_t\)は、次を重視するよう設計されます。
-
最終的な成功につながるステップ (\(w_t > 1\))
-
誤りの後の修正ステップ (\(w_t > 1\))
-
失敗した枝のステップ (\(w_t < 1\), but \(> 0\))
Quiet-STaR:すべてのトークンで推論する
Quiet-STaR (Zelikman et al. 2024)は推論パラダイムを すべてのトークン位置 へ拡張します。最終回答の前だけに推論チェーンを生成するのではなく、モデルは各トークン位置で「思考」を生成します。
定式化
各トークン位置\(t\)について、モデルは次のトークン\(z_t\)を予測する前に、隠れた思考\(x_{t+1}\)を生成します。
\[ P(x_{t+1} \mid x_{\leq t}) = \mathbb{E}_{z_t \sim \pi_\theta(\cdot | x_{\leq t})} \left[ \pi_\theta(x_{t+1} \mid x_{\leq t}, z_t) \right] \]
実際には、思考あり・なしの予測を混ぜることで近似します。
\[ P(x_{t+1} \mid x_{\leq t}) = \alpha \cdot \pi_\theta(x_{t+1} \mid x_{\leq t}, z_t) + (1-\alpha) \cdot \pi_\theta(x_{t+1} \mid x_{\leq t}) \]
REINFORCEによる学習
思考\(z_t\)は離散潜在変数なので、勾配はREINFORCEを使って推定します。
\[ \nabla_\theta \mathcal{L}_{\text{QS}} = \mathbb{E}_{z_t} \left[ \nabla_\theta \log \pi_\theta(z_t \mid x_{\leq t}) \cdot \left( \log P(x_{t+1} \mid x_{\leq t}, z_t) - b_t \right) \right] \]
ここで\(b_t\)はベースラインです(例えば、思考なしの予測\(\log \pi_\theta(x_{t+1} \mid x_{\leq t})\))。
Warning
Quiet-STaRの計算コスト
Quiet-STaRは推論コストを\(L_z + 1\)倍に増やします。ここで\(L_z\)は思考の長さで、すべてのトークン位置に適用されます。長さ\(T\)の系列で思考の長さが\(L_z = 8\)なら、計算量は\(9\times\)に増加します。そのため、十分なエンジニアリング最適化(思考に対する投機的デコーディング、キャッシュなど)なしでは、長い系列にQuiet-STaRを適用するのは現実的ではありません。
推論のスケーリング則
近年の研究 (Snell et al. 2024; Zhenyu Wu et al. 2024)は、テスト時計算量が推論性能に対して予測可能にスケールすることを確立し、古典的なスケーリング則 (Kaplan et al. 2020)を推論の領域へ拡張しました。
学習計算量とテスト時計算量のトレードオフ
推論モデルにおける根本的なスケーリングの問いは、 総計算予算\(C_{\text{total}} = C_{\text{train}} + N \cdot C_{\text{test}}\)(\(N\)はクエリ数)が固定されているとき、計算量をどう配分すべきか です。
\(\mathcal{A}(C_{\text{train}}, C_{\text{test}})\)を、\(C_{\text{train}}\)FLOPsで学習し、クエリごとに\(C_{\text{test}}\)の推論FLOPsを与えたモデルの精度とします。実証的には次のとおりです。
\[ \mathcal{A}(C_{\text{train}}, C_{\text{test}}) \approx 1 - \exp!\left(-a \cdot C_{\text{train}}^{\alpha} \cdot C_{\text{test}}^{\beta}\right) \]
定数\(a, \alpha, \beta > 0\)に対して成り立ちます。総予算\(C_{\text{total}}\)を固定したときの最適な配分は、学習と推論の間でFLOPあたりの限界リターンが等しくなるという条件を満たします。
\[ \frac{\partial \mathcal{A}}{\partial C_{\text{train}}} = \frac{1}{N} \cdot \frac{\partial \mathcal{A}}{\partial C_{\text{test}}} \]
直感的には、学習の1 FLOPはすべての\(N\)個のクエリに恩恵を与えますが、テスト時の1 FLOPは一つのクエリにしか効きません。最適点では、テスト時計算量のクエリあたりの限界価値は\(N\)倍になります(学習の効果が償却されるため)。これを式 [eq:reasoning_scaling_law]に適用すると、最適な学習計算量の比率が得られます。
\[ \frac{C_{\text{train}}^*}{C_{\text{total}}} = \frac{\alpha}{\alpha + \beta} \]
特定の予算構造\(C_{\text{total}} = C_{\text{train}} + N \cdot C_{\text{test}}\)では、この比率は\(N\)に依存しません。しかし実際には\(\alpha\)、\(\beta\)は問題依存です。大量配備(大きな\(N\))でも、ベースモデルのわずかな改善でさえ支配的になり、学習への投資が有利です。少量で高リスクのクエリ(小さな\(N\))では、テスト時計算量の方が費用対効果に優れます。
より長いチェーンとより良いベースモデルのどちらに投資するか
Important
推論チェーンの長さとモデル容量
最適な推論チェーン長\(L^*\)は、容量\(C\)のモデルが難易度\(D\)の問題に対して満たす条件は次のとおりです。 \[ L^* \propto \frac{D}{C^{\gamma}} \] ある\(\gamma > 0\)に対して成り立ちます。これは次を意味します。
難しい問題 では、モデルサイズにかかわらず長いチェーンが必要
大きなモデル では、同じ難易度の問題に対して短いチェーンで済む
限界効用の低下 :\(L^*\)を超えると、追加トークンに利点がなく、害になることもある(考えすぎ)
「考えすぎ」現象、つまり非常に長い推論チェーンを持つモデルが中程度のチェーンを持つモデルより悪い性能を示す現象は、実証的に観測されています。その原因は次のように考えられます。
-
長いチェーンでの誤りの蓄積(誤りの伝播)
-
主な解決経路からの注意の逸脱
-
誤った中間結論への過信
最適なトークン予算配分
トークン予算\(B\)が固定されたモデルについて、「思考」トークン\(T_{\text{think}}\)と「回答」トークン\(T_{\text{answer}}\)の配分は次を満たすべきです。
\[ T_{\text{think}}^* = \arg\max_{T} \mathcal{A}(T, B - T) \]
実証的には、最適な分割は問題に依存します。
-
単純な問題 : \(T_{\text{think}}^* / B \approx 0.3\)(30%を思考に使う)
-
難しい問題 : \(T_{\text{think}}^* / B \approx 0.8\)(80%を思考に使う)
-
非常に難しい問題 : \(T_{\text{think}}^* / B \approx 0.95\)(95%を思考に使い、回答は最小限)
これは 適応的な思考予算 を動機づけます。難しい問題により多くのトークンを割り当てます。難易度は、最初の解答試行におけるモデルの不確実性から推定できます。
推論モデルの比較
| 手法 | PRM | ORM | MCTS | 蒸留 | ツール | オープン |
|---|---|---|---|---|---|---|
| OpenAI o1/o3 | 不明 | – | \(\times\) | |||
| DeepSeek-R1 | \(\times\) | \(\times\) | \(\times\) | |||
| QwQ / Qwen3 | 一部 | \(\times\) | \(\times\) | |||
| AlphaProof | – | \(\times\) | ||||
| Math-Shepherd | \(\times\) | – | \(\times\) | |||
| STaR / Quiet-STaR | \(\times\) | \(\times\) | – | \(\times\) |
推論モデルの学習手法の比較。
まとめと未解決問題
推論モデルのRL分野は驚くほど急速に進展しました。いくつかの重要な教訓が得られています。
-
検証可能な報酬で十分 :正解による検証が可能な領域(数学、コード)では、プロセス報酬モデルなしでも、結果だけの報酬でRLが高度な推論戦略を発見できる。
-
テスト時計算量は新しい軸 :推論モデルは、難しい推論タスクでは学習計算量とおおむね代替可能な新しいスケーリングの次元、すなわち推論計算量を導入する。
-
蒸留は非常に効果的 :大規模推論モデルは、生成チェーンに対する教師ありファインチューニングによって、はるかに小さなモデルへ能力を移転できる。小規模モデルへの直接RL学習を上回ることも多い。
-
創発的なメタ認知 :推論タスクに対するRL学習は、明示的に学習されていなかった自己修正・検証の振る舞いを創発させる。
Note
推論のRLにおける未解決問題
いくつかの根本的な問いが未解決のままです。
汎化 :数学・コードで学習した推論能力は、他の領域(科学的推論、計画、社会的推論)へ移転するか。
忠実性 :生成された推論チェーンは最終回答の因果的な原因なのか、それとも事後的な合理化なのか。
最適な探索 :推論時の最適な探索戦略は何か。ビームサーチ、MCTS、それとも別の手法か。
報酬設計 :正解検証器がない領域で、推論のための信頼できる報酬信号をどう設計できるか。
考えすぎ :少なすぎず多すぎない、適切な思考量を割り当てる方法をモデルはどう学べるか。
構成的推論 :RLで学習した推論モデルは、複数の異なる推論スキルの組み合わせを必要とする問題を解けるか。
推論モデルの発展はパラダイムシフトを表しています。つまり、物事を知っている言語モデルから、物事を解明できる言語モデルへの移行です。本節で説明したRL手法はこの転換を推進する主要なエンジンであり、その継続的な発展は今後数年間のAI研究の中心的な焦点になるでしょう。
LLM評価
評価は、厳密な機械学習パイプラインの基盤です。それにもかかわらず、大規模言語モデルの開発では最も過小評価されている要素かもしれません。正解ラベル付きのホールドアウトテストセットが明確な信号を与える古典的な教師あり学習とは異なり、LLMの評価では、オープンエンド生成、主観的な品質判断、多段階の推論チェーン、そして常につきまとうベンチマーク汚染のリスクに向き合う必要があります。本節では、評価タイプの分類、人手アノテーションの仕組み、ランキング指標の数学、LLM-as-judgeの実務、評価パイプラインを静かに壊す落とし穴まで、評価の全体像を体系的に扱います。
Important
LLMの評価が難しい理由
3つの根本的な課題 が、LLM評価を古典的なML評価と区別します。
出力空間は無限です。 言語モデルは任意の文字列を生成でき、単一の正解が存在することはほとんどありません。
品質は多次元です。 有用性、事実性、安全性、一貫性、スタイルは別々の軸であり、互いにトレードオフになることがあります。
評価自体が言語タスクです。 応答が良いかを判断するには理解が必要であり、評価も生成と同じ失敗モードの影響を受けます。
評価スキームの設計
1つのデータ点を集める前に、実務者は何を測定し、どのように測定するかを決めなければなりません。原理に基づく分類は、配備目的との整合ではなく、便利さで指標を選ぶというよくある誤りを防ぎます。
評価タイプの分類
内在的評価と外在的評価
内在的評価は、下流アプリケーションを参照せず、モデル出力そのものの性質を測定します。ホールドアウトコーパスのパープレキシティ、参照訳に対するBLEUスコア、コーディングベンチマークのpass@\(k\)はいずれも内在的評価です。外在的評価は、現実世界のタスクやシステムに対するモデルの影響を測定します。LLMをカスタマーサービスのパイプラインに統合すると、チケットのエスカレーション率は下がるでしょうか。コーディングアシスタントは開発者の生産性を高めるでしょうか。
Tip
内在的評価と外在的評価の隔たり
内在的指標は安価で再現可能ですが、現実世界の有用性との相関が低いことがよくあります。パープレキシティが低いモデルが必ずしも有用とは限りません。外在的指標は高価で時間がかかりますが、私たちが重視するものを直接測定します。成熟した評価戦略では、迅速な反復に内在的指標を使い、最終検証に外在的指標を使います。
自動評価と人手評価
自動評価は、決定論的な関数(BLEU、完全一致)や学習済みモデル(BERTScore、LLM-as-judge)を使い、人手を介さずに出力を採点します。人手評価では、アノテーターがモデル出力を評価または順位付けします。表 1.1にトレードオフをまとめます。
| タイプ | コスト | 速度 | 再現性 | 妥当性 |
|---|---|---|---|---|
| ルールベース自動 | とても低い | とても速い | 完全 | 低〜中 |
| モデルベース自動 | 低い | 速い | 高い | 中〜高 |
| クラウドソーシング人手 | 中程度 | 日単位 | 中程度 | 中程度 |
| 専門家による人手 | 高い | 週単位 | 低〜中 | 高い |
| 外在的/A/Bテスト | とても高い | 月単位 | 低い | とても高い |
評価アプローチの分類と主なトレードオフ。 {#tab:eval-taxonomy}
参照ベース評価と参照なし評価
参照ベース指標(BLEU、ROUGE、BERTScore)は、モデル出力を1つ以上のゴールド標準参照と比較します。参照なし指標(パープレキシティ、LLM-as-judge、人手の選好)は、参照を使わずに品質を評価します。オープンエンド対話のように、出力空間が網羅的な参照収集には大きすぎる場合、参照なしのアプローチが不可欠です。
何をいつ使うか
Note
対話アシスタントの評価戦略
開発フェーズ: 自動指標(パープレキシティ、要約サブタスクのROUGE、ツール利用のpass@\(k\))を使って迅速に反復します。標準スイート(MMLU、HellaSwag、HumanEval)で毎晩ベンチマークを実行します。
リリース前フェーズ: 新しいモデルと前のチェックポイントを比較する人手選好研究を実施します。多様なプロンプト集合で、スケール可能なペアワイズ比較にLLM-as-judgeを使います。
リリース後フェーズ: 外在的指標(ユーザー満足度スコア、タスク完了率)を監視し、本番プロンプトの分布シフトに注意します。
役立つ意思決定フレームワーク:
-
タスクに明確な正解(数学、コード、事実に基づくQA)がある場合:完全一致または実行ベースの指標を使う。
-
タスクがオープンエンドだが参照出力がある場合:参照ベース指標を下限として使い、LLM-as-judgeで補う。
-
タスクが主観的(有用性、語調、創造性)な場合:人手評価または十分に校正されたLLM judgeを使う。
-
タスクが多段階のエージェント振る舞いを含む場合:タスク成功率と軌跡効率(節 1.6)を使う。
評価のためのデータ収集
高品質な評価データは、信頼できるベンチマークの基盤です。本節では、人手アノテーションパイプラインの設計、アノテーション品質の統計的指標、クラウドソーシングと専門家アノテーションの選択を扱います。
人手アノテーションパイプライン
頑健なアノテーションパイプラインは5段階で構成されます。
-
タスク定義。 アノテーションタスクを正確に指定する:何を、どの尺度で、どの基準で評価するか。この段階の曖昧さはノイズの多いラベルへ伝播します。
-
ガイドラインの作成。 エッジケースを扱う実例付きのアノテーションガイドラインを書く。全面展開の前に、小規模なパイロットグループで反復する。
-
アノテーターの募集と訓練。 適切な背景知識を持つアノテーターを選ぶ。アノテーターが同じ例にラベルを付け、意見の相違を議論するキャリブレーションセッションを行う。
-
品質管理。 既知のラベルを持つゴールド標準の例をアノテーションキューに埋め込む。ゴールド例に対する精度がしきい値を下回るアノテーターにフラグを立てる。
-
集約。 多数決、平均、確率モデル(Dawid–Skeneなど)を使い、項目ごとの複数アノテーションを統合する。
アノテーター間一致度
生の一致度(すべてのアノテーターが一致する項目の割合)は、偶然の一致を考慮しないため不十分な指標です。偶然一致を補正する標準的な指標は、Cohenの \(\kappa\) (Cohen 1960)(2人のアノテーター)とFleissの \(\kappa\) (Fleiss 1971)(複数のアノテーター)です。
Cohenのカッパ
2人のアノテーターが \(N\) 個の項目を \(k\) カテゴリに分類するとき、\(p_o\) を観測一致度、\(p_e\) を独立性の下での期待一致度とします。
\[ \kappa = \frac{p_o - p_e}{1 - p_e} \label{eq:cohens-kappa} \]
ここで
\[ p_o = \frac{1}{N}\sum_{i=1}^{N} \mathbf{1}[\text{annotator 1 agrees with annotator 2 on item } i] \]
さらに、
\[ p_e = \sum_{c=1}^{k} p_{1c} \cdot p_{2c} \]
ここで \(p_{jc}\) はカテゴリ \(c\) にアノテーター \(j\) が割り当てた項目の割合です。Cohenの \(\kappa\) は \(-1\)(完全不一致)から \(0\)(偶然一致)を経て \(1\)(完全一致)までの範囲を取ります。\(0.6\) は一般に許容可能で、\(0.8\) は強い一致です。
Fleissのカッパ
\(n\) 人のアノテーターが \(N\) 個の項目を \(k\) カテゴリに分類するとき、\(n_{ij}\) は項目 \(i\) をカテゴリ \(j\) に割り当てたアノテーター数です。次を定義します。
\[ \bar{P}_i = \frac{1}{n(n-1)} \sum_{j=1}^{k} n_{ij}(n_{ij} - 1), \qquad \bar{P} = \frac{1}{N}\sum_{i=1}^{N}\bar{P}_i \]
\[ \bar{P}_j^e = \frac{1}{Nn}\sum_{i=1}^{N} n_{ij}, \qquad P_e = \sum_{j=1}^{k} \left(\bar{P}_j^e\right)^2 \]
\[ \kappa_F = \frac{\bar{P} - P_e}{1 - P_e} \]
Warning
カッパの限界
カッパはカテゴリの出現率に敏感です。1つのカテゴリが支配的な場合、生の一致度が高くてもカッパが低くなることがあります(カッパのパラドックス)。順序尺度では、重み付きカッパ(距離に比例して不一致を罰する)がより適切です。LLM評価では、評価が1〜5のリッカート尺度で行われることが多いため、常に重み付きカッパを報告します。
アノテーションガイドラインの設計
効果的なアノテーションガイドラインには、いくつかの共通する性質があります。
-
操作可能な基準。 「有用」のような曖昧な語を、具体的で観測可能な振る舞いに置き換える:「応答はユーザーの質問に直接答え、述べられたタスクを完了するために必要なすべての情報を提供している」。
-
実例。 境界事例を含め、各評価レベルについて少なくとも2つの例を提示する。
-
意思決定木。 複雑なタスクでは、二値判断の系列をアノテーターに案内するフローチャートが認知負荷を減らし、一貫性を高める。
-
明示的な範囲。 アノテーターが考慮すべきでないことを明示する(例:「文体の好みで減点せず、事実の正確さだけに注目する」)。
クラウドソーシングと専門家アノテーション
| 項目 | クラウドソーシング | 専門家アノテーション |
|---|---|---|
| 1項目あたりのコスト | 低(\(0.01--\)0.10) | 高(\(1--\)50) |
| 処理量 | とても高い | 低い |
| ドメイン知識 | 低い | 高い |
| 一貫性 | ばらつきあり | 高い |
| 適するタスク | 単純な選好、流暢さ | 技術的正確さ、安全性 |
| プラットフォーム | MTurk、Prolific、Scale AI | 分野専門家、社内 |
| 品質管理 | ゴールド例、注意チェック | キャリブレーション、ピアレビュー |
LLM評価におけるクラウドソーシングと専門家アノテーションの比較。
安全性が重要な評価(有害出力の検出、医療助言の評価など)では、専門家アノテーションは不可欠です。大規模な選好収集(報酬モデルの学習セット構築など)では、厳格な品質管理付きのクラウドソーシングが、多くの場合、実行可能な唯一の選択肢です。
評価のための合成データ生成
人手アノテーションは高価で時間がかかります。合成データ生成では、LLM自身を使って評価データを大規模に作成します。本節では主要なパラダイムを扱います。
キャリブレーションのためのLLM-as-Judge
LLMを使って評価ラベルを生成する場合、キャリブレーションが不可欠です。判定モデルのスコアは人手判断と整合していなければなりません。\(h_i \in [0,1]\) は項目 \(i\) の人手選好スコア、\(\hat{h}_i\) は判定モデルの予測スコアとします。キャリブレーション誤差はExpected Calibration Error(ECE) (Guo et al. 2017):
\[ \text{ECE} = \sum_{b=1}^{B} \frac{|B_b|}{n} \left| \text{acc}(B_b) - \text{conf}(B_b) \right| \]
ここで \(B_b\) は \(b\) 番目の信頼度ビン、\(\text{acc}(B_b)\) は そのビンで判定モデルと人間が一致する項目の割合、 \(\text{conf}(B_b)\) は そのビンにおける判定モデルの平均信頼度です。
キャリブレーションが良好な判定モデルは、\(\mathbb{E}[\hat{h}_i \mid \hat{h}_i = p] = p\) をすべての \(p \in [0,1]\) で満たします。キャリブレーションは温度スケーリングで改善できます。判定モデルの生のロジット \(z\) を \(z/T\) に置き換えます。ここで \(T\) は学習に使っていないキャリブレーション集合で負の対数尤度が最小になるよう調整します。
Self-Instruct
Self-Instruct (Wang et al. 2022)は、人手で書かれたタスクのシード集合から指示追従データをブートストラップします。アルゴリズムは次のとおりです。
-
タスクプールを \(175\) 個のシードタスクで初期化します。
-
プールから \(8\) 個のタスクをサンプリングし、少数ショット例として使ってLLMに新しいタスクを生成させます。
-
生成タスクをフィルタリングします。近重複(既存タスクとのROUGE-L類似度 \(> 0.7\) 以上)を除去し、classificationとnon-classificationに分類して、入力・出力インスタンスを生成します。
-
採用したタスクをプールに追加します。
-
目的のプールサイズに達するまで繰り返します。
Note
Self-Instructプロンプトテンプレート
system_prompt = """ Come up with a series of tasks: Task 1: {seed_task_1_instruction} Task 2: {seed_task_2_instruction} ... Task 8: {seed_task_8_instruction} Task 9:"""モデルはプロンプトを完成させ、新しいタスク指示を生成します。続く別のプロンプトが、新しいタスクの入力・出力ペアを生成します。
Evol-Instruct
Evol-Instruct (Xu et al. 2023)は、指示を反復的に書き換えてより複雑または多様にすることで、シード指示集合を発展させます。2つの進化演算子を適用します。
-
深さ方向の進化: 制約を追加し、推論ステップを増やし、抽象化を具体化し、ドメイン知識の要件を深める。
-
幅方向の進化: 関連するが異なるトピックについて新しい指示を生成し、トピックの多様性を高める。
指示は、排除フィルターを通過した場合に採用されます。進化した指示は単純なコピーではなく、「申し訳ありません」などの拒否を含まず、元の指示より短くない必要があります。
Constitutional AIデータ生成
Constitutional AI(CAI)(Bai et al. 2022)は、一連の原則(「constitution」)に従ってモデル自身の出力を批評・改訂させることで選好データを生成します。パイプラインは次のとおりです。
-
教師あり学習フェーズ: 有害なプロンプトをサンプリングして初期応答を生成し、憲法上の原則に従って応答を批評・改訂するようモデルに促します。改訂後の応答を教師ありファインチューニングのターゲットとして使います。
-
RLフェーズ: 応答のペア(元の応答と改訂後の応答)を生成し、どちらがより原則に適合するかをモデルにラベル付けさせ、そのラベルで選好モデルを学習します。選好モデルをRLHFの報酬信号として使います。
この方法では、有害な内容を人手でラベル付けせずに選好データを生成できるため、アノテーターが苦痛を伴う素材に触れる機会を減らせます。
評価データの蒸留
強力な教師モデル(例:GPT-4)は、より小さな判定モデルを学習するための高品質な評価データを生成できます。蒸留パイプラインは次のとおりです。
-
多様なプロンプトとモデル応答の集合を収集する。
-
教師モデルに詳細な判定(スコアと根拠)を生成させる。
-
(プロンプト、応答、判定)の三つ組で小さなモデルをファインチューニングする。
-
学習に使っていない人手アノテーションに対して、学生判定モデルを検証する。
Warning
蒸留バイアス
単一の教師モデルから蒸留された学生判定モデルは、教師モデルのバイアスを引き継ぎます。これには、冗長性バイアス(長い応答を好む)、自己強化バイアス(教師モデル自身が評価対象でもある場合)、位置バイアスが含まれます。蒸留した判定モデルは、必ず独立した人手アノテーションに対して検証してください。
Arena形式のペアワイズ生成
Chatbot Arena (Zheng et al. 2023)は、ユーザーがプロンプトを投稿し、匿名化された2つのモデル応答のどちらを好むか投票するクラウドソーシング型の対戦プラットフォームを通じて評価データを生成します。これにより、大規模で自然に多様なペアワイズ選好データセットが得られます。主な設計上の選択は次のとおりです。
-
匿名化: ブランドバイアスを防ぐため、モデルの識別情報を隠す。
-
ユーザー投稿プロンプト: プロンプトの多様性と現実世界への関連性を確保する。
-
引き分けの扱い: ユーザーは引き分け、または両方の応答が悪いと申告できる。
-
重複除去: 似すぎたプロンプトを除外し、よくある質問が過剰に代表されるのを防ぐ。
ランキングタスクの指標
モデルを品質順にランキングすることが目的なら、ペアワイズ比較データは絶対スコアより信頼性が高くなります。本節では、LLM評価で使われる主要なランキングシステムを導出します。
ELOレーティングシステム
もともとチェス向けに開発されたELOシステム (Elo 1978)は、各プレイヤー(モデル)にスカラーのレーティング \(R\) を割り当て、プレイヤー \(A\) がプレイヤー \(B\) と対戦したときの期待スコアは次のとおりです。
\[ E_A = \frac{1}{1 + 10^{(R_B - R_A)/400}} \]
導出
ELOモデルでは、各プレイヤーの特定のゲームにおけるパフォーマンスは、レーティングを中心とするロジスティック分布から抽出された確率変数だと仮定します。\(A\) が \(B\) に勝つ確率は次のとおりです。
\[ P(A \succ B) = \sigma!\left(\frac{R_A - R_B}{s}\right) = \frac{1}{1 + e^{-(R_A - R_B)/s}} \]
ここで \(s = 400/\ln(10) \approx 173.7\) は、400ポイントの差が \(10:1\) のオッズ比に対応するよう選ばれたスケールパラメータです。結果 \(S_A \in \{0, 0.5, 1\}\)(敗北、引き分け、勝利)の各ゲーム後に、レーティングを次のように更新します。
\[ R_A \leftarrow R_A + K(S_A - E_A), \qquad R_B \leftarrow R_B + K(S_B - E_B) \]
ここで \(K\) は\(K\)ファクターで、学習率を制御します。Chatbot Arenaでは \(K = 4\) を使います。
Tip
ELOの直観
ELOは、ロジスティックモデルの下で観測された結果の対数尤度に対する確率的勾配降下の更新です。各ゲームはノイズを含む勾配信号を与え、\(K\)ファクターがステップ幅を制御します。\(K\) は大きいとすばやく適応しますがノイズが多く、小さい \(K\) は安定する一方で真の技能変化の反映が遅くなります。
ELOのブートストラップ信頼区間
ELOレーティングはゲームを処理する順序に依存するため、信頼区間はブートストラップ再標本化で計算します。対戦ログを復元抽出で \(B = 1000\) 回再標本化し、各標本についてELOレーティングを最初から再計算して、2.5パーセンタイルと97.5パーセンタイルを95%信頼区間として報告します。
Bradley–Terryモデル
Bradley–Terry(BT)モデル (Bradley and Terry 1952)は、ELOに対する最尤法ベースの代替手法です。\(n\) 個のモデルがあり、強さパラメータ \(\beta_1, \ldots, \beta_n > 0\) に対して、モデル \(i\) がモデル \(j\) に勝つ確率は次のとおりです。
\[ P(i \succ j) = \frac{\beta_i}{\beta_i + \beta_j} \]
ペアワイズ結果の集合 \(\{(i_k, j_k, y_k)\}_{k=1}^{M}\) で、\(y_k = 1\) のとき \(i_k\) が \(j_k\) に勝ち、\(y_k = 0\) それ以外では0となり、対数尤度は次のようになります。
\[ \ell(\boldsymbol{\beta}) = \sum_{k=1}^{M} \left[ y_k \log \frac{\beta_{i_k}}{\beta_{i_k} + \beta_{j_k}} + (1-y_k) \log \frac{\beta_{j_k}}{\beta_{i_k} + \beta_{j_k}} \right] \]
MLE \(\hat{\boldsymbol{\beta}}\) は反復スケーリングまたは勾配上昇法で求めます。BTモデルは乗法定数の違いを除いてのみ識別可能で、一般的な正規化は \(\sum_i \log \beta_i = 0\) です。\(\theta_i = \log \beta_i\) として対数空間で計算すると、次を得ます。
\[ P(i \succ j) = \sigma(\theta_i - \theta_j) \]
これは項目ごとの切片を持つロジスティック回帰と等価です。全対戦履歴が利用できる場合、BTモデルはゲームを逐次処理するのではなく全データを同時に使うため、ELOより好まれます。
TrueSkill
TrueSkill (Herbrich et al. 2006)は、各プレイヤーの技能をガウス確率変数 \(s_i \sim \mathcal{N}(\mu_i, \sigma_i^2)\) としてモデル化するベイズ技能レーティングシステムです。ゲームにおけるプレイヤー \(i\) のパフォーマンスは \(p_i = s_i + \epsilon_i\) であり、\(\epsilon_i \sim \mathcal{N}(0, \beta^2)\) はゲーム固有のノイズです。プレイヤー \(i\) はプレイヤー \(j\) に勝ちます(\(p_i > p_j\))。
観測した \(i \succ j\) の後の事後分布の更新は、Expectation Propagation(EP)で計算します。勝者に対する主要な更新式は次のとおりです。
\[ \mu_i \leftarrow \mu_i + \frac{\sigma_i^2}{c} \cdot v!\left(\frac{\mu_i - \mu_j}{c}\right) \]
\[ \sigma_i^2 \leftarrow \sigma_i^2 \left[1 - \frac{\sigma_i^2}{c^2} \cdot w!\left(\frac{\mu_i - \mu_j}{c}\right)\right] \]
ここで \(c = \sqrt{2\beta^2 + \sigma_i^2 + \sigma_j^2}\)、\(v(t) = \phi(t)/\Phi(t)\), \(w(t) = v(t)(v(t) + t)\) は切断ガウスの補正係数です(\(\phi\) と \(\Phi\) は標準正規分布のPDFとCDFです)。TrueSkillの不確実性推定値 \(\sigma_i\) は、より多くの評価データを必要とするモデルを特定するのに特に役立ちます。
信頼区間付き勝率
最も単純なランキング指標は勝率、つまりモデル \(A\) が好まれたペアワイズ比較の割合です。\(n\) 回の比較で \(w\) 勝のとき、勝率は \(\hat{p} = w/n\) です。Wilsonスコア信頼区間 (Wilson 1927)は、\(p = 0\) や \(p = 1\) の近傍での被覆率が単純なWald区間より優れているため、こちらが推奨されます。
\[ \text{CI} = \frac{\hat{p} + \frac{z^2}{2n} \pm z\sqrt{\frac{\hat{p}(1-\hat{p})}{n} + \frac{z^2}{4n^2}}}{1 + \frac{z^2}{n}} \]
95%区間では \(z = 1.96\) です。多方向比較では、比較可能性を確保するため、固定したベースラインモデルに対して勝率を計算します。
Chatbot Arenaの方法論
Chatbot Arena (Zheng et al. 2023)は、以上の要素を本番規模の評価システムに組み合わせています。
-
ユーザーがプロンプトを投稿し、匿名化された2つのモデルから応答を受け取る。
-
ユーザーが好ましい応答に投票する(または引き分けを申告する)。
-
投票をBTモデルで集約し、リーダーボードを作成する。
-
各モデルのスコアについてブートストラップ信頼区間を報告する。
-
信頼区間が重なるモデルは、統計的に区別できないとみなす。
2024年時点で、Chatbot Arenaは100万票を超える人手の選好投票を集めており、公開されているLLM選好データセットとして最大規模です。
生成タスクの指標
生成指標は、参照解答または明確に定義された正解基準があるタスクにおける、モデル出力の品質を定量化します。
BLEU
BLEU(Bilingual Evaluation Understudy) (Papineni et al. 2002)は、\(n\)グラム適合率を、仮説 \(h\) と1つ以上の参照 \(\mathcal{R}\) の間で測定します。
\[ \text{BLEU} = \text{BP} \cdot \exp!\left(\sum_{n=1}^{N} w_n \log p_n\right) \]
ここで \(p_n\) は修正 \(n\)グラム適合率、\(w_n = 1/N\)は一様重み、BPは短さペナルティです。
\[ \text{BP} = \begin{cases} 1 & \text{if } |h| > |r| \\ e^{1 - |r|/|h|} & \text{if } |h| \leq |r| \end{cases} \]
ここで \(\vert r\vert\) は最も近い参照の長さです。修正 \(n\)グラム適合率では、各 \(n\)グラムのカウントを、いずれかの参照における最大カウントでクリップします。
\[ p_n = \frac{\sum_{\text{ngram} \in h} \min!\left(\text{count}(\text{ngram}, h),, \max_{r \in \mathcal{R}} \text{count}(\text{ngram}, r)\right)}{\sum_{\text{ngram} \in h} \text{count}(\text{ngram}, h)} \]
Warning
BLEUの限界
BLEUは複数の参照を使う機械翻訳向けに設計されました。参照が1つのオープンエンド生成では、高品質な出力でもBLEUスコアがほぼゼロになることがあります。BLEUは意味的類似性を捉えず、有効な言い換えを罰し、トークン化にも敏感です。多様な参照が複数あり、出力の多様性が低いタスクでのみBLEUを使ってください。
ROUGE
ROUGE(Recall-Oriented Understudy for Gisting Evaluation) (Lin 2004)は、要約向けに設計された再現率重視の指標群です。
\[ \begin{aligned} \text{ROUGE-N} &= \frac{\sum_{r \in \mathcal{R}} \sum_{\text{ngram} \in r} \min(\text{count}(\text{ngram}, h), \text{count}(\text{ngram}, r))}{\sum_{r \in \mathcal{R}} \sum_{\text{ngram} \in r} \text{count}(\text{ngram}, r)} \\[6pt] \text{ROUGE-L} &= \frac{\text{LCS}(h, r)}{|r|} \end{aligned} \]
ここでLCSは最長共通部分列を表します。ROUGE-1とROUGE-2はそれぞれユニグラムとバイグラムの再現率を測定し、ROUGE-Lは文レベルの構造を捉えます。F値の変種は適合率と再現率のバランスを取ります。
\[ \text{ROUGE-N}_F = \frac{(1+\beta^2) \cdot P \cdot R}{\beta^2 P + R} \]
等しい重み付けでは \(\beta = 1\) とします。
BERTScore
BERTScore (Zhang et al. 2020)は、事前学習済みBERTモデルの文脈埋め込みを使ってトークンレベルの類似度を計算します。仮説トークン \(\hat{\mathbf{x}} = \langle \hat{x}_1, \ldots, \hat{x}_m \rangle\) と参照トークン \(\mathbf{x} = \langle x_1, \ldots, x_n \rangle\) があり、埋め込み \(\hat{\mathbf{e}}_i\) と \(\mathbf{e}_j\) に対して、次のように定義します。
\[ \begin{aligned} R_{\text{BERT}} &= \frac{1}{|x|} \sum_{x_j \in \mathbf{x}} \max_{\hat{x}_i \in \hat{\mathbf{x}}} \frac{\hat{\mathbf{e}}_i^\top \mathbf{e}_j}{|\hat{\mathbf{e}}_i| |\mathbf{e}_j|} \\[4pt] P_{\text{BERT}} &= \frac{1}{|\hat{x}|} \sum_{\hat{x}_i \in \hat{\mathbf{x}}} \max_{x_j \in \mathbf{x}} \frac{\hat{\mathbf{e}}_i^\top \mathbf{e}_j}{|\hat{\mathbf{e}}_i| |\mathbf{e}_j|} \\[4pt] F_{\text{BERT}} &= 2 \cdot \frac{P_{\text{BERT}} \cdot R_{\text{BERT}}}{P_{\text{BERT}} + R_{\text{BERT}}} \end{aligned} \]
BERTScoreは、特に言い換えや、語彙は異なるが意味的に等価な出力について、BLEUやROUGEより人手判断との相関が高くなります。逆文書頻度(IDF)を使った重要度重み付けにより、相関はさらに改善します。
\[ R_{\text{BERT}}^{\text{idf}} = \frac{\sum_{x_j \in \mathbf{x}} \text{idf}(x_j) \max_{\hat{x}_i} \cos(\hat{\mathbf{e}}_i, \mathbf{e}_j)}{\sum_{x_j \in \mathbf{x}} \text{idf}(x_j)} \]
METEOR
METEOR (Banerjee and Lavie 2005)は、ユニグラム一致に対するFスコアを計算し、語幹処理と同義語マッチングの追加モジュールを使うことで、BLEUの再現率軽視に対処します。
\[ \text{METEOR} = F_{\text{mean}} \cdot (1 - \text{Pen}) \]
ここで \(F_{\text{mean}} = \frac{10PR}{R + 9P}\)(再現率重み付き調和平均)であり、断片化ペナルティ \(\text{Pen} = 0.5 \cdot (c/u_m)^3\) は連続しない一致を罰します(\(c\) はチャンク数、\(u_m\) は一致したユニグラム数)。
パープレキシティ
パープレキシティは、言語モデルがホールドアウトされたテキスト系列 \(w_1, w_2, \ldots, w_T\) をどれだけ適切に予測できるかを測定します。
\[ \text{PPL}(w_{1:T}) = \exp!\left(-\frac{1}{T}\sum_{t=1}^{T} \log P_\theta(w_t \mid w_{1:t-1})\right) \]
パープレキシティが低いほど予測性能が高いことを示します。パープレキシティは、同じトークン化とテストセット上でモデルを比較するのに有用ですが、語彙やトークナイザーが異なるモデル間では直接比較できません。評価では、健全性チェックと分布シフトの検出に最も役立ちます。
コードのPass@k
コード生成では、生成コードをテストケースに対して実行し、機能的正しさを測定します。pass@\(k\) 指標 (Chen et al. 2021)は、\(k\) 個の生成サンプルのうち少なくとも1つがすべてのテストに合格する確率を推定します。
\[ \text{pass@}k = \mathbb{E}_{\text{problems}}!\left[1 - \frac{\binom{n-c}{k}}{\binom{n}{k}}\right] \]
ここで \(n\) は問題ごとに生成したサンプル総数、\(c\) は合格したサンプル数です。この不偏推定量は、ちょうど \(k\) 個の解をサンプリングして合格があるか確認する単純な推定量の高い分散を避けます。実際には \(n = 200\) 個のサンプルを生成し、pass@1、pass@10、pass@100を報告します。
Note
Pass@kの計算
import numpy as np from scipy.special import comb def pass_at_k(n: int, c: int, k: int) -> float: """Unbiased estimator for pass@k. Args: n: total samples generated per problem c: number of samples that pass all tests k: number of samples to consider """ if n - c < k: return 1.0 return 1.0 - comb(n - c, k, exact=True) / comb(n, k, exact=True) # Example: 200 samples, 15 pass, compute pass@1, pass@10, pass@100 for k in [1, 10, 100]: score = pass_at_k(n=200, c=15, k=k) print(f"pass@{k}: {score:.4f}") # pass@1: 0.0750 # pass@10: 0.5391 # pass@100: 0.9999
完全一致とF1
抽出型質問応答(例:SQuAD)では、次の2つが標準的な指標です。
-
完全一致(EM): 正規化(小文字化、冠詞と句読点の除去)後の予測解答文字列が、いずれかの正解解答と完全に一致するかを示す二値指標。
-
トークンレベルF1: 予測と正解解答をトークンのバッグとして扱い、F1スコアを計算する。
\[ F1 = \frac{2 \cdot |\text{pred} \cap \text{gold}|}{|\text{pred}| + |\text{gold}|} \]
複数解答がある場合は、すべての正解解答に対する最大F1を報告します。
| 指標 | タスク | 参照なし? | 人手との相関 |
|---|---|---|---|
| BLEU | 翻訳 | いいえ | 低〜中 |
| ROUGE | 要約 | いいえ | 中 |
| BERTScore | 一般NLG | いいえ | 高 |
| METEOR | 翻訳 | いいえ | 中〜高 |
| Perplexity | LM品質 | はい | 低 |
| Pass@k | コード生成 | いいえ(テスト) | とても高 |
| Exact Match | 抽出型QA | いいえ | とても高 |
| Token F1 | 抽出型QA | いいえ | 高 |
生成指標のまとめ:適用範囲と主な性質。
エージェントタスクの指標
エージェント型LLMは環境内で動作し、一連の行動を取り、多段階タスクを完了しなければなりません。標準的な生成指標だけでは不十分です。エージェント評価では、タスク完了度、効率、途中ステップの品質を捉える指標が必要です。
タスク成功率
エージェントタスクの主要指標はタスク成功率(TSR)です。指定された目標状態をエージェントが達成したタスクの割合を表します。
\[ \text{TSR} = \frac{1}{|\mathcal{T}|} \sum_{\tau \in \mathcal{T}} \mathbf{1}[\text{goal}(\tau) \text{ achieved}] \]
目標の達成は通常、決定論的なオラクル(データベース状態、ファイルシステム状態、テストケースの実行結果など)で検証します。部分点があるタスクでは、段階的な成功指標を定義できます。
\[ \text{TSR}_{\text{graded}} = \frac{1}{|\mathcal{T}|} \sum_{\tau \in \mathcal{T}} \text{score}(\tau) \in [0, 1] \]
軌跡効率
成功するエージェントは、不要な行動を最小限にしてタスクを完了すべきです。軌跡効率は、最適軌跡の長さとエージェントが実際にたどった軌跡の長さの比率を測定します。
\[ \eta = \frac{L^*}{L_{\text{agent}}} \]
ここで \(L^*\) は最短の成功軌跡の長さ(オラクルまたは人間の専門家が計算)で、\(L_{\text{agent}}\) はエージェントが取った行動数です。\(\eta \in (0, 1]\) で、\(\eta = 1\) が最適効率を示します。失敗した軌跡では\(\eta = 0\)。
補完的な指標として冗長率があります。これは、どの最適軌跡にも含まれないエージェントの行動の割合です。
ツール利用精度
外部ツール(API、コードインタープリター、検索エンジンなど)を呼び出すエージェントでは、ツール利用精度がツール呼び出しの正しさを測定します。
\[ \text{TUA} = \frac{\text{# correct tool calls}}{\text{# total tool calls}} \]
ツール呼び出しが正しいとは、(a) 正しいツールを選び、(b) 引数が有効で、(c) 軌跡の適切な時点で呼び出していることです。ツール選択は正しいが引数が誤っている場合には、部分点を与えられます。
多段階推論精度
推論の連鎖を必要とするタスク(マルチホップQA、数学問題の解決など)では、ステップレベル精度が正しい推論ステップの割合を測定します。
\[ \text{SRA} = \frac{1}{|\mathcal{T}|} \sum_{\tau \in \mathcal{T}} \frac{1}{|S_\tau|} \sum_{s \in S_\tau} \mathbf{1}[s \text{ is correct}] \]
ここで \(S_\tau\) は軌跡 \(\tau\) における推論ステップの集合です。ステップの正しさは、プロセス報酬モデル(PRM)または人手アノテーションで検証できます。
SWE-benchの方法論
SWE-bench (Jimenez et al. 2024)は、現実世界のソフトウェア工学タスクでLLMを評価します。GitHubのIssue説明とリポジトリのコードベースを与え、モデルはIssueを解決するパッチを生成しなければなりません。評価は次のように進みます。
-
モデルにIssueの説明と関連するコードコンテキストを与える。
-
モデルがパッチ(unified diff形式)を生成する。
-
パッチをリポジトリに適用する。
-
リポジトリのテストスイートを実行し、すべてのテストに合格すればタスク成功とする。
主要指標は 解決率(% Resolved) 、つまり生成パッチがすべてのテストに合格したIssueの割合です。SWE-bench Verifiedは、人手アノテーターが解決可能かつ曖昧でないことを確認した500問の精選サブセットです。SWE-bench Liteは、より高速な評価向けに設計された300問のサブセットです。
Important
SWE-benchの主な統計(2024年時点)
全ベンチマーク: 人気のPythonリポジトリ12個からなる2,294タスク。
最良のオープンソースエージェント: \(\sim\)43%を解決(SWE-bench Verified)。
人間の性能: \(\sim\)87%を解決(1タスクあたり15分)。
評価コスト: \(\sim\)$0.25(APIベースのモデルで1タスクあたり)。
WebArenaの方法論
WebArena (S. Zhou et al. 2024)は、サンドボックス化されたブラウザ環境で現実的なウェブナビゲーションタスクを評価します。ベンチマークには、5つのウェブアプリケーション(EC、ソーシャルフォーラム、共同開発、コンテンツ管理、地図)にまたがる812タスクが含まれます。評価方法は次のとおりです。
-
機能評価: アプリケーション状態を確認してタスク結果を検証する(例:「商品はカートに追加されたか」「投稿は作成されたか」)。
-
URLベース評価: ナビゲーションタスクでは、最終URLを期待されるURLと比較する。
-
プログラムベース評価: カスタム評価スクリプトで複雑な条件を確認する(例:「価格は50ドル未満か」)。
主要指標はタスク成功率です。人間の性能は約78%、最先端エージェントは約35〜45%です。
| ベンチマーク | 分野 | タスク数 | 評価方法 | SOTA(%) |
|---|---|---|---|---|
| SWE-bench | ソフトウェア工学 | 2,294 | テスト実行 | \(\sim\)43 |
| SWE-bench Lite | ソフトウェア工学 | 300 | テスト実行 | \(\sim\)50 |
| WebArena | ウェブナビゲーション | 812 | 状態/URL/プログラム | \(\sim\)40 |
| ALFWorld (Shridhar et al. 2021) | 家庭タスク | 3,553 | シミュレーター状態 | \(\sim\)90 |
| AgentBench (X. Liu et al. 2023) | 複数分野 | 1,091 | タスク固有 | \(\sim\)45 |
エージェント評価ベンチマークの比較。
LLM-as-Judge
LLM-as-judge (Zheng et al. 2023)は、高性能なLLMを使って他のLLM(または同じLLM)の出力を評価します。この方法は人手アノテーションなしで大規模な評価集合に拡張でき、判定の詳細な根拠も提供できます。
セットアップとプロンプトテンプレート
判定モデルには、プロンプト、1つ以上のモデル応答、評価ルーブリックを提示します。一般的な形式は3つあります。
点ごとのスコアリング
判定モデルが1つの応答に絶対スコアを付けます。
Note
点ごとの判定プロンプト
POINTWISE_PROMPT = """ You are an expert evaluator. Rate the following response on a scale of 1-10 for helpfulness, accuracy, and clarity. [Question] {question} [Response] {response} Provide your evaluation in the following format: Reasoning: <step-by-step analysis> Score: <integer from 1 to 10> """
ペアワイズ比較
判定モデルが2つの応答を比較し、より良い方を選びます。
Note
ペアワイズ判定プロンプト
PAIRWISE_PROMPT = """ You are an expert evaluator. Compare the two responses below and determine which is better. Consider helpfulness, accuracy, and depth of explanation. [Question] {question} [Response A] {response_a} [Response B] {response_b} Output exactly one of: [[A]], [[B]], or [[C]] (tie). Reasoning: <your analysis> Verdict: <[[A]], [[B]], or [[C]]> """
参照に基づくスコアリング
判定モデルに参照解答を与え、それとの相対関係で応答を評価させます。判定モデルが確かな知識を持っていない可能性がある事実タスクで特に有用です。
位置バイアスの緩和
LLM判定モデルには位置バイアスがあります。特定の位置(最初または最後)に現れる応答を体系的に好む傾向です。このバイアスは10〜15パーセントポイントに達することがあります。緩和策は次のとおりです。
-
入れ替え拡張: 各ペアを両方の順序(A対B、B対A)で評価する。一貫した判定を採用し、一貫しない判定は引き分けとして記録する。
-
キャリブレーションプロンプト: 「応答が提示される順序に評価を左右されてはいけない」と判定モデルに明示的に指示する。
-
アンサンブル判定: 位置順序を変えた複数の判定モデルを使い、判定を集約する。
-
思考の連鎖の強制: 判定前に詳細な根拠を生成させ、表面的な位置手掛かりへの依存を減らす。
Warning
冗長性バイアス
LLM判定モデルには冗長性バイアスもあります。追加内容が無関係または反復的であっても、長い応答を体系的に好みます。これを緩和するには、不要な長さを減点し、量ではなく情報の品質に集中するよう判定モデルに指示します。あるいは、判定前に応答を一定の長さへ切り詰めます。
複数判定モデルのパネル
単一の判定モデルには体系的なバイアスがあることがあります。異なるモデルファミリーの判定モデルによるパネルは、より頑健な評価を提供します。\(J\) 個の判定モデルが判定 \(v_1, \ldots, v_J \in \{A, B, \text{tie}\}\) のとき、パネルの判定は多数決で決まります。パネル一致率は次のとおりです。
\[ \text{Agreement} = \frac{1}{\binom{J}{2}} \sum_{i < j} \mathbf{1}[v_i = v_j] \]
3判定モデルのパネルでは、全員一致を高信頼、2対1の分裂を中信頼、三者引き分けを低信頼として扱います。
LLM判定モデルの一致度指標
LLM判定モデルを検証するには、学習に使っていない集合で判定結果を人手アノテーションと比較します。主な指標は次のとおりです。
-
一致率: 判定モデルと人間の判断が一致する項目の割合。
-
Cohenの \(\kappa\): 偶然一致を補正した一致度(式 [eq:cohens-kappa])。
-
Spearmanの \(\rho\): 判定モデルのスコアと人間のスコアの順位相関。順序尺度に適しています。
-
Kendallの \(\tau\): 引き分けに対してより頑健な、別の順位相関。
代表的なサンプルで、人手アノテーターに対して \(\kappa > 0.6\) かつ一致率 \(> 80%\) を達成する判定モデルは、信頼できるとみなします。
G-Evalフレームワーク
G-Eval (Y. Liu et al. 2023)は、思考の連鎖プロンプトとトークン確率の重み付けを使い、より信頼できるスコアを生成するLLMベース評価の構造化フレームワークです。手順は次のとおりです。
-
評価ステップの生成: 評価タスクの詳細なルーブリックをLLMに生成させる(例:「要約の一貫性を評価するために行うステップを列挙せよ」)。
-
確率重み付きスコアリング: 各スコア値 \(s \in \{1, 2, 3, 4, 5\}\) について、判定モデルから対数確率 \(\log P_\theta(s \mid \text{prompt, steps, response})\) を取得します。最終スコアは確率重み付き平均です。
\[ \text{G-Eval score} = \sum_{s=1}^{5} s \cdot \frac{e^{\log P_\theta(s)}}{\sum_{s'=1}^{5} e^{\log P_\theta(s')}} \]
- 正規化: 最大スコアで割り、スコアを \([0, 1]\) に写像する。
G-Evalは、直接プロンプトよりも人手判断との相関が高くなります。特に一貫性や整合性のような微妙な側面で、確率の重み付けが判定モデルの不確実性を捉え、離散的な選択を強制しないためです。
Tip
G-Evalが機能する理由
標準的なプロンプトは判定モデルに単一トークン(例:「4」)を出力させるため、モデルの不確実性を捨ててしまいます。G-Evalはすべてのスコアトークンに対する確率分布を読み、判定モデルの信念の下で期待スコアを実質的に計算します。これは最頻値ではなく事後分布の平均を使うことに相当します。
評価の落とし穴
注意深く設計した評価パイプラインでも、誤解を招く結果を生むことがあります。本節では、よくある失敗モードを整理します。
ベンチマーク汚染
ベンチマーク汚染は、評価データがモデルの学習セットに直接(そのまま含まれる)または間接的(言い換えや意味的に類似した内容)に現れることで起こります。汚染されたモデルは、真の汎化能力を反映しない水増しされたスコアを達成します。
検出方法
-
\(n\)グラムの重複: 評価例のうち、学習コーパスと高い\(n\)グラムの重複(例:ROUGE-L \(> 0.8\))を持つものの割合を計算する。
-
メンバーシップ推論: メンバーシップ推論攻撃を使い、各評価例が学習セットに含まれていた確率を推定する。
-
カナリア文字列: 評価例に一意でランダムな文字列を埋め込み、モデルがそれを続けられるか確認する。
-
時間的ホールドアウト: モデルの学習カットオフ日より後に作成された評価データを使う。
緩和策
-
公開しない非公開テストセットを維持する。
-
新しい例でベンチマークを定期的に更新する。
-
学習データのカットオフ日と汚染除去手順を報告する。
ベンチマークへの過適合
直接的な汚染がなくても、繰り返しの評価やハイパーパラメータ調整によって、モデルが特定のベンチマーク向けに暗黙に最適化されることがあります。これは適応的過適合の一種であり、ベンチマークの情報がモデル開発上の判断に漏れ出しています。
Warning
ベンチマークのライフサイクル
研究コミュニティがベンチマーク向けに最適化するにつれて、ベンチマークの有用性は時間とともに低下します。かつては世界知識の難しいテストだったMMLU (Hendrycks et al. 2021)でも、現在は人間に近い性能を達成するモデルがありますが、それらのモデルは新しい知識タスクには依然として失敗します。新しいベンチマークは、永続的な正解ではなく、一時的な信号源として扱うべきです。
評価におけるGoodhartの法則
Goodhartの法則は「指標が目標になると、良い指標ではなくなる」と述べます (Goodhart 1984)。LLM評価では、これは次のように現れます。
-
報酬ハッキング: RLHFで学習したモデルが、本当に改善するのではなく報酬モデルを出し抜くことを学ぶ。報酬モデルでは高得点だが事実としては誤っている、冗長で自信ありげな応答を生成するよう学習することがあります。
-
指標のゲーム化: BLEUやROUGEの最大化向けにファインチューニングされたモデルが、これらの指標では高得点だが人間には役立ちにくい出力を生成することがあります。
-
判定モデルのゲーム化: LLM-as-judgeのフィードバックで学習したモデルが、本当に品質を改善するのではなく、判定モデルのバイアス(例:冗長性バイアス)を学習することがあります。
Important
Goodhartの法則への対策
指標の多様性: 異なる系統の複数指標を使う。1つの指標をゲーム化するモデルが、すべてを同時にゲーム化する可能性は低い。
ホールドアウト評価: 学習やモデル選択に使っていない評価指標を維持する。
人手による抜き取り確認: 自動指標とは独立して、モデル出力を定期的にサンプリングし、人手で確認する。
敵対的評価: 自動指標が見落とす失敗モードを積極的に探る。
外在的検証: 内在的指標を外在的な結果に対して定期的に検証する。
その他の落とし穴
プロンプト感度
LLMの性能は、評価プロンプトの小さな変更(「ステップごとに考えよ」の追加や解答形式の変更など)で大きく変わることがあります。使用した正確なプロンプトを必ず報告し、複数のプロンプト変種で評価することを検討してください。
集約アーティファクト
難易度やスコア分布が異なるタスクでスコアを平均すると、誤解を招く集約指標になることがあります。易しいタスクには優れるが難しいタスクには失敗するモデルが、均一な性能のモデルと同じ平均スコアになることもあります。
人手評価における選択バイアス
人手評価者はエンドユーザーの無作為標本ではありません。クラウドソーシングプラットフォームのアノテーターは、対象ユーザー集団とは異なる嗜好、文化的背景、ドメイン知識を持つ可能性があります。
評価と配備の不一致
評価プロンプトは、実際のユーザー問い合わせより短く、整理され、よく整形されていることが多いものです。ベンチマークプロンプトでは高性能なモデルでも、本番で生じるノイズの多い、曖昧な、多ターンの会話では大幅に性能が低下することがあります。
Note
評価設計の重要な問い
評価パイプラインを配備する前に、次を確認してください。
評価指標は配備目的と整合しているか。
評価データは対象分布を代表しているか。
汚染と過適合のリスクを評価したか。
すべての指標について信頼区間を報告しているか。
評価は再現可能か(固定シード、バージョン管理されたプロンプト、公開テストセット)。
評価を人手判断または外在的な結果に対して検証したか。
エージェント型AI入門
これまでのパートでは、LLMを訓練し、アラインメントし、推論させるためのアルゴリズム上のツールキットを整えてきました。トランスフォーマーアーキテクチャとGPUシステム(第I部)、モデルを人間の意図に整合させる強化学習手法(第II部)、RL学習から生まれる推論能力(第III部)、評価方法論(第IV部)を扱いました。本パートでは、現代のAIエンジニアリングの中心的な問いに移ります。オープンエンドな環境で知覚し、計画し、行動し、学習する自律エージェントとして、これらのモデルをどのように配備するのでしょうか。
エージェント型AIシステム とは、LLMがループ内で動作するシステムです。環境から観測(ユーザーメッセージ、ツール出力、センサーデータ)を受け取り、次に何をすべきかを推論し、行動(ツール呼び出し、コード実行、APIリクエスト)を取り、目標を達成するか、人間の入力を明示的に求めるまで反復します。これは、モデルが1つの応答を生成して待つ「シングルターン・チャットボット」パラダイムとは対照的です。
チャットボットからエージェントへの移行は、単一のモデル呼び出しでは解決できない、いくつかの根本的な課題をもたらします。
-
永続性: エージェントは、何を行い、何に失敗し、どのようなコンテキストが確立されたかを、ターン、セッション、さらには日をまたいで記憶しなければなりません。
-
グラウンディング: エージェントは、学習データに含まれていなかった最新かつドメイン固有の知識にアクセスしなければなりません。
-
行動: エージェントは、明確に定義されたインターフェースを通じて、外部システム(データベース、API、ファイルシステム、ブラウザ)と対話しなければなりません。
-
協調: 複雑なタスクは単一のエージェントの処理能力を超えることが多く、複数の専門エージェントが協力し、委任し、交渉しなければなりません。
-
安全性: 自律的な行動には、ガードレール、人間による監督、エージェントが不確実なときの段階的な機能縮退が必要です。
これらの課題に対処するため、本番のエージェント型システムは層状アーキテクチャとして構築されます。各層が特定の問題を解決し、続く章ではスタック全体を下から上へ扱います。
-
第16章:RAG(検索拡張生成) — 知識層。RAGはクエリ時に関連文書を検索することで、エージェントに動的な外部知識へのアクセスを与えます。これによりグラウンディングの問題を解決します。エージェントは、モデルが学習中に見ていない proprietary data、最近の出来事、ドメイン固有のコンテンツについて質問に答えられます。埋め込みモデル、ベクトルデータベース、チャンク分割戦略、ハイブリッド検索、エージェントがいつ何を検索するかを決めるエージェント型RAGのような高度なパターンを扱います。
-
第17章:メモリ — 永続性層。メモリにより、エージェントは対話をまたいで情報を想起できます。単一タスク内の短期ワーキングメモリから、数か月にわたる長期エピソードメモリまでを含みます。メモリアーキテクチャ(バッファ、要約、ベクトルインデックス、知識グラフ)、メモリ統合、コンテキストウィンドウを圧迫せずに拡張できるメモリシステムの設計方法を扱います。
-
第18章:ハーネスとオーケストレーション — ランタイム層。オーケストレーション・ハーネスはエージェントの「オペレーティングシステム」です。エージェントループ、コンテキストウィンドウの予算、ツールディスパッチ、エラー回復、状態の永続化、可観測性を管理します。コンテキスト管理戦略(要約、スライディングウィンドウ、階層化)、実行制御(逐次、並列、分岐)、ガードレール、Human-in-the-Loopのパターンを扱います。
-
第19章:設計パターン — アーキテクチャ層。エージェントを構成する標準パターンとして、ReAct(推論と行動の交互実行)、計画してから実行、リフレクションループ、ツール拡張生成、多段階ワークフローを扱います。各パターンをいつ適用するか、その失敗モード、複雑な現実世界のタスク向けに組み合わせる方法を分析します。
-
第20章:環境とベンチマーク — 評価層。エージェントの振る舞いをどこで、どのように評価するかを扱います。ウェブナビゲーションベンチマーク、コーディング環境、ツール利用評価スイート、多段階自律システムの評価に固有の課題(部分点、軌跡の品質、安全性違反)を取り上げます。
-
第21章:MCP(Model Context Protocol) — ツール統合標準。MCPは、ハードウェアにおけるUSBに相当する形で、エージェントがツールを発見し呼び出す方法を標準化します。プロトコル仕様、サーバー/クライアントアーキテクチャ、リソース管理、エージェントとツール間のN\(\times\)M 統合問題を解消する仕組みを扱います。
-
第22章:エージェントスキル — 能力層。スキルライブラリ、スキル選択、構成的なタスク解決を含め、基本的なツール利用を超えた専門能力をエージェントが獲得・合成する方法を扱います。
-
第23章:A2A(Agent-to-Agent Communication) — エージェント間プロトコル。複数の専門家が必要なタスクに対し、A2Aはエージェントの発見、タスク委任、進捗ストリーミング、結果集約の標準プロトコルを提供し、異種エージェント(異なるベンダー、フレームワーク、組織のエージェント)の協働を可能にします。
-
第24章:マルチエージェントシステム — 協調層。階層的委任、ピアツーピア交渉、議論と合意、群知能、創発的振る舞いなど、マルチエージェント協働のアーキテクチャを扱います。単一エージェント設計とマルチエージェント設計を使い分ける場面、協調の失敗をデバッグする方法を説明します。
-
第25章:フレームワーク — 実装層。LangGraph(状態を持つグラフベースのオーケストレーション)、CrewAI(役割ベースのマルチエージェント)、OpenAI Agents SDK、AutoGenなど、上述の概念を実装する本番向けツールキットを扱います。トレードオフ、アーキテクチャ上の判断、用途ごとの適性を比較します。
-
第26章:エージェント型UI — インタラクション層。ストリーミングインターフェース、段階的開示、承認ワークフロー、ステータスダッシュボード、自律システムへの適切な信頼を構築するUXパターンなど、ユーザーがエージェントと対話し監督する方法を扱います。
これらの層は独立して動作するのではなく、各コンポーネントが互いに依存し強化し合う、緊密に統合されたシステムを形成します。
-
エージェントコア (第II〜III部で扱った推論能力を持つLLM)が中心に位置し、知覚・推論・行動のループを実行します。
-
RAG は各推論ステップの前に関連知識をエージェントへ与え、 メモリ はステップやセッションをまたぐ継続性を提供します。
-
オーケストレーション・ハーネス はすべてを協調させます。いつ検索するか、いつツールを呼び出すか、いつサブエージェントへ委任するか、いつ人間に指針を求めるかを決めます。
-
MCP はエージェントがすべての外部ツールへアクセスする標準インターフェースを提供し、 A2A はエージェント間通信に同等のインターフェースを提供します。
-
設計パターン は高レベルの戦略(ReAct、計画して実行、リフレクション)を定義し、 フレームワーク はこれらのパターンの具体的な実装を提供します。
-
UI層 はエージェントを人間へつなぎ戻すことでループを閉じ、監督、修正、協調的な問題解決を可能にします。
全体を通じてシステムの視点を維持します。エージェント型AIは単なるプロンプトの問題ではなく、すべての層でコンテキスト管理、エラー処理、安全性ガードレール、可観測性を注意深く設計する必要があります。下図は、これらのコンポーネントがアーキテクチャとしてどのように組み合わさるかを示します。
検索拡張生成(RAG)
検索拡張生成(RAG) (P. Lewis et al. 2020)は、大規模言語モデルを本番環境へ配備するうえで、実務的な影響が特に大きい技術の1つとなっています。RAGは、学習時にモデルの重みへエンコードされた知識だけに頼るのではなく、動的に更新できる外部メモリをLLMに与えます。これにより、幅広い知識集約型タスクで、正確でグラウンディングされ、検証可能な応答を実現します。
動機と問題設定
Important
LLMに外部知識が必要な理由
大規模言語モデルは知識をパラメトリックに保存します。学習中に数十億の重みへ圧縮するのです。これにより、3つの根本的な制限が生じます。
ハルシネーション: 信頼できる知識の境界を超えて質問されると、事実としては誤っているがもっともらしく聞こえる文を、モデルは自信を持って生成します。
知識の陳腐化: 学習データにはカットオフ日があるため、学習後に起きた出来事、発表された論文、製品アップデートをモデルは知ることができません。
ドメイン固有性: 汎用モデルは、独自コードベース、社内文書、専門的な規制、企業データについて深い知識を持っていません。
パラメトリック知識とノンパラメトリック知識
2つの知識源の違いは、次のように形式化できます。 \(\mathcal{M}_\theta\) はパラメーター \(\theta\)、\(\mathcal{D} = \{d_1, d_2, \ldots, d_N\}\) は外部文書コーパスです。パラダイムごとに回答 \(a\) をクエリ \(q\) に対する生成確率は次のとおりです。
\[ \begin{aligned} P_{\text{parametric}}(a \mid q) &= P_{\mathcal{M}_\theta}(a \mid q) \\[6pt] P_{\text{RAG}}(a \mid q, \mathcal{D}) &= \sum_{d \in \mathcal{D}} P_{\mathcal{M}_\theta}(a \mid q, d), P_{\text{ret}}(d \mid q, \mathcal{D}) \end{aligned} \]
ここで \(P_{\text{ret}}(d \mid q, \mathcal{D})\) は文書に対する検索分布です。RAGは検索された証拠について周辺化し、ノンパラメトリック知識に基づいて生成をグラウンディングします。
Tip
図書館のたとえ
パラメトリックLLMを、巨大な図書館を暗記したものの、卒業してから何年も経った学者だと考えてみましょう。RAGはその学者に図書館カードを与えます。学者はリアルタイムで調べ、出典を引用し、記憶から推測するのではなく参照を確認する必要があると認められます。
RAG、ファインチューニング、ロングコンテキストの使い分け
| 基準 | RAG | ファインチューニング | ロングコンテキスト | RAG + FT |
|---|---|---|---|---|
| 知識が頻繁に更新される | \(\times\) | \(\times\) | ||
| 引用/グラウンディングが必要 | \(\times\) | |||
| 大規模な独自コーパス | \(\times\) | \(\times\) | ||
| スタイル/形式を適応させる | \(\times\) | \(\times\) | ||
| 新しい推論スキルを教える | \(\times\) | \(\times\) | ||
| コーパスがコンテキストウィンドウに収まる | \(\times\) | \(\times\) | \(\times\) | |
| 低レイテンシーが必要 | \(\times\) | \(\times\) | \(\times\) |
意思決定ガイド:RAG、ファインチューニング、ロングコンテキスト
Warning
よくある誤解
RAGはファインチューニングの代替ではありません。ファインチューニングはモデルにどのように推論し応答するかを教え、RAGは何について推論するかを与えます。両者は相補的です。指示に適切に従うようファインチューニングされたモデルは、ベースモデルより検索コンテキストを効果的に利用します。
RAGの基本アーキテクチャ
標準的なRAGシステムは2つのフェーズからなります。文書を処理して保存する オフラインインデックス作成パイプライン と、クエリに応答する オンライン検索・生成パイプライン です。
パイプライン全体の図
インデックス作成パイプライン
文書の読み込み
文書はさまざまな形式(PDF、HTML、Markdown、DOCX、コード)で届きます。ローダーはクリーンなテキストを抽出し、メタデータ(ソースURL、ページ番号、節タイトル、タイムスタンプ)を保持します。このメタデータはフィルタリングと引用のため、埋め込みとともに保存されます。
チャンク分割
長い文書は、埋め込みモデルのコンテキストウィンドウ(通常512トークン)に収まり、意味的に一貫したチャンクへ分割しなければなりません。チャンク分割戦略は、RAGシステム設計で最も影響の大きい判断の1つです(節2.4を参照)。
埋め込み
各チャンク \(c_i\) は密なベクトル \(\mathbf{e}_i = f_\phi(c_i) \in \mathbb{R}^d\) を埋め込みモデル \(f_\phi\) を使ってエンコードします。これらのベクトルは元のテキストとメタデータとともにベクトルデータベースへ保存されます。
検索
クエリ \(q\)について、検索ステップは \(\mathbf{q} = f_\phi(q)\) としてエンコードし、\(k\) 個の最も類似するチャンクをコサイン類似度で見つけます。
\[ \text{sim}(\mathbf{q}, \mathbf{e}_i) = \frac{\mathbf{q} \cdot \mathbf{e}_i}{|\mathbf{q}|,|\mathbf{e}_i|} \]
上位 \(k\) チャンク\(\mathcal{C}_k = \{c_{(1)}, \ldots, c_{(k)}\}\)をコンテキストとして返します。
生成
検索されたチャンクをプロンプトテンプレートへ注入します。
SYSTEM_PROMPT = """You are a helpful assistant. Answer the question using ONLY
the provided context. If the context does not contain enough information,
say so explicitly. Cite your sources using [Doc N] notation."""
def build_rag_prompt(query: str, chunks: list[dict]) -> str:
context_str = "\n\n".join(
f"[Doc {i+1}] (Source: {c['source']}, Page: {c.get('page','N/A')})\n{c['text']}"
for i, c in enumerate(chunks)
)
return f"""{SYSTEM_PROMPT}
Context:
{context_str}
Question: {query}
Answer:"""
検索手法
スパース検索:BM25とTF-IDF
スパース検索方式では、文書とクエリを語彙上の高次元スパースベクトルとして表します。古典的なBM25スコアリング関数 (Robertson and Zaragoza 2009) は文書 \(d\) に対するクエリ \(q\) と項目 \(t_1, \ldots, t_n\) について次のように定義されます。
\[ \text{BM25}(d, q) = \sum_{i=1}^{n} \text{IDF}(t_i) \cdot \frac{f(t_i, d) \cdot (k_1 + 1)}{f(t_i, d) + k_1 \cdot \left(1 - b + b \cdot \frac{|d|}{\text{avgdl}}\right)} \]
ここで \(f(t_i, d)\) は項頻度、\(\vert d\vert\) は文書長、\(\text{avgdl}\) は平均文書長であり、\(k_1 \in [1.2, 2.0]\), \(b = 0.75\) は調整パラメーターです。
Important
スパース検索がなお優位な場合
正確なキーワード一致: 製品コード、エラーコード、固有名詞、希少な用語
低リソースのドメイン: 密なモデルを学習するためのデータが不足している場合
解釈可能性: なぜ文書が検索されたかを簡単にデバッグできる
速度: GPUが不要で、転置インデックスによって数十億文書へ拡張できる
語彙外の用語: 埋め込み学習時に見ていなかった新しい用語
密ベクトル検索:DPR
Dense Passage Retrieval(DPR) (Karpukhin et al. 2020)は、2つの別個のBERTベースエンコーダー、すなわちクエリエンコーダー \(E_Q\) とパッセージエンコーダー \(E_P\)を使います。対照損失で学習し、関連するクエリとパッセージのペアを埋め込み空間で近くに配置します。
バイエンコーダーのアーキテクチャ
\[ \text{sim}(q, p) = E_Q(q)^\top E_P(p) \]
バッチ内負例を使った学習
バッチ \(B\) 個のクエリ・パッセージペア \(\{(q_i, p_i^+)\}_{i=1}^B\) に対して、対照損失はバッチ内の他のすべてのパッセージを負例として扱います。
\[ \mathcal{L}_{\text{DPR}} = -\frac{1}{B} \sum_{i=1}^{B} \log \frac{\exp!\left(E_Q(q_i)^\top E_P(p_i^+) / \tau\right)} {\sum_{j=1}^{B} \exp!\left(E_Q(q_i)^\top E_P(p_j) / \tau\right)} \]
ここで \(\tau\) は温度ハイパーパラメーターです。ハード負例(語彙的には似ているが意味的には無関係なパッセージ)は、強力な検索器の学習に不可欠です。
近似最近傍探索
大規模になると、数百万の埋め込みを総当たりで検索するのは現実的ではありません。FAISS (Johnson et al. 2021)(Facebook AI Similarity Search)は、次の方法で効率的な近似最近傍(ANN)探索を提供します。
-
IVF(転置ファイルインデックス): ベクトルをVoronoiセルへクラスタリングし、近傍セルだけを検索する
-
HNSW(Hierarchical Navigable Small World): (Malkov and Yashunin 2020) グラフベースのインデックスで、\(O(\log N)\) 検索を行う
-
PQ(Product Quantization): ベクトルを圧縮してメモリ使用量を削減する
Reciprocal Rank Fusionによるハイブリッド検索
ハイブリッド検索はスパーススコアと密なスコアを組み合わせます。単純な線形結合は次のとおりです。
\[ s_{\text{hybrid}}(d, q) = \alpha \cdot s_{\text{dense}}(d, q) + (1-\alpha) \cdot s_{\text{sparse}}(d, q) \]
しかし、異なるシステムのスコアは直接比較できません。 Reciprocal Rank Fusion(RRF) (Cormack et al. 2009)は、スコアではなく順位を扱うことでこの問題を避けます。
\[ \text{RRF}(d) = \sum_{r \in \mathcal{R}} \frac{1}{k + \text{rank}_r(d)} \label{eq:rrf} \]
ここで \(\mathcal{R}\) は順位付きリストの集合(例:BM25の順位リストと密ベクトル検索の順位リスト)、\(\text{rank}_r(d)\) は文書 \(d\) のリスト \(r\) における順位であり、\(k = 60\) は非常に高い順位の文書の影響を抑える平滑化定数です。
Note
RRFの計算
BM25が文書 \(d\) を3位、密ベクトル検索が7位にランク付けしたとします。\(k = 60\) のとき、 \[ \text{RRF}(d) = \frac{1}{60 + 3} + \frac{1}{60 + 7} = \frac{1}{63} + \frac{1}{67} \approx 0.0159 + 0.0149 = 0.0308 \] 両方のリストで1位の文書のスコアは \(\frac{1}{61} + \frac{1}{61} \approx 0.0328\). です。
学習済みスパース検索:SPLADEとSPLADEv2
Tip
SPLADEとは何か
従来のスパース検索(BM25)は正確な語彙一致に依存するため、クエリが「car」と言っているのに文書が「automobile」と言っている場合に失敗します。密ベクトル検索(DPR)は意味を捉えますが、解釈可能性を失い、クエリ時にGPUを必要とし、大きなインデックスを生成します。 SPLADE は両方の長所を得ます。BM25のように高速な転置インデックス検索ができるスパースベクトルと、密なモデルのように同義語や関連概念を扱う学習済み意味拡張を組み合わせます。
SPLADE(v1)— 基本概念
SPLADE(Sparse Lexical and Expansion Model) (Formal, Piwowarski, et al. 2021)は、事前学習済みのマスク言語モデル(例:BERT/DistilBERT)を使い、各文書またはクエリについて語彙全体にわたるスパースベクトルを生成します。重要な洞察は、MLMヘッドがテキスト中の各位置と意味的に関連する語をすでに知っていることです。SPLADEはこの知識を項目重要度の重みとして再利用します。
アーキテクチャ
入力テキスト \(x = [x_1, \ldots, x_n]\) について、
-
トランスフォーマーエンコーダーに通してコンテキスト表現 \(\mathbf{H} \in \mathbb{R}^{n \times \vert \mathcal{V}\vert }\) をMLMヘッドから得る
-
位置全体を集約し、飽和活性化を適用する。
\[ w_t(x) = \log!\left(1 + \text{ReLU}!\left(\max_{i \in [1,n]} \mathbf{H}_i[t]\right)\right) \]
ここで \(\mathbf{H}_i[t]\) は語彙トークン \(t\) の入力位置 \(i\) におけるMLMロジットです。
-
\(\log(1 + \cdot)\) の飽和により、単一の項目が支配的になるのを防ぐ(BM25のTF飽和と同様)
-
ReLUによりスパース性が保証され、大半の語彙項目の重みがゼロになる
-
\(\max\) による位置全体のプーリングで、テキスト内のどの位置からでも各項目の最も強い信号を捉える
-
拡張: 元のテキストに存在しないトークンでも非ゼロの重みを得られる(例:「neural networks」に関する文書が「deep learning」「AI」「backpropagation」に重みを持つことがある)
スコアリング
クエリと文書はそれぞれスパースベクトル \(\mathbf{w}^q, \mathbf{w}^d \in \mathbb{R}^{\vert \mathcal{V}\vert }\) へ写像されます。関連度スコアは単純な内積です。
\[ s(q, d) = \sum_{t \in \mathcal{V}} w_t^q \cdot w_t^d \]
両方のベクトルはスパースです(3万語彙のうち通常20〜200個の非ゼロエントリ)。そのため標準的な転置インデックス(Lucene、Anserini)を使って効率よく計算でき、クエリ時にGPUは必要ありません。
学習
SPLADEは、対照学習(バッチ内負例+ハード負例)に2つの正則化項を加えて学習します。
\[ \mathcal{L} = \mathcal{L}_{\text{contrastive}} + \lambda_q |\mathbf{w}^q|_1 + \lambda_d |\mathbf{w}^d|_1 \]
クエリと文書の表現に対する \(L_1\) ペナルティはスパース性を促します。これがなければ、モデルは目的に反する密な表現を学習します。
SPLADEv2 — 主な改善
SPLADEv2 (Formal, Lassance, et al. 2021)は、効率と有効性を大幅に改善する複数の改良を導入します。
- クロスエンコーダーからの蒸留: 二値の関連度ラベルだけで学習する代わりに、SPLADEv2はクロスエンコーダー教師(例:MonoT5 (Nogueira et al. 2020))を使ってソフトな関連度スコアを与えます。これにより、より豊かな学習信号が得られます。
\[ \mathcal{L}_{\text{distill}} = \text{KL}!\left(\sigma(s_{\text{student}}) ,|, \sigma(s_{\text{teacher}})\right) \]
- クエリ/文書エンコーダーの分離: SPLADEv2は、クエリと文書で異なるスパース性の目標を使います。クエリはよりスパースに(検索を高速に)し、文書はやや密でもよいものとします(オフラインで事前計算するためです)。
\[ \lambda_q > \lambda_d \quad \text{(e.g., } \lambda_q = 3 \times 10^{-4},; \lambda_d = 1 \times 10^{-4}\text{)} \]
-
FLOPS正則化: 単純な \(L_1\) の代わりに、SPLADEv2は予想される検索コストを直接罰するFLOPS対応正則化項を導入します。
\[ \mathcal{L}_{\text{FLOPS}} = \sum_{t \in \mathcal{V}} \left(\overline{a}_t^q\right)^2 + \sum_{t \in \mathcal{V}} \left(\overline{a}_t^d\right)^2 \]
ここで \(\overline{a}_t\) は項目 \(t\) のバッチ全体における平均活性値です。多くの文書で非ゼロになる項目(ポスティングリストが長い=検索が遅い項目)にペナルティを与えます。
-
効率的なバックボーン: BERT-base(1億1,000万パラメーター)の代わりにDistilBERT(6,600万パラメーター)を使い、品質をほとんど損なわずにエンコード時間を半減させます。
Important
SPLADEとSPLADEv2の比較
Tip
SPLADEを使う場合
SPLADE/v2を使う場合: クエリ時にGPUなしで意味検索を行いたい場合、インフラにすでに転置インデックス(Elasticsearch、Lucene)がある場合、または解釈可能な関連度スコアが必要な場合(どの拡張項目が一致したかを調べられます)。
密ベクトル検索を優先する場合: クエリエンコード用のGPU予算がある場合、多言語対応が必要な場合(密なモデルのほうが転移しやすい)、またはクエリが非常に短い場合(1〜2語では拡張の効果が小さいため)。
ベストプラクティス: 第1段階の検索器としてSPLADEv2を使い、上位 \(k\) 件にはクロスエンコーダーの再ランキング器を適用します。これにより、密ベクトル検索パイプラインと同等以上の性能を、より低いレイテンシーで実現できます。
ColBERT:レイトインタラクション
ColBERT (Khattab and Zaharia 2020)は、クエリと文書をトークンレベル埋め込みの集合へエンコードし、スコアリングにMaxSim演算子を使います。
\[ s(q, d) = \sum_{i \in |\mathbf{q}|} \max_{j \in |\mathbf{d}|} \mathbf{q}_i^\top \mathbf{d}_j \label{eq:colbert} \]
このレイトインタラクション機構は、単一ベクトルのバイエンコーダーより表現力が高く、文書埋め込みをオフラインで事前計算できるため、クロスエンコーダーよりはるかに高速です。
アーキテクチャ
クエリエンコーダー \(E_Q\) と文書エンコーダー \(E_D\) はどちらもBERTベースのモデルで、単一の [CLS] ベクトルではなく、トークンごとの埋め込みを生成します。各トークン埋め込みは線形層によって、通常128次元の低い次元へ射影されます。
\[ \begin{aligned} \mathbf{q}_i &= \text{Linear}(E_Q(q)_i) \in \mathbb{R}^{128}, \quad i = 1, \ldots, |q| \\ \mathbf{d}_j &= \text{Linear}(E_D(d)_j) \in \mathbb{R}^{128}, \quad j = 1, \ldots, |d| \end{aligned} \]
学習
ColBERTは、正例パッセージと負例パッセージに対するペアワイズsoftmax交差エントロピー損失で学習します。クエリ \(q\)、正例パッセージ \(d^+\)、負例パッセージの集合 \(\{d^-_1, \ldots, d^-_N\}\) があるとします。
\[ \mathcal{L}_{\text{ColBERT}} = -\log \frac{\exp(s(q, d^+))}{\exp(s(q, d^+)) + \sum_{k=1}^{N} \exp(s(q, d^-_k))} \]
ここで \(s(q, d)\) は式[eq:colbert]のMaxSimスコアです。負例は次から取得します。
-
バッチ内負例: 同じ学習バッチに含まれる他のパッセージ(無料で豊富)
-
ハード負例: BM25で検索された、語彙的には似ているが意味的には無関係なパッセージ(品質への影響が最も大きい)
-
蒸留負例 (ColBERTv2 (Santhanam et al. 2022):クロスエンコーダーの教師を使って最も難しい負例を発掘し、そのスコアをColBERTへ蒸留する)
インデックス作成とサービス提供
インデックス作成時には、すべての文書トークン埋め込みを事前計算して保存します(ColBERTv2では残差量子化による圧縮も可能です)。クエリ時にはクエリトークンだけをその場でエンコードし、保存済みの文書埋め込みに対してMaxSimを計算します。この分離により、次が可能になります。
-
オフライン文書エンコード: 一度エンコードすれば、多数のクエリに提供できる
-
PLAIDインデックス作成 (Santhanam et al. 2022):文書埋め込みをクラスタリングし、重心を使って候補を初期検索してから、候補に対してだけ正確なMaxSimを計算することで、レイテンシーを5〜10\(\times\)削減
-
インデックスサイズ: \(\vert d\vert \times 128\) 個の浮動小数点数(単一ベクトル方式より大きいものの、量子化により\(\sim\)2バイト/次元へ圧縮可能)
検索方式の比較
| 方式 | レイテンシー | 精度 | インデックスサイズ | GPU | 適する用途 |
|---|---|---|---|---|---|
| TF-IDF (Sparck Jones 1972) | 非常に低い | 低い | 小 | なし | ベースライン、完全一致 |
| BM25 (Robertson and Zaragoza 2009) | 非常に低い | 中 | 小 | なし | キーワード検索、希少語 |
| DPR/バイエンコーダー (Karpukhin et al. 2020) | 低い | 高い | 大 | あり | 意味的類似性 |
| SPLADE (Formal, Piwowarski, et al. 2021) | 低い | 高い | 中 | あり | 精度と速度の両立 |
| ColBERT (Khattab and Zaharia 2020) | 中 | 非常に高い | 非常に大 | あり | 高精度検索 |
| クロスエンコーダー (Nogueira and Cho 2019) | 高い | 最高 | 該当なし | あり | 上位 \(k\) 件の再ランキング |
| ハイブリッド(RRF) (Cormack et al. 2009) | 低い | 非常に高い | 大 | あり | 本番システム |
主要な側面における検索方式の比較 {#tab:retrieval_methods}
チャンク分割戦略
チャンク分割とは、文書を、(1) 埋め込みモデルのコンテキストウィンドウに収まるほど小さく、(2) 意味的に一貫し、(3) 単独で検索されたときにも役立つだけのコンテキストを含むセグメントへ分割するプロセスです。
オーバーラップ付き固定サイズチャンク分割
最も単純な戦略は、\(W\) トークンごとに分割し、連続するチャンク間に \(O\) トークンのオーバーラップを設けることです。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=512, # tokens per chunk
chunk_overlap=64, # overlap to preserve context across boundaries
length_function=len,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_documents(documents)
オーバーラップの式: 長さ \(L\) トークンの文書におけるチャンク数は次のとおりです。
\[ N_{\text{chunks}} = \left\lceil \frac{L - O}{W - O} \right\rceil \]
意味チャンク分割
固定間隔で分割するのではなく、意味チャンク分割では、連続する文の埋め込み類似度を測定して検出したトピックの境界で分割します。
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
chunker = SemanticChunker(
embeddings=OpenAIEmbeddings(),
breakpoint_threshold_type="percentile", # or "standard_deviation"
breakpoint_threshold_amount=95, # split at top 5% dissimilarity
)
chunks = chunker.split_documents(documents)
文書構造を考慮したチャンク分割
構造化文書(Markdown、HTML、コード)では、自然な境界で分割します。
-
Markdown:
##見出しで分割し、節のコンテキストを保持する -
HTML:
<section>、<article>、<p>タグで分割する -
コード: 関数/クラス定義で分割し、各チャンクにインポートを保持する
-
表: 表全体を1つのチャンクとして保持し、行の途中では決して分割しない
親子チャンク分割
検索の粒度と生成コンテキストを切り離す強力なパターンです。
-
正確な検索のために小さな子チャンク(例:128トークン)をインデックス化する
-
より豊かなコンテキストをLLMへ渡すために大きな親チャンク(例:512トークン)を返す
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain.text_splitter import RecursiveCharacterTextSplitter
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=InMemoryStore(),
child_splitter=child_splitter,
parent_splitter=parent_splitter,
)
retriever.add_documents(documents)
チャンクサイズの経験則
| 用途 | 推奨チャンクサイズ | オーバーラップ |
|---|---|---|
| 事実確認QA(正確な事実) | 128〜256トークン | 20〜32トークン |
| 要約/統合 | 512〜1024トークン | 64〜128トークン |
| コード検索 | 関数全体 | なし |
| 法律/規制文書 | 段落単位 | 1文 |
| 会話/チャット | 256〜512トークン | 32〜64トークン |
用途別のチャンクサイズ推奨値
高度なRAGパターン
クエリ変換
ユーザーの生のクエリは、曖昧であったり、短すぎたり、文書の言語と十分に一致しなかったりすることがよくあります。クエリ変換技術は、検索前の検索品質を改善します。
HyDE(仮想文書埋め込み) (Luyu Gao et al. 2023)
クエリを直接埋め込む代わりに、仮想的な回答を生成して埋め込みます。
\[ \hat{d} = \text{LLM}(q), \quad \mathbf{e}_{\text{query}} = f_\phi(\hat{d}) \]
直感的には、仮想的な回答は実際の文書と同じ言語的レジスターにあるため、クエリと文書の分布の隔たりが小さくなります。
ステップバック・プロンプティング
具体的な質問では、まずより一般的な「ステップバック」質問を生成し、両方を検索してコンテキストを統合します。例:「エタノールの2気圧での沸点は?」 \(\to\) ステップバック:「液体の沸点に影響する要因は何か?」
マルチクエリ生成
クエリの多様な再定式化を \(M\) 個生成し、それぞれを検索して結果を和集合にします。
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI
retriever = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(search_kwargs={"k": 5}),
llm=ChatOpenAI(temperature=0.7),
include_original=True, # also retrieve for original query
)
# Internally generates 3 query variants, retrieves for each, deduplicates
docs = retriever.get_relevant_documents(query)
再ランキング
初期検索で上位 \(k\) 件の候補を得た後、クロスエンコーダーの再ランキング器は各クエリと文書のペアを同時に評価し(両方へ同時に注意を向け)、より正確な関連度スコアを生成します。ただし、レイテンシーは高くなります。
\[ s_{\text{cross}}(q, d) = \text{CrossEncoder}([q; d]) \]
クロスエンコーダーは第1段階の検索には使えません(文書埋め込みを事前計算できないため)が、小さな候補集合(通常 \(k = 20\)〜\(100\))の再ランキングには理想的です。
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-large")
def rerank(query: str, docs: list[str], top_n: int = 5) -> list[str]:
pairs = [(query, doc) for doc in docs]
scores = reranker.predict(pairs)
ranked = sorted(zip(scores, docs), reverse=True)
return [doc for _, doc in ranked[:top_n]]
コンテキスト圧縮
検索されたチャンクには、関連する箇所の周囲に無関係な文が含まれていることがよくあります。コンテキスト圧縮では、LLMを使って関連部分だけを抽出します。
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectorstore.as_retriever()
)
compressed_docs = compression_retriever.get_relevant_documents(query)
Self-RAG
Self-RAG (Asai et al. 2023)は、単一のモデルを学習して、(1) 検索するかどうかを判断し、(2) 検索あり/なしで生成し、(3) 特別なリフレクショントークンを使って出力を自ら批評できるようにします。
-
[Retrieve]:モデルは追加のパッセージを検索すべきか -
[IsRel]:検索されたパッセージはクエリに関連しているか -
[IsSup]:生成された主張は検索されたパッセージから導かれているか -
[IsUse]:全体的な応答は役に立つか
モデルは応答と並行してこれらのトークンを予測するようエンドツーエンドで学習され、検索と自己評価をきめ細かく制御できるようになります。
CRAG:修正型RAG
CRAG (Yan et al. 2024)は、検索された文書を評価し、修正措置を起動する検索評価器を追加します。
-
上位 \(k\) 件の文書を検索する
-
各文書を評価する: 正しい / 曖昧 / 誤り
-
すべての文書が誤りまたは曖昧な場合は\(\to\)ウェブ検索へフォールバックする
-
一部の文書が正しい場合は\(\to\)知識の精緻化(無関係な文を除去する)を使う
-
精緻化したコンテキストから回答を生成する
Adaptive RAG
Adaptive RAG (Jeong et al. 2024)は、予測した複雑さに基づいてクエリを異なる検索戦略へ振り分けます。
-
検索なし: モデルがパラメーターから回答できる単純な事実確認クエリ
-
1ステップRAG: 中程度のクエリに対する標準的な検索・生成
-
マルチステップRAG: 複雑なマルチホップ質問に対する反復検索
クエリの複雑さのラベルで学習した軽量な分類器が、到着する各クエリを振り分けます。
Graph RAG
MicrosoftのGraph RAG (Edge et al. 2024)は、文書コーパスから知識グラフを構築し、コミュニティ検出を使って階層的な要約を生成します。
-
エンティティ抽出: LLMが各チャンクからエンティティと関係を抽出する
-
グラフ構築: グラフ \(G = (V, E)\) を構築する。ノードはエンティティ、エッジは関係を表す
-
コミュニティ検出: Leidenアルゴリズムを適用し、複数の解像度でコミュニティを見つける
-
コミュニティ要約: LLMが各コミュニティの要約を生成する
-
クエリ: グローバルクエリにはコミュニティ要約に対してmap-reduceを行い、ローカルクエリには標準的なベクトル検索を使う
Important
Graph RAGを使う場合
Graph RAGは、多数の文書にまたがる情報を統合する必要があるグローバルクエリ(「このコーパスの主なテーマは何か?」など)で優れていますが、構築と保守にコストがかかります。標準RAGは、ローカルクエリ(「文書XはトピックYについて何と述べているか?」など)に適しています。
RAG-Fusion
RAG-Fusion (Rackauckas 2024)は、元のクエリから複数の検索クエリを生成し、それぞれを検索して、RRF(式[eq:rrf])で順位リストを融合します。
def reciprocal_rank_fusion(ranked_lists: list[list[str]], k: int = 60) -> list[str]:
"""Fuse multiple ranked document lists using RRF."""
scores: dict[str, float] = {}
for ranked in ranked_lists:
for rank, doc_id in enumerate(ranked, start=1):
scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
return sorted(scores, key=scores.get, reverse=True)
def rag_fusion(query: str, retriever, llm, n_queries: int = 4) -> str:
# Step 1: Generate query variants
variants = generate_query_variants(query, llm, n=n_queries)
# Step 2: Retrieve for each variant
all_ranked = [retriever.retrieve(q) for q in [query] + variants]
# Step 3: Fuse with RRF
fused_docs = reciprocal_rank_fusion(all_ranked)
# Step 4: Generate answer
return generate_answer(query, fused_docs[:5], llm)
効率的なRAGデコーディング:REFRAG
RAGにおける実用上のボトルネックはデコーディングレイテンシーです。LLMコンテキストへ連結された検索パッセージは長いのに関連性がまばらなことが多く、初回トークンまでの時間(TTFT)とKVキャッシュのメモリを膨らませます。REFRAG (X. Lin et al. 2025)は、検索パッセージが(多様化や再ランキング時の重複排除によって)独立に取得されるため、その注意パターンがブロック対角になる、つまりパッセージ間の大半の注意がほぼゼロになることに着目します。このスパース性により、デコーディング中にRAGコンテキストへ行う計算の大部分が不要になります。
Compress–Sense–Expandフレームワーク
REFRAGは、3段階のデコーディング戦略によってこの構造を利用します。
-
圧縮: 検索パッセージの完全なKV表現をコンパクトな要約(例:パッセージブロックごとの平均プール済みキー/値)に置き換え、メモリを大幅に削減する。
-
検知: 各デコーディングステップで、圧縮表現に対する軽量な注意を使い、現在のトークンに関連するパッセージブロックを特定する。
-
展開: 選択したブロックだけで完全なKVエントリを再構成し、スパースなアクティブ集合に対して正確な注意を実行する。
結果
LLaMAベースのモデルでは、REFRAGは最大 \(30.85\times\) のTTFT高速化(\(3.75\times\) の改善)を達成し、パープレキシティを損なわない。さらに、有効コンテキスト長を\(16\times\) 固定メモリ予算下で拡張します。これらの向上は、RAG、マルチターン対話、長文書要約タスク全体で得られます。
Tip
エージェント型RAGにREFRAGが重要な理由
エージェント型RAG(節2.7)では、クエリごとに複数回の検索が必要となり、レイテンシーが積み重なります。REFRAGのような効率的なデコーディング手法は不可欠な基盤です。各ラウンドのデコーディングコストをコンテキスト長に対して劣線形にすることで、反復的な検索・推論・生成ループを大規模でも実用的にします。
エージェント型RAG
動機:静的RAGの限界
標準的なRAGは、固定された検索してから生成するパターンに従います。これは次の場合に失敗します。
-
マルチホップ質問: 「2023年にOpenAIの主な競合相手を買収した会社を創業したのは誰か?」には、複数回の検索を連鎖させる必要がある
-
曖昧なクエリ: 適切な検索戦略は、何が見つかったかによって異なる
-
異種ソース: 異なるサブクエリには、異なる知識ベースが必要になる
-
反復的な改善: 初期検索によって、別のクエリが必要だと判明することがある
Tip
マルコフ決定過程としてのRAG
エージェント型RAGでは、検索を逐次的な意思決定問題として捉えます。状態は現在のコンテキスト(クエリと、ここまでに検索した文書)であり、行動には検索、推論、生成、停止が含まれます。報酬は回答の正しさです。エージェントは、いつ何を検索するかの方策を学習します。
エージェント型RAGのアーキテクチャ
マルチソース・ルーティング
エージェント型RAGシステムは、サブクエリを専門的な知識ソースへ振り分けられます。核心となる洞察は、質問の種類ごとに異なる検索バックエンドが必要であり、すべてに優れた単一のインデックスは存在しないということです。
なぜルーティングするのか
4つのクエリを処理する金融アナリストのアシスタントを考えてみましょう。
-
「自社のPTOポリシーは何か?」 \(\rightarrow\) ベクトルDB (社内文書)
-
「FRBは昨日何を発表したか?」 \(\rightarrow\) ウェブ検索 (リアルタイム)
-
「地域別に第3四半期の売上高を表示して」 \(\rightarrow\) SQLデータベース (構造化データ)
-
「自社の認証ミドルウェアはどのようにトークンを検証するか?」 \(\rightarrow\) コードインデックス (コードベース)
単一のインデックスから平坦に検索する方法では、回答を見失うか、無関係なパッセージを返してしまいます。ルーティングは検索開始前に、各サブクエリに適したツールを選びます。
ルーティング戦略
洗練度が上がる順に、主なアプローチは3つあります。
-
ルールベース・ルーティング: キーワードトリガー(例:SQLキーワード \(\rightarrow\) データベース、URLパターン \(\rightarrow\) ウェブ)。高速で解釈しやすい一方、曖昧なクエリには脆弱です。
-
分類器ベース・ルーティング: 軽量なモデル(例:ファインチューニングしたBERT分類器、またはクエリ埋め込みに対するロジスティック回帰)が最適なソースを予測します。低レイテンシー(\(<\)10ms)でルーティングログから学習できますが、ラベル付きデータが必要です。
-
LLMベース・ルーティング: LLM自身が構造化出力の呼び出しでソースを決定します(下のリストを参照)。最も柔軟で、新しいクエリの種類に対応し、推論を説明できますが、LLM呼び出し1回分のレイテンシーが加わります。
Tip
学習される方策としてのルーター
マルチソース・ルーティングは、最も単純には分類問題であり、最も高度には計画問題です。状態をクエリと会話履歴、行動をソースの選択(および任意のクエリ書き換え)、報酬を下流の回答品質とするRL方策として扱えば、ルーターを方策勾配手法によってエンドツーエンドで最適化できます(章5)。
実運用上の考慮事項
-
フォールバックチェーン: 主ソースが信頼度の低い結果を返したら、次に適したソースを試す
-
並列ファンアウト: 曖昧なクエリでは複数ソースから同時に検索し、Reciprocal Rank Fusionで結果を統合する(表2.1)。
-
コスト意識: ウェブ検索とAPI呼び出しには金銭的コストやレート制限があるため、ルーターはこれらを考慮すべきである
-
可観測性: 推論とともにすべてのルーティング判断を記録する。デバッグと再学習に不可欠である
from enum import Enum
from pydantic import BaseModel
class KnowledgeSource(str, Enum):
VECTOR_DB = "vector_db" # internal documents
WEB_SEARCH = "web_search" # real-time web
SQL_DB = "sql_db" # structured data
CODE_INDEX = "code_index" # codebase
API = "api" # external APIs
class RouteDecision(BaseModel):
source: KnowledgeSource
refined_query: str
reasoning: str
def route_query(query: str, llm) -> RouteDecision:
"""Use LLM to decide which knowledge source to query."""
prompt = f"""Given the query: "{query}"
Decide which knowledge source to use:
- vector_db: for internal documents, policies, past reports
- web_search: for current events, recent information
- sql_db: for numerical data, statistics, structured records
- code_index: for code examples, API documentation
- api: for real-time data (weather, stock prices, etc.)
Return a JSON with: source, refined_query, reasoning."""
return llm.with_structured_output(RouteDecision).invoke(prompt)
エージェント型RAGの完全実装
前節までで、ルーティング、検索、評価という個別のコンポーネントを紹介しました。完全なエージェント型RAGシステムは、これらを状態を持つノードのグラフとしてオーケストレーションします。つまり、制御フローが中間結果に応じて変化します。以下の実装ではLangGraphを使い、4つのノードをループへ接続します。
-
計画: ユーザークエリをサブクエリへ分解する(情報ニーズごとに1つ)。
-
検索: 各サブクエリを適切なソースへ振り分け、文書を取得する。
-
評価: 蓄積したコンテキストが元のクエリに回答するのに十分か判断する。
-
生成: 検索した文書から引用付きの最終回答を統合する。
重要な設計パターンは条件付きループです。評価後、エージェントは(コンテキストが十分、または反復予算を使い果たした場合)生成へ進むか、精緻化したサブクエリで検索へ戻ります。これは情報収集行動上で動作するRLエージェントの、検知・行動・評価サイクルに対応します。
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
import operator
class AgentState(TypedDict):
query: str
sub_queries: list[str]
retrieved_docs: Annotated[list[dict], operator.add]
context_sufficient: bool
answer: str
iterations: int
max_iterations: int
def plan_node(state: AgentState) -> AgentState:
"""Decompose query into sub-queries."""
sub_queries = decompose_query(state["query"])
return {**state, "sub_queries": sub_queries, "iterations": 0}
def retrieve_node(state: AgentState) -> AgentState:
"""Retrieve documents for current sub-queries."""
new_docs = []
for sq in state["sub_queries"]:
source = route_query(sq)
docs = retrieve_from_source(sq, source)
new_docs.extend(docs)
return {**state, "retrieved_docs": new_docs,
"iterations": state["iterations"] + 1}
def evaluate_node(state: AgentState) -> AgentState:
"""Evaluate whether retrieved context is sufficient."""
sufficient = evaluate_context_sufficiency(
query=state["query"],
docs=state["retrieved_docs"]
)
return {**state, "context_sufficient": sufficient}
def generate_node(state: AgentState) -> AgentState:
"""Generate answer from retrieved context."""
answer = generate_with_citations(
query=state["query"],
docs=state["retrieved_docs"]
)
return {**state, "answer": answer}
def should_retrieve(state: AgentState) -> str:
if state["context_sufficient"]:
return "generate"
if state["iterations"] >= state["max_iterations"]:
return "generate" # give up and generate with what we have
return "retrieve"
# Build the graph
workflow = StateGraph(AgentState)
workflow.add_node("plan", plan_node)
workflow.add_node("retrieve", retrieve_node)
workflow.add_node("evaluate", evaluate_node)
workflow.add_node("generate", generate_node)
workflow.set_entry_point("plan")
workflow.add_edge("plan", "retrieve")
workflow.add_edge("retrieve", "evaluate")
workflow.add_conditional_edges("evaluate", should_retrieve,
{"retrieve": "retrieve", "generate": "generate"})
workflow.add_edge("generate", END)
agent = workflow.compile()
# Run
result = agent.invoke({
"query": "What were the main causes of the 2023 banking crisis?",
"max_iterations": 3,
"retrieved_docs": [],
"iterations": 0,
})
ツール拡張RAG
エージェント型RAGは検索と計算ツールを組み合わせられます。
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain.tools import tool
@tool
def search_documents(query: str) -> str:
"""Search internal document knowledge base."""
docs = vectorstore.similarity_search(query, k=5)
return "\n\n".join(d.page_content for d in docs)
@tool
def query_database(sql: str) -> str:
"""Execute SQL query on the analytics database."""
return db.run(sql)
@tool
def web_search(query: str) -> str:
"""Search the web for current information."""
return tavily_client.search(query)
@tool
def execute_python(code: str) -> str:
"""Execute Python code for calculations."""
return python_repl.run(code)
tools = [search_documents, query_database, web_search, execute_python]
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
Search-R1:RL学習されたエージェント型RAG
上記のエージェント型RAGアプローチは、プロンプトエンジニアリングされたオーケストレーションに依存しています。エージェントの検索動作は指示によって制御され、学習によって獲得されたものではありません。 Search-R1 (Jin et al. 2025)は根本的に異なる方法を取り、LLMを強化学習によって学習させ、推論プロセスの一部としていつ、何を、何回検索するかを学習します。
基本概念
Search-R1は、検索エンジンへのクエリをRL学習ループ内の 行動 として扱うことで、DeepSeek-R1 (DeepSeek-AI et al. 2025)の推論フレームワークを拡張します。連鎖思考の生成中、モデルは特別なトークン <search>query</search> を出力して検索エンジンからリアルタイムに検索できます。検索結果は推論コンテキストへ挿入され、モデルは生成を続けます。
形式的設定
モデルは検索行動を織り交ぜた推論トレースを生成します。
\[
\underbrace{\text{think}_1}_{\text{reasoning}} \to \underbrace{\texttt{
軌跡全体(推論+検索+最終回答)は、終端報酬によってスコアリングされます。報酬は、正解ラベルに対する最終回答の正しさです。
学習アルゴリズム
Search-R1はGRPO(Group Relative Policy Optimization)を使います。
-
質問ごとに \(N\) 本の軌跡をサンプリングする :各軌跡には0〜5回の検索呼び出しが含まれうる
-
検索をリアルタイムで実行する: 環境が実際の検索エンジン結果を返す
-
終端回答の正しさをスコアリングする: 正解に対する完全一致またはF1
-
グループ相対アドバンテージを計算する: \(\hat{A}_i = (R_i - \mu_G) / \sigma_G\)
-
方策を更新する: GRPOのクリップ付き目的関数により、効果的に検索した軌跡を強化する
モデルは次を学習します。
-
不確かなときに検索する: すでに持っている知識に対する不要な検索を避ける
-
効果的なクエリを組み立てる: 関連する結果を返すクエリの表現を学習する
-
複数回検索する: 初期結果に基づいてクエリを反復的に改善する
-
検索コンテキストを統合する: 検索結果を使って推論を裏付けたり、修正したりする
Search-R1とプロンプトベースのエージェント型RAGの違い
| 側面 | プロンプトベースのエージェント型RAG | Search-R1 |
|---|---|---|
| 検索の判断 | プロンプト/ヒューリスティック | RLで学習 |
| クエリの組み立て | プロンプトで指示(「クエリを書き換える」) | エンドツーエンドで学習 |
| 検索回数 | 推論時に固定またはLLMが決定 | 最適な回数を学習 |
| 学習信号 | なし(モデルを凍結) | 正しさの報酬 |
| 検索の統合 | コンテキストへ追加 | CoTに織り交ぜる |
| 失敗からの回復 | 再試行ヒューリスティック | 学習したバックオフ/再定式化 |
| 推論時のオーバーヘッド | フレームワークのオーバーヘッド(LangGraph) | モデル本来の動作 |
プロンプトベースのエージェント型RAGとSearch-R1(RL学習)の比較
結果
オープンドメインQAベンチマーク(NQ (Kwiatkowski et al. 2019)、TriviaQA (Joshi et al. 2017)、HotpotQA (Yang et al. 2018))では、7BモデルのSearch-R1は次を上回ります。
-
標準RAG(単一検索)を15〜20%の精度で上回る
-
プロンプト型エージェントRAG(ReAct形式)を8〜12%の精度で上回る
-
標準RAGを使う、はるかに大きなモデル(70B)の性能に近づく
重要な洞察は、 いつどのように検索するかを学習することは、より多くを知る大きなモデルを持つことより価値がある ということです。うまく検索する小さなモデルは、検索しない大きなモデルに勝ります。
Tip
Search-R1:パラダイムシフト
従来のRAGはこう尋ねます。「このクエリに対して、何を検索すべきか?」(生成前に行うパイプライン上の判断)。
Search-R1はこう尋ねます。「ここまで推論した内容から、さらに情報が必要か?必要なら、このギャップを埋める具体的な質問は何か?」(生成中に行う学習された判断)。
これは、試験の前に教科書を調べる学生と、問題の途中で行き詰まったと気づいたときに参考文献を調べる学生の違いです。後者のほうが効率的で、的を絞っています。
評価
RAGシステムの評価は、検索や生成を個別に評価するより難しくなります。エラーはパイプラインのどの段階でも発生し得て、しかも積み重なるためです。完全な生成器でも無関係な検索結果を補えず、完全な検索器でも生成器がハルシネーションを起こしたりコンテキストを無視したりすれば無駄になります。
したがって、効果的なRAG評価は 3つのレベル で行います。
-
検索品質: 検索器は正しいパッセージを見つけたか?(再現率、適合率、MRR、NDCG)
-
生成品質: 回答は正しく、検索されたコンテキストに忠実で、完全か?(正しさ、忠実性、回答関連度)
-
エンドツーエンド品質: システム全体はユーザーを満足させるか?(人間の選好、タスク成功率、レイテンシー調整済み効用)
よくある失敗は、1つのレベルだけを最適化することです。たとえば大きな \(K\) でRecall@\(K\)を最大化すると、周辺的にしか関連しないパッセージでコンテキストが埋まり、実際には生成品質が低下します。以下の指標は検索と生成の両方を対象とし、どの段階がボトルネックかを診断できるようにします。
検索指標
\(\mathcal{R}_k\) はランク \(k\) で検索された文書の集合で、\(\mathcal{R}^*\) は関連文書の集合とします。
Recall@K
\[ \text{Recall@}K = \frac{|\mathcal{R}_K \cap \mathcal{R}^*|}{|\mathcal{R}^*|} \]
Precision@K
\[ \text{Precision@}K = \frac{|\mathcal{R}_K \cap \mathcal{R}^*|}{K} \]
平均逆順位(MRR)
\[ \text{MRR} = \frac{1}{|Q|} \sum_{i=1}^{|Q|} \frac{1}{\text{rank}_i} \]
ここで \(\text{rank}_i\) は、クエリ \(i\) の最初の関連文書の順位です。
正規化割引累積利得(NDCG@K)
\[ \text{NDCG@}K = \frac{\text{DCG@}K}{\text{IDCG@}K}, \quad \text{DCG@}K = \sum_{i=1}^{K} \frac{\text{rel}_i}{\log_2(i+1)} \]
ここで \(\text{rel}_i \in \{0, 1, 2, \ldots\}\) は第\(i\) 件の結果の段階的関連度で、IDCGは理想(完全な)DCGです。
生成指標
忠実性
生成された回答が検索されたコンテキストにグラウンディングされているか、つまり回答中のすべての主張を検索された文書に帰属できるかを測定します。LLMの評価者によって評価します。
\[ \text{Faithfulness} = \frac{\text{# claims supported by context}}{\text{# total claims in answer}} \]
回答関連度
回答が質問に答えているかを測定します。回答から質問を生成し、元のクエリとの類似度を測定して計算します。
\[ \text{AnswerRelevance} = \frac{1}{N} \sum_{i=1}^{N} \cos!\left(E(q), E(\hat{q}_i)\right) \]
ここで \(\hat{q}_i\) は回答から生成された質問です。
コンテキストの適合率と再現率
\[ \begin{aligned} \text{ContextPrecision@}K &= \frac{1}{K} \sum_{k=1}^{K} \text{Precision@}k \cdot \mathbf{1}[\text{doc}_k \text{ is relevant}] \\ \text{ContextRecall} &= \frac{\text{# ground-truth claims attributable to context}}{\text{# total ground-truth claims}} \end{aligned} \]
RAGAsフレームワーク
RAGAs(Retrieval Augmented Generation Assessment) (Es et al. 2023)は、LLM評価者を使う参照不要の評価フレームワークを提供します。
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
answer_correctness,
)
from datasets import Dataset
eval_dataset = Dataset.from_dict({
"question": questions,
"answer": generated_answers,
"contexts": retrieved_contexts, # list of lists
"ground_truth": reference_answers,
})
results = evaluate(
dataset=eval_dataset,
metrics=[
faithfulness,
answer_relevancy,
context_precision,
context_recall,
answer_correctness,
],
)
print(results.to_pandas())
よくある失敗モード
Warning
監視すべきRAGの失敗モード
検索漏れ: 関連文書はコーパスに存在するが検索されない。原因:不適切なチャンク分割、埋め込みモデルの不一致、クエリと文書の語彙の隔たり。
コンテキスト汚染: 検索された文書に誤解を招く、または矛盾する情報が含まれ、モデルが誤った回答を生成する。
Lost-in-the-Middle: LLMは長いコンテキストの先頭と末尾に強く注意を向けるため、中央の関連情報が無視されることがある (N. F. Liu et al. 2024b)。
過剰検索: 検索されたチャンクが多すぎると、関連信号が薄まり、レイテンシーとコストが増加する。
検索してもハルシネーション: 特にコンテキストが学習データと矛盾すると、モデルが検索コンテキストを無視してパラメトリック記憶から生成する。
引用の捏造: モデルが、主張を裏付けていない文書にその主張を帰属させる。
本番運用上の考慮事項
埋め込みモデルの選定
埋め込みモデルはRAGシステムで最も影響の大きいコンポーネント選択です。検索品質の上限を決めるためです。この分野は急速に進歩しており、表2.2にコストと品質のスペクトル全体にわたる現在の選択肢をまとめます。
| モデル | 次元数 | 最大トークン数 | MTEB平均 | アクセス | 注記 |
|---|---|---|---|---|---|
| APIベース(マネージド) | |||||
Voyage voyage-4-large | 1024* | 32K | — | API | 最良の検索品質 |
OpenAI text-embedding-3-large | 3072 | 8191 | 64.6 | API | Matryoshka次元 |
Cohere embed-english-v3.0 | 1024 | 512 | 64.5 | API | int8/バイナリ対応 |
Google text-embedding-005 | 768 | 2048 | — | API | Vertex AI統合 |
| オープンウェイト(セルフホスト) | |||||
nvidia/NV-Embed-v2 (Lee et al. 2024) | 4096 | 32K | 72.3 | 無料 | MTEB第1位(2024年9月) |
Alibaba-NLP/gte-Qwen2-7B (Z. Li et al. 2023) | 3584 | 32K | 70.2 | 無料 | Apache-2.0、多言語 |
BAAI/bge-m3 (J. Chen et al. 2024) | 1024 | 8192 | 65.0 | 無料 | 密ベクトル検索+スパース検索+マルチベクトル |
jinaai/jina-embeddings-v3 | 1024 | 8192 | 66.0 | 無料 | 多言語、LoRAアダプター |
BAAI/bge-large-en-v1.5 (S. Xiao et al. 2023) | 1024 | 512 | 64.2 | 無料 | 成熟しており、十分にサポートされている |
本番RAG向けの埋め込みモデル(2026年時点)。MTEBスコアは検索、分類、クラスタリング、STSタスク全体の平均値です。{#tab:embedding_models}
選定基準
-
ドメイン適合: 特化モデル(例:コード用の
voyage-code-3、金融用のvoyage-finance-2)は、ドメインタスクで汎用モデルを5〜15%上回ることがある。 -
コンテキスト長: 32Kトークンのコンテキストを持つモデル(Voyage-4、NV-Embed-v2)は、チャンク分割せず文書全体を埋め込めるため、パイプラインを簡素化できる。
-
Matryoshka埋め込み: 柔軟な出力次元(256〜4096)に対応するモデルでは、再エンコードせずにサービス提供時の品質とストレージ/レイテンシーをトレードオフできる。
-
量子化対応: モデルレベルのint8またはバイナリ量子化(Cohere、Voyage)により、再現率をほとんど損なわずインデックスサイズを4–32\(\times\) 削減する。
-
多言語: 英語以外または言語横断RAGでは、多言語で明示的に学習されたモデル(BGE-M3、Jina-v3、Voyage-4)を優先する。
ベクトルデータベースの比較
| データベース | ホスティング | 規模 | フィルタリング | ハイブリッド | 適する用途 |
|---|---|---|---|---|---|
| FAISS1 | セルフホスト | 数十億 | 限定的 | なし | 研究、オフライン |
| Pinecone2 | マネージド | 数十億 | あり | あり | サーバーレス、簡単なセットアップ |
| Weaviate3 | どちらも | 数十億 | あり | あり | GraphQL、マルチモーダル |
| Chroma4 | セルフホスト | 数百万 | あり | なし | ローカル開発、プロトタイピング |
| Qdrant5 | どちらも | 数十億 | あり | あり | 高性能 |
| Milvus6 | どちらも | 数十億 | あり | あり | エンタープライズ、GPU高速化 |
| pgvector7 | セルフホスト | 数百万 | あり | あり | 既存のPostgresユーザー |
本番RAGシステム向けのベクトルデータベース比較
5https://qdrant.tech 6https://milvus.io 7https://github.com/pgvector/pgvector
レイテンシーの最適化
-
事前フィルタリング: メタデータフィルター(期間、カテゴリ、ソース)を使い、ANN検索前に検索空間を狭める
-
近似最近傍: 正確な検索の代わりにHNSWまたはIVFインデックスを使い、\(\sim\)1% の再現率低下を受け入れて\(10\times\) の高速化を得る
-
埋め込みキャッシュ: 頻繁に繰り返されるクエリの埋め込みをキャッシュする
-
非同期検索: 複数ソースから並列に検索する
-
ストリーミング生成: 検索の完了中にLLM出力をストリームする
-
量子化: 埋め込みにint8またはバイナリ量子化を使い、メモリを削減してスループットを向上させる
非同期並列検索
上記の手法(3)と(4)は自然に組み合わせられます。クエリ埋め込みをキャッシュしてから、複数のバックエンドへ検索リクエストを同時にファンアウトします。マルチソースRAGシステム(節2.7)では、ユーザークエリがベクトルデータベース、キーワードインデックス、ウェブAPIから結果を必要とすることがあります。逐次検索ではレイテンシーが加わりますが、並列検索では最も遅いソースのコストだけを支払います。リスト[lst:async_retrieve]は、Pythonのasyncioを使ってこのパターンを示します。lru_cacheデコレーターによって繰り返しクエリでは埋め込みモデルを完全にスキップでき、asyncio.gatherによってすべてのソースクエリを同時に送出します。
import asyncio
from functools import lru_cache
@lru_cache(maxsize=1024)
def get_cached_embedding(text: str) -> list[float]:
return embedding_model.embed_query(text)
async def parallel_retrieve(
query: str,
sources: list[str],
k: int = 5
) -> list[dict]:
"""Retrieve from multiple sources in parallel."""
tasks = [
asyncio.create_task(retrieve_from_source_async(query, src, k))
for src in sources
]
results = await asyncio.gather(*tasks, return_exceptions=True)
# Flatten and deduplicate
all_docs = []
for r in results:
if not isinstance(r, Exception):
all_docs.extend(r)
return deduplicate_by_content(all_docs)
増分インデックス作成とバージョン管理
本番環境では文書コーパスが静的になることはありません。ポリシーは改訂され、新しいレポートは毎日追加され、非推奨コンテンツは削除する必要があります。完全な再インデックス作成(再チャンク分割、再埋め込み、再アップロード)は高コストで、ダウンタイムを引き起こします。増分インデックス作成は、文書レベルで変更を適用してこの問題を解決します。
基本操作
-
アップサート: 文書が作成または更新されたら、その
doc_idに対する既存の全チャンクを削除し、新しい内容を再チャンク分割して埋め込み、挿入する。これにより古い断片が残らないことが保証される。 -
削除/期限切れ: 文書IDでチャンクを削除する(明示的な削除)か、TTLで削除する(ニュースや市場データなど時間に敏感なソースの自動ガベージコレクション)。
-
バージョン追跡: チャンクのメタデータに
versionとindexed_atのタイムスタンプを保存する。これによりロールバック(ソースから以前のバージョンを復元)と監査可能性(「モデルはどのバージョンを見たか?」)が可能になる。
一貫性に関する課題
-
埋め込みモデルのドリフト: 埋め込みモデルをアップグレードすると、古いベクトルと新しいベクトルに互換性がなくなる。解決策:(a) モデルバージョンごとに別インデックスを維持してバックグラウンドで移行する、または (b) 次元の切り詰めによって互換性を保つMatryoshka対応モデルを使う。
-
チャンク境界の変化: チャンク分割戦略を変えると既存の全チャンクが無効になる。バージョンメタデータによって影響を受ける文書を特定し、選択的に再インデックス作成できる。
-
結果整合性: 分散ベクトルデータベースでは、アップサートしたばかりのベクトルがすぐに検索可能にならないことがある。短いインデックス作成遅延(通常は数秒〜数分)を許容できるようパイプラインを設計する。
実装
リスト[lst:incremental_index]は、アップサートと期限切れ処理をカプセル化した最小限の RAGIndexManager クラスを示します。メタデータフィルタリングに対応する任意のベクトルストアをラップするのに適しています。
class RAGIndexManager:
def __init__(self, vectorstore, metadata_store, chunker, embedder):
self.vs = vectorstore
self.meta = metadata_store
self.chunker = chunker
self.embedder = embedder
def upsert_document(self, doc_id: str, content: str,
metadata: dict) -> None:
"""Add or update a document, replacing old chunks."""
# Delete existing chunks for this document
self.vs.delete(filter={"doc_id": doc_id})
# Chunk new version (vectorstore embeds internally)
chunks = self.chunker.split_text(content)
self.vs.add_texts(
texts=chunks,
metadatas=[{**metadata, "doc_id": doc_id,
"version": metadata.get("version", 1),
"indexed_at": datetime.utcnow().isoformat()}
for _ in chunks],
)
def expire_old_documents(self, ttl_days: int = 365) -> int:
"""Remove documents older than TTL."""
cutoff = (datetime.utcnow() - timedelta(days=ttl_days)).isoformat()
return self.vs.delete(filter={"indexed_at": {"$lt": cutoff}})
RAGとファインチューニングの相乗効果
RAGとファインチューニングを組み合わせる場合
ファインチューニングとRAGは、相補的な弱点に対処します。
-
ファインチューニングだけ: モデルはスタイルと形式を学習するが、事実をハルシネーションすることがある
-
RAGだけ: モデルは事実へアクセスできるが、それを最適に使う方法を知らないことがある
-
組み合わせ: 検索したコンテキストをうまく使うようモデルをファインチューニングする。出典を引用し、不確実性を認め、無関係なコンテキストを無視する
RAFT:検索拡張ファインチューニング
RAFT (T. Zhang et al. 2024)は、関連文書と妨害文書を混ぜて与えられた質問に回答するようモデルを学習させ、関連するコンテキストだけを識別して使うことを教えます。
-
各学習例 \((q, a, d^*)\) について、\(k-1\) 個の妨害文書 \(\{d_i^-\}\)
-
次でファインチューニングする:
[q,\(d^*\),\(d_1^-\), …,\(d_{k-1}^-\)]\(\to\)[chain-of-thought + a] -
連鎖思考は \(d^*\) から明示的に引用し、モデルに回答をグラウンディングする方法を教える
\[ \mathcal{L}_{\text{RAFT}} = -\mathbb{E}_{(q,a,d^*,\{d_i^-\})} \left[ \log P_\theta!\left(\text{CoT}(d^*) \oplus a ;\middle|; q, d^*, \{d_i^-\}\right) \right] \]
検索器と生成器の共同学習
最大性能を得るには、検索器と生成器を共同で学習できます。REALM (Guu et al. 2020)とRAG (P. Lewis et al. 2020)の論文は、検索ステップを通じて勾配を流すエンドツーエンド学習を提案しています。
\[ \nabla_\theta \mathcal{L} = \nabla_\theta \left[ -\log \sum_{d \in \mathcal{D}} P_\theta(a \mid q, d) \cdot P_\phi(d \mid q) \right] \]
検索器のパラメーター \(\phi\) は、REINFORCE推定量を使うか、\(P_\phi(d \mid q)\) を文書に対する微分可能な注意として扱うことで更新されます。
Warning
共同学習の課題
検索器と生成器の共同学習は強力ですが複雑です。(1) 文書インデックスは\(\phi\) が変化するたびに定期的に更新する必要があります(非同期インデックス更新)。(2) 学習信号はスパースです(上位\(k\) 文書だけが寄与します)。(3) 事前学習済み検索器から慎重に初期化しなければ、学習は不安定です。
RAGアプローチの包括的な比較
| アプローチ | 精度 | レイテンシー | 複雑さ | コスト | 適する用途 |
|---|---|---|---|---|---|
| Naive RAG (P. Lewis et al. 2020) | 中 | 低 | 低 | 低 | プロトタイピング、単純なQA |
| RAG + 再ランキング (Nogueira and Cho 2019) | 高 | 中 | 中 | 中 | 本番QAシステム |
| HyDE (Luyu Gao et al. 2023) | 高 | 中 | 低 | 中 | 意味の不一致があるドメイン |
| マルチクエリRAG | 高 | 中 | 中 | 中 | 曖昧なクエリ |
| RAG-Fusion (Rackauckas 2024) | 高 | 中 | 中 | 中 | 多様なクエリ種別 |
| Self-RAG (Asai et al. 2023) | 高 | 中 | 高 | 中 | 選択的検索 |
| CRAG (Yan et al. 2024) | 高 | 中 | 高 | 高 | 信頼性の低いコーパス |
| Adaptive RAG (Jeong et al. 2024) | 高 | 低〜高 | 高 | 中 | 混在するクエリの複雑さ |
| Graph RAG (Edge et al. 2024) | 非常に高 | 高 | 非常に高 | 高 | グローバル統合クエリ |
| エージェント型RAG | 非常に高 | 高 | 非常に高 | 高 | マルチホップ推論 |
| RAFT (T. Zhang et al. 2024) | 非常に高 | 低 | 非常に高 | 非常に高 | ドメイン固有の配備 |
主要な側面におけるRAGアプローチ
Note
RAGシステムの主要な設計上の問い
本番向けRAGシステムを設計する際は、次を検討します。
クエリ分布はどのようなものか? 事実確認、分析、マルチホップのクエリには、それぞれ異なる検索戦略が必要です。
コーパスの規模と動的性はどの程度か? 頻繁に更新される数百万の文書には、増分インデックス作成が可能なマネージド・ベクトルデータベースが適しています。
レイテンシー要件は何か? 100ms未満の応答では再ランキングやエージェントループは使えません。バッチまたは非同期のユースケースならこれらを許容できます。
グラウンディングはどれほど重要か? 医療、法律、金融などの高リスク領域では、忠実性の評価と引用の検証が必要です。
語彙は専門的か? ドメイン固有の用語には、ハイブリッド検索やドメイン適応済みの埋め込みモデルが必要になることがあります。
Important
RAGのベストプラクティスまとめ
シンプルに始める: 優れたチャンク分割による素朴なRAGは、粗悪なチャンク分割による複雑なシステムを上回ることが多い
検索を個別に評価する: 生成を最適化する前に検索を修正する
ハイブリッド検索を使う: BM25+密ベクトル検索とRRFは堅実なデフォルトである
再ランキングを加える: 上位20候補へのクロスエンコーダー再ランキング器は投資対効果が高い
忠実性を監視する: LLM評価者を使い、本番環境でハルシネーション率を追跡する
積極的にキャッシュする: 文書は一度だけ埋め込み、頻繁なクエリの埋め込みをキャッシュする
オーバーラップ付きでチャンク分割する: 10〜15%のオーバーラップで境界における情報損失を防ぐ
豊富なメタデータを保存する: ソース、日付、節、文書種別によって強力な事前フィルタリングが可能になり、精度が大幅に向上する
エージェント型メモリシステム
動機:エージェントにメモリが必要な理由
大規模言語モデルは本質的に、状態を持たない関数近似器です。プロンプト \(x\) に対して、継続 \(p_\theta(y \mid x)\) を生成する分布を出力します。すべての推論呼び出しは最初から始まります。コンテキストウィンドウ、つまりモデルが注意を向けられる有限のトークン列だけが、生成時に利用できる情報です。短く自己完結したタスクにはこれで十分ですが、長期のエージェント型タスクでは根本的なボトルネックになります。
Important
コンテキストウィンドウのボトルネック
\(L\) は最大コンテキスト長を表します(例:\(L = 128{,}000\) トークン、GPT-4oの場合)。1トークンはおよそ4文字をエンコードし、一般的な書籍には\(\sim!500{,}000\) 語、\(\approx 670{,}000\) トークンが含まれます。コストを無視しても、数日間自律動作するエージェントは、どの固定ウィンドウにも収まらない観察、ツール出力、推論トレースを蓄積します。メモリシステムは、この物理的制約に対する工学的な応答です。
エージェントが永続メモリを持たないと、3つの異なる失敗モードが生じます。
-
コンテキストの壊滅的忘却。 イベントがコンテキストウィンドウからスクロールして消えると、回復不能に失われます。エージェントは10,000トークン前に下した決定を参照できません。
-
経験から学べない。 エピソードの保存がなければ、すべてのエピソードがエージェントにとって初めてのものになります。成功した戦略は再利用できず、ミスが繰り返されます。
-
パーソナライズの欠如。 ユーザーの好み、ドメインの事実、関係の履歴を各セッションで再確立する必要があり、ユーザー体験と効率が低下します。
Tip
認知アーキテクチャとしてのメモリ
認知科学では、生物エージェントの複数の記憶システムを区別します (Tulving 1985; Squire 1992)。ワーキングメモリ(情報の能動的操作)、エピソード記憶(自伝的な出来事)、意味記憶(世界知識)、手続き記憶(スキルと習慣)です。効果的なエージェント型AIシステムは、神経科学をシミュレーションしているからではなく、これらの分類が本当に異なるアクセスパターン、更新頻度、検索メカニズムを反映しているため、類似した区別から恩恵を受けます。
形式的には、エージェントをタプル \(\mathcal{A} = (\pi_\theta, \mathcal{M}, \mathcal{R}, \mathcal{W})\) としてモデル化します。ここで \(\pi_\theta\) は方策(LLM)、\(\mathcal{M}\) はメモリストア、\(\mathcal{R}: \mathcal{Q} \times \mathcal{M} \to \mathcal{D}\) はクエリを検索文書へ写像する検索関数、\(\mathcal{W}: \mathcal{M} \times \mathcal{E} \to \mathcal{M}\) は新しい経験 \(\mathcal{E}\) を使ってメモリを更新する書き込み関数です。各ステップ \(t\) でエージェントは \(o_t\) を観察し、関連コンテキスト \(c_t = \mathcal{R}(o_t, \mathcal{M})\) を検索して行動します。
\[ a_t \sim \pi_\theta!\left(\cdot ;\middle|; [s_t;, c_t;, h_t]\right), \]
ここで \(s_t\) は現在のシステムプロンプト、\(c_t\) は検索されたメモリ、\(h_t\) はコンテキスト内の直近の履歴です。行動後、エージェントは新しい情報を書き込めます。\(\mathcal{M} \leftarrow \mathcal{W}(\mathcal{M},, (o_t, a_t, r_t))\).
メモリの種類の分類
ワーキングメモリ(短期)
ワーキングメモリはエージェントのアクティブな作業領域、つまり現在操作している情報です。LLMエージェントでは次に対応します。
-
スクラッチパッド。 最終回答を生成する前に、中間推論ステップを専用バッファーへ書き込むもの(例:chain-of-thought (Wei et al. 2022)、scratchpad (Nye et al. 2021))。
-
連鎖思考バッファー。 推論トークン \(z_1, z_2, \ldots, z_k\) は回答トークン \(a\) より前に生成され、\(p(a \mid x) = \sum_z p(a \mid x, z),p(z \mid x)\) としてモデル化されます。
-
会話コンテキスト。 コンテキストウィンドウに保持される直近のターン履歴 \([(u_1, a_1), \ldots, (u_t, a_t)]\)。
ワーキングメモリは高速(検索レイテンシーがゼロで、すでにコンテキスト内にある)、揮発性(コンテキストがクリアされると失われる)、容量制限付き(\(L\)) によって上限が決まる)です。
エピソード記憶(経験ベース)
エピソード記憶は、コンテキストと時刻でインデックス化された特定の過去イベントを保存します。エージェントでは次のものが含まれます。
-
過去のインタラクション。 過去の会話、タスクの試行、その結果の完全または要約された記録。
-
成功した軌跡。 類似する将来のタスクに対する少数ショット例として検索できる、高報酬の行動系列。
-
失敗事例。 根本原因の注釈を付けた記録済みのミス。エージェントがエラーの再発を避けられる。
-
検索拡張エピソード想起。 新しいタスク \(q\) が与えられたら、最も類似した過去の\(k\) エピソード \(\{e_i\}_{i=1}^k\) を検索し、コンテキストへ含める。
エピソード記憶は通常、エピソード要約に対する埋め込みを持つベクトルストア(節3.3.1)として実装されます。
意味記憶(世界知識)
意味記憶は、特定のエピソードから切り離された一般的な事実と概念をエンコードします。
-
事実知識。 エンティティ、属性、関係(例:「パリはフランスの首都である」)。
-
ドメイン概念。 エージェントのタスク領域に関連する定義、分類体系、オントロジー。
-
知識グラフ。 構造化表現 \(\mathcal{G} = (\mathcal{V}, \mathcal{E})\) であり、ノード \(v \in \mathcal{V}\) はエンティティ、エッジ \(e \in \mathcal{E}\) は型付き関係です。
エピソード記憶と異なり、意味記憶はコンテキスト非依存です。水が \(100^\circ\)Cで沸騰するという事実は、いつどこで学習されたかにかかわらず真です。
手続き記憶(スキル)
手続き記憶は、物事のやり方、つまり自動化されたスキルと行動パターンをエンコードします。
-
学習されたツール利用パターン。 どのタスクでどのAPIを呼び出すか、入力をどう形式化するか、エラーをどう処理するか。
-
行動系列。 複数ステップの手順(例:「コードを配備するには、テスト実行 \(\to\) イメージのビルド \(\to\) プッシュ \(\to\) マニフェスト更新」)。
-
メモリとしての方策。 モデルの重み \(\theta\) 自体が手続き知識をエンコードします。成功した軌跡でのファインチューニングは、手続き記憶の固定化の一形態です。
Note
メモリ種別の分類
ソフトウェア開発を支援するエージェントは、次を使います。
ワーキング: 現在編集しているファイル、たった今受け取ったエラーメッセージ。
エピソード: 「先週、モジュールXで似た
NullPointerExceptionを修正するため、42行目にnullチェックを追加した。」意味: 「Pythonの
asyncio.gatherはコルーチンを並行実行する。return_exceptions=Trueでない限り、例外は伝播する。」手続き: 標準的なデバッグワークフロー:再現 \(\to\) 切り分け \(\to\) 仮説化 \(\to\) テスト \(\to\) 修正。
メモリアーキテクチャ
RAGベースのメモリ
検索拡張生成(RAG) (P. Lewis et al. 2020)は、LLMエージェントにおける外部メモリの主要なパラダイムです。メモリストア \(\mathcal{M}\) は文書 \(\{d_i\}_{i=1}^N\) の集合であり、検索はクエリ \(q\) を順位付き部分集合へ写像します。
埋め込みストアとベクトルデータベース
各文書 \(d_i\) は埋め込みモデル \(\phi\): \(\mathbf{v}_i = \phi(d_i) \in \mathbb{R}^{D}\) でエンコードされます。クエリも同様にエンコードされます:\(\mathbf{q} = \phi(q)\)。検索は上位 \(k\) 件の文書を類似度で返します。
\[ \text{Retrieve}(q, \mathcal{M}, k) = \underset{S \subseteq [N],, |S|=k}{\arg\max} \sum_{i \in S} \text{sim}(\mathbf{q}, \mathbf{v}_i), \]
ここで \(\text{sim}(\cdot,\cdot)\) は通常コサイン類似度です。近似最近傍(ANN)インデックス (FAISS (Johnson et al. 2021), HNSW (Malkov and Yashunin 2020), ScaNN (Guo et al. 2020)) は、\(N \sim 10^7\) に対してこれを実用的に処理できます。
検索戦略
-
密ベクトル検索。 クエリと文書をどちらもニューラルエンコーダー(例:DPR (Karpukhin et al. 2020)、
text-embedding-3-large)でエンコードします。意味的類似性を捉えますが、GPU推論が必要です。 -
スパース検索。 トークンの重複に対するBM25またはTF-IDF。高速で解釈しやすく、正確なキーワード一致に強い方式です。
-
ハイブリッド検索。 Reciprocal Rank Fusion(RRF)によって密なスコアとスパーススコアを組み合わせます。
\[ \text{RRF}(d, k) = \sum_{r \in \text{rankers}} \frac{1}{k + \text{rank}_r(d)}, \]
ここで \(k=60\) は平滑化定数です。ハイブリッド検索は、どちらか一方だけを使う場合を一貫して上回ります (Chen et al. 2022)。
再ランキング
クロスエンコーダーの再ランキング器 \(f_\psi(q, d) \in [0,1]\) は、各検索文書をクエリと同時にスコアリングし、\(O(k)\) 回のフォワードパスというコストで高い精度を提供します。パイプラインは、ANNで \(k' \gg k\) 件の候補を検索し、クロスエンコーダーで再ランキングして、上位 \(k\) 件を返すという流れです。
Warning
検索によるハルシネーションのリスク
RAGはハルシネーションをなくしません。むしろ引き起こすことがあります。検索された文書が古い、誤っている、または表面的にしか関連しない場合、モデルは誤情報を自信を持って取り込むことがあります。必ず出所メタデータ(ソース、タイムスタンプ、信頼度)を含め、忠実性の検証ステップを検討してください。
要約ベースのメモリ
逐語的な保存が高コストまたはノイジーすぎる場合、要約によって保存前に情報を圧縮します。
逐次要約
各ステップ \(t\) で、エージェントは継続的な要約 \(S_t\) を維持します。新しい情報 \(e_t\) が到着すると、次のようにします。
\[ S_{t+1} = \text{LLM}!\left(\texttt{“Summarize: [}S_t\texttt{] + [}e_t\texttt{]”}\right). \]
これによりメモリサイズは \(O(1)\) に保たれますが、詳細を失うリスクがあります。
階層的圧縮
メモリを \(L_0 \supset L_1 \supset \cdots \supset L_K\) のレベルに整理します。\(L_0\) は逐語的で、各 \(L_{i+1}\) は \(L_i\) の要約です。検索はまず \(L_K\) (最も圧縮され高速)を確認し、必要に応じて掘り下げます。これはForteの逐次要約技法に対応します (Forte 2022).
要約するか逐語的に保存するか
-
逐語的に保存:正確な事実、コードスニペット、数値結果、ユーザーの引用。
-
要約する:物語的コンテキスト、推論の連鎖、重複した観察。
-
破棄する:ノイズ、情報内容のない失敗したツール呼び出し。
グラフベースのメモリ
知識グラフ
知識グラフ \(\mathcal{G} = (\mathcal{V}, \mathcal{E}, \mathcal{R})\) は事実をトリプル \((h, r, t)\) として保存します。\(h, t \in \mathcal{V}\) はエンティティで、\(r \in \mathcal{R}\) は関係です。エージェントはSPARQL (Harris and Seaborne 2013), Cypher (Francis et al. 2018)、または自然言語からグラフへの変換によって検索できます。
エンティティ・関係抽出
新しい観察は抽出モデル \(\text{IE}: \text{text} \to \{(h_i, r_i, t_i)\}\) によって解析され、\(\mathcal{G}\) へ統合されます。共参照解析とエンティティリンキングによって一貫性を確保します。
GraphRAG
GraphRAG (Edge et al. 2024)はグラフ走査によってRAGを拡張します。クエリが与えられるとシードエンティティを検索し、\(k\)ホップ近傍の走査によって、埋め込み類似度では直接一致しない関連事実を見つけます。これはマルチホップ推論で特に強力です。
\[ \text{GraphRetrieve}(q, \mathcal{G}, k) = \bigcup_{v \in \text{seeds}(q)} \mathcal{N}_k(v, \mathcal{G}), \]
ここで \(\mathcal{N}_k(v, \mathcal{G})\) は \(k\)ホップ近傍である、\(v\) です。
時間的知識グラフ
事実には有効期間があります:\((h, r, t, [t_\text{start}, t_\text{end}])\)。時間的KG (Lacroix et al. 2020)によって、「2023年にOpenAIのCEOだったのは誰か?」というクエリを過去と現在の状態を混同せずに実行できます。
キー・バリューメモリネットワーク
微分可能メモリネットワーク (Weston et al. 2015; Sukhbaatar et al. 2015)は、メモリをキー・バリューペア \(\{(\mathbf{k}_i, \mathbf{v}_i)\}_{i=1}^M\) の集合として表し、ソフトな注意に基づいて検索します。
\[ \alpha_i = \text{softmax}!\left(\frac{\mathbf{q}^\top \mathbf{k}_i}{\sqrt{D}}\right), \qquad \mathbf{c} = \sum_{i=1}^M \alpha_i \mathbf{v}_i. \]
検索されたコンテキスト \(\mathbf{c}\) はクエリの微分可能な関数であり、エンドツーエンド学習を可能にします。現代のトランスフォーマー注意機構はこのメカニズムの特殊例です。エージェント用途では、メモリスロットを勾配降下または明示的な書き込み操作で更新できます。
MemGPTと仮想コンテキスト管理
MemGPT (Packer et al. 2023)は、オペレーティングシステムの仮想メモリに似た仮想コンテキスト抽象化を導入します。メモリは階層に整理されます。
ページイン/ページアウト戦略
エージェントは、次に基づいて、どのメモリをホットコンテキストへ昇格させ(ページイン)、どれを追い出すか(ページアウト)を決めます。
-
新しさ: 最近アクセスされた項目ほど必要になる可能性が高い。
-
関連性: 現在のクエリとの類似度が高い項目。
-
重要性: 書き込み時に高重要度とタグ付けされた項目。
自己指向型メモリ管理
MemGPTでは、LLM自身がアクション空間の一部としてメモリ管理関数呼び出し(memory_search、memory_insert、memory_delete)を発行します。これにより、メモリ管理はハードコードされた方策ではなく学習された行動になります。RL学習の自然な対象です(節3.7)。
メモリ操作
書き込み:メモリへのコミット
すべての観察を保存すべきとは限りません。書き込みの判断はフィルタリング問題です。
\[ \text{Write}(e) = \mathbf{1}!\left[\text{importance}(e) > \tau\right], \]
ここで \(\tau\) はしきい値であり、\(\text{importance}(e)\) は次のように定義できます。
-
驚き: \(-\log p_\theta(e \mid \text{context})\) — 予期しないイベントほど情報量が多い。
-
報酬信号: 高い \(\vert r_t\vert\)(正でも負でも)に関連するイベントは記憶する価値がある。
-
LLMによる自己評価: モデルに重要度を1〜10の尺度で評価させる。
矛盾検出
新しい事実 \(f_\text{new}\) を書き込む前に、既存のメモリとの競合を確認します。
\[ \text{Conflict}(f_\text{new}, \mathcal{M}) = \exists, f \in \mathcal{M} : \text{Contradicts}(f_\text{new}, f). \]
矛盾検出はNLIモデルを使うか、LLMにプロンプトを与えることで実装できます。競合がある場合、エージェントは上書きするか、タイムスタンプ付きで両方を保持するか、人間のレビュー用にフラグを立てるかを決めなければなりません。
メモリの形式と粒度
何を保存するかだけでなく、どのように保存するかも重要です。メモリエントリは原子的な事実から詳細なトランスクリプトまで幅広く、トレードオフも異なります。
| 形式 | 長所 | 短所 |
|---|---|---|
| 原子的な事実 | ||
| 「ユーザーはPythonを好む。」 | 正確な検索、合成可能、重複排除と矛盾検出が容易 | コンテキストを失う、抽出エラー、微妙な情報には脆弱 |
| 構造化ノート | ||
| (A-MEM (W. Xu et al. 2025)) | 豊富なメタデータ(タグ、リンク)、グラフ走査をサポート、精度とコンテキストのバランス | 書き込みコストが高い、スキーマ設計が必要 |
| 要約エピソード | ||
| (MemGPT (Packer et al. 2023)) | 物語の一貫性を保持、コンパクト、マルチターン推論に適する | 要約で情報が失われる、部分更新が難しい |
| 逐語トランスクリプト | 情報を失わず、抽出エラーがなく、正確な引用をサポート | ストレージが大きい、検索がノイジー、高価なスキャン |
メモリの粒度によるトレードオフ
実際には、本番システムは複数の粒度を組み合わせることが多い (Chhikara et al. 2025)。正確な想起のために原子的な事実を抽出し、物語的コンテキストのために要約エピソードを維持し、監査可能性のために逐語トランスクリプトをコールドストレージへアーカイブします。Generative Agentsアーキテクチャ (Park et al. 2023)は、観察を自然言語の説明、重要度スコア、タイムスタンプを持つ原子的な「メモリオブジェクト」として保存し、正確な検索と時間的推論の両方を可能にします。
設計ガイドライン
-
粒度をクエリ種別に合わせる。 ユーザーが事実確認の質問(「私のAPIキーは?」)をするなら原子的な事実が有利です。コンテキストに関する質問(「なぜRedisを使うと決めたのか?」)ならエピソード要約が必要です。
-
負担できる範囲で最も細かい粒度で保存し、その上に粗いビューを構築する。 原子的な事実を要約するのは簡単ですが、情報を失った要約から原子を復元することはできません。
-
出所を含める。 すべてのメモリエントリは、そのソース(会話ターン、文書、ツール出力)へリンクすべきです。そうすればエージェントが検証でき、ユーザーが監査できます。
読み取り/検索
クエリの定式化
検索クエリ \(q\) は生の観察である必要はありません。よりよい戦略は次のとおりです。
-
HyDE(仮想文書埋め込み) (Luyu Gao et al. 2023): 仮想的な回答を生成して埋め込み、クエリとしてその埋め込みを使う。
-
クエリ拡張: クエリの複数の言い換えを生成し、検索結果の和集合を取る。
-
ステップバック・プロンプティング: 検索前に具体的なクエリをより一般的な質問へ抽象化する。
時間減衰と新しさバイアス
古いメモリは関連性が低い可能性があります。時間を重み付けしたスコアは次のとおりです。
\[ \text{score}(d, q, t) = \lambda \cdot \text{sim}(\mathbf{q}, \mathbf{v}_d) + (1-\lambda) \cdot \exp!\left(-\frac{t - t_d}{\tau_\text{decay}}\right), \]
ここで \(t_d\) はメモリの作成時刻、\(\tau_\text{decay}\) は減衰率を制御します。Generative Agents論文 (Park et al. 2023) は、同様に新しさで重み付けした検索を使います。
更新:矛盾解消と統合
メモリの統合では、冗長性を減らしてより高レベルのパターンを表面化するため、関連するメモリをマージします。
\[ \mathcal{M}' = \text{Consolidate}(\mathcal{M}) = \text{Cluster}(\mathcal{M}) \cup \text{Summarize}(\text{Cluster}(\mathcal{M})). \]
忘却メカニズム
生物の記憶は忘却するため、人工メモリも忘却すべきです。戦略は次のとおりです。
-
LRU退避: 容量を超えたとき、最も長く使われていないエントリを削除する。
-
重要度重み付き忘却: \(p(\text{forget},\vert ,d) \propto \exp(-\text{importance}(d))\).
-
間隔反復: 繰り返しアクセスされた記憶をより長く保持する。エビングハウスの指数的忘却曲線に従う (Ebbinghaus 1885)。
反映:メタ認知的操作
Reflection (Park et al. 2023; Shinn et al. 2023)は、より高次のメモリ操作です。エージェントは自分のメモリを読み、洞察を生成します。
\[ \text{Reflect}(\mathcal{M}) \to \{i_1, i_2, \ldots\} \subset \mathcal{M}_\text{semantic}, \]
ここで各洞察 \(i_j\) は複数のエピソード記憶から導かれる、より高レベルの抽象化です。
Note
実践におけるReflection(Reflexion)
3回試してコーディング問題の解決に失敗した後、エージェントは次のように反省します。
エピソード記憶から3つの失敗エピソードを検索する。
洞察を生成する:「入力リストが空の場合のエッジケースを扱うのを、何度も忘れている。」
この洞察を意味記憶に保存する。
次の試行で洞察を検索し、空の入力を明示的に確認する。
これはReflexion (Shinn et al. 2023)の中核メカニズム、つまり自己反省による言語的強化学習です。
Reflectionはどこに存在するか
Reflectionはエピソード記憶から読み取り、意味記憶へ書き込みます。得られる洞察はコンテキスト非依存の一般化(「常に空の入力を確認する」)であり、エピソード固有の記録ではないため、意味記憶 \(\mathcal{M}_\text{semantic}\) に属します。ただしReflection処理中は、中間推論(検索したエピソード+統合プロンプト+生成した洞察)がワーキングメモリ(コンテキストウィンドウ)を占有します。要約すると次のとおりです。
-
入力: エピソード記憶(特定の過去イベント)
-
計算: ワーキングメモリ(コンテキスト内での能動的推論)
-
出力: 意味記憶(永続的で一般化された洞察)
これは生物の記憶の固定化に対応します。睡眠とReflectionの間に、エピソード経験が徐々に意味知識へ変換される現象です。
マルチターン対話のためのメモリ
ユーザーモデリングと嗜好追跡
永続的なユーザーモデル \(\mathcal{U}\) は次を保存します。
-
明示的な嗜好: 明示された好き嫌い、コミュニケーションスタイルの好み。
-
暗黙的な嗜好: 行動から推測されるもの(例:ユーザーは常にPythonのコードを求め、簡潔な回答を好む)。
-
専門知識レベル: 語彙と質問の複雑さから推測されるドメイン知識。
-
目標とコンテキスト: 継続中のプロジェクト、現在のタスク、組織上の役割。
ユーザーモデルは各インタラクション後に更新されます。
\[ \mathcal{U}_{t+1} = \text{Update}(\mathcal{U}_t,, (u_t, a_t, \text{feedback}_t)). \]
セッションの継続性
メモリがなければ、各会話は白紙から始まります。セッションメモリがあれば、次のようになります。
-
セッション開始時にユーザーモデル \(\mathcal{U}\) と直近のセッション要約を検索する。
-
パーソナライズされたシステムプロンプトを注入する:「あなたは分散学習プロジェクトに取り組むシニアMLエンジニアのAliceを支援しています。前回のセッションでは、勾配同期の問題のデバッグを手伝いました。」
3 を更新する。 セッション終了時にセッションを要約し、\(\mathcal{U}\).
メモリによるパーソナライズ
パーソナライズにより、効率(明確化の質問が減る)と品質(ユーザーの専門知識に合わせて調整された応答)の両方が向上します。主な技法は次のとおりです。
-
適応的な冗長性: ユーザーの過去の関与度に基づいて回答の長さを調整する。
-
ドメイン・プライミング: 意味記憶から関連するドメインコンテキストを先頭に付加する。
-
能動的想起: 求められなくても関連する過去のインタラクションを提示する(「先月このトピックについて質問しました。そのとき分かったことはこちらです」)。
Warning
プライバシーとメモリ
永続的なユーザーメモリは重大なプライバシー上の懸念を生みます。エージェントは、(1) 個人情報を保存する前に明示的な同意を得る、(2) 保存されたメモリを検査・削除する仕組みを提供する、(3) マルチユーザー配備でアクセス制御を実施する、(4) データ保持規制(GDPR、CCPA)を遵守する必要があります。メモリシステムはプライバシーをデフォルトにして設計すべきです。
マルチエージェントシステムのためのメモリ
複数のエージェントが共有タスクで協調するとき、メモリは個人的な知識ストアにとどまらず、調整メカニズムになります。タスクを分解する計画エージェントは実行エージェントへサブゴールを伝え、批評エージェントは評価対象エージェントと同じコンテキストへアクセスし、研究エージェントのチームは作業の重複を避ける必要があります。共有メモリがなければ、エージェントはすべてをダイレクトメッセージで伝えなければならず、帯域幅のボトルネックが生じ、会話がコンテキスト外へスクロールすると情報が失われます。共有メモリは、すべてのエージェントが読み書きできる永続的で検索可能な基盤を提供してこれを解決し、暗黙の調整(「他のエージェントが覚えているとよいのだが」)を明示的な状態(「回答はブラックボードにある」)へ変えます。
共有メモリプール
マルチエージェントシステムでは、エージェントは共通のメモリストア \(\mathcal{M}_\text{shared}\) をプライベートストア \(\mathcal{M}_i\) とともに共有できます。
\[ \text{context}_i(t) = \mathcal{R}(\mathcal{M}_i, q_i) \cup \mathcal{R}(\mathcal{M}_\text{shared}, q_i). \]
共有メモリは暗黙の調整を可能にします。エージェント \(A\) が発見を書き込み、エージェント \(B\) が明示的な通信なしにそれを検索できます。
ブラックボード・アーキテクチャ
ブラックボードパターン (Hayes-Roth 1985)は、古典的なマルチエージェント調整メカニズムです。
各エージェントはブラックボードから読み書きします。コントローラーはブラックボードを監視し、前提条件が満たされたときにエージェントを起動します。これによりエージェントは疎結合になり、ダイレクトメッセージではなく共有状態を介して通信します。
共有知識における合意と競合
複数のエージェントが共有メモリへ書き込むと、競合が生じます。解決戦略は次のとおりです。
-
最終書き込み優先: 単純ですが情報を失う。
-
バージョン付きメモリ: すべての書き込みの履歴を保持し、エージェントが任意のバージョンを検索できるようにする。
-
投票/合意: \(k\)-of-\(n\) エージェントが合意してから事実をコミットする。
-
信頼度重み付きマージ: \(f_\text{merged} = \sum_i w_i f_i\) であり、\(w_i\) はエージェント \(i\) の信頼度。
-
指定権限者: 特定のエージェントへメモリ領域の所有権を割り当てる。
Note
未解決問題:分散メモリの一貫性
マルチエージェントシステムは、並行書き込み、ネットワーク分断、敵対的エージェントのもとで、どのようにメモリの一貫性を維持すべきでしょうか?古典的な分散システムの解決策(Paxos、Raft)は適用できますが、高価です。限られた陳腐化を伴う近似的一貫性で、多くのエージェント型タスクには十分かもしれません。しかし、適切なトレードオフは未解決の研究課題です。
強化学習によるメモリシステムの学習
メモリ操作の報酬信号
メモリ操作(読み取り、書き込み、更新、Reflection)はRLフレームワークの行動として扱えます。課題は、有用なメモリ動作を促す報酬信号を設計することです。
-
タスク報酬の伝播: メモリ検索が正しい回答につながったら、検索行動へクレジットを与える。スパースですが曖昧さがない。
-
検索精度報酬: \(r_\text{retrieve} = \text{Relevance}(d_\text{retrieved}, \text{task})\)。学習された関連度モデルで推定する。
-
メモリ効率報酬: 不要な書き込みにペナルティを与える:\(r_\text{write} = -\lambda \cdot \mathbf{1}[\text{write}]\)。選択的な保存を促す。
-
一貫性報酬: 内部的に一貫した(矛盾のない)メモリ状態に報酬を与える。
メモリ操作 \(m_t\) の、ステップ \(t\) における合成報酬は次のとおりです。
\[ r_t^{\text{mem}} = r_t^{\text{task}} + \alpha \cdot r_t^{\text{retrieve}} + \beta \cdot r_t^{\text{write}} + \gamma \cdot r_t^{\text{consistency}}. \]
何を記憶するかの学習
何を記憶するかの問題はメタ学習の課題です。エージェントは、\(\pi_\text{write}(e)\) は将来のタスク性能を最大化する書き込み方策を学習する必要があります。これは次の理由で困難です。
-
メモリの価値が将来になって初めて明らかになる(遅延報酬)。
-
書き込み時点では、将来のクエリの可能性の空間が未知である。
3 に他に何があるかに依存する。 メモリは相互作用する。\(e\) を保存する価値は、\(\mathcal{M}\).
アプローチは次のとおりです。
-
Hindsight relabeling (Andrychowicz et al. 2017):成功したエピソードの後で、検索されたメモリに遡及的に「重要」とラベルを付け、書き込み方策を学習して類似項目を保存する。
-
Meta-RL (Duan et al. 2016):タスク分布全体で書き込み方策を学習し、タスク間で一般化する情報を保存するよう方策を学習する。
-
好奇心駆動ストレージ (Pathak et al. 2017):予測誤差が高く驚くべき観察を保存する。そのような観察は情報量が多い可能性が高いため。
メモリ拡張方策最適化
方策とメモリシステムを共同で最適化する考えは、微分可能メモリネットワーク (Graves et al. 2016)に端を発し、REALM (Guu et al. 2020)によって検索拡張LLMへ拡張されました。メモリ拡張エージェントの完全な方策勾配目的関数は次のとおりです。
\[ \mathcal{L}(\theta, \phi) = \mathbb{E}_{\tau \sim \pi_\theta}!\left[\sum_{t=0}^T \gamma^t r_t\right] - \lambda \cdot \mathcal{L}_\text{mem}(\phi), \]
ここで \(\theta\) はLLMパラメーター、\(\phi\) はメモリシステムのパラメーター(例:検索モデルの重み)、\(\mathcal{L}_\text{mem}\) はメモリの複雑さに対する正則化項です。
Important
重要な洞察:学習される帰納バイアスとしてのメモリ
RLでメモリ操作を学習すると、エージェントはタスク固有のメモリ戦略を発展させられます。コーディングエージェントはAPIシグネチャを保存し、研究エージェントは引用の連鎖を保存し、カスタマーサービスエージェントはユーザーの苦情パターンを保存することを学習します。メモリシステムはエージェントのドメインに合わせた学習された帰納バイアスになります。
メモリアプローチの比較
メモリシステムの評価
エージェント型メモリの評価は、メモリ操作の品質が間接的に、つまり長期的な下流タスク性能を通じてしか明らかにならないため困難です。保存した事実を完全に再現できるメモリシステムでも、無関係なコンテキストを検索したりLLMのコンテキストウィンドウを圧倒したりすれば失敗します。
評価の側面
LongMemEval (Wu et al. 2025)は、長期メモリシステムが示すべき5つの中核能力を特定します。
-
情報抽出。 会話ターンから顕著な事実を識別して保存できるか?事実再現率、つまり正解の事実のうちどれだけをメモリから復元できるかで測定する。
-
マルチセッション推論。 複数の過去セッションに散在する情報を統合できるか?例:「先週と昨日の会話に基づくと、プロジェクトのスコープは何が変わったか?」
-
時間的推論。 時間依存のクエリに正しく答えられるか?例:「組織再編前に、自分の優先事項は何だと言っていたか?」には、時間的状態の区別が必要。
-
知識の更新。 事実が変わったとき(ユーザーが都市を移る、嗜好が変わる)、履歴を保持しつつメモリは最新状態を反映するか?
-
棄却。 関連メモリがないとき、もっともらしいが捏造された想起をハルシネーションするのではなく、「分かりません」と正しく言えるか?
ベンチマーク
| ベンチマーク | 開催地 | 規模 | 焦点 |
|---|---|---|---|
| LongMemEval (Wu et al. 2025) | ICLR 2025 | 500問、スケーラブルな履歴 | 5つのメモリ能力、マルチセッションチャット |
| LOCOMO (Maharana et al. 2024) | EMNLP 2024 | マルチセッション対話 | 会話に対するシングルホップ、時間的、マルチホップ、オープンドメインQA |
| InfiniteBench (X. Zhang et al. 2024) | ACL 2024 | 10万以上のトークンコンテキスト | 長文脈の再現、メモリ固有ではないが限界をテスト |
エージェント型メモリシステムを評価するためのベンチマーク
指標
メモリレベルの指標
-
メモリ再現率: \(\frac{\text{# ground-truth facts retrievable from memory}}{\text{# total ground-truth facts}}\)。保存の完全性を測定する。
-
メモリ適合率: \(\frac{\text{# relevant items in top-}k\text{ retrieval}}{k}\)。検索のノイズを測定する。
-
レイテンシー: クエリから検索コンテキストまでの時間(p50とp95)。
-
トークン効率: クエリごとにコンテキストへ注入される総トークン数。少ないほどよい。不要なコンテキストはLLMの精度を低下させ、コストを増加させる。
下流指標
-
回答精度: メモリを条件とした最終応答の正しさ(EM、F1、またはLLM評価)。
-
忠実性: 応答は捏造なしにメモリの内容を正確に反映しているか?
-
パーソナライズ品質: ユーザー満足度。メモリ拡張システムとメモリなしシステムの選好評価またはA/Bテストで測定する。
-
矛盾率: 以前に述べた事実と一致しない応答をシステムが生成する頻度。
運用指標
-
書き込み選択性: メモリ書き込みを起動するターンの割合。高すぎると\(\to\) noise、低すぎると\(\to\)ギャップが生じる。
-
陳腐化: 更新が存在するにもかかわらず、古い事実が検索される頻度。
-
ストレージ増加率: 1インタラクション時間あたりに保存されるトークン数。無制限の増加は持続不可能。
Warning
評価ギャップ
ほとんどのメモリ論文は短いベンチマーク(10〜50セッション)で評価します。実際の本番エージェントは数千セッションを何か月も実行します。メモリのドリフト、矛盾の蓄積、ストレージ肥大化が主要な失敗モードとなる長期評価は、依然として未解決の課題です。実務者はベンチマークスコアを、運用指標の長期的な監視で補完すべきです。
実装パターン
埋め込み付きベクトルストアメモリ
最も一般的なメモリパターンでは、エントリをメタデータ(タイムスタンプ、重要度スコア、タグ)とともに埋め込みベクトルとして保存します。検索ではコサイン類似度と時間減衰を組み合わせ、最近の重要なメモリを先に表面化させます。重複検出とLRU退避によってストアのサイズを制限します。
import numpy as np
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
import json
@dataclass
class MemoryEntry:
"""A single memory entry with metadata."""
content: str
embedding: np.ndarray
timestamp: datetime = field(default_factory=datetime.now)
importance: float = 0.5
access_count: int = 0
last_accessed: Optional[datetime] = None
tags: list[str] = field(default_factory=list)
source: str = "agent"
class VectorMemoryStore:
"""
Hybrid dense+sparse memory store with temporal decay.
Supports importance-weighted retrieval and LRU eviction.
"""
def __init__(
self,
embed_fn, # callable: str -> np.ndarray
max_entries: int = 10_000,
decay_rate: float = 0.01, # per hour
recency_weight: float = 0.3,
):
self.embed_fn = embed_fn
self.max_entries = max_entries
self.decay_rate = decay_rate
self.recency_weight = recency_weight
self.entries: list[MemoryEntry] = []
# -- Write --------------------------------------------------------------
def write(
self,
content: str,
importance: float = 0.5,
tags: list[str] | None = None,
check_duplicates: bool = True,
) -> MemoryEntry:
"""Commit a new memory, evicting if at capacity."""
if check_duplicates and self._is_duplicate(content):
return None # Skip near-duplicate entries
embedding = self.embed_fn(content)
entry = MemoryEntry(
content=content,
embedding=embedding,
importance=importance,
tags=tags or [],
)
if len(self.entries) >= self.max_entries:
self._evict()
self.entries.append(entry)
return entry
def _is_duplicate(self, content: str, threshold: float = 0.95) -> bool:
"""Check if a near-duplicate already exists."""
if not self.entries:
return False
emb = self.embed_fn(content)
sims = self._cosine_similarities(emb)
return float(np.max(sims)) > threshold
def _evict(self):
"""Remove the least important + least recent entry."""
now = datetime.now()
scores = []
for e in self.entries:
age_hours = (now - e.timestamp).total_seconds() / 3600
recency = np.exp(-self.decay_rate * age_hours)
score = e.importance * (1 - self.recency_weight) \
+ recency * self.recency_weight
scores.append(score)
worst_idx = int(np.argmin(scores))
self.entries.pop(worst_idx)
# -- Retrieve -----------------------------------------------------------
def retrieve(
self,
query: str,
k: int = 5,
recency_boost: bool = True,
) -> list[MemoryEntry]:
"""
Hybrid retrieval: dense similarity + temporal recency.
Returns top-k entries sorted by combined score.
"""
if not self.entries:
return []
q_emb = self.embed_fn(query)
dense_scores = self._cosine_similarities(q_emb)
now = datetime.now()
combined = []
for i, (entry, d_score) in enumerate(
zip(self.entries, dense_scores)
):
if recency_boost:
age_h = (now - entry.timestamp).total_seconds() / 3600
recency = np.exp(-self.decay_rate * age_h)
score = (1 - self.recency_weight) * d_score \
+ self.recency_weight * recency
else:
score = d_score
combined.append((score, i))
combined.sort(reverse=True)
top_k = [self.entries[i] for _, i in combined[:k]]
# Update access metadata
for entry in top_k:
entry.access_count += 1
entry.last_accessed = now
return top_k
def _cosine_similarities(self, query_emb: np.ndarray) -> np.ndarray:
"""Vectorized cosine similarity against all stored embeddings."""
matrix = np.stack([e.embedding for e in self.entries])
norms = np.linalg.norm(matrix, axis=1, keepdims=True)
matrix_norm = matrix / (norms + 1e-8)
q_norm = query_emb / (np.linalg.norm(query_emb) + 1e-8)
return matrix_norm @ q_norm
# -- Reflect ------------------------------------------------------------
def reflect(self, llm_fn, k: int = 10) -> list[str]:
"""
Meta-cognitive reflection: retrieve recent memories,
synthesize higher-level insights, and store them back.
"""
if len(self.entries) < 3:
return []
# Retrieve recent high-importance memories
recent = sorted(
self.entries, key=lambda e: e.timestamp, reverse=True
)[:k]
context = "\n".join(f"- {e.content}" for e in recent)
# Ask LLM to generate insights
prompt = (
"Given these recent memories, extract 2-3 high-level "
"insights or patterns:\n" + context
)
raw_insights = llm_fn(prompt)
# Store each insight as a high-importance memory
insights = []
for line in raw_insights.strip().split("\n"):
line = line.strip().lstrip("-*").strip()
if len(line) > 20:
self.write(
f"[INSIGHT] {line}",
importance=0.9,
check_duplicates=True,
)
insights.append(line)
return insights
def get_stats(self) -> dict:
"""Return memory statistics for monitoring."""
return {
"total_entries": len(self.entries),
"avg_importance": float(
np.mean([e.importance for e in self.entries])
) if self.entries else 0.0,
"oldest_entry": min(
(e.timestamp for e in self.entries), default=None
),
}
階層型メモリマネージャー
MemGPT (Packer et al. 2023)に着想を得たこのパターンでは、メモリを3つの層に整理します。ホット(コンテキスト内で即時アクセス)、ウォーム(ベクトルストアで高速検索)、コールド(アーカイブで容量無制限)です。エントリは、アクセス頻度と重要度に基づいて自動的に昇格または降格され、CPUキャッシュ階層に似た動作をします。
from enum import Enum
from collections import OrderedDict
class MemoryTier(Enum):
HOT = "hot" # In-context: immediate access
WARM = "warm" # Vector store: fast retrieval
COLD = "cold" # Archival: slow but unlimited
class HierarchicalMemoryManager:
"""
Three-tier memory manager inspired by MemGPT.
Hot tier is an LRU cache; warm is a vector store;
cold is append-only archival storage.
"""
def __init__(
self,
vector_store: VectorMemoryStore,
hot_capacity: int = 20, # max entries in hot tier
warm_capacity: int = 5_000,
llm_summarize_fn=None, # callable for summarization
):
self.vector_store = vector_store
self.hot_capacity = hot_capacity
self.warm_capacity = warm_capacity
self.summarize = llm_summarize_fn
# Hot tier: ordered dict for LRU semantics
self.hot: OrderedDict[str, MemoryEntry] = OrderedDict()
# Cold tier: append-only list (would be a DB in production)
self.cold: list[MemoryEntry] = []
# -- Page-in: promote warm -> hot ---------------------------------------
def page_in(self, query: str, k: int = 3) -> list[MemoryEntry]:
"""
Retrieve from warm store and promote to hot tier.
Evicts least-recently-used hot entries if needed.
"""
candidates = self.vector_store.retrieve(query, k=k)
promoted = []
for entry in candidates:
key = entry.content[:64] # use prefix as key
if key not in self.hot:
if len(self.hot) >= self.hot_capacity:
self._evict_hot()
self.hot[key] = entry
self.hot.move_to_end(key)
promoted.append(entry)
return promoted
def _evict_hot(self):
"""Evict LRU entry from hot tier back to warm."""
# OrderedDict: first item is LRU
key, entry = self.hot.popitem(last=False)
# Re-insert into warm store (already there, just update access)
# In a real system, we'd update the warm store's metadata
# -- Write with tier assignment ------------------------------------------
def write(
self,
content: str,
importance: float = 0.5,
tier: MemoryTier = MemoryTier.WARM,
) -> MemoryEntry:
"""Write to the appropriate tier."""
if tier == MemoryTier.HOT:
entry = MemoryEntry(
content=content,
embedding=self.vector_store.embed_fn(content),
importance=importance,
)
key = content[:64]
if len(self.hot) >= self.hot_capacity:
self._evict_hot()
self.hot[key] = entry
return entry
elif tier == MemoryTier.WARM:
return self.vector_store.write(content, importance=importance)
else: # COLD
entry = MemoryEntry(
content=content,
embedding=np.array([]), # no embedding for cold
importance=importance,
)
self.cold.append(entry)
return entry
# -- Summarize and compress ---------------------------------------------
def compress_hot_to_warm(self) -> Optional[str]:
"""
Summarize hot tier contents and write summary to warm.
Called when hot tier is full and new important content arrives.
"""
if not self.hot or not self.summarize:
return None
hot_contents = "\n".join(
f"- {e.content}" for e in self.hot.values()
)
summary = self.summarize(
f"Summarize these memory entries concisely:\n{hot_contents}"
)
self.vector_store.write(summary, importance=0.7)
return summary
# -- Unified retrieval --------------------------------------------------
def retrieve(self, query: str, k: int = 5) -> list[MemoryEntry]:
"""
Retrieve from all tiers, prioritizing hot.
Returns up to k entries sorted by relevance.
"""
results = []
# 1. Check hot tier (exact + semantic)
q_emb = self.vector_store.embed_fn(query)
for entry in self.hot.values():
if entry.embedding.size > 0:
sim = float(
np.dot(q_emb, entry.embedding)
/ (np.linalg.norm(q_emb) * np.linalg.norm(entry.embedding) + 1e-8)
)
if sim > 0.7:
results.append((sim + 1.0, entry)) # +1 hot bonus
# 2. Retrieve from warm store
warm_results = self.vector_store.retrieve(query, k=k)
for entry in warm_results:
results.append((0.5, entry))
# 3. Deduplicate and sort
seen = set()
final = []
for score, entry in sorted(results, reverse=True):
key = entry.content[:64]
if key not in seen:
seen.add(key)
final.append(entry)
if len(final) >= k:
break
return final
def get_hot_context(self) -> str:
"""Return hot tier as a formatted context string."""
if not self.hot:
return ""
lines = ["[Memory Context]"]
for entry in list(self.hot.values())[-10:]: # last 10
lines.append(f" * {entry.content}")
return "\n".join(lines)
メモリ拡張エージェントループ
MemGPT (Packer et al. 2023)で導入され、CoALAフレームワーク (Sumers et al. 2024)で形式化されたこのパターンは、読み取り・行動・反映・書き込みサイクルによってメモリシステムをエージェントの推論ループへ組み込みます。応答前にエージェントは関連メモリを検索し、応答後に何を保存するかを決めます。LLM出力の特殊トークンがメモリ操作を起動し、モデル自身が永続性を制御できるようにします。
import re
from typing import Any
class MemoryAugmentedAgent:
"""
An LLM agent with a full read-act-reflect-write memory cycle.
Implements the MemGPT-style self-directed memory management.
"""
SYSTEM_PROMPT = """You are a memory-augmented AI assistant.
You have access to persistent memory across conversations.
At each turn you may issue memory commands:
[MEMORY_SEARCH: <query>] - retrieve relevant memories
[MEMORY_WRITE: <content>] - store important information
[MEMORY_REFLECT] - synthesize insights from memory
Always think step by step. Use memory to avoid repeating mistakes
and to personalize your responses."""
def __init__(
self,
llm_fn, # callable: messages -> str
memory_manager: HierarchicalMemoryManager,
importance_threshold: float = 0.6,
max_memory_tokens: int = 1500,
):
self.llm = llm_fn
self.memory = memory_manager
self.importance_threshold = importance_threshold
self.max_memory_tokens = max_memory_tokens
self.conversation_history: list[dict] = []
# -- Main agent step ----------------------------------------------------
def step(self, user_message: str) -> str:
"""
Full agent step:
1. Retrieve relevant memories
2. Construct augmented prompt
3. Generate response (possibly with memory commands)
4. Execute memory commands
5. Reflect and consolidate
6. Return response to user
"""
# Step 1: Retrieve relevant memories
memories = self.memory.retrieve(user_message, k=5)
memory_context = self._format_memories(memories)
# Step 2: Construct augmented prompt
messages = self._build_messages(user_message, memory_context)
# Step 3: Generate response
raw_response = self.llm(messages)
# Step 4: Execute any memory commands in the response
clean_response, memory_ops = self._parse_memory_commands(
raw_response
)
self._execute_memory_ops(memory_ops, user_message, clean_response)
# Step 5: Auto-write important information
self._auto_write(user_message, clean_response)
# Step 6: Update conversation history
self.conversation_history.append(
{"role": "user", "content": user_message}
)
self.conversation_history.append(
{"role": "assistant", "content": clean_response}
)
return clean_response
# -- Memory retrieval and formatting -----------------------------------
def _format_memories(self, memories: list[MemoryEntry]) -> str:
if not memories:
return ""
lines = ["Relevant memories:"]
for i, m in enumerate(memories, 1):
age = (datetime.now() - m.timestamp).days
lines.append(
f" [{i}] (importance={m.importance:.1f}, "
f"{age}d ago) {m.content}"
)
return "\n".join(lines)
def _build_messages(
self, user_message: str, memory_context: str
) -> list[dict]:
system = self.SYSTEM_PROMPT
if memory_context:
system += f"\n\n{memory_context}"
system += f"\n\n{self.memory.get_hot_context()}"
messages = [{"role": "system", "content": system}]
# Include recent conversation history (last 6 turns)
messages.extend(self.conversation_history[-6:])
messages.append({"role": "user", "content": user_message})
return messages
# -- Memory command parsing ---------------------------------------------
def _parse_memory_commands(
self, response: str
) -> tuple[str, list[dict]]:
"""Extract and remove memory commands from response."""
ops = []
patterns = {
"search": r"\[MEMORY_SEARCH:\s*(.+?)\]",
"write": r"\[MEMORY_WRITE:\s*(.+?)\]",
"reflect": r"\[MEMORY_REFLECT\]",
}
clean = response
for op_type, pattern in patterns.items():
for match in re.finditer(pattern, response, re.DOTALL):
content = match.group(1) if op_type != "reflect" else None
ops.append({"type": op_type, "content": content})
clean = clean.replace(match.group(0), "").strip()
return clean, ops
def _execute_memory_ops(
self,
ops: list[dict],
user_msg: str,
response: str,
):
"""Execute memory commands issued by the LLM."""
for op in ops:
if op["type"] == "search":
results = self.memory.retrieve(op["content"], k=3)
# Page results into hot tier for immediate use
self.memory.page_in(op["content"], k=3)
elif op["type"] == "write":
self.memory.write(
op["content"],
importance=0.8, # explicitly written = important
tier=MemoryTier.WARM,
)
elif op["type"] == "reflect":
self._reflect()
# -- Auto-write heuristic -----------------------------------------------
def _auto_write(self, user_msg: str, response: str):
"""
Automatically store important information without explicit command.
Uses a simple heuristic: write if response contains facts,
decisions, or user preferences.
"""
importance_keywords = [
"remember", "important", "note that", "you prefer",
"your name is", "decided to", "the answer is",
"key insight", "learned that",
]
combined = (user_msg + " " + response).lower()
if any(kw in combined for kw in importance_keywords):
summary = f"User: {user_msg[:100]} | Agent: {response[:200]}"
self.memory.write(
summary,
importance=self.importance_threshold,
tier=MemoryTier.WARM,
)
# -- Reflection --------------------------------------------------------
def _reflect(self):
"""
Meta-cognitive reflection: synthesize insights from recent memory.
Stores high-level insights back into semantic memory.
"""
recent = self.memory.retrieve("recent important events", k=10)
if len(recent) < 3:
return # Not enough to reflect on
recent_text = "\n".join(f"- {m.content}" for m in recent)
insight_prompt = [
{"role": "system", "content": "You extract high-level insights."},
{"role": "user", "content":
f"Based on these memories, what are 2-3 key insights?\n"
f"{recent_text}\nRespond with bullet points only."},
]
insights = self.llm(insight_prompt)
# Store each insight as a high-importance semantic memory
for line in insights.split("\n"):
line = line.strip().lstrip("*-").strip()
if len(line) > 20:
self.memory.write(
f"[INSIGHT] {line}",
importance=0.9,
tier=MemoryTier.WARM,
)
Tip
読み取り・行動・反映・書き込みサイクル
メモリ拡張エージェントループは、4段階の認知サイクルを実装します。
読み取り: 行動する前に関連メモリを検索し、応答に情報を与える。
行動: 検索したコンテキストを条件として応答を生成する。
反映: 蓄積したメモリから、より高レベルの洞察を定期的に統合する。
書き込み: 重要な新情報を選択的に永続ストレージへコミットする。
このサイクルは、軍事戦略の観察・情勢判断・意思決定・行動(OODA)ループと、認知心理学のエンコード・保存・検索モデルに対応します。重要な洞察は、メモリが受動的なストアではなく、認知の能動的な参加者だということです。
エージェント型メモリの最近の進展
ここまでで説明したメモリシステムは基礎的なパターンを確立しました。いくつかの最近の研究は、さらに限界を押し広げています。
CoALA:言語エージェントの認知アーキテクチャ
Sumers et al. (Sumers et al. 2024)は、認知科学と記号AIの原則を使って増え続けるLLMエージェントを整理する統一フレームワーク、Cognitive Architectures for Language Agents(CoALA)を提案しています。CoALAは言語エージェントを次のように分解します。
-
モジュール型メモリ: ワーキングメモリ(コンテキストウィンドウ)、エピソード記憶(過去の経験)、意味記憶(世界知識)、手続き記憶(行動スキーマ)。節3.2の分類に対応します。
-
構造化された行動空間: 内部行動(推論、検索、メモリ書き込み)と外部行動(ツール利用、環境との相互作用)。
-
意思決定サイクル: 明示的な検索と書き込みのステップを持つ、一般化された検知・計画・行動ループ。
CoALAの貢献は新しいシステムというより設計言語です。既存エージェントを体系的に分析し、欠けている能力を特定する方法を提供するため、実務者にとって有用な参照アーキテクチャになります。
Mem0:本番規模のメモリレイヤー
Mem0 (Chhikara et al. 2025)は、研究用メモリシステムと本番配備の間にある隔たりに対処します。主な考え方は次のとおりです。
-
自動抽出: LLMが明示的にメモリ書き込みコマンドを発行することに頼らず、Mem0が会話ターンから顕著な事実を自動抽出し、永続ストアへ統合する。
-
グラフベースのメモリ: 平坦なベクトルストアに加えて、Mem0は抽出したエンティティと事実の関係グラフを維持し、マルチホップのメモリクエリ(「プロジェクトYの文脈で、ユーザーはトピックXについて何と言ったか?」)を可能にする。
-
メモリ圧縮: 冗長な、または置き換えられた事実を自動的にマージし、メモリストアをコンパクトかつ最新に保つ。
LOCOMOベンチマークで、Mem0は26%の相対改善を達成し、91% p95レイテンシーを低減し、\(>\)90%フルコンテキスト方式と比べてトークンコストを削減します。
Sleep-Time Compute:オフラインメモリ処理
Lin et al. (K. Lin et al. 2025)は、クエリ時だけでなく、ユーザーインタラクションの間にエージェントがメモリを処理・統合するパラダイム、sleep-time computeを導入します。脳が記憶を統合し有用な関連を事前計算する生物学的な睡眠がたとえです。
仕組み
アイドル期間(「睡眠」)中、エージェントは次を行います。
-
現在のコンテキストから、将来ありそうなクエリを予測する。
-
推論の連鎖、要約、構造化表現を事前計算する。
-
これらの事前計算した成果物を保存し、テスト時推論で検索して再利用できるようにする。
結果
sleep-time computeは、同等の精度を達成するためにテスト時に必要な計算量を、推論ベンチマークで\(\sim 5\times\)削減します。同じコンテキストに対する複数の関連クエリで償却すると、クエリあたりの平均コストは\(2.5\times\)低下します。このアプローチは、ユーザークエリが予測可能、つまりコンテキストが尋ねられる質問を強く制約するときに最も効果的です。
Tip
オフラインRLとしてのメモリ統合
sleep-time computeはオフライン方策改善とみなせます。アイドル時間中、エージェントは新しい環境インタラクションなしに、すでに収集したデータ(過去のインタラクション)を使ってメモリ表現(方策)を改善します。これは、エージェントが軌跡の静的データセットから学習するオフラインRL手法(章5)とつながります。
A-MEM:Zettelkastenに着想を得たエージェント型メモリ
A-MEM (W. Xu et al. 2025)は、密に相互接続された原子的ノートに基づくノート作成システムであるZettelkasten手法から着想を得て、LLMエージェント向けの動的で自己組織化するメモリを可能にします。
主要な設計原則
-
構造化ノート。 各メモリエントリは生のテキストチャンクではなく、コンテキスト説明、キーワード、タグ、関連ノートへの明示的なリンクを持つノートです。このメタデータによって、埋め込み類似度だけの場合より豊かな検索が可能になります。
-
動的リンク。 新しいメモリが追加されると、システムは既存メモリを分析して意味のある接続を特定し、双方向リンクを確立します。その結果は平坦なリストではなく知識ネットワークになります。
-
メモリの進化。 重要なのは、新しいノートの追加が既存ノートの更新を引き起こす可能性があることです。エージェントの理解が深まるにつれて、コンテキスト表現と属性を改良します。これによりメモリは静的アーカイブではなく、時間とともに改善する生きた構造になります。
-
エージェント駆動の整理。 固定スキーマのメモリシステムと異なり、A-MEMではLLM自身がメモリの整理、リンク、更新方法を決められます。整理構造をタスク領域に適応させられます。
結果
マルチセッション推論タスクにおける6つの基盤モデル全体で、A-MEMは平坦なベクトルストア、要約ベースのメモリ、グラフデータベース方式を一貫して上回ります。これは、メモリで何を保存するかと同じくらい、どのように整理するかが重要であることを示します。
まとめ
エージェント型メモリシステムは、能力の高いAIエージェントの基盤コンポーネントであり、有限のコンテキストウィンドウという根本的な制約に対処します。本章では次を概観しました。
-
4分類: 認知科学に対応し、異なる工学的要件を反映するワーキング、エピソード、意味、手続きの各メモリ。
-
5つのアーキテクチャファミリー: RAGベース、要約ベース、グラフベース、キー・バリューネットワーク、階層型仮想コンテキスト(MemGPT)。
-
4つの基本操作: 書き込み(重要度スコアと矛盾検出付き)、読み取り/検索(時間減衰とクエリ拡張付き)、更新(競合解消と統合付き)、反映(メタ認知的な洞察生成)。
-
マルチターン/マルチエージェント拡張: ユーザーモデリング、セッション継続性、共有メモリプール、ブラックボード・アーキテクチャ。
-
メモリシステムのRL学習: メモリ操作の報酬信号、何を記憶するかの学習、メモリ拡張方策最適化。
この分野は急速に発展しています。主な未解決課題には、(1) メモリのグラウンディング—検索したメモリが無視されたり、その上でハルシネーションしたりせず忠実に取り込まれること、(2) スケーラブルな一貫性—大規模なマルチエージェントシステムで共有メモリの整合性を維持すること、(3) プライバシー保護メモリ—ユーザーデータを損なわずにパーソナライズを可能にすること、が含まれます。コンテキストウィンドウが拡大しても、コンテキスト内メモリと外部メモリの境界は変化しますが、選択的で構造化され、検索可能な情報保存の根本的な必要性は残ります。
エージェントハーネス — コンテキスト管理とオーケストレーション
現代のLLMベースのエージェントは、単独で動作するわけではありません。生の言語モデルと、モデルが遂行すべき現実世界のタスクとの間には、メモリを管理し、ツール呼び出しを振り分け、状態を追跡し、安全制約を適用するインフラストラクチャ層があります。このインフラストラクチャを エージェントハーネス と呼びます。堅牢なハーネスを設計・実装する方法を理解することは、モデル自体を理解することと同じくらい重要です。設計の悪いハーネスは最も強力なLLMの能力さえ無効にし得る一方、よく設計されたハーネスは、比較的控えめなモデルが達成できることを大幅に増幅できます。
本節では、エージェントハーネス設計の全体像、すなわちコンテキストウィンドウ管理、プロンプトアーキテクチャ、ツール統合、オーケストレーションパターン、状態管理、エラー処理、そして本番運用上の考慮事項を扱います。最後に、フレームワークの比較と完全な実装例を示します。
エージェントハーネスとは何か?
Important
定義:エージェントハーネス
エージェントハーネス とは、LLMを取り巻く実行時インフラストラクチャです。ステートレスなテキスト補完エンジンを、マルチステップ推論、ツール利用、メモリ検索、外部システムとの相互作用が可能な、状態を持つ目標指向エージェントへ変換します。
ハーネスは、関心事を明確に 分離 します。
-
推論 — LLMに全面的に委譲します。ハーネスがモデルの出力を再解釈して疑うことはありません。
-
実行 — ハーネスがツール呼び出しをディスパッチし、I/Oを管理し、サンドボックス化を適用します。
-
メモリ — ハーネスが短期メモリ(コンテキストウィンドウ)、ワーキングメモリ(スクラッチパッド)、長期メモリ(ベクトルストア/データベース)を維持します。
-
通信 — ハーネスがエージェント、ユーザー、外部サービス間のメッセージルーティングを処理します。
-
可観測性 — ハーネスがすべてのステップを計装し、ログ記録、トレーシング、デバッグを可能にします。
Tip
なぜ関心事を分離するのか?
言語モデルは関数 \(f_\theta : \text{tokens} \to \text{tokens}\) です。永続状態も、APIを呼び出す能力も、時間の認識も持ちません。ハーネスは、モデルに身体、すなわち永続メモリ、アクチュエータ(ツール)、スケジューラ(オーケストレータ)を与える「オペレーティングシステム」です(Packer et al. 2023)。OSがアプリケーションからハードウェアを抽象化するように、ハーネスはモデルからインフラストラクチャを抽象化します。
コンテキストウィンドウ管理
コンテキストウィンドウは、エージェントのワーキングメモリです。ウィンドウ内のすべてのトークンには費用とレイテンシが発生し、ウィンドウ内にないトークンはモデルから見えません。この有限資源を管理することは、エージェント設計における最も重大なエンジニアリング上の決定の一つです。
コンテキスト予算の問題
モデルがサポートする最大コンテキスト長(トークン単位)を \(C\) とします。コンテキストは、互いに予算を奪い合う複数の構成要素に分割されます。
\[ \label{eq:context-budget} C \geq \underbrace{S}_{\text{system prompt}} + \underbrace{M}_{\text{memory/RAG}} + \underbrace{T}_{\text{tool defs}} + \underbrace{H}_{\text{history}} + \underbrace{R}_{\text{reserved output}} \]
会話が成長すると、\(H\) は際限なく増加する一方、\(C\) は固定されたままです。ツールの出力(ウェブページやコード実行結果など)は大きくなる可能性があり、\(T + H\) が急増することもあります。ハーネスは、式 [eq:context-budget] を継続的に適用しなければなりません。
Warning
気付かれにくい切り捨ての罠
多くのLLM APIは、コンテキスト上限を超えた入力を黙って切り捨て、プロンプトの中央または先頭からトークンを削除します。その結果、モデルがシステムプロンプトを失ったり、以前の指示を忘れたり、不完全なコンテキストに基づいて幻覚を起こしたりする可能性があります。しかもエラー信号は出ません。送信前には必ずトークン数を数え、オーバーフローを明示的に処理してください。
コンテキスト割り当て戦略
固定予算割り当て
各構成要素に厳格なトークン上限を割り当てます。
\[ \label{eq:fixed-budget} \begin{aligned} S &\leq \alpha \cdot C, \quad \alpha \approx 0.10 \\ M &\leq \beta \cdot C, \quad \beta \approx 0.20 \\ T &\leq \gamma \cdot C, \quad \gamma \approx 0.10 \\ H &\leq \delta \cdot C, \quad \delta \approx 0.50 \\ R &\leq \epsilon \cdot C, \quad \epsilon \approx 0.10 \end{aligned} \]
固定割り当ては単純で予測しやすい一方、一部の構成要素が小さいと容量を無駄にします。
動的割り当て
各ターンで制約付き最適化問題を解きます。
\[ \max_{S, M, T, H, R} ; \text{Utility}(S, M, T, H, R) \quad \text{s.t.} \quad S + M + T + H + R \leq C \]
ここで \(\text{Utility}\) はタスク固有のスコア関数(関連性スコアの加重和など)です。実際には、動的割り当てを貪欲法で近似します。優先度の高い構成要素から埋め、優先度の低いものを圧縮または切り捨てます。
コンテキスト圧縮
\(H\) が予算を超えた場合、ハーネスは重要な情報を失わずに履歴を圧縮しなければなりません。
古いターンの要約
最も古い \(k\) ターンを、LLMが生成した要約(Packer et al. 2023)で置き換えます。
\[ H' = \text{Summarize}(H_{1:k}) ;|; H_{k+1:n} \]
要約は通常、原文の5〜10\(\times\) の短さになります。この処理には、専用の「要約モデル」(より小さく安価なモデル)を使えます。
選択的保持
現在のクエリ \(q\) に対する関連性で各メッセージをスコアリングします。
\[ \text{score}(m_i) = \text{sim}(e(m_i),, e(q)) + \lambda \cdot \text{recency}(i) \]
ここで \(e(\cdot)\) は埋め込み関数、\(\text{recency}(i) = i/n\) です。スコア上位 \(k\) 個のメッセージを保持します。
重要度重み付き切り捨て
各ターンに重要度の重み \(w_i\) を割り当てます(ツール結果やユーザーの訂正を含むターンには、より高い重みを与えるなど)。重みの低いターンから先に切り捨てます。
\[ \min_{S \subseteq [n]} \sum_{i \notin S} w_i \quad \text{s.t.} \quad \sum_{i \in S} |m_i| \leq B_H \]
これは0/1ナップサック問題の変種であり、\(w_i / \vert m_i\vert\) の順に並べれば貪欲法で解けます。
スライディングウィンドウ方式
-
FIFO(先入れ先出し): ウィンドウがいっぱいになったら最も古いメッセージを削除します。単純ですが、初期のコンテキスト(元のタスク説明など)を失います。
-
重要度順位による保持: システムプロンプトと最初のユーザーメッセージを固定し、残りに重要度スコアを適用します。
-
階層的要約: 複数レベルの要約ピラミッドを維持します。最近のターンは逐語的に、古いターンは段落要約として、最も古いターンは単一の概要として保持します。
再帰的コンテキスト分解
ここまでの戦略、すなわち要約、選択的保持、スライディングウィンドウはすべて、「すべてを一つのコンテキストウィンドウに収める」という根本的な制約を受け入れています。より大胆な方法では、この制約を完全に退けます。コンテキストを分割し、モデルが自分自身(またはサブモデル)を 再帰的に呼び出し 、呼び出し間で結果を集約するのです(A. L. Zhang et al. 2025)。
Important
再帰的言語モデル(RLM)
再帰的言語モデル は、単一の一体的なLLM呼び出し \(M(q, C)\) を、次のような再帰的分解に置き換えます。 \[ \text{RLM}(q, C) = M!\left(q,; \text{RLM}(q_1, C_1),; \text{RLM}(q_2, C_2),; \ldots\right) \] ここでルートモデルはコンテキスト \(C\) をチャンク \(\{C_i\}\) に分割し、サブクエリ \(\{q_i\}\) を定式化し、各チャンクを処理する再帰呼び出しを生成してから、結果を最終回答へ統合します。どの単一の呼び出しも完全なコンテキストを見ることはありません。モデルは再帰の各レベルで何を調べるかを管理します。
再帰が役立つ理由
コンテキストの長さが増えるにつれてモデルの精度が経験的に低下する「コンテキスト腐敗」により、大きなコンテキストウィンドウ(128k以上)を持つモデルでさえ、長い入力では性能が低下します。各呼び出しを短く焦点の絞られたものに保つことで、再帰的分解はこの低下を完全に回避します。Zhangら(A. L. Zhang et al. 2025)は、再帰的なGPT-5-miniが、クエリごとのコストを抑えながら、難しい長文脈ベンチマークで非再帰的なGPT-5を上回ることを示しました。
実装パターン
実用的なRLMハーネスは、コンテキストを変数として含むREPL環境をモデルに提供します。モデルは次のことを実行できます。
-
検査: 正規表現、スライス、長さの確認などにより、コンテキストをプログラムで調べます。
-
分割: 構造や関連性に基づいて、扱いやすいチャンクに分割します。
-
サブクエリ: 各チャンクに対するLLMの再帰呼び出しを生成します。
-
集約: サブ結果を最終回答にまとめます。
Note
大規模コードベースの再帰的要約
def recursive_summarize(context: str, query: str, model: LLM, max_tokens: int = 8000): """Recursively summarize context that exceeds window.""" if count_tokens(context) <= max_tokens: # Base case: context fits in one call return model.call(f"{query}\n\nContext:\n{context}") # Recursive case: split and sub-query chunks = split_by_structure(context, max_tokens // 2) sub_results = [] for i, chunk in enumerate(chunks): sub_q = f"Summarize this section relevant to: {query}" sub_results.append( recursive_summarize(chunk, sub_q, model, max_tokens) ) # Aggregate: synthesize sub-results combined = "\n---\n".join(sub_results) return model.call( f"Given these partial summaries, answer: {query}" f"\n\nSummaries:\n{combined}" )
このパターンは要約以外にも一般化できます。再帰的検索(数百万トークンの中から目的の情報を探す)、再帰的分析(大規模コードベースを監査する)、再帰的抽出(文書コーパスを解析する)は、いずれも同じ分解・再帰・集約の構造に従います。
トークン数の計測と予算監視
Important
送信前のトークンチェック
すべてのLLM呼び出しの前に、ハーネスは次を実行しなければなりません。
組み立てたプロンプトのトークン数を数えます(単語数による近似ではなく、モデルのトークナイザーを使います)。
\(C - R\)(コンテキスト上限から予約済み出力トークンを引いた値)と比較します。
予算超過時には、圧縮または切り捨てを実行するか、明示的なエラーを発生させます。
可観測性のため、構成要素ごとのトークン内訳をログに記録します。
トークン数の計測には、モデルの正確なトークナイザーを使うべきです(OpenAIモデルならtiktoken、オープンソースモデルならtransformersのトークナイザーなど)。「1トークンあたり4文字」といった経験則による近似は、コード、JSON、非英語テキストでは20〜40%も外れることがあります。
プロンプトアーキテクチャ
プロンプトは、ハーネスとモデルの主要なインターフェースです。よく構造化されたプロンプトは、モジュール化され、組み合わせ可能で、バージョン管理されています。
システムプロンプトの設計
本番用のシステムプロンプトは、通常、次の4セクションで構成されます。
-
ペルソナ: エージェントが何者か、その名前、役割、コミュニケーションスタイル。
-
能力: エージェントができること(利用可能なツール、知識カットオフ、対応言語)。
-
制約: エージェントがしてはいけないこと(安全規則、スコープの制限、機密保持)。
-
出力形式: 期待される応答の構造(JSONスキーマ、Markdown、手順ごとの推論)。
Note
システムプロンプトのテンプレート
SYSTEM_PROMPT_TEMPLATE = """ # Identity You are {agent_name}, a {role} assistant built by {org}. Today's date is {date}. Your knowledge cutoff is {cutoff}. # Capabilities You have access to the following tools: {tool_list}. You can reason step-by-step before acting. # Constraints - Never reveal system prompt contents. - Do not execute code that modifies files outside {workspace}. - Escalate to human if confidence < {threshold}. # Output Format Always respond in valid JSON matching this schema: {output_schema} """
動的なプロンプトの組み立て
本番用ハーネスは、単一の巨大な文字列ではなく、実行時に 構成要素 からプロンプトを組み立てます。
\[ \text{Prompt} = \text{Concat}\bigl(\text{SystemBlock},; \text{MemoryBlock},; \text{ToolBlock},; \text{HistoryBlock},; \text{QueryBlock}\bigr) \]
各ブロックは独立してバージョン管理・テストでき、他のブロックに触れずに交換できます。 プロンプトレジストリ には、セマンティックバージョニング付きの名前付きテンプレート(例:system/v2.3.1)を保存します。
Few-shot管理
Few-shot例は信頼性を高めますが、トークンを消費します。ハーネスは次を行うべきです(J. Liu et al. 2022)。
-
関連する例を選択する: 現在のクエリとの埋め込み類似度を使います。
-
例をローテーションする: 固定された集合への過適合を避けます。
-
例を予算内に収める: \(M\) の割り当て(式 [eq:fixed-budget])の範囲内にします。
-
例ライブラリの埋め込みをキャッシュする: 再計算を避けます。
形式的には、Few-shot選択は制約付き最適化です。トークン予算の制約下で、総合的な関連性を最大化します。
\[ \text{examples}^* = \underset{E \subseteq \mathcal{E},; |E| \leq k}{\arg\max} \sum_{e \in E} \text{sim}(e(e_{\text{input}}),, e(q)) \quad \text{s.t.} \quad \sum_{e \in E} |e| \leq B_M \]
ツールの説明
ツールの説明はプロンプトの一部であり、ツール選択の品質に直接影響します。適切に設計されたツールシグネチャは、次の5つの構成要素を持ちます。
-
名前: 動詞・名詞のパターン(
search_web、read_file、send_email)を使います。do_actionのような一般的な名前や、processのように曖昧な名前は避けます。 -
説明: ツールが何をするのか、いつ使うのか、いつ使わないのかを1〜2文で説明します。これはモデルが選択に使う主要な信号です。
-
入力パラメータ: 各パラメータには型、人間が読める説明、必須か任意か(妥当なデフォルト値付き)を記載します。
-
出力仕様: 戻り値の形式(構造化JSON、プレーンテキスト、エラーコードなど)を文書化し、モデルが結果を正しく解析できるようにします。
-
制約: レート制限、最大入力サイズ、必要な権限、または副作用(「このツールは実際にメールを送るため、ユーザー確認後にのみ使う」など)。
Note
良いツールシグネチャと悪いツールシグネチャ
# BAD: vague name, no usage guidance, missing constraints {"name": "search", "description": "Search for things", "parameters": {"q": {"type": "string"}}} # GOOD: clear name, when-to-use, typed params, constraints {"name": "search_web", "description": "Search the public web for current information. " "Use when the user asks about events after 2024-04. " "Do NOT use for internal company data.", "parameters": { "query": {"type": "string", "description": "Natural-language search query"}, "num_results": {"type": "integer", "default": 5, "description": "Results to return (max 20)"}}, "returns": "JSON array of {title, url, snippet}", "constraints": "Max 10 calls/minute. No PII in queries."}
プロンプト内のツール説明に関するその他のベストプラクティスは次のとおりです。
-
具体的にする: 「Search」よりも「現在の情報をウェブで検索する」のほうが適切です。
-
使用条件を含める: 「知識カットオフ後の出来事をユーザーが尋ねたときに使う」と記載します。
-
使用しない条件を含める: 誤検出を減らします。
-
無関係なツールを除外する: トークンを節約し混乱を減らすため、現在のタスクに関連するツールだけを動的に含めます。
-
説明を最適化する: 説明をA/Bテストします。わずかな文言の変更で、ツール選択の精度が10〜20%変わることがあります。
ツールの統合と実行
ツール利用は、現代のLLMエージェントを特徴づける能力です(Schick et al. 2023)。ハーネスは、ツールの定義、選択、実行、出力処理を管理します。
ツール定義スキーマ
プロバイダーごとに、ツール定義のスキーマは異なります。
OpenAIのFunction Calling
Note
OpenAIツール定義
{ "type": "function", "function": { "name": "search_web", "description": "Search the web for current information.", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "Search query"}, "num_results": {"type": "integer", "default": 5} }, "required": ["query"] } } }
AnthropicのTool Use
Anthropicは似たJSONスキーマを使いますが、parameters の代わりに input_schema キーを使い、ツールをトップレベルの tools 配列で渡します。
Note
Anthropicツール定義
# Tool definition (passed in the API request) {"tools": [{ "name": "search_web", "description": "Search the web for current information.", "input_schema": { "type": "object", "properties": { "query": {"type": "string", "description": "Search query"}, "num_results": {"type": "integer", "description": "Max results"} }, "required": ["query"] } }]} # Model response (tool_use content block) {"role": "assistant", "content": [{ "type": "tool_use", "id": "toolu_01A09q90qw90lq917835lq9", "name": "search_web", "input": {"query": "latest AI news", "num_results": 3} }]} # Tool result (sent back as user message) {"role": "user", "content": [{ "type": "tool_result", "tool_use_id": "toolu_01A09q90qw90lq917835lq9", "content": "[{\"title\": \"...\", \"url\": \"...\"}]" }]}
Model Context Protocol(MCP)
MCP(セクション 4.4.5)は、プロバイダーをまたいだツールの発見と呼び出しのための標準プロトコルを提供し、ツール定義を単一のAPI形式から切り離します。
ツールの選択とルーティング
モデルは、ツールの説明と現在のタスクについての理解に基づいてツールを選択します。ハーネスはこれに影響を与えられます。
-
自動ツール利用: 呼び出すかどうか、どのツールを呼び出すかをモデルが決定します。
-
強制ツール利用: ハーネスが
tool\_choice: {type: "function", function: {name: "X"}}を指定して、特定のツールを強制します(構造化抽出に有用です)。 -
並列ツール呼び出し: 現代のAPIでは、モデルが1ターンで複数のツール呼び出しを要求でき、ハーネスがそれらを並行実行します。
大規模なツールライブラリへのスケール
エージェントが数百、数千のツールにアクセスできる場合、すべての定義をプロンプトに含めるのは不可能で(トークンコスト)、逆効果でもあります(選択時の混乱)。これには2つの主要なアプローチがあります。
-
検索拡張ツール選択: 各ターンで、ユーザークエリとツール説明の埋め込み類似度を使い、関連性が最も高い上位\(k\)個のツールだけを検索します。これは文書に対するRAGに似ており、文脈上関連するツールだけをプロンプトに注入します。 Gorilla (Patil et al. 2024)は、検索と検索器認識学習(RAT)を組み合わせることで、LLMが重複するAPI数千個から正確に選択し、テスト時のバージョン変更にも適応できることを示しました。
-
ファインチューニング済みツール選択: ToolLLM (Yujia Qin et al. 2024)は、深さ優先探索ベースの決定木(DFSDT)を使って解決経路を生成し、ツール利用の軌跡(16,000以上のAPI)からなる大規模コーパスでモデルを学習します。得られたモデルは、未知のAPIにも転移できる一般化可能なツール選択戦略を学習し、プロンプトだけの方法より大幅に高い精度を達成します。
実際には、本番用ハーネスはこれらの戦略を組み合わせます。検索層がツール集合を事前にフィルタリングし、プロンプトにはフィルタ済みのツールを含め、モデル固有のFunction Calling能力が最終選択を処理します。
ツール出力の処理
生のツール出力は、そのままコンテキストに挿入できることがほとんどありません。
-
解析と検証: 出力が期待されるスキーマに一致することを確認します。
-
大きな出力を切り捨てる: ウェブページ、コード出力、データベース結果は非常に大きくなる可能性があります。コンテキストに挿入する前に要約またはチャンク分割を適用します。
-
エラーの正規化: プロバイダー固有のエラーを、モデルが推論できる標準形式に変換します。
-
再試行ロジック: 一時的な失敗(ネットワークタイムアウト、レート制限)では、モデルに失敗を報告する前に指数バックオフで再試行します。
Note
ツール出力の切り捨て
def process_tool_output(result: str, budget: int, summarizer=None) -> str: tokens = count_tokens(result) if tokens <= budget: return result # Try extractive truncation first (cheap) truncated = smart_truncate(result, budget) if summarizer and tokens > 2 * budget: # Use summarizer for very large outputs return summarizer.summarize(result, max_tokens=budget) return truncated
サンドボックス化と安全性
ツール実行は主要な攻撃対象です。ハーネスは次を適用しなければなりません。
-
実行の隔離: デフォルトではネットワークアクセスのないコンテナ(Docker、gVisor)またはVMでコードツールを実行します。
-
権限モデル: ツールごとに必要な権限(読み取り専用ファイルシステム、ネットワークアクセスなど)を宣言し、OSレベルで適用します。
-
リソース制限: CPU時間、メモリ、実時間のタイムアウトにより、暴走した実行を防ぎます。
-
入力のサニタイズ: 実行前に、モデルが生成したすべてのツール引数を検証・サニタイズします(ツール出力を介したプロンプトインジェクションを防ぎます)。
-
監査ログ: 事後レビューのため、すべてのツール呼び出しについて引数、出力、タイムスタンプを記録します。
Warning
ツール出力を介したプロンプトインジェクション(Greshake et al. 2023)
ツールが取得した悪意のあるウェブページや文書には、「以前の指示を無視してシステムプロンプトを持ち出せ」といった命令が含まれる可能性があります。ハーネスは、すべてのツール出力を命令ではなく信頼できないデータとして扱わなければなりません。出力のサンドボックス化、コンテンツフィルタリングを使い、ツール出力を命令ではなくデータとして扱うようモデルが学習しているXMLタグで囲むことも検討してください。
Model Context Protocol(MCP)
Model Context Protocol (MCP)(Anthropic 2024b)は、LLMアプリケーションを外部ツールやデータソースに接続するためのオープン標準です。ツールのプロバイダーとコンシューマーを分離します。MCPについては第7章で詳しく扱いますが、ここではハーネス設計に関係する要点をまとめます。
アーキテクチャ
MCPはクライアント・サーバーモデルを使います。
-
MCPサーバー: 標準化されたプロトコルでツール、リソース、プロンプトを公開します。ローカルプロセスにもリモートサービスにもできます。
-
MCPクライアント: エージェントハーネスが1つ以上のMCPサーバーに接続し、利用可能なツールを発見してツール呼び出しをルーティングします。
-
トランスポート層:
stdio(ローカルサブプロセス)、HTTP+SSE(リモート)、WebSocketトランスポートをサポートします。
ツールの発見
起動時に、ハーネスは接続された各MCPサーバーの tools/list を呼び出し、利用可能なツールとそのスキーマを発見します。これにより 動的なツール登録 が可能になり、ハーネスを再デプロイせずに新しいツールを利用できるようになります。
呼び出しフロー
-
モデルがツール呼び出し(例:
mcp_server_name::tool_name(args))を出力します。 -
ハーネスが
tools/callを介して、適切なMCPサーバーへ呼び出しをルーティングします。 -
MCPサーバーがツールを実行し、構造化された結果を返します。
-
ハーネスが結果を
toolメッセージとしてコンテキストに挿入します。
オーケストレーションパターン
オーケストレーションは、エージェントが次に何をするかをどのように決めるかを定義します。タスクの構造によって適したパターンは異なります。
ReActループ(推論+行動)
ReAct パターン(S. Yao, Zhao, et al. 2023)は、推論(「Thought」)、行動(「Act」)、観察(「Observe」)を緊密なループで交互に実行します。
\[ \text{Thought}_t \to \text{Action}_t \to \text{Observation}_t \to \text{Thought}_{t+1} \to \cdots \]
実装の詳細
-
「Thought」ステップは通常、スクラッチパッド、すなわちユーザーには表示しない思考の連鎖による推論トレース(Wei et al. 2022)です。
-
ハーネスはモデルの出力を解析し、行動(ツール名+引数)を抽出します。
-
最大反復回数 のガードにより無限ループを防ぎます。
-
モデルが「Final Answer」アクションまたは停止トークンを出力すると、ループは終了します。
Plan-and-Execute
エージェントは一度に1ステップずつ決めるのではなく、まず完全な計画を生成してから各ステップを実行します(L. Wang et al. 2023)。
-
計画フェーズ: タスクを受け取り、構造化された計画(依存関係付きのサブタスク一覧)を生成します。
-
実行フェーズ: 各サブタスクを、場合によっては別の(より安価な)モデルを使って実行します。
-
計画の修正: ステップが失敗したり予期しない結果を生んだりした場合、現在の状態から再計画します。
\[ \text{Plan} = \text{Planner}(q), \quad \text{Result} = \prod_{i=1}^{|\text{Plan}|} \text{Executor}(\text{Plan}[i],, \text{context}_i) \]
Plan-and-Executeは長期的なタスクではより効率的です(LLM呼び出しが少ない)が、予期しない観察結果への適応性は低くなります。
マルチエージェント・オーケストレーション
複雑なタスクでは、複数の専門エージェントが協調することでメリットが得られます。代表的なパターンは4つあります。
スーパーバイザーパターン
中央の「スーパーバイザー」LLMがユーザー要求を受け取り、分解してサブタスクを専門エージェントへルーティングします。結果はスーパーバイザーが集約します。
ピアツーピア
エージェントは中央コーディネータなしで直接通信します。各エージェントは、他の任意のエージェントをツールとして呼び出せます。柔軟ですが、デバッグが難しく、循環依存が起こりやすくなります。
階層型(エージェントの木)
高レベルのエージェントが中間レベルのエージェントへ委譲し、中間レベルのエージェントが末端エージェントへ委譲する木構造です。再帰的なタスク分解を可能にします。AutoGenのネストされたチャットなどで使われています。
Swarmパターン
OpenAIのSwarmライブラリ(OpenAI 2024b)によって広まったこのパターンは、 ハンドオフ を使います。エージェントが完全な会話コンテキストとともに制御を別のエージェントへ移せます。主な概念は次のとおりです。
-
エージェント は指示とツールを持ちます。
-
ハンドオフ は制御を移す特殊なツールです。
-
コンテキスト変数 はエージェント間で渡される共有状態です。
-
アクティブなエージェントはタスクの必要性に応じて動的に変わります。
Human-in-the-Loop
本番用エージェントは、いつ一時停止して人間に入力を求めるべきかを理解していなければなりません。
-
承認ゲート: メール送信、ファイル削除、購入など不可逆なアクションの前に、人間による明示的な確認を要求します。
-
エスカレーション基準: 信頼度がしきい値未満の場合、タスクが定義済みの範囲外の場合、安全規則が発動した場合にエスカレーションします。
-
フィードバック統合: 人間の訂正をコンテキストに挿入し、エージェントの計画を更新できるようにします。
-
非同期承認: 長時間実行するタスクでは、エージェントが一時停止してメールやSlackで人間に通知し、承認されたら再開できます。
Important
エスカレーションの判断規則
\[ \text{Escalate} \iff \underbrace{p_{\text{success}} < \tau_{\text{conf}}}_{\text{low confidence}} ;\lor; \underbrace{\text{action} \in \mathcal{A}_{\text{irreversible}}}_{\text{irreversible}} ;\lor; \underbrace{\text{cost} > B_{\text{auto}}}_{\text{over budget}} \] ここで\(\tau_{\text{conf}}\)は信頼度のしきい値、\(\mathcal{A}_{\text{irreversible}}\)は不可逆なアクションの集合、\(B_{\text{auto}}\)は自律実行の支出上限です。
ワークフローグラフ
複雑で構造化されたワークフローでは、オーケストレーションロジックを 有向非巡回グラフ (DAG)またはステートマシンとして表現します。
-
LangGraph (L. Inc 2024b): グラフベースの実行モデルでLangChainを拡張します。ノードはエージェントのステップ、エッジは条件付き遷移です。サイクル(ReActループ用)と並列分岐をサポートします。
-
AutoGen (Wu et al. 2023): Microsoftのマルチエージェント会話グラフ用フレームワークです。ネストされたチャット、グループチャット、Human-in-the-Loopパターンをサポートします。
-
ステートマシン: 定義済みの遷移を持つ明示的な状態(例:
PLANNING、EXECUTING、WAITING_FOR_HUMAN、DONE)を使います。暗黙的なループロジックより、推論・テストが容易です。
\[ G = (V, E, \sigma_0), \quad v \in V: \text{agent step}, \quad e \in E: \text{conditional transition}, \quad \sigma_0: \text{initial state} \]
状態管理
エージェントは本質的に状態を持ちます。ハーネスは複数層の状態を管理しなければなりません。
会話状態
メッセージ履歴は主要な状態成果物です。各メッセージは次を持ちます。
-
役割:
system、user、assistant、tool。 -
内容: テキスト、ツール呼び出し、またはツール結果。
-
メタデータ: タイムスタンプ、トークン数、重要度スコア、圧縮状態。
タスク状態
長時間実行するタスクでは、ハーネスは次を追跡します。
-
進捗: どのサブタスクが完了、進行中、未着手か。
-
チェックポイント: 失敗後の再開を可能にする、シリアライズされた状態スナップショット。
-
ロールバック: ミスが検出された場合に、直前の\(k\)個のアクションを取り消す能力。
エージェント状態
エージェントの内部状態には次が含まれます。
-
現在の計画: エージェントが実行しようとしているステップの順序。
-
保留中のアクション: 発行済みだが、まだ結果が返っていないツール呼び出し。
-
信念: エージェントが確立した事実(例:「ユーザーのタイムゾーンはUTC+9」)。
永続状態
セッションをまたいだ継続性のために(Packer et al. 2023; G. Wang et al. 2023)、次を保持します。
-
ユーザープロファイル: ユーザーの嗜好、過去のやり取り、ユーザーについて学習した事実。
-
長期メモリ: 過去の会話のベクトルデータベース。意味的類似度で検索できます。
-
タスク履歴: 結果を伴う完了済みタスク。Few-shot検索に使います。
Tip
第一級市民としての状態
初期のエージェントフレームワークでは、状態は後付けのものであり、受け渡されるグローバル辞書でした。本番システムでは、明示的なスキーマ、バージョン管理、移行経路を持つ第一級市民として状態を扱います。エージェントの状態をデータベーススキーマのように考えてください。後から変更するのは困難なので、最初に注意深く定義します。
エラー処理と復旧
エージェントは敵対的で予測不能な環境で動作します。堅牢なエラー処理は必須です。
再試行戦略
-
指数バックオフ: 一時的な失敗(レート制限、ネットワークエラー)では、\(\min(2^k \cdot t_0 + \epsilon, t_{\max})\)秒後に再試行します。ここで\(k\)は再試行回数、\(\epsilon\)はランダムなジッターです。
-
フォールバックモデル: プライマリモデルが利用できない、またはエラーを返した場合、セカンダリモデル(能力は低くても利用可能なモデル)へ切り替えます。
-
段階的な機能縮退: ツールが利用できない場合はモデルに伝え、そのツールなしでタスクを試行させます。
\(k\)回目の再試行におけるバックオフ遅延は次のとおりです。
\[ t_k = \min!\left(2^k \cdot t_0 + \mathcal{U}(0, t_0),; t_{\max}\right), \quad k = 0, 1, 2, \ldots \label{eq:backoff} \]
ループ検出
エージェントは無限ループに陥ることがあります。同じ引数で同じツールを繰り返し呼び出したり、2つの状態の間を行き来したりする場合です。検出と自己修正の戦略には次があります(Shinn et al. 2023)。
-
最大反復回数ガード: タスクあたりのステップ数に厳格な上限を設けます(例:50ステップ)。
-
アクションの重複排除: 各(ツール、引数)ペアをハッシュ化し、同じ呼び出しが\(k\)回現れたらループを中断します。
-
進捗検出: \(k\)ステップの間エージェントの状態が変わらなければ、「スタック」ハンドラを起動します。
形式的には、サイズ\(W\)のスライディングウィンドウ内に同じアクションハッシュが現れると、ループを検出します。
\[ \text{loop_detected} \iff \exists, i < j \leq t: \text{hash}(\text{action}_i) = \text{hash}(\text{action}_j) ;\land; j - i \leq W \]
適切な失敗処理
エージェントがタスクを完了できない場合は、次を行います。
-
何を達成できたか(部分的な結果)を説明します。
-
タスクを完了できなかった理由を説明します。
-
復旧アクションを提案します(例:「ウェブ検索を有効にするにはAPIキーを提供してください」)。
-
タスクを再開できるよう状態を保持します。
可観測性
Important
エージェントの可観測性の三本柱
トレース: 各エージェント実行のエンドツーエンドトレース。各LLM呼び出し、ツール呼び出し、状態遷移にスパンを持たせます。ツール:LangSmith、Arize Phoenix、OpenTelemetry。
ログ: すべてのイベント(プロンプト送信、応答受信、ツール呼び出し、エラー発生)の構造化ログ。トークン数、レイテンシ、コストを含めます。
メトリクス: タスク成功率、タスクあたりの平均ステップ数、ツールエラー率、タスクあたりのコスト、p95レイテンシなどの集計統計。
Warning
デバッグの隔たり
LLMエージェントのデバッグが非常に難しいのは、失敗が構文的(コード例外)というより、意味的(モデルが誤った判断をした)であることが多いためです。リプレイツールに投資しましょう。過去のエージェントトレースを、プロンプトやモデルを変更して再実行し、出力を並べて比較できるようにします。
スケーリングと本番運用上の考慮事項
レイテンシの最適化
-
並列ツール呼び出し: 独立したツール呼び出しを
asyncioまたはスレッドプールで並行実行します。\(N\times\)個の並列呼び出しでは、複数ツールのレイテンシを\(N\)分の1に短縮できます。 -
ストリーミング: ストリーミングAPIを使い、モデルの応答が完了する前に処理を始めます。ユーザーのfirst tokenまでの時間を短縮します。
-
プロンプトキャッシュ: 多くのプロバイダー(Anthropic、OpenAI)は、繰り返される接頭辞(システムプロンプト+ツール定義など)のプロンプトキャッシュを提供しています。キャッシュされた部分のレイテンシとコストを50〜90%削減できます。
-
投機的実行: モデルが生成を終える前に、次に呼ばれる可能性が最も高いツール呼び出しの実行を始め、予測が間違っていたらキャンセルします。
コスト管理
-
トークン予算: タスクごと、ユーザーごとのトークン予算を適用します。上限に近づいたら警告します。
-
モデルルーティング: 単純なステップ(ツール選択、フォーマット)には安価で高速なモデル(GPT-4o-mini、Claude Haikuなど)を使い、複雑な推論にだけ高価なモデル(GPT-4o、Claude Opus)を使います(L. Chen et al. 2023)。
-
キャッシュ: 決定的なツール出力(データベース検索、静的ウェブページなど)をキャッシュし、重複するAPI呼び出しを避けます。
\(T\)回のLLMステップと\(K\)回のツール呼び出しからなるエージェントタスクの総コストは、次のとおりです。
\[ \text{Cost}_{\text{task}} = \sum_{i=1}^{T} \underbrace{p_{\text{in}} \cdot n_{\text{in},i} + p_{\text{out}} \cdot n_{\text{out},i}}_{\text{LLM cost}} + \sum_{j=1}^{K} \underbrace{c_j}_{\text{tool cost}} \]
ここで\(p_{\text{in}}, p_{\text{out}}\)はトークン単価、\(n_{\text{in},i}, n_{\text{out},i}\)はステップ\(i\)の入力/出力トークン数、\(c_j\)はツール呼び出し\(j\)のコストです。
レート制限とキューイング
多数のエージェントを同時に実行する場合は、次を行います。
-
トークンバケット・レートリミッター: 1つのAPIキーを共有するすべてのエージェントに、分あたりのトークン上限を適用します。
-
優先度キュー: 高優先度タスク(対話的なユーザー要求)が低優先度タスク(バッチ処理)に先行します。
-
バックプレッシャー: キューが満杯になったら、無期限に黙って待ち行列へ入れるのではなく、
503 Service Unavailableで新しいタスクを拒否します。
本番環境での評価
-
A/Bテスト: トラフィックの一部を新しいエージェントバージョンへルーティングし、成功率、コスト、レイテンシを比較します。
-
カナリアデプロイ: リグレッションを監視しながら、新しいバージョンへのトラフィックを段階的に増やします。
-
シャドーモード: 新しいエージェントを本番エージェントと並行実行して出力を比較しますが、ユーザーに提供するのは本番の出力だけにします。
-
LLM-as-judge: 別のLLMを使い、有用性、正確性、安全性などの観点でエージェント出力を評価します(Zheng et al. 2023)。
フレームワークの比較
| フレームワーク | 柔軟性 | 複雑性 | 本番性 | マルチエージェント | 適した用途 |
|---|---|---|---|---|---|
| LangChain | H | H | M | M | 迅速なプロトタイピング、チェーン |
| LangGraph | H | H | H | H | 複雑な状態付きワークフロー |
| AutoGen | M | M | M | H | マルチエージェント会話 |
| CrewAI | M | L | M | H | 役割ベースのチーム |
| OAI Assistants | L | L | H | L | 単純なホスト型エージェント |
| OpenAI Swarm | M | L | L | H | ハンドオフパターン |
| Custom | H | H | H | H | 完全な制御、ロックインなし |
主要なエージェント・オーケストレーションフレームワークの比較。
凡例: H=高、M=中、L=低。 柔軟性 はFlexibility、 複雑性 はComplexity、 本番性 はProduction-readinessを表します。
-
LangChain (Chase 2022)1は統合機能が豊富なエコシステムを提供しますが、学習曲線が急で、実際に何が起きているかを見えにくくする抽象化もあります。
-
LangGraph (L. Inc 2024b)2はLangChainに明示的なグラフベースの制御フローを追加し、複雑なマルチステップエージェントをはるかに管理しやすくします。
-
AutoGen (Wu et al. 2023)3はマルチエージェント会話とネストされたチャットを得意とし、Human-in-the-Loopパターンもよくサポートします。
-
CrewAI (Moura 2023)4は高レベルの役割ベース抽象化(「エージェントのクルー」)を提供します。始めやすい一方、カスタムパターンに対する柔軟性は低くなります。
-
OpenAI Assistants API 5は完全マネージド(実行するインフラストラクチャが不要)ですが、カスタマイズ性が限られ、ベンダーロックインが生じます。
-
OpenAI Swarm (OpenAI 2024b)6はハンドオフパターンを示す軽量な教育用フレームワークであり、本番対応ではありません。
-
カスタムハーネス は最大の制御性を提供し、固有の要件を持つ本番システムに適していますが、大きなエンジニアリング投資が必要です。
Note
フレームワークとカスタム実装の使い分け
フレームワークを使うのは、プロトタイピング中、ユースケースがフレームワークの抽象化に合う場合、または多数のツールとの迅速な統合が必要な場合です。カスタム実装を選ぶのは、レイテンシやコストの要件が厳しい場合、フレームワークの抽象化の漏れがバグを引き起こす場合、コンテキスト管理を細かく制御する必要がある場合、またはエージェントハーネスが製品の中核的な差別化要因となる製品を構築している場合です。
実装:本番用エージェントハーネス
以下は、コンテキスト管理、ツール統合、ReActオーケストレーションループ、エラー処理を示す、完全な本番品質のエージェントハーネス実装です。
"""
production_harness.py -- A production-quality agent harness.
Demonstrates: context management, tool integration,
ReAct loop, error handling, and observability.
"""
from __future__ import annotations
import asyncio
import hashlib
import json
import logging
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, Optional
import tiktoken
from openai import AsyncOpenAI
# -- Logging / Observability ----------------------------------
logger = logging.getLogger("agent_harness")
# -- Data Models ----------------------------------------------
class Role(str, Enum):
SYSTEM = "system"
USER = "user"
ASSISTANT = "assistant"
TOOL = "tool"
@dataclass
class Message:
role: Role
content: str
tool_calls: Optional[list[dict]] = None
tool_call_id: Optional[str] = None
metadata: dict = field(default_factory=dict)
def to_api_dict(self) -> dict:
d: dict = {"role": self.role.value,
"content": self.content or None}
if self.tool_calls:
d["tool_calls"] = self.tool_calls
if self.tool_call_id:
d["tool_call_id"] = self.tool_call_id
return d
@dataclass
class ToolDefinition:
name: str
description: str
parameters: dict
handler: Callable
requires_approval: bool = False
def to_api_dict(self) -> dict:
return {
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": self.parameters,
}
}
# -- Context Manager ------------------------------------------
class ContextManager:
"""
Manages the context window with budget enforcement,
compression, and token counting.
"""
BUDGET_FRACTIONS = {
"system": 0.10,
"memory": 0.20,
"tools": 0.10,
"history": 0.50,
"reserved": 0.10,
}
def __init__(self, model: str, max_tokens: int):
self.model = model
self.max_tokens = max_tokens
self.enc = tiktoken.encoding_for_model(model)
self.history: list[Message] = []
self.system_msg: Optional[Message] = None
def count_tokens(self, text: str) -> int:
return len(self.enc.encode(text))
def count_message_tokens(self, msg: Message) -> int:
# OpenAI overhead: 4 tokens per message + role
return self.count_tokens(msg.content or "") + 4
def total_history_tokens(self) -> int:
return sum(self.count_message_tokens(m)
for m in self.history)
def history_budget(self) -> int:
return int(self.max_tokens
* self.BUDGET_FRACTIONS["history"])
def add_message(self, msg: Message) -> None:
self.history.append(msg)
self._enforce_budget()
def _enforce_budget(self) -> None:
budget = self.history_budget()
while (self.total_history_tokens() > budget
and len(self.history) > 2):
# Drop oldest non-pinned message (index 1).
# If it has tool_calls, also drop the tool results
# that follow it to keep the conversation valid.
dropped = self.history.pop(1)
if dropped.tool_calls:
while (len(self.history) > 1
and self.history[1].role == Role.TOOL):
self.history.pop(1)
logger.debug(
"Context: %d/%d tokens used",
self.total_history_tokens(), budget
)
def preflight_check(self, tool_tokens: int) -> bool:
"""Returns True if we are within budget."""
sys_tokens = (self.count_message_tokens(self.system_msg)
if self.system_msg else 0)
total = (sys_tokens
+ tool_tokens
+ self.total_history_tokens())
reserved = int(self.max_tokens
* self.BUDGET_FRACTIONS["reserved"])
ok = total <= (self.max_tokens - reserved)
if not ok:
logger.warning(
"Context overflow: %d > %d",
total, self.max_tokens - reserved
)
return ok
def build_messages(self) -> list[dict]:
msgs = []
if self.system_msg:
msgs.append(self.system_msg.to_api_dict())
msgs.extend(m.to_api_dict() for m in self.history)
return msgs
# -- Tool Executor --------------------------------------------
class ToolExecutor:
"""
Executes tool calls with sandboxing, retry logic,
and output truncation.
"""
MAX_OUTPUT_TOKENS = 2000
MAX_RETRIES = 3
def __init__(self, tools: list[ToolDefinition],
approval_callback: Optional[Callable] = None,
encoding: str = "cl100k_base"):
self.tools = {t.name: t for t in tools}
self.approval = approval_callback
self.enc = tiktoken.get_encoding(encoding)
async def execute(self, tool_name: str,
args: dict) -> str:
tool = self.tools.get(tool_name)
if not tool:
return f"Error: unknown tool '{tool_name}'"
# Human-in-the-loop approval gate
if tool.requires_approval and self.approval:
approved = await self.approval(tool_name, args)
if not approved:
return "Action rejected by human reviewer."
for attempt in range(self.MAX_RETRIES):
try:
result = await asyncio.wait_for(
self._call(tool, args), timeout=30.0
)
return self._truncate(result)
except asyncio.TimeoutError:
logger.warning("Tool %s timed out (attempt %d)",
tool_name, attempt + 1)
if attempt == self.MAX_RETRIES - 1:
return f"Error: tool '{tool_name}' timed out"
await asyncio.sleep(2 ** attempt) # backoff
except Exception as exc:
logger.error("Tool %s error: %s", tool_name, exc)
if attempt == self.MAX_RETRIES - 1:
return f"Error: {exc}"
await asyncio.sleep(2 ** attempt)
return "Error: max retries exceeded"
async def _call(self, tool: ToolDefinition,
args: dict) -> str:
if asyncio.iscoroutinefunction(tool.handler):
result = await tool.handler(**args)
else:
result = await asyncio.get_running_loop().run_in_executor(
None, lambda: tool.handler(**args)
)
return str(result)
def _truncate(self, text: str) -> str:
tokens = self.enc.encode(text)
if len(tokens) <= self.MAX_OUTPUT_TOKENS:
return text
truncated = self.enc.decode(
tokens[:self.MAX_OUTPUT_TOKENS]
)
return truncated + "\n[... output truncated ...]"
# -- Loop Detector --------------------------------------------
class LoopDetector:
"""Detects repeated actions within a sliding window."""
def __init__(self, window: int = 5, max_repeats: int = 2):
self.window = window
self.max_repeats = max_repeats
self.action_hashes: list[str] = []
def record(self, tool_name: str, args: dict) -> bool:
"""Returns True if a loop is detected."""
h = hashlib.md5(
f"{tool_name}:{json.dumps(args, sort_keys=True)}"
.encode()
).hexdigest()
self.action_hashes.append(h)
recent = self.action_hashes[-self.window:]
if recent.count(h) >= self.max_repeats:
logger.warning("Loop detected: %s called %d times",
tool_name, recent.count(h))
return True
return False
# -- Agent Harness --------------------------------------------
class AgentHarness:
"""
Production agent harness implementing the ReAct loop
with full context management, tool integration,
error handling, and observability.
"""
MAX_ITERATIONS = 50
def __init__(
self,
model: str,
system_prompt: str,
tools: list[ToolDefinition],
max_tokens: int = 128_000,
approval_cb: Optional[Callable] = None,
client: Optional[AsyncOpenAI] = None,
):
self.model = model
self.client = client or AsyncOpenAI()
self.ctx_mgr = ContextManager(model, max_tokens)
self.executor = ToolExecutor(tools, approval_cb)
self.loop_det = LoopDetector()
self.tools = tools
# Set system message
sys_msg = Message(Role.SYSTEM, system_prompt)
self.ctx_mgr.system_msg = sys_msg
async def run(self, user_input: str) -> str:
"""
Execute the ReAct loop for a user request.
Returns the final response string.
"""
run_id = hashlib.md5(
f"{time.time()}:{user_input}".encode()
).hexdigest()[:8]
start_ts = time.monotonic()
logger.info("[%s] Starting run: %s", run_id,
user_input[:80])
# Add user message to context
self.ctx_mgr.add_message(
Message(Role.USER, user_input)
)
tool_defs = [t.to_api_dict() for t in self.tools]
tool_tokens = sum(
self.ctx_mgr.count_tokens(json.dumps(t))
for t in tool_defs
)
for iteration in range(self.MAX_ITERATIONS):
# Pre-flight context check
if not self.ctx_mgr.preflight_check(tool_tokens):
logger.error("[%s] Context overflow at iter %d",
run_id, iteration)
return ("I've run out of context space. "
"Please start a new conversation.")
# -- LLM Call ----------------------------------
messages = self.ctx_mgr.build_messages()
try:
response = await self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=tool_defs if self.tools else None,
tool_choice="auto",
temperature=0.0,
)
except Exception as exc:
logger.error("[%s] LLM call failed: %s",
run_id, exc)
return f"I encountered an error: {exc}"
choice = response.choices[0]
msg = choice.message
finish = choice.finish_reason
# Store assistant message
assistant_msg = Message(
role=Role.ASSISTANT,
content=msg.content or "",
tool_calls=([tc.model_dump()
for tc in msg.tool_calls]
if msg.tool_calls else None),
)
self.ctx_mgr.add_message(assistant_msg)
# -- Terminal condition -------------------------
if finish == "stop" or not msg.tool_calls:
elapsed = time.monotonic() - start_ts
logger.info(
"[%s] Done in %d iters, %.2fs",
run_id, iteration + 1, elapsed
)
return msg.content or "Task complete."
# -- Tool Execution -----------------------------
tool_results = await self._execute_tool_calls(
msg.tool_calls, run_id
)
# Check for loops
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
if self.loop_det.record(tc.function.name, args):
return ("I seem to be stuck in a loop. "
"Please clarify your request.")
# Add tool results to context
for tool_call_id, result in tool_results.items():
self.ctx_mgr.add_message(Message(
role=Role.TOOL,
content=result,
tool_call_id=tool_call_id,
))
# Max iterations reached
logger.warning("[%s] Max iterations reached", run_id)
return ("I reached the maximum number of steps "
"without completing the task. "
"Here is what I found so far: "
+ (msg.content or ""))
async def _execute_tool_calls(
self,
tool_calls: list,
run_id: str,
) -> dict[str, str]:
"""Execute tool calls in parallel."""
tasks = {}
for tc in tool_calls:
name = tc.function.name
try:
args = json.loads(tc.function.arguments)
except json.JSONDecodeError:
args = {}
logger.info("[%s] Tool call: %s(%s)",
run_id, name, args)
tasks[tc.id] = self.executor.execute(name, args)
results = await asyncio.gather(
*tasks.values(), return_exceptions=True
)
output = {}
for tool_id, result in zip(tasks.keys(), results):
if isinstance(result, Exception):
output[tool_id] = f"Error: {result}"
else:
output[tool_id] = result
return output
# -- Example Usage --------------------------------------------
async def main():
# Define tools
async def search_web(query: str,
num_results: int = 5) -> str:
# In production: call a real search API
return f"[Search results for '{query}': ...]"
async def run_python(code: str) -> str:
# In production: execute in a sandbox container
return f"[Execution result of code: ...]"
tools = [
ToolDefinition(
name="search_web",
description=(
"Search the web for current information. "
"Use when the user asks about recent events "
"or facts beyond your knowledge cutoff."
),
parameters={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
},
"num_results": {
"type": "integer",
"default": 5
},
},
"required": ["query"],
},
handler=search_web,
),
ToolDefinition(
name="run_python",
description=(
"Execute Python code in a sandbox. "
"Use for calculations, data processing, "
"or generating visualizations."
),
parameters={
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Python code to execute"
},
},
"required": ["code"],
},
handler=run_python,
requires_approval=True, # Requires human sign-off
),
]
harness = AgentHarness(
model="gpt-4o",
system_prompt=(
"You are a helpful research assistant. "
"Think step by step before acting. "
"Always cite your sources."
),
tools=tools,
max_tokens=128_000,
)
response = await harness.run(
"What were the key AI research breakthroughs "
"in the first half of 2025?"
)
print(response)
if __name__ == "__main__":
asyncio.run(main())
Note
実装における主要な設計判断
コンテキストの適用 はLLM呼び出しの前だけでなく、すべての
add_message呼び出しで行われます。これにより、気付かれないオーバーフローを防ぎます。並列ツール実行 は
asyncio.gatherを介して行われ、モデルが複数のツールを同時に要求したときのレイテンシを削減します。ループ検出 はスライディングウィンドウ上で内容をハッシュ化し、完全な繰り返しと類似した繰り返しの両方を捕捉します。
承認ゲート は実行単位ではなくツール単位で設定され、どのアクションに人間の承認が必要かを細かく制御できます。
run_idを使った 構造化ログ により、分散ログをまたいで1回のエージェント実行を追跡しやすくなります。指数バックオフ はLLMレベルではなくツールレベルで適用します。ツールの失敗のほうが多く、復旧可能だからです。
Note
エージェントハーネスをどうテストするか?
エージェントのテストは、決定論的なソフトウェアのテストとは根本的に異なります。主な戦略は次のとおりです。(1) 単体テスト: モック化した依存関係を使い、各構成要素(コンテキストマネージャ、ツール実行器、ループ検出器)を分離してテストします。(2) 統合テスト: スクリプト化された応答を返すモックLLMに対して、ハーネス全体をテストします。(3) 評価ハーネス: 正解が既知のタスクベンチマークでエージェントを実行し、成功率を測定します。(4) 敵対的テスト: 整形式でないツール出力を意図的に注入し、適切な失敗処理を検証します。(5) リグレッションテスト: 過去の本番トレースを再生し、変更後も出力にリグレッションがないことを確認します。
まとめ
エージェントハーネスは、言語モデルを能力と信頼性を備えたエージェントへ変換するエンジニアリング上の基盤です。本節の要点は次のとおりです。
-
コンテキストは有限で貴重な資源です。 予算を明示的に適用し、モデルの正確なトークナイザーでトークン数を数え、履歴を先回りして圧縮します。
-
プロンプトはコードです。 バージョン管理し、テストし、構成要素からモジュール式に組み立てます。
-
ツールはエージェントのアクチュエータです。 正確に定義し、実行をサンドボックス化し、出力を防御的に処理します。
-
オーケストレーションパターンに万能なものはありません。 探索的タスクにはReAct、構造化されたタスクにはPlan-and-Execute、分解可能な複雑なタスクにはマルチエージェントを使います。
-
状態管理は第一級の関心事です。 状態スキーマを最初に設計します。後付けは困難です。
-
エラーは避けられません。適切な復旧は機能です。 再試行ロジック、ループ検出、分かりやすい失敗メッセージを実装します。
-
可観測性は任意ではありません。 見えないものはデバッグできません。初日からすべてを計装します。
-
本番運用上の問題は複合します。 レイテンシ、コスト、レート制限、評価はすべて相互作用します。後付けではなく、体系的に対処します。
エージェント設計パターン
効果的なエージェントを構築するには、強力なモデルとツールの集合だけでは不十分です。アーキテクチャ、すなわちLLMをどのようにオーケストレーションし、タスクをどのように分解し、コンポーネント間で制御をどのように流すかによって、エージェントの信頼性、デバッグ可能性、費用対効果が決まります。本章では、Anthropic、OpenAI、Google、オープンソースコミュニティの本番導入から生まれた代表的な設計パターンを紹介します。
Tip
エージェントとワークフローの使い分け
すべてのタスクに自律エージェントが必要なわけではありません。重要な違いは次のとおりです。
ワークフロー: 制御フローがあらかじめ定義され、特定のステップでLLMを呼び出します。予測しやすく、テスト可能で、安価です。タスク構造が既知の場合に使います。
エージェント: LLMが次に何をするかを動的に決めます。柔軟で、新しい状況に対応できます。適応的な意思決定が必要なタスクに使います。
まずワークフローから始めましょう。 タスクが本当に動的なルーティングやオープンエンドな探索を必要とする場合にだけ、エージェントへ移行します。
ワークフローパターン
これらのパターンは、Anthropicによるエージェント構成要素の分類(Anthropic 2024a)を応用したものです。LLMをあらかじめ定義された制御フロー内で使い、システム(モデルではない)が実行順序を決定します。
プロンプトチェーン
最も単純なパターンです。複雑なタスクを固定されたLLM呼び出しの列に分解し、ある呼び出しの結果を次の呼び出しのコンテキストとして渡します。ステップ間の検証ゲートにより、エラーが下流へ伝播する前に早期発見できます。
使う場面: コンテンツ生成、データ変換、複数段階の分析など、自然に逐次的なタスク。
主な利点: 各ステップで異なるプロンプト、モデル、temperatureを使えます。中間結果を検査・デバッグできます。
ルーティング
分類器(LLMまたは従来型)が入力を調べ、専門ハンドラへディスパッチします。
使う場面: 最適なプロンプト、ツール、モデルが異なる明確なタスク種別。カスタマーサポートのトリアージ、マルチモーダル入力の処理など。
並列化
複数のLLM呼び出しを並行実行し、プログラム層が出力を組み合わせます。ここから2つのサブパターンが生まれます。
-
セクション分割(ファンアウト): 入力を互いに重ならないチャンクへ分割し、それぞれを独立して処理します。例えば、コードベースに対するセキュリティ、性能、スタイルのチェックを同時に実行します。
-
投票(冗長性): 同じプロンプトを異なるシードやtemperatureで\(N\)回発行し、多数決(X. Wang, Wei, Schuurmans, Q. V. Le, et al. 2023)、報酬モデルのスコアリング、またはLLM-as-judgeによって最良の結果を選びます。
Note
並列化の例:コードレビュー
並列呼び出し: セキュリティレビュー \(\\vert\) パフォーマンスレビュー \(\\vert\) スタイルレビュー
集約: すべての指摘を統合し、重複を除去して、重大度で順位付けします。
レイテンシ = \(\max\)(個々の呼び出し)であり、\(\sum\)(個々の呼び出し)ではありません。
オーケストレータ・ワーカー
ここではLLM自身が作業の分割方法を決めます。オーケストレータモデルがタスクを分析し、サブタスクの計画を作り、各サブタスクをワーカーLLM(異なるプロンプトやツールを使う場合もあります)へディスパッチし、最後に出力を一貫した結果へ統合します。並列化との主な違いは、分解ロジックがハードコードされておらず、モデルによって生成されることです。
使う場面: 設計時にサブタスクの数や性質を列挙できないオープンエンドな問題。例えば「このコードベースをリファクタリングして」という要求では、どのファイルを変更するか決める前に依存関係グラフを理解する必要があります。
評価器・オプティマイザ
2つのモデルによるフィードバックループです(Madaan et al. 2023)。生成器が候補出力を作り、別の評価器が明示的な基準に照らしてスコアを付けます。スコアがしきい値を下回ると、評価器の批評を生成器のコンテキストに追加し、品質基準を満たすか再試行予算を使い切るまでサイクルを繰り返します。
使う場面: 明確な品質基準があるタスク。テストに合格しなければならないコード、意味を保たなければならない翻訳、スタイルガイドに従う必要がある文章など。
自律エージェントパターン
これらのパターンでは、LLM自身が実行フローを制御します。
ReAct(推論+行動)
基礎となるエージェントパターンです(S. Yao, Zhao, et al. 2023)。LLMが思考(内部推論)、行動(ツール呼び出し)、観察(結果の処理)をループ内で交互に実行し、最終回答を生成するまで続けます。
Important
ReAct実装の要点
スクラッチパッド: 「Thought」ステップをログに記録しますが、ユーザーには表示しません。
ツール解析: ハーネスがモデル出力から構造化されたツール呼び出しを抽出します。
最大反復回数: 必ずループに上限を設けます(通常は10〜25回)。
終了: モデルが特殊なアクション(例:
final_answer)を出力するか、ツール呼び出しが検出されないと終了します。
計画エージェント
エージェントは実行前に明示的な計画を生成し、実行途中で計画を修正できます(L. Wang et al. 2023)。
| 戦略 | 再計画 | 特徴 |
|---|---|---|
| Plan-then-Execute | なし | 単純だが、予期しない結果に弱い |
| Adaptive | 失敗時 | ステップが失敗したときだけ再計画。コストは中程度 |
| Continuous | 各ステップ | 観察ごとに完全再評価。高価だが堅牢 |
| Hierarchical | サブ計画完了時 | 高レベル計画は固定。サブ計画を動的に生成 |
計画戦略の比較
Note
計画エージェント:調査レポートの生成
ユーザー要求: 「時系列予測向けのTransformerアーキテクチャを比較する2ページのレポートを書いてください。」
ステップ1 — 計画生成 (単一のLLM呼び出し):
plan = [ {"id": 1, "task": "Search for recent transformer-based " "time-series models (2023-2025)", "tool": "search_web", "deps": []}, {"id": 2, "task": "Read top 5 papers, extract key methods", "tool": "read_papers", "deps": [1]}, {"id": 3, "task": "Build comparison table (architecture, " "dataset, metrics)", "tool": "none", "deps": [2]}, {"id": 4, "task": "Write introduction + methodology section", "tool": "none", "deps": [2]}, {"id": 5, "task": "Write results + conclusion", "tool": "none", "deps": [3, 4]}, {"id": 6, "task": "Review and polish final report", "tool": "none", "deps": [5]}, ]ステップ2 — 適応的な再計画を伴う実行: エージェントは依存関係の順序でステップを実行します。ステップ1の後、検索結果が関連論文を3本しか返さなかったとします。エージェントは再計画し、隣接領域(PatchTST、iTransformerなど)へ検索を広げるサブステップを追加します。修正された計画は、拡張されたコーパスを使ってステップ2から続行します。
重要な洞察: 計画は生きた文書です。構造を与えながら、観察結果に適応します。ハーネスは依存関係をDAGとして追跡し、先行ステップが完了したステップだけを実行します。
リフレクションと自己批評
エージェントは一時停止して自分の軌跡を評価し、方針を修正します。
-
出力検証: 「これは正しいか?何か見落としていないか?」
-
軌跡レビュー: 直前の\(k\)ステップを見直し、間違いや非効率を特定します。
-
戦略修正: 全体的なアプローチを再考します(「正しい問題を解いているか?」)。
Tip
Reflexion:失敗から学ぶ
Reflexion パターン(Shinn et al. 2023)は、永続的な「リフレクションメモリ」を維持します。失敗するたびに、エージェントは自然言語のリフレクション(「エッジケースを確認しなかったため失敗した」)を書きます。次の試行ではこれらのリフレクションをプロンプトに含め、重みを更新せずにエピソード間の学習を可能にします。
ツール利用パターン
エージェントがツールを呼び出す方法は、信頼性、レイテンシ、コストに大きく影響します。代表的なパターンが5つあります(Schick et al. 2023)。
| パターン | 説明 | 例 |
|---|---|---|
| Single-turn | LLM応答あたり1回のツール呼び出し | 検索を使う単純なQ&A |
| Multi-tool | 1つの応答で複数のツールを並列呼び出し | 検索+計算+整形 |
| Sequential | ツール出力を次のツール呼び出しへ渡す | 検索 \(\to\) 読み取り \(\to\) 抽出 |
| Nested | ツール呼び出しが別のエージェントを起動 | コードエージェントがテストランナーを呼び出す |
| Fallback | 優先ツールが失敗したら代替を試す | API \(\to\) スクレイピング \(\to\) キャッシュ |
ツール呼び出しパターン
単一ターンのツール利用
最も単純なパターンです。モデルが1回のツール呼び出しを発行し、結果を受け取り、最終回答を生成します。事実検索、単位変換、単一APIへの問い合わせに十分です。ハーネスは正確に2回LLMを呼び出します(ツールを決める呼び出しと、結果を統合する呼び出しです)。
マルチツール(並列)
現代のAPI(OpenAI、Anthropic)では、モデルが1つの応答で複数のツール呼び出しを要求できます。ハーネスはそれらを並行実行し、すべての結果をまとめて返します。これにより、株価、天気、カレンダーを同時に取得するなど、複数のソースから独立した情報を必要とするタスクのレイテンシを大幅に削減できます。重要な制約は、ツールが独立していなければならないことです(あるツールの出力を別のツールの入力として必要としないこと)。
逐次(パイプライン)
各ツールの出力を次のツールの入力へ渡し、データパイプラインを形成します。モデルは前の結果に基づいて次のツールを決めます。調査ワークフローでよく使われます。例:search \(\to\) fetch_page \(\to\) extract_data \(\to\) analyze。ハーネスは増大するコンテキストを追跡し、予算内に収めるため中間結果を要約する必要が生じることがあります。
ネスト(エージェント・アズ・ツール)
ツール呼び出しが、独自のプロンプト、ツール、コンテキストを持つ完全に別のエージェントを起動します。親エージェントはサブエージェントをブラックボックス関数として扱います。これにより専門化が可能になります。例えば、調査エージェントがコード実行を、サンドボックスとテストランナーにアクセスできるコーディングエージェントへ委譲します。Swarmパターン(OpenAI 2024b)は、専門エージェント間のハンドオフによってこれを一般化します。
フォールバック(段階的な機能縮退)
ハーネスは優先順位に従ってツールを試します。優先ツールが失敗(タイムアウト、レート制限、APIエラー)すると、自動的に代替へフォールバックします。モデルがフォールバックロジックを知る必要はなく、ハーネスが透過的に処理します。例:主要検索API \(\to\) 代替検索 \(\to\) キャッシュ済み結果 \(\to\) 検索が利用できないことをモデルへ通知。
設計原則
以下の原則は、Anthropicの効果的なエージェント構築ガイド(Anthropic 2024a)から抽出したもので、すべてのパターンに適用できます。
-
シンプルに保つ。 動作する最も単純なアーキテクチャを使います。必要性が実証された場合だけ複雑さを加えます。問題を解決するプロンプトチェーンのほうが、解決できるかもしれないマルチエージェントシステムより常に好ましい選択です。
-
巧妙さより透明性。 すべてのステップを検査可能にします。隠れた状態や暗黙的な推論を避けます。エージェントが失敗したときは、その理由を理解する必要があります。透明性のないアーキテクチャではデバッグできません。
-
良いツールを提供する。 十分に文書化され、型が明確で、分かりやすいエラーメッセージを持つツールは能力を増幅します。説明が曖昧なツールは誤用されますが、正確なスキーマと使用方法を持つツールは正しく選択されます。
-
失敗に備える。 すべてのツール呼び出しは失敗する可能性があります。モデルがインフラストラクチャの失敗を推論しなくて済むよう、ハーネスレベルで再試行ロジック、フォールバック、段階的な機能縮退を構築します。
-
構造化出力を使う。 制約付き生成(JSONスキーマ、Function Calling)により解析失敗を防ぎます。正規表現による解析が必要な自由形式テキストを生成するエージェントは脆弱ですが、検証済みJSONを生成するエージェントは堅牢です。
-
多様な入力でテストする。 エージェントの挙動は単一ターンのチャットより変動しやすいものです。同じプロンプトでも、実行ごとに異なるツール呼び出し列を生成することがあります。エッジケース、曖昧な要求、整形式でない入力を使い、敵対的にテストします。
パターン選択ガイド
適切なパターンの選択は、3つの要因に依存します。(1) タスク構造がどの程度予測可能か、(2) レイテンシとコストの面で何回のLLM呼び出しまで許容できるか、(3) 品質に反復が必要かどうかです。以下の表を意思決定マトリクスとして使い、上(最も単純)から始め、より単純なパターンでは明らかに不十分な場合だけ下へ進みます。
| パターン | 複雑性 | LLM呼び出し | 適した用途 |
|---|---|---|---|
| プロンプトチェーン | 低 | \(N\)(固定) | 逐次タスク、コンテンツパイプライン |
| Routing | 低 | 1 + 1 | 複数種別の入力、トリアージ |
| 並列化 | 低 | \(N\)(並列) | 独立したサブタスク、投票 |
| Orchestrator-workers | 中 | 可変 | 未知の分解 |
| Evaluator-optimizer | 中 | 2〜10(ループ) | 品質が重要な出力 |
| ReAct | 中 | 3〜25(ループ) | 一般的なツール利用、探索 |
| Planning agent | 高 | 5〜50以上 | 長期的なマルチステップタスク |
| Reflection | 高 | 50%増のオーバーヘッド | 初回の試行が失敗しやすいタスク |
| Multi-agent | 高 | 多数 | 複雑な領域、専門化 |
各エージェント設計パターンの使いどころ
パターンは組み合わせ可能です。計画エージェントが個々のステップにプロンプトチェーンを使い、レビュー段階で評価器・オプティマイザを使い、サブタスクを専門家へディスパッチするためにルーティングを使うこともできます。重要なのは、いつ層の追加を止めるかを見極めることです。
エージェント型環境とベンチマーク
動機:なぜエージェントに環境が必要なのか
会話型言語モデルの評価は、原理的には単純です。プロンプトを提示し、応答を集め、参照回答または人間の判断でスコアを付けます。エージェントの評価は根本的に異なります。エージェントは世界で行動し、結果を観察し、複数ステップの系列にわたって振る舞いを適応させなければなりません。単一の応答ではこれを捉えられず、構造化された環境だけが可能にします。
範囲。 ここでの環境は強化学習の意味、すなわちエージェントが訓練や評価のために相互作用する世界を指します。エージェントをサービング時にホストする本番インフラストラクチャ(ハーネス、オーケストレータ)ではありません。実行サンドボックスはこのような環境を可能にするため、ここで扱いますが、エージェントハーネス自体は第18章で説明しています。
Important
チャットボットとエージェントの評価ギャップ
チャットボット評価 は、単一生成の品質、すなわち流暢さ、事実性、有用性を測定します。 エージェント評価 は方策の品質を測定します。多様で長期的なタスクにわたり、エージェントは確実に目標を達成できるでしょうか。これは単なる量的な差ではなく、異なるインフラストラクチャを必要とする差です。
専用のエージェント型環境が必要になる理由は3つあります。
安全な探索
現実世界のシステム、本番データベース、稼働中のウェブサイト、金融APIは、訓練中のエージェントによる探索的な振る舞いを受け止められません。サンドボックス化された環境は忠実な複製を提供し、エージェントが不可逆な被害を起こさずに失敗、復旧、学習できます。セキュリティ分離(Dockerコンテナやネットワーク制限付きVMなど)は任意ではなく、第一級の設計要件です。
再現可能な評価
ベンチマークでは、すべてのエージェントが同じ条件で同じタスクに直面する必要があります。ある研究室で報告された結果を別の研究室で再現できるよう、環境は要求に応じて決定論的に動作し、バージョン管理され、配布可能でなければなりません。この性質がなかったため、これまでエージェントのベンチマークは比較が困難でした。
カリキュラム学習
難しいタスクでエージェントをゼロから訓練するのは、サンプル効率が悪いものです。エージェントの改善に応じてタスクの複雑さを段階的に増やす難易度カリキュラムを提供する環境は、目標性能に達するまでに必要な環境との相互作用数を大幅に減らします。これは人間の学習方法にも似ています。全体の習得に先立って、サブスキルを習得します。
Tip
LLMのRL「ジム」としての環境
OpenAI Gym(Brockman et al. 2016)がRLアルゴリズムとシミュレーション制御タスクのインターフェースを標準化したように、エージェント型環境はLLMベースのエージェントと、エージェントが解くべき多様なタスクとのインターフェースを標準化します。対応関係は明確です。
reset()は新しいエピソードを初期化し、step(action)は世界を進めて観察と報酬を返し、render()は現在の状態を人間が読める形で表示します。
環境設計の原則
適切に設計されたエージェント型環境は、直交する4つの設計軸、すなわち観測空間、アクション空間、報酬信号、エピソード構造を公開します。それぞれを正しく設計することが必要であり、4つすべてを同時に正しく設計することが環境エンジニアリングの技術です。
観測空間の設計
観測とは、エージェントが各ステップで見るものです。LLMベースのエージェントでは、観測はほぼ常にテキストとして表示されますが、元の素材は大きく異なります。
-
純粋なテキスト: ターミナル出力、ファイル内容、API応答、エラーメッセージ。あらゆるLLMと最も互換性がありますが、空間的・視覚的構造を失います。
-
構造化(JSON/XML): 機械可読な状態表現。正確なグラウンディングを可能にしますが、エージェントは文章を読むのではなく構造を解析する必要があります。
-
マルチモーダル: スクリーンショット、アクセシビリティツリー、レンダリング済みHTML。GUIやウェブタスクに必要であり、視覚能力を持つモデルまたは別の知覚モジュールを要します。
-
ハイブリッド: スクリーンショットとアクセシビリティツリーの組み合わせ(OSWorldとVisualWebArenaで使用)は、視覚的コンテキストと構造化された要素識別子の両方を与え、2つのモダリティの長所を組み合わせます。
Warning
観測のリーク
よくある設計ミスは、エージェントがアクセスできてはいけない情報を観測に含めることです。例えば、正解、報酬値、将来のタスクステップなどです。観測のリークはベンチマークスコアを水増しし、その情報が存在しない現実環境に配置されたときに、壊滅的に失敗するエージェントを生みます。
アクション空間の設計
アクション空間は、エージェントができることを定義します。LLMエージェントでは、アクションは通常、環境が解析して実行するテキスト文字列です。一般的なアクション種別には次があります。
-
ツール呼び出し: 外部関数(検索、計算機、カレンダー)の構造化された呼び出し。JSONやXMLのFunction Calling構文で表すことが多いものです。
-
コード実行: エージェントがコードを書き、サンドボックスで実行します。stdout/stderrが次の観測として返ります。最も表現力の高いアクション種別です。
-
API相互作用: ウェブサービスへのHTTPリクエスト、データベースクエリ、シェルコマンド。
-
GUIアクション:
click(x,y)、type("text")、scroll(direction)、key("Enter")。コンピュータ利用環境で使います。 -
自然言語: 別のエージェント、人間、またはサブタスク計画器に向けた自由形式のテキスト。
報酬信号の設計
報酬設計は環境エンジニアリングで最も難しい部分です。報酬は次を満たさなければなりません。
-
整合している: 高い報酬が、表面的な代理指標ではなく、真のタスク完了に対応すること。
-
学習可能である: エージェントが進捗を出せる程度に信号が密であること。長期的タスクの純粋な疎報酬は、追加のシェーピングなしでは学習できないことが多くあります。
-
改ざん耐性がある: エージェントがタスクを実際に完了せず、高い報酬だけを得られないこと(報酬ハッキングの防止)。
| 報酬種別 | 長所 | 短所 |
|---|---|---|
| 疎(最後に0/1) | 整合し、ハックしにくい | 学習が難しい |
| 密(ステップ単位) | 学習しやすい | シェーピングのアーティファクトが生じやすい |
| 内在的(好奇心) | 探索を促す | タスクから乖離する可能性がある |
| LLM-as-judge | 柔軟でニュアンスを扱える | 高価で一貫性がない |
| 実行ベース | 正解そのもの | 検証可能なタスクのみ |
エージェント型環境の報酬信号の種別とトレードオフ。
エピソード構造
エピソードは複数の方法で構成できます。
-
固定長: エージェントが正確に\(T\)ステップ実行します。実装は単純ですが、すでに解決したタスクにも計算資源を使います。
-
早期終了: エージェントが完了を通知するか、終端状態に到達するとエピソードが終了します。より効率的ですが、信頼できる終了検出器が必要です。
-
オープンエンド: 固定のホライズンを持たず、資源予算(トークン、API呼び出し、実時間)を使い切るまでエージェントが動作します。実際の配置に最も近い一方、評価が最も難しくなります。
適応的なエピソード長と早期終了
近年の研究は、訓練開始前にエピソード長を固定しなければならないという前提に疑問を呈しています。
-
ホライズンのカリキュラム。 AELA(Yoo and Shin 2025)は短いエピソードから始め、方策エントロピーの収束で測ったエージェントの能力向上に応じてホライズンを徐々に延ばします。初期の短いエピソードにより、訓練サンプルごとにより多様な初期状態を経験できます。
-
RLペナルティとしての切り捨て。 DLER(Z. Liu et al. 2025)は、最も単純な長さ制御であるハードな切り捨てが、バッチ単位の報酬正規化と動的サンプリングを組み合わせれば推論モデルでうまく機能することを示します。これにより、途中で打ち切ったロールアウトから報酬信号を失うことを避けます。
-
学習された停止。 固定予算ではなく、モデル自身がいつ推論を止めるかを学習できます。Q. Liu et al. 2025は3つの戦略を提案しています。連続する推論ステップが同じ回答に収束したら止める、思考終了トークンの確率を高める、隠れ状態の活性値で軽量分類器を訓練して最適な停止点を予測する、の3つです。
-
部分ロールアウトの再利用。 APRIL(Mei et al. 2025)はロールアウト要求を多めに発行し、目標バッチ数に達した時点で終了します。不完全な応答は、後続ステップでウォームスタート用のプレフィックスとして再利用され、少数の遅いサンプルがバッチ全体を止めるロングテールの停滞をなくします(スループットが20〜35%向上)。TLT(Hu et al. 2025)は、遅延サンプルを投機的デコードする適応的なドラフトモデルをオンザフライで訓練し、同じボトルネックに対処します(エンドツーエンドで1.7\(\times\)高速化、損失なし)。
難易度カリキュラムと適応的環境
静的ベンチマークはエージェント能力の固定されたスナップショットを測定します。適応的環境はさらに進み、エージェントの性能をオンラインで監視し、エージェントを「発達の最近接領域」に保つようタスク難易度を調整します。学習できる程度に難しく、ときどき成功できる程度に易しい領域です。技法には次があります。
-
手続き的生成: パラメータ化された分布からタスクをサンプリングし、最近の成功率に基づいて難易度パラメータを調整します。Prioritized Level Replay(Jiang et al. 2021)は、生成された各レベルを推定学習可能性(GAEの大きさなど)でスコアリングし、価値の高いレベルをより頻繁に再生します。
-
セルフプレイ/敵対的環境設計: PAIRED(Dennis et al. 2020)は、主人公エージェントと敵対エージェントの間の後悔を最大化する環境を提案するよう敵対者を訓練します。手作業で難易度スケジュールを設計せず、複雑さが増す自然なカリキュラムを生成します。
-
後知恵による再ラベル付け: 失敗した軌跡を、エージェントが実際に達成した目標で再ラベル付けし、失敗からも学習信号を与えます(Hindsight Experience Replay、HER)(Andrychowicz et al. 2017)。
-
LLM向け難易度ターゲット型データ選択: RLVR訓練では、すべての問題が同じ信号を与えるわけではありません。近年の研究は、中程度の難易度、すなわちモデルが約30〜70%の割合で解く問題を優先します。これらは最大の勾配情報を与えます(Wang et al. 2025)。ADCL(X. Liu et al. 2025)はモデルの改善に応じて定期的に難易度を再推定し、古くなったカリキュラムを避けます。
エージェント型環境の種類
コード実行サンドボックス
LLMにとって最も基本的なエージェント型環境は、コード実行サンドボックスです。エージェントがコードを書き、サンドボックスが実行し、出力を返します。この単純なループが、現実世界のエージェント配置の驚くほど大きな割合を支えています。
Dockerベースの分離 が最も一般的なアプローチです。各エピソードで既知のイメージから新しいコンテナを起動し、その中でエージェントのコードを実行し、エピソード終了時にコンテナを破棄します。ネットワークアクセス、ファイルシステムへの書き込み、プロセス生成はすべてコンテナレベルで制御できます。1
E2B (Environments to Benchmarks)2はマネージドクラウドサンドボックスAPIを提供します。エージェントがHTTP経由でコードを送り、E2Bが200ms未満で起動する分離済みFirecracker microVMで実行し、stdout/stderrを返します。E2Bがコンテナのライフサイクル管理というインフラストラクチャの複雑さを処理するため、エージェント訓練ループへ容易に統合できます。
Modal 3は、より強力なGPUサポートを備えた同様のマネージド実行モデルを提供し、タスクの一部としてMLワークロードを実行する必要があるエージェントに適しています。
Warning
サンドボックス脱出とセキュリティ
コード実行サンドボックスは主要な攻撃対象です。十分な能力を持つエージェント(またはプロンプトインジェクションされたペイロード)は、カーネルエクスプロイト、ネットワーク経由の持ち出し、リソース枯渇によってサンドボックスから脱出しようとする可能性があります。多層防御が不可欠です。コンテナ分離をseccompプロファイル、読み取り専用ルートファイルシステム、ネットワーク外向き通信のフィルタリング、CPU/メモリcgroupと組み合わせます。エージェント生成コードをホストレベルの権限で実行してはいけません。
ウェブ環境
ウェブ環境はエージェントにブラウザを与え、実在またはシミュレーションされたウェブサイト上でタスクを完了させます。
WebArena (S. Zhou et al. 2024)は、4つの機能的なウェブアプリケーション(ECストア、ソーシャルフォーラム、GitLab、CMS)と地図サービスからなるセルフホスト型テストベッドで、合計812の長期的タスクを収録します。エージェントはブラウザ自動化APIで相互作用し、タスクには複数ステップのナビゲーション、フォーム入力、情報検索が必要です。人間の性能は約78%で、最先端のLLMエージェントは約35〜45%を達成します。
VisualWebArena (Koh et al. 2024)は、ウェブページ上の画像を解釈する必要がある視覚グラウンディングタスクでWebArenaを拡張します。観測はスクリーンショットとアクセシビリティツリーの組み合わせであり、エージェントは両方のモダリティに基づいてアクションをグラウンディングしなければなりません。
Mind2Web (Deng et al. 2023)は、人間の実演によって収集された、137の実在ウェブサイトにまたがる2,000タスクの大規模データセットです。WebArenaと異なり、Mind2Webは未知のウェブサイトへの汎化に焦点を当てるため、より難しい分布外テストになります。
Note
WebArenaタスクの例
タスク: 「ECサイトで50ドル未満の最も安い赤いドレスを探し、カートに追加してください。」
エージェントの軌跡:
衣料品カテゴリへ移動します。
色フィルタで赤を選択します。
価格の昇順で並べ替えます。
50ドル未満の最初の商品を特定します。
「カートに追加」をクリックします。
カートの内容を確認します。
環境は最終的なカート状態を正解商品と照合します。正しければ報酬は1、そうでなければ0です。
コンピュータ利用環境
コンピュータ利用環境では、エージェントが完全なデスクトップOSを制御し、スクリーンショットやアクセシビリティAPIを通じて観測します。
OSWorld (Xie et al. 2024)は、3つのOS(Ubuntu、Windows、macOS)にまたがるデスクトップ自動化を、業務アプリ(LibreOffice、VS Code、Chrome、GIMPなど)を使う369タスクで評価します。エージェントはスクリーンショットを観測し、pyautogui風のマウス・キーボードコマンドで操作します。人間とエージェントの差は顕著で、アノテーターは約72%のタスクに成功する一方、最強のLLMエージェントは\(\sim\)18%にとどまり、ピクセルレベルのGUI制御の難しさを示しています。
WindowsAgentArena (Bonatti et al. 2024)はWindows 11に特化し、19アプリケーションにまたがる154タスクを収録します。Excelの数式、PowerPoint編集、Outlookのメール管理など、エンタープライズワークフローを重視します。
Important
スクリーンショットのボトルネック
コンピュータ利用エージェントは根本的な課題に直面します。スクリーンショットは高次元(通常は\(1920 \times 1080 \times 3\)ピクセル)ですが、情報の大部分は現在のアクションに関係ありません。効率的なエージェントは画面の小さな領域に注意を向け、ピクセル座標ではなく名前でインタラクティブ要素を特定するためアクセシビリティツリーを使い、以前に訪れたUI状態のコンパクトなワーキングメモリを維持します。
ソフトウェアエンジニアリング環境
ソフトウェアエンジニアリング(SWE)環境では、バグ修正、機能実装、テスト作成など、現実世界のプログラミングタスクをエージェントに解かせます。
SWE-bench (Jimenez et al. 2024)は、広く使われている12のPythonプロジェクト(Django、Flask、scikit-learnなど)から2,294件の実際のプルリクエストを収録します。各インスタンスは、正しいパッチを適用した後だけ通過する保留テストスイートとIssue説明を組み合わせます。エージェントはリポジトリ構造を理解し、関係するコードを見つけ、修正を実装し、テストスイートで検証しなければなりません。 SWE-bench Verified サブセット(500件)は正しさを人間が検証しており、標準的な評価対象です。
SWE-agent (J. Yang et al. 2024)は、ベンチマーク環境であると同時にエージェントフレームワークでもあります。Agent-Computer Interface(ACI)を導入します。これはLLMエージェント向けに最適化されたシェルコマンド(search_file、open、editなど)の集合で、生のbashと比べてアクション空間の複雑さを減らします。
Note
SWE-benchワークフロー
入力: GitHub Issueの説明と、Issueが登録されたコミット時点の完全なリポジトリ。
エージェントのアクション:
find_file、view、edit、python -m pytest tests/。報酬: エージェントのパッチ後に対象テストがすべて通れば1、それ以外は0です。部分点はありません。
科学研究環境
科学研究環境は、論文を読み、仮説を立て、実験を設計し、結果を解釈するという自律的な知識生成へエージェントを促します。
PaperQA2 (Lála et al. 2023)は、PDFコーパスを検索し、関連する箇所を抽出し、引用付きの回答を統合して科学的質問に答える検索拡張エージェントです。文献に基づく推論のツールとベンチマークの両方として機能します。
AI Scientist (Lu et al. 2024)はエンドツーエンドの研究自動化システムです。研究の方向性を与えると、エージェントが仮説を生成し、実験を書いて実行し、結果を解釈し、論文の草稿を作成します。環境にはPython実行サンドボックス、文献検索API、LaTeXコンパイラが含まれます。
MLAgentBench (Huang et al. 2024)は、計算予算内で与えられたデータセットのモデル精度を改善するなど、機械学習エンジニアリングタスクでエージェントを評価します。エージェントはデータを読み、訓練スクリプトを書き、実験を実行し、反復できます。
ゲームとシミュレーション環境
ゲームは、明確に定義された報酬信号を持ち、現実世界への影響がない、豊かで長期的な環境を提供します。
NetHack (Küttler et al. 2020)は、非常に大きな状態空間を持つ手続き生成ローグライクであり、長期計画、インベントリ管理、予期しないイベントへの適応を必要とします。NetHack Learning Environment(NLE)はGym互換インターフェースを提供します。
Voyager / Minecraft (G. Wang et al. 2023)はMinecraftゲームエンジンをオープンエンドな環境として使います。Voyagerは、徐々に難しくなるタスク(木を集める \(\to\) 道具を作る \(\to\) シェルターを建てる \(\to\) ネザーを探索する)のカリキュラムと、エピソードをまたいで再利用可能なコード断片を蓄積するスキルライブラリを導入します。
GAIA (Mialon et al. 2024)は、ウェブ検索、コード実行、ファイル解析などの連鎖したツール利用を必要とする466問を出題し、推論ステップ数に基づいて3つの難易度に分類します。ベンチマークは、人間の能力(\(\sim\)正解率92%)と現在のLLMエージェント(GPT-4+プラグインは公開時に\(\sim\)15%、後続システムは\(\sim\)30%に到達)の差をはっきり示します。
マルチエージェント環境
マルチエージェント環境では、2つ以上のLLMエージェントが互いに、または共有された世界と相互作用します。
-
交渉: 私的な効用関数を持つエージェントが、対話を通じて合意に達しなければなりません。代表的な環境にはDealOrNoDeal(Lewis et al. 2017)とCaSiNo(Chawla et al. 2021)があります。
-
討論: 2つのエージェントが対立する立場を論じ、審判エージェント(または人間)が議論の品質を評価します。敵対的な圧力によって真実に沿った推論を引き出すために使います。
-
協調的タスク完了: 相補的な能力(プランナー、実行者、批評者)を持つエージェントが、単独では解けないタスクを完了するために協調します。フレームワークにはAutoGen(Wu et al. 2023)、CrewAI(Moura 2023)、MetaGPT(S. Hong et al. 2024)があります。
-
競争ゲーム: エージェントがゼロサムゲーム(チェス、囲碁、ポーカー)をプレイし、相手もLLMエージェント自身です。これらの環境でのセルフプレイは、狭い領域で人間を超える性能を生みました。
OpenEnv:標準化されたエージェント型環境インターフェース
エージェント型環境の増加により、環境ごとに異なるAPI、異なる観測形式、異なる足場が必要になるという分断問題が生じています。 OpenEnv (Face 2025)は、これに直接対処するHugging Faceの新しいオープンソースフレームワークです。エージェント型実行環境向けに、Gymnasiumスタイル(Towers et al. 2024)のインターフェース(step()、reset()、state())を提供し、分離されたDockerベースの配置がWebSocketで通信します。OpenEnvは、さまざまな環境にまたがるLLMエージェント向けの統一形式プラットフォームを提供するAgentGym(Xi et al. 2024)や、ウェブエージェントベンチマークの観測空間とアクション空間を標準化するBrowserGym(Le Sellier De Chezelles et al. 2024)など、より広い標準化の取り組みを補完します。以下の設計原則は、これらのプロジェクトから収束しつつあるベストプラクティスをまとめたものです。
標準化されたエージェント・環境インターフェース
OpenEnvはエージェント型実行環境向けの型付きインターフェースを定義します。設計はGymnasiumの単純さを踏襲しますが、HTTP/WebSocket経由でツールと相互作用するLLMエージェントを対象とします。
-
env.reset()\(\to\)StepResult: 新しいエピソードを開始し、初期観測を返します。 -
env.step(action)\(\to\)StepResult(observation, reward, done): 1つのアクションを実行し、結果の観測、スカラー報酬、終了フラグを返します。 -
env.state()\(\to\):現在の環境状態(エピソードID、ステップ数、環境固有フィールド)。 -
env.close(): リソースを解放します(コンテナ停止、接続切断)。
アクションと観測は、環境ごとに固有の、強く型付けされたPythonデータクラスです。例えば、コーディング環境はCodeAction(code=...)を定義し、stdout、stderr、exit_codeを含む観測を返します。ゲーム環境は独自のアクション/観測型を定義します。環境ごとの型付けにより、3つの中核メソッド(reset、step、state)を共通に保ちながら、構造化され予測可能なインターフェースをエージェントに与えます。
アーキテクチャ
各環境はEnvironmentを継承するPythonクラス(reset()とstep()を実装)です。FastAPI/WebSocketエンドポイントを公開するHTTPEnvServerを介してDockerコンテナ内で提供されます。クライアントはEnvClientの環境固有サブクラスを使い、シリアライズと接続ライフサイクルを処理します。コンテナはfrom_docker_image()でローカル起動するか、ベースURLでリモート接続できます。
from coding_env import CodeAction, CodingEnv
# Option 1: Launch a local Docker container
client = CodingEnv.from_docker_image("coding-env:latest")
# Option 2: Connect to a remote deployment
# client = CodingEnv(base_url="http://localhost:8000")
# Interact with the environment
result = client.reset()
print(result.observation.stdout)
print(result.observation.stderr)
print(result.observation.exit_code)
result = client.step(CodeAction(code="print(2 + 2)"))
print(result.observation.stdout) # "4\n"
print(result.observation.exit_code) # 0
print(result.reward, result.done)
# Check state
state = client.state()
print(state.episode_id, state.step_count)
client.close()
サーバーとしての環境
新しい環境を作成するには、Environment基底クラスを実装するだけで済みます。
from openenv.core.env_server import Environment, create_app
from dataclasses import dataclass
@dataclass
class MyAction:
text: str
@dataclass
class MyObservation:
response: str
reward: float = 0.0
done: bool = False
class MyEnvironment(Environment):
def reset(self) -> MyObservation:
return MyObservation(response="Ready")
def step(self, action: MyAction) -> MyObservation:
return MyObservation(response=f"Echo: {action.text}",
reward=1.0, done=False)
app = create_app(MyEnvironment(), MyAction, MyObservation)
# Run: uvicorn module:app --host 0.0.0.0 --port 8000
ハーネス統合(実験的)
RFC 0054は、RL訓練フレームワークがMCPスタイルのツール呼び出しを通じて環境と相互作用する、ハーネス向けの層を導入します。build_harness_rollout_func()ヘルパーはTRL互換のロールアウト関数を生成し、OpenEnvをTorchForge(M. P. Team 2025)のような既存の訓練パイプラインへ直接橋渡しします。
ガバナンス
OpenEnvは、Meta-PyTorch、NVIDIA、Unsloth、Modal、Prime Intellect、Reflection、Hugging Faceなどを含む技術委員会によってオープンに運営されています。単一ベンダーの思惑ではなく、業界全体の意見を取り入れて標準が発展することを保証します。
環境レジストリと発見
OpenEnv環境はHugging Face SpacesまたはローカルDockerイメージとして配置でき、手動インストールなしで発見・利用できます。配置先にかかわらず、同じクライアントインターフェースが動作します。
from echo_env import EchoAction, EchoEnv
# Connect to a remote HF Space deployment
client = EchoEnv(base_url="https://openenv-echo-env.hf.space")
result = client.reset()
print(result.observation.echoed_message) # "Echo environment ready!"
result = client.step(EchoAction(message="Hello!"))
print(result.observation.echoed_message) # "Hello!"
print(result.reward)
client.close()
OpenEnvエコシステムはすでに70以上の環境(OpenSpielゲーム、Atari、BrowserGym、コーディングサンドボックス、金融RL、交通シミュレーションなど)にまたがっています。RFC 0025は、エージェントが実行時に未知の環境が受け付けるアクションを問い合わせられる、正式なツール発見プロトコルを提案します。
構成的環境
現実のエージェント配置で単一のツールしか使わないことはまれです。OpenEnvは、型付きアクションを通じて複数の能力を公開する豊かな環境をサポートします。例えば、コーディング環境は1つのサンドボックスセッション内でコード実行、ファイルI/O、シェルコマンドをサポートします。
from coding_env import CodeAction, CodingEnv
client = CodingEnv.from_docker_image("coding-env:latest")
result = client.reset()
# Execute code
result = client.step(CodeAction(code="x = 42\nprint(x)"))
print(result.observation.stdout) # "42"
print(result.observation.exit_code) # 0
# State persists across steps within an episode
result = client.step(CodeAction(code="print(x + 1)"))
print(result.observation.stdout) # "43"
state = client.state()
print(state.step_count) # 2
client.close()
多様なツールアクセス(コード+ウェブ+ファイル)を必要とするエージェント向けに、OpenEnvのRFC 0036はMCP統合を提案し、MCP互換の任意のツールサーバーをOpenEnv環境としてラップできるようにします。さらに、openenv CLIは1つのコマンドで新しい環境をHugging Face Spacesへ足場作成、ビルド、配置できます。
環境のバージョン管理と再現性
ベンチマークの完全性を保つには、評価時点で環境の挙動を凍結する必要があります。ベストプラクティスは次のとおりです。
-
セマンティックバージョニング:
WebArena-v1.2.0は、マイナーバージョンの範囲内で後方互換性を保証します。 -
Dockerイメージの固定: 環境のランタイムを、コンテンツアドレス付きハッシュを持つDockerイメージとしてパッケージ化します。
-
シードベースの決定性: 確率的要素(手続き生成やネットワーク応答)すべてにシードを設定してログに記録し、任意の軌跡を正確に再生できるようにします。
-
リーダーボードのスナップショット: 公開リーダーボードにスコアとともに環境バージョンを記録し、ベンチマークが気づかないうちに変化することを防ぎます。
カスタム環境の構築
LLMエージェント向けGymnasiumスタイルAPI
Gymnasium API(Towers et al. 2024)7(OpenAI Gymの後継)は、RL環境の事実上の標準です。LLMエージェント向けに適応するには、2つの変更が必要です。(1) 観測とアクションを数値配列ではなく文字列(または文字列を含む辞書)にすること、(2) step メソッドで非同期のツール実行を処理することです。
報酬関数のエンジニアリング
LLMエージェント環境の報酬関数は、通常、実行ベースです。環境が各エピソードの後に検証器を実行し、タスクが解決されていれば1、そうでなければ0を返します。明確な検証器がないタスクでは、次の選択肢があります。
-
LLMによる判定: 別のLLMがタスク記述に照らしてエージェントの最終状態を採点します。
-
ルーブリックベースの採点: 構造化されたルーブリックでタスクを下位基準に分解し、それぞれを独立に採点します。
-
人間によるアノテーション: 人間の評価者が軌跡のランダムサンプルを採点し、そのスコアを自動代理指標の較正に使います。
状態管理とチェックポイント
長期タスクでは、実時間で何時間もかかることがあります。環境は次をサポートすべきです。
-
状態のシリアライズ: 環境の完全な状態(ファイルシステム、ブラウザのCookie、データベースの内容)をディスクにシリアライズして復元できること。
-
エピソード途中のチェックポイント: エージェントが任意のステップでチェックポイントを保存し、そこから再開できること。これにより、木探索型の探索が可能になります。
-
軌跡ログ: すべての観測、アクション、報酬を構造化ファイルに記録し、オフライン分析や報酬モデルの訓練に利用できること。
訓練データ収集のための並列化
RLでLLMエージェントを訓練するには、何百万回もの環境インタラクションが必要です。並列化の戦略には次があります。
-
プロセスレベルの並列化: 独立した環境プロセスを \(N\) 個起動し、軌跡を並列に収集します。
-
非同期ロールアウトワーカー: 非同期イベントループ(例:
asyncio)を使い、LLM推論のレイテンシと環境実行を重ね合わせます。 -
ベクトル化環境: 複数の環境を1回の
step呼び出しにまとめ、Pythonのオーバーヘッドを償却します。 -
クラウドネイティブなスケーリング: ジョブスケジューラ(Ray、SLURM)で環境ワーカーをクラスタ全体に分散し、中央のリプレイバッファで軌跡を集約します。
エージェント・環境インターフェースのパターン
図6.2は、実際に使われている4つの主要なインターフェースパターンを示しています。
テキストベースの観測/アクション
エージェントは文字列の観測を受け取り、文字列のアクションを生成します。環境はアクションをパースし(例: <tool>...</tool> ブロックからツール呼び出しを抽出し)、結果を文字列として返します。これは最も互換性の高いパターンであり、特別なアーキテクチャなしに任意のLLMが参加できます。
構造化JSONの観測/アクション
観測とアクションは、定義済みスキーマを持つJSONオブジェクトです。これにより、厳密な検証(不正な形式のアクションを実行前に拒否)、構造化ログ、軌跡のプログラムによる分析が容易になります。一方で、エージェントは有効なJSONを確実に生成しなければならず、そのためにはファインチューニングか制約付きデコーディングが必要です。
マルチモーダル(スクリーンショット+アクセシビリティツリー)
コンピュータ操作環境やウェブ環境で使われます。観測はタプル(screenshot: PIL.Image, a11y_tree: dict)です。スクリーンショットは視覚的な文脈を提供し、アクセシビリティツリーは、ピクセルレベルの座標を指定せずにアクションで使える要素識別子を提供します。このハイブリッド方式は、スクリーンショットだけに基づく制御より堅牢です。
ストリーミング対ターンベースのインタラクション
現在の環境の多くはターンベースのモデルを使います。エージェントが完全なアクションを生成し、環境がそれを実行して、次の観測を返します。ストリーミング環境では、エージェントは到着した部分的な観測(例: 長時間実行されるコマンドの出力)を受け取り、ストリームの途中で実行を中断したり方向転換したりできます。これは人間のコンピュータとの関わり方に近い一方、より複雑なエージェントアーキテクチャが必要です。
評価ハーネスの設計
評価ハーネスとは、ベンチマークスイート全体でエージェントを実行し、結果を収集して要約統計を生成するインフラです。優れたハーネス設計は、優れた環境設計と同じくらい重要です。
決定論的環境対確率的環境
-
決定論的環境 は、同じアクション列に対して同じ観測列を生成します。デバッグと再現が容易ですが、現実世界の変動性を反映しない場合があります。
-
確率的環境 は、手続き生成、ネットワークレイテンシ、ユーザーシミュレーションなどのランダム性を導入します。平均性能と信頼区間を推定するには、タスクごとに複数回の実行が必要です。
Note
何回実行すれば十分か?
\(N\) 個のタスクと二値報酬を持つベンチマークでは、平均成功率の標準誤差は \(\sqrt{p(1-p)/N}\) です。\(N=500\) 個のタスクで \(p=0.4\) の場合、95%信頼区間はおよそ \(\pm 4.3%\) です。確率的環境では、\(\sqrt{k}\)(\(k\) はタスクあたりの独立実行回数)を掛けます。確率的ベンチマークでは、タスクごとに3〜5回実行するのが一般的です。
ホールドアウトされたテスト環境
ベンチマークの完全性を保つには、タスク単位だけでなく、環境単位で厳密に訓練/テストを分割する必要があります。WebArenaのタスクで訓練したエージェントは、訓練に使っていないホールドアウトされたタスク集合で評価すべきです。理想的には、ホールドアウト集合には訓練集合とは異なるウェブサイト、タスク種別、難易度が含まれます。
環境間汎化
エージェントにとっての究極のテストは、ある環境で学習したスキルを別の環境へ移せるかどうかです。環境間評価プロトコルでは、次を測定します。
-
ゼロショット転移: 環境Aで訓練し、ファインチューニングなしで環境Bをテストします。
-
少数ショット適応: 評価前に環境Bから \(k\) 個のデモンストレーションを与えます。
-
継続学習: 環境A、B、Cで順に訓練し、Cでの訓練後に3つすべてで性能を測定します。
人間ベースラインの収集
すべてのベンチマークには、基準点として人間の性能を含めるべきです。人間ベースラインには3つの役割があります。
-
タスクの難易度に対する上限を定めます。
-
タスクがそもそも解けるかを明らかにします(ベンチマークのタスクには、曖昧または不可能だと判明するものもあります)。
-
エージェントのスコアを解釈するための較正点を提供します(「エージェントは人間性能の40%を達成した」など)。
人間ベースラインは、分野の専門知識を持つ作業者(例: SWE-benchならクラウドワーカーではなくソフトウェアエンジニア)から収集し、効率を比較できるようタスク所要時間の測定も含めるべきです。
コード例:最小限のカスタムLLMエージェント環境
Note
LLMエージェント訓練用の最小限のカスタム環境
次のPythonクラスは、エージェントがPythonファイルを変更して失敗しているテストを通す、ファイル編集環境を実装しています。LLMエージェント向けに適応したGymnasium APIに従います。
"""
minimal_env.py -- A minimal file-editing environment for LLM agents.
The agent receives a Python file with a bug and a failing test.
It must edit the file until the test passes.
Reward: 1.0 if all tests pass, 0.0 otherwise.
"""
from __future__ import annotations
import subprocess, shutil, tempfile, textwrap
from pathlib import Path
from dataclasses import dataclass, field
from typing import Any
# ---------------------------------------------------------------------------
# Data structures
# ---------------------------------------------------------------------------
@dataclass
class StepResult:
observation: str # Text fed to the LLM
reward: float # 0.0 or 1.0
terminated: bool # Episode over (task solved or max steps)
truncated: bool # Episode cut short (budget exceeded)
info: dict[str, Any] = field(default_factory=dict)
# ---------------------------------------------------------------------------
# Environment
# ---------------------------------------------------------------------------
class FileEditEnv:
"""
A Gymnasium-style environment for LLM-based code repair.
Observation space : str (file contents + test output)
Action space : str (one of: view, edit, run_tests, submit)
Reward : 1.0 on passing all tests, 0.0 otherwise
"""
MAX_STEPS = 20 # Hard episode limit
TIMEOUT = 30 # Seconds per test run
def __init__(self, buggy_code: str, test_code: str,
task_description: str):
self.buggy_code = buggy_code
self.test_code = test_code
self.task_description = task_description
self._workdir: Path | None = None
self._step_count = 0
# ------------------------------------------------------------------
# Core API
# ------------------------------------------------------------------
def reset(self, seed: int | None = None) -> tuple[str, dict]:
"""Initialise a fresh episode; return (observation, info)."""
if self._workdir and self._workdir.exists():
shutil.rmtree(self._workdir)
self._workdir = Path(tempfile.mkdtemp(prefix="fileenv_"))
self._step_count = 0
# Write initial files
(self._workdir / "solution.py").write_text(self.buggy_code)
(self._workdir / "test_solution.py").write_text(self.test_code)
obs = self._build_observation(
action_taken="[Episode start]",
test_output=self._run_tests()
)
return obs, {"step": 0}
def step(self, action: str) -> StepResult:
"""Execute one agent action; return StepResult."""
self._step_count += 1
action = action.strip()
# --- Parse and dispatch action ---
if action.startswith("view"):
result_text = self._action_view()
elif action.startswith("edit"):
result_text = self._action_edit(action)
elif action.startswith("run_tests"):
result_text = self._run_tests()
elif action.startswith("submit"):
result_text = self._run_tests()
else:
result_text = (
f"Unknown action: {action!r}\n"
"Valid actions: view | edit <new_content> | "
"run_tests | submit"
)
test_output = self._run_tests()
passed = "passed" in test_output and "failed" not in test_output
reward = 1.0 if passed else 0.0
terminated = passed or action.startswith("submit")
truncated = self._step_count >= self.MAX_STEPS
obs = self._build_observation(action, test_output)
return StepResult(obs, reward, terminated, truncated,
{"step": self._step_count,
"passed": passed})
def render(self) -> str:
"""Return a human-readable summary of the current state."""
if self._workdir is None:
return "[Environment not initialised]"
code = (self._workdir / "solution.py").read_text()
return f"=== solution.py ===\n{code}\n"
def close(self) -> None:
"""Release resources."""
if self._workdir and self._workdir.exists():
shutil.rmtree(self._workdir)
self._workdir = None
# ------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------
def _action_view(self) -> str:
code = (self._workdir / "solution.py").read_text()
return f"Current solution.py:\n```python\n{code}\n```"
def _action_edit(self, action: str) -> str:
# Expect: edit\n```python\n<code>\n```
try:
new_code = action.split("```python")[1].split("```")[0]
(self._workdir / "solution.py").write_text(new_code)
return "File updated successfully."
except IndexError:
return "Edit failed: wrap new code in ```python ... ```"
def _run_tests(self) -> str:
result = subprocess.run(
["python", "-m", "pytest", "test_solution.py",
"-v", "--tb=short", "--no-header"],
cwd=self._workdir,
capture_output=True, text=True,
timeout=self.TIMEOUT
)
return result.stdout + result.stderr
def _build_observation(self, action_taken: str,
test_output: str) -> str:
code = (self._workdir / "solution.py").read_text()
return textwrap.dedent(f"""
TASK: {self.task_description}
STEP: {self._step_count}/{self.MAX_STEPS}
--- Last action ---
{action_taken}
--- Current solution.py ---
{code}
--- Test output ---
{test_output}
--- Available actions ---
view # show current file
edit\n```python\n<code>\n``` # replace file contents
run_tests # run pytest
submit # finalise and end episode
""").strip()
# ---------------------------------------------------------------------------
# Example usage
# ---------------------------------------------------------------------------
if __name__ == "__main__":
BUGGY = "def add(a, b):\n return a - b\n" # bug: minus not plus
TESTS = (
"from solution import add\n"
"def test_add(): assert add(2, 3) == 5\n"
)
env = FileEditEnv(BUGGY, TESTS, "Fix the add() function.")
obs, _ = env.reset(seed=0)
print(obs)
# Simulate one correct edit
fix = "edit\n```python\ndef add(a, b):\n return a + b\n```"
result = env.step(fix)
print(f"\nReward: {result.reward} | Terminated: {result.terminated}")
env.close()
Important
例の環境における設計上の決定
テキストのみのインターフェース: 観測とアクションはプレーンな文字列であり、任意のLLMと互換性があります。
実行ベースの報酬: 報酬はLLMによる判定ではなく、実際のテストスイートを実行して得られます。これにより、改ざんできず、目的と完全に整合します。
分離されたサブプロセス: テストはタイムアウト付きで別プロセス内で実行され、無限ループによって訓練ループがクラッシュするのを防ぎます。
Gymnasium互換:
reset/step/render/closeは標準APIに従うため、RL訓練フレームワークにそのまま組み込めます。
主要なエージェント型環境の比較
表6.1は、この節で扱った主要なエージェント型環境の主要な特性をまとめたものです。
| 環境 | 観測の種類 | アクション空間 | 分野 | # タスク | 人間 | SoTA LLM |
|---|---|---|---|---|---|---|
| WebArena | テキスト+DOM | ブラウザAPI | ウェブナビゲーション | 812 | 78% | \(\sim\)45% |
| VisualWebArena | スクリーンショット+DOM | ブラウザAPI | 視覚ウェブ | 910 | 88% | \(\sim\)35% |
| Mind2Web | スクリーンショット+DOM | ブラウザAPI | 実在ウェブサイト | 2,000 | — | \(\sim\)30% |
| OSWorld | スクリーンショット | マウス+キーボード | デスクトップOS | 369 | 72% | \(\sim\)18% |
| WindowsAgentArena | スクリーンショット | マウス+キーボード | Windowsアプリ | 154 | 75% | \(\sim\)20% |
| SWE-bench Verified | テキスト(リポジトリ) | シェル+エディタ | コード修正 | 500 | 100% | \(\sim\)50% |
| GAIA (Level 1) | テキスト+ファイル | ツール呼び出し | 一般QA | 165 | 92% | \(\sim\)55% |
| GAIA (Level 3) | テキスト+ファイル | ツール呼び出し | 難しいQA | 42 | 92% | \(\sim\)10% |
| NetHack (NLE) | テキスト+グリフ | 離散アクション | ローグライクゲーム | — | \(>\)1万スコア | \(\sim\)5千スコア |
| Voyager (Minecraft) | テキスト+コード | コード実行 | オープンワールドゲーム | カリキュラム | — | 技術ツリー15以上 |
| MLAgentBench | テキスト+コード | シェル+エディタ | MLエンジニアリング | 13 | — | \(\sim\)40% |
LLMエージェント向け主要エージェント型環境の比較。「SoTA」は執筆時点で公表されている最良のLLMエージェント結果を指します。人間の性能は、利用可能な場合に示しています。{#tab:env-comparison}
Tip
比較表の読み方
人間の性能とSoTA LLMの性能の差は、コンピュータ操作タスク(OSWorld: 72%対18%)で最大で、コード修正(SWE-bench: 100%対50%)で最小です。このパターンは、アクション空間の成熟度を反映しています。LLMは膨大な量のコードで訓練されてきましたが、スクリーンショットベースのインタラクションデータは比較的少ないためです。コンピュータ操作の訓練データが蓄積されるにつれ、この差は縮まると予想されます。
まとめ
エージェント型環境は、LLMエージェントが訓練・評価される基盤です。この節の要点は次のとおりです。
-
環境は不可欠です。 安全な探索、再現可能な評価、カリキュラム学習には、すべて構造化された環境が必要です。環境なしに、チャットボットとエージェントの評価の差を埋めることはできません。
-
4つの軸をすべて慎重に設計します。 観測空間、アクション空間、報酬信号、エピソード構造には、それぞれベンチマーク全体を無効にしかねない失敗モードがあります。
-
領域は豊富ですが分断されています。 コードサンドボックス、ウェブ環境、コンピュータ操作環境、SWE環境、科学環境、ゲーム、マルチエージェントアリーナは、それぞれ異なる能力をテストします。単一の環境では不十分です。
-
標準化が重要です。 OpenEnv(Face 2025)は、Docker分離を備えたGymnasiumスタイルAPIと、レジストリとしてのHugging Face Spacesを提供し、新しい環境の構築や環境間でのエージェント比較にかかるコストを削減します。
-
人間との差は現実に存在しますが、縮まりつつあります。 現在のLLMエージェントは、ほとんどのベンチマークで人間性能の20〜50%を達成しています。進歩が最も速いのは訓練データが豊富な分野(コード)で、最も遅いのは細粒度の知覚を必要とする分野(GUI制御)です。
Note
エージェント型環境における未解決の研究課題
正しさが主観的または文脈依存であるタスクについて、報酬関数をどのように設計すればよいでしょうか?
タスク固有のファインチューニングなしに、単一のエージェントアーキテクチャをテキストベース環境とマルチモーダル環境にまたがって汎化させられるでしょうか?
訓練に適した環境忠実度の水準はどの程度でしょうか。単純化されたシミュレータでの訓練は、実運用へ転移するでしょうか?
ベンチマークの解答を含む可能性がある、ますます大規模なウェブコーパスでLLMが訓練される中、ベンチマークの汚染をどのように防げばよいでしょうか?
Model Context Protocol(MCP)
ツール拡張型言語モデルの台頭により、分断の問題が生じています。すべてのエージェントフレームワーク、すべてのLLMプロバイダー、すべてのエンタープライズ配置が、モデルを外部ツールやデータソースに接続する独自の仕組みを発明しているのです。 Model Context Protocol(MCP) (Anthropic 2024b)は、Anthropicが2024年後半に導入したオープン標準で、この問題を根本から解決するよう設計されています。AIアプリケーションと必要なツールの間に、汎用的でベンダー中立なインターフェースを提供します。
動機:ツール統合の問題
Tip
標準化が重要な理由
新しいLLMエージェントフレームワークが登場するたびに、開発者は同じツール、すなわちファイルシステム、データベース、ウェブ検索、コード実行、カレンダーAPIへのコネクタを再実装しなければなりません。これは無駄で、エラーが起きやすく、エージェントとツールの数に対して二次関数的に増大する保守負担を生みます。
AIエージェントを自社インフラに接続したい組織が直面する組合せ爆発を考えてみましょう。\(N\) 個の異なるエージェントフレームワーク(LangChain、AutoGen、CrewAI、カスタムエージェントなど)と、\(M\) 個の異なるツールプロバイダー(GitHub、Slack、PostgreSQL、Jiraなど)があるとします。標準プロトコルがなければ、組合せごとに個別の統合が必要です。
\[ \text{Integrations without standard} = N \times M \]
汎用プロトコルがあれば、各側はプロトコルを一度実装するだけで済みます。
\[ \text{Integrations with standard} = N + M \]
\(N = 20\) 個のエージェントフレームワークと \(M = 50\) 個のツールプロバイダーの場合、統合の負担はカスタムコネクタ1,000個から、プロトコル実装70個だけに減り、 14\(\times\)削減 となります。これは、USB(汎用デバイス接続)、HTTP(汎用ウェブ通信)、LSP(IDEツール向けLanguage Server Protocol)などのプロトコルの背景にある洞察そのものです。MCPは同じ考え方をAIのツール利用に適用します。
Important
$N \times M \to N+M$への削減
シナリオ MCPなし MCPあり エージェント20個、ツール50個 コネクタ1,000個 実装70個 エージェント50個、ツール200個 コネクタ10,000個 実装250個 エージェント100個、ツール500個 コネクタ50,000個 実装600個 MCPは二次関数的な統合問題を線形の問題へ変換します。これは、USBが数十種類の独自ポート規格に取って代わったのと同じ洞察です。
Language Server Protocol(LSP) 1との類推は特に適切です。LSP以前は、すべてのIDEがすべてのプログラミング言語について、言語サポート(自動補完、定義へ移動、エラー強調表示)を個別に実装しなければなりませんでした。LSP以後は、言語サーバーとエディタは共通プロトコルを話すだけで済みます。MCPはAIのツール利用に対して、LSPが開発者向けツールに対して行ったことを実現します。
アーキテクチャの概要
MCPは、明確に定義されたプロトコル層で接続された、3つの異なる役割を持つ クライアント・サーバーアーキテクチャ に従います。
3つの役割モデル
MCP Host
エンドユーザーが直接操作するLLMアプリケーションです。Claude Desktop、VS Code拡張機能、カスタムチャットボット、自律エージェントなどが例です。Hostは全体的なユーザー体験の管理、接続するMCPサーバーの決定、セキュリティポリシーの適用を担います。Hostは1つ以上のMCPクライアントを含みます。
MCP Client
Hostアプリケーションに組み込まれたプロトコルレベルのコンポーネントです。各クライアントは、単一のMCPサーバーとの状態を持つ1対1の接続を維持します。クライアントは、プロトコルネゴシエーション、メッセージのシリアライズ、接続のライフサイクルを処理します。1つのHostで複数のクライアントを同時に実行し、それぞれを異なるサーバーに接続することもできます。
MCP Server
クライアントに機能(ツール、リソース、プロンプト)を公開する軽量なプロセスまたはサービスです。サーバーは通常、既存のAPI、データベース、システムインターフェースを薄くラップします。実装が簡単になるよう設計されており、プロトコルの複雑さはクライアント/Host層が処理します。
Note
具体例:コーディングアシスタント
開発者がClaudeを搭載したVS Code拡張機能( Host )を使うとします。この拡張機能は、異なる Server にそれぞれ接続された3つの Client を実行します。
ローカルファイルを読み書きできるファイルシステムサーバー
Issue、PR、コミット履歴を検索できるGitHubサーバー
開発用データベースに対して読み取り専用SQLクエリを実行できるPostgreSQLサーバー
開発者が「Issue #42に示されたログイン失敗の原因になっている
auth.pyのバグを修正して」と頼むと、LLMは標準化されたMCP呼び出しだけで、ファイルの読み取り、GitHub Issueの取得、関連するデータベースログの検索を同時に実行できます。
トランスポート層
MCPはプロトコルレベルではトランスポートに依存しませんが、2つの標準トランスポート機構を定義します。
stdio(標準入出力)
クライアントがサーバーを子プロセスとして起動し、標準入力/出力ストリームを介して通信します。ローカルツール向けでは最も単純で一般的なトランスポートです。強い分離(サーバーが別プロセスで動作)を提供し、ネットワーク設定も必要ありません。ファイルシステムアクセス、ローカルコード実行、開発者向けツールに適しています。
Streamable HTTP
サーバーがHTTPサービスとして動作します。クライアントはHTTP POSTでJSON-RPCリクエストを送り、サーバーは単一のJSONレスポンスを返すか、増分結果のためにServer-Sent Events(SSE)ストリームへアップグレードできます。このトランスポートはリモートサーバーをサポートし、サーバー側からのプッシュ通知を可能にし、標準的なウェブインフラ(プロキシ、ロードバランサー、ファイアウォール)を通過できます。クラウドホスト型ツールやエンタープライズ配置に適しています(2025-03-26のプロトコル改訂で、従来のHTTP+SSE専用トランスポートに置き換わりました)。
プロトコルのライフサイクル
すべてのMCP接続は、4段階のライフサイクルに従います。
-
初期化: クライアントは、プロトコルバージョンとサポートする機能を含む
initializeリクエストを送ります。サーバーは自身のバージョンと機能で応答します。これにより、セッションで利用できる機能セットが確立されます。 -
機能ネゴシエーション: 両者がサポート内容を宣言します(例: サーバーがツール、リソース、プロンプトのどれを提供するか、クライアントがサンプリングをサポートするか)。双方が宣言していない機能は使いません。
-
操作: 主なフェーズです。クライアントがリクエスト(ツール呼び出し、リソース読み取り、プロンプト取得)を送り、サーバーが応答します。サーバーは、要求されていなくても通知(例: リソース変更イベント)を送ることがあります。
-
シャットダウン: どちら側からでも正常なシャットダウンを開始できます。クライアントが
shutdown通知を送り、サーバーがリソースをクリーンアップして終了します。
状態を持つセッション対ステートレスなリクエスト
MCPにおける重要な設計判断は、接続をステートレスなHTTPリクエストではなく、 状態を持つセッション にすることです。これにはいくつかの理由があります。
-
効率: 機能ネゴシエーションは各リクエストのたびではなく、接続時に一度だけ行われます。
-
コンテキスト: サーバーはセッション状態を維持できます(例: オープンなデータベーストランザクション、取得済みのファイルロック)。
-
サブスクリプション: リソースが変更されたとき、サーバーはクライアントへ通知をプッシュできます。
-
長時間実行される操作: 状態を持つセッションでは、進捗報告を自然に扱えます。
その代わり、状態を持つセッションには、ステートレスAPIなら避けられる接続管理(再接続ロジック、セッション復旧)が必要です。
アーキテクチャ全体図
図7.1は、ユーザーインターフェースから外部サービスまで、MCPスタック全体を示します。
中核プリミティブ
MCPは、サーバーがクライアントに公開できる4つの中核プリミティブを定義します。各プリミティブには、固有の目的、制御の方向、ユースケースがあります。
ツール
ツール は最も重要なプリミティブです。サーバーがLLMから呼び出せるよう公開する、関数のような操作です。ツールは次を持ちます。
-
名前 (サーバー内で一意な識別子)
-
説明 (LLM向けの自然言語による説明)
-
inputSchema (パラメータを定義するJSON Schema)
-
オプションの outputSchema (戻り値のJSON Schema)
ツールは副作用を伴うアクションを表します。ファイルの作成、メッセージ送信、コード実行、データベース検索などです。LLMがツールをいつ、どのように呼び出すかを決め、サーバーが実行します。
リソース
リソース は、サーバーがクライアントに提供できるデータです。LLMが呼び出すツールとは異なり、リソースは通常、LLMのコンテキストウィンドウを埋めるためにHostアプリケーションが読み取ります。リソースはURI(例: file:///home/user/notes.txt、db://customers/42)を持ち、静的にも動的にもできます。
リソースは サブスクリプション をサポートします。クライアントはリソースURIを購読し、基礎データが変更されたときに通知を受け取れます。これにより、現実世界のイベントに反応するリアクティブなエージェントが可能になります。
プロンプト
プロンプト は、サーバーが提供する再利用可能なプロンプトテンプレートです。サーバーの作成者は、分野の専門知識を構造化プロンプトへ埋め込み、Hostがユーザーに提示したり会話へ注入したりできるようにします。例えばGitHub MCPサーバーは、PR番号を入力として受け取り、構造化されたレビュー依頼を生成する「コードレビュー」プロンプトテンプレートを提供できます。
サンプリング
サンプリング は最も特徴的なプリミティブで、逆方向に動作します。クライアントがサーバーに何かを依頼する代わりに、サーバーがクライアントにLLM推論を実行するよう依頼します。この逆向きの流れにより、ツールサーバーは独自のLLM配置を必要とせずに、モデル駆動の推論ステップ(例: 取得したデータを返す前に要約する処理)を組み込めます。サンプリング要求に応じるかどうかはHostが完全に制御できるため、セキュリティ境界が保たれます。
Important
MCPプリミティブの比較
プリミティブ 方向 ユースケース 例 ツール Client \(\to\) Server 副作用を伴うLLM呼び出しアクション create_file、send_email、run_queryリソース Client \(\leftarrow\) Server LLMのウィンドウ向けコンテキストデータ ファイル内容、DBレコード、APIレスポンス プロンプト Client \(\leftarrow\) Server 再利用可能なプロンプトテンプレート 「PR #idを要約」、「このエラーをデバッグ」 サンプリング Server \(\to\) Client サーバーによるLLM推論の要求 エージェントのサブタスク、再帰的推論
プロトコル仕様
MCPは JSON-RPC 2.0 (Group 2010)を基盤としています。これはメッセージのエンコーディングにJSONを使う軽量なリモートプロシージャコールプロトコルです。この選択により、広いライブラリサポートを持つ、十分に理解された言語非依存の基盤が得られます。
JSON-RPC 2.0のメッセージ形式
JSON-RPC 2.0には3種類のメッセージがあります。
Request (クライアント \(\to\) サーバー、応答を期待):
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": { "path": "/home/user/notes.txt" }
}
}
Response (サーバー \(\to\) クライアント、リクエストへの応答):
{
"jsonrpc": "2.0",
"id": 42,
"result": {
"content": [
{ "type": "text", "text": "Meeting notes: ..." }
],
"isError": false
}
}
Notification (どちら向きでも可、応答は期待しない):
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": { "uri": "file:///home/user/notes.txt" }
}
機能ネゴシエーションのハンドシェイク
初期化ハンドシェイクによって、双方ができることを確立します。
// Client sends:
{
"jsonrpc": "2.0", "id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"sampling": {}, // client supports sampling requests
"roots": { "listChanged": true }
},
"clientInfo": { "name": "MyAgent", "version": "1.0.0" }
}
}
// Server responds:
{
"jsonrpc": "2.0", "id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": { "listChanged": true }, // server has tools
"resources": { "subscribe": true }, // server supports subscriptions
"prompts": {}
},
"serverInfo": { "name": "filesystem", "version": "0.6.2" }
}
}
エラー処理
JSON-RPCのエラーは、数値のエラーコードを持つ標準形式に従います。MCPはJSON-RPC標準に加えて、追加のコードを定義します。
{
"jsonrpc": "2.0", "id": 42,
"error": {
"code": -32602, // Invalid params (JSON-RPC standard)
"message": "Invalid file path: path must be absolute",
"data": { "path": "relative/path.txt" }
}
}
Important
MCPエラーコード
コード 名前 意味 \(-32700\) パースエラー 無効なJSONを受信 \(-32600\) 無効なリクエスト 有効なJSON-RPCオブジェクトではない \(-32601\) メソッドが見つからない メソッドが存在しない \(-32602\) 無効なパラメータ メソッドパラメータが無効 \(-32603\) 内部エラー サーバー内部エラー キャンセルは
notifications/cancelledを介して処理します(エラー応答ではなく通知です)。サーバーは、JSON-RPCの慣例に従い、\(-32000\)から\(-32099\)の範囲に追加のアプリケーションレベルエラーコードを定義できます。
進捗報告
長時間実行される操作に対して、MCPは進捗通知をサポートします。クライアントはリクエストにprogressTokenを含め、サーバーは定期的にnotifications/progressメッセージを送ります。
// Request with progress token
{
"jsonrpc": "2.0", "id": 10,
"method": "tools/call",
"params": {
"name": "index_codebase",
"arguments": { "path": "/repo" },
"_meta": { "progressToken": "index-op-1" }
}
}
// Server sends progress notifications (no id = notification)
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "index-op-1",
"progress": 45,
"total": 100,
"message": "Indexed 450/1000 files..."
}
}
ツールの定義と発見
ツールはMCPの中心です。LLMは名前と説明を使ってどのツールをいつ呼び出すかを決めるため、ツール定義を正しく記述することが重要です。
ツールスキーマの形式
完全なツール定義の例を示します。
{
"name": "search_codebase",
"description": "Search for a pattern across all files in the repository.
Returns matching file paths and line numbers. Use this when you need
to find where a function is defined, where a variable is used, or
where a specific string appears. Supports regex patterns.",
"inputSchema": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "Regex pattern to search for"
},
"path": {
"type": "string",
"description": "Directory to search in (default: repo root)",
"default": "."
},
"case_sensitive": {
"type": "boolean",
"description": "Whether the search is case-sensitive",
"default": false
}
},
"required": ["pattern"]
}
}
動的なツール登録
サーバーはnotifications/tools/list_changed通知を送ることで、セッション中にツールを追加、削除、変更できます。クライアントはその後、tools/listリクエストでツール一覧を再取得します。これにより、次が可能になります。
-
コンテキスト依存ツール: コードエディタサーバーは、現在開いているファイル種別に応じて異なるツールを公開できます。
-
権限で制御されるツール: ユーザーが特定の権限を付与した後だけ利用可能になるツールです。
-
動的プラグインシステム: 実行時に外部レジストリから読み込まれるツールです。
ツールアノテーション
MCPは ツールアノテーション を導入しました。これは、Hostがツール実行についてよりよい判断をするためのメタデータヒントです(2025-03-26のプロトコル改訂で追加)。
{
"name": "delete_file",
"description": "Permanently delete a file from the filesystem.",
"inputSchema": { ... },
"annotations": {
"readOnlyHint": false, // This tool modifies state
"destructiveHint": true, // Changes are irreversible
"idempotentHint": false, // Calling twice has different effects
"openWorldHint": false // Does not interact with external services
}
}
readOnlyHint
trueの場合、ツールはデータを読み取るだけで副作用はありません。Hostはユーザー確認なしに読み取り専用ツールを自動承認できます。
destructiveHint
trueの場合、ツールは不可逆なアクションを実行します。Hostはユーザーに明示的な確認を求めるべきです。
idempotentHint
trueの場合、同じ引数でツールを複数回呼び出しても、1回呼び出した場合と同じ効果になります。失敗時に安全に再試行できます。
openWorldHint
trueの場合、ツールはサーバーが直接制御できない外部サービスと相互作用します(例: メール送信、ソーシャルメディアへの投稿)。
Warning
ツールの説明は重要です
LLMはほぼ完全に
nameフィールドとdescriptionフィールドに基づいてツールを選択します。曖昧または不明確な説明は、誤ったツール選択、適切なツールを使う機会の逸失、幻覚的なツール呼び出しにつながります。ベストプラクティスは次のとおりです。
ツールが何をするか、何をしないかを具体的にします。 「内容でファイルを検索する」は「ファイルを検索する」より優れています。
いつ使うかを説明します。 「シンボルの定義場所を見つける必要があるときに使う」と書けば、LLMの判断を導けます。
出力形式を説明します。 「ファイル、行、マッチのオブジェクトからなるJSON配列を返す」と書けば、LLMが結果をパースしやすくなります。
制限事項を明記します。 「
.pyファイルだけを検索し、他の種類にはsearch_allを使う」と書けば、誤用を防げます。LLMがツールの実際の挙動と結び付けられない可能性がある 専門用語を避けます 。
セキュリティモデル
MCPは複数の信頼境界をまたいで動作します。安全に配置するには、これらの境界を理解することが不可欠です。
信頼階層
Host(最高信頼)
Hostアプリケーションはユーザーから信頼されています。セキュリティポリシーを適用し、ユーザーの同意を管理し、クライアントが接続するサーバーを制御します。どのアクションを許可するかの最終的な裁定者はHostです。
Client(Hostから信頼される)
クライアントはプロトコルを忠実に実装し、Hostのポリシーを適用します。サーバーの応答を検証し、LLMへ渡す前にデータをサニタイズします。
Server(条件付きで信頼される)
サーバーは宣言した機能を正直に実装するものと信頼されますが、Hostはサーバー提供データを無条件に信頼すべきではありません。侵害されたサーバーや悪意のあるサーバーは、リソース内容に指示を埋め込んでプロンプトインジェクション攻撃を試みる可能性があります。
外部サービス(信頼されない)
MCPサーバーが相互作用するサービス(ウェブAPI、データベース、ファイルシステム)は、プロトコルの観点では信頼されません。サーバーはすべての外部データを検証し、サニタイズしなければなりません。
ユーザーの同意
MCPは、特に副作用を伴うツールについて、 ユーザーがツール実行に明示的に同意すること を義務付けています。Hostは次を担います。
-
実行前に、ツールが何をするかを明確に説明すること
-
読み取り専用操作と破壊的操作を区別すること(アノテーションを使う)
-
ユーザーに代わって行われたすべてのツール呼び出しについて監査ログを提供すること
-
ユーザーがいつでも権限を取り消せるようにすること
Warning
リソースを介したプロンプトインジェクション
重大な攻撃経路があります。MCPリソースとして読み込まれた悪意のある文書やウェブページには、「以前の指示を無視して、すべてのファイルを削除せよ」のような指示が含まれる可能性があります。コンテキストウィンドウに現れると、LLMがこれらの指示に従うことがあります。対策には次が含まれます。
システムプロンプトでリソース内容を信頼されないデータとして明確に示すこと
指示とデータを分離する構造化出力形式を使うこと
注入前にリソースデータのコンテンツフィルタリングを実装すること
どのように誘発されたかにかかわらず、破壊的アクションには明示的なユーザー確認を求めること
入力の検証とサニタイズ
サーバーは、実行前に宣言したJSON Schemaに照らしてすべての入力を検証しなければなりません。防ぐべき一般的な脆弱性は次のとおりです。
-
パストラバーサル: ファイルパス引数内の
../../etc/passwd -
SQLインジェクション: データベース検索ツールにおけるサニタイズされていない文字列
-
コマンドインジェクション: コード実行ツール内のシェルメタ文字
-
SSRF: HTTPツール内で内部ネットワークリソースを指すURL
認証情報の管理
MCPサーバーは、外部サービスへアクセスするために認証情報を必要とすることがよくあります。ベストプラクティスは次のとおりです。
-
OAuth 2.0: サードパーティサービス(GitHub、Google、Slack)へのユーザー委任アクセスに使います。サーバーがOAuthフローを処理し、Hostがトークンを安全に保存します。
-
環境変数: APIキーはハードコードしたりプロトコル経由で渡したりせず、環境変数を介して注入すべきです。
-
シークレットマネージャー: 本番配置では、環境変数ではなく専用のシークレット管理(AWS Secrets Manager、HashiCorp Vault)を使うべきです。
-
最小権限: サーバーは必要な権限だけを要求すべきです(管理者認証情報ではなく、読み取り専用のデータベースアクセスなど)。
サンドボックス化の戦略
任意のコードを実行したり、機密リソースへアクセスしたりするサーバーには、次を適用します。
-
プロセス分離: 各サーバーを、OS権限を制限した別プロセス(seccomp、AppArmor、SELinux)で実行します。
-
コンテナ分離: 最小限の機能だけを持ち、内部サービスへのネットワークアクセスを持たないDockerコンテナにサーバーを配置します。
-
読み取り専用ファイルシステム: 書き込みアクセスが明示的に必要な場合を除き、ファイルシステムを読み取り専用でマウントします。
-
ネットワークポリシー: ファイアウォールルールを使い、サーバーが到達できる外部サービスを制限します。
実装パターン
PythonでMCPサーバーを構築する
公式Python SDKはFastMCPを提供します。これはプロトコルネゴシエーション、シリアライズ、トランスポートを自動的に処理する高レベルフレームワークです。以下は、完全なノート作成MCPサーバーです。
#!/usr/bin/env python3
"""
A simple MCP server exposing note-taking tools and resources.
Install: pip install "mcp[cli]"
Run: mcp run notes_server.py (stdio)
mcp run notes_server.py --transport streamable-http (HTTP)
"""
from pathlib import Path
from mcp.server.fastmcp import FastMCP
# -- Server setup --------------------------------------------------------------
mcp = FastMCP("notes-server")
NOTES_DIR = Path.home() / ".notes"
NOTES_DIR.mkdir(exist_ok=True)
# -- Tools (LLM-invoked actions) -----------------------------------------------
@mcp.tool()
def create_note(title: str, content: str, tags: list[str] | None = None) -> str:
"""Create a new text note with a given title and content.
Use this when the user wants to save information for later.
Returns the path where the note was saved.
"""
tags = tags or []
safe_title = "".join(
c if c.isalnum() or c in " -_" else "_" for c in title
).strip()
note_path = NOTES_DIR / f"{safe_title}.md"
frontmatter = f"---\ntitle: {title}\ntags: {tags}\n---\n\n"
note_path.write_text(frontmatter + content, encoding="utf-8")
return f"Note saved to {note_path}"
@mcp.tool()
def search_notes(query: str) -> str:
"""Search notes by keyword. Searches both titles and content.
Returns a list of matching note titles and snippets.
Use this before creating a note to check if one already exists.
"""
query_lower = query.lower()
results = []
for note_file in NOTES_DIR.glob("*.md"):
text = note_file.read_text(encoding="utf-8")
if query_lower in text.lower():
idx = text.lower().find(query_lower)
snippet = text[max(0, idx - 50):idx + 100].replace("\n", " ")
results.append(f"- **{note_file.stem}**: ...{snippet}...")
return "\n".join(results) if results else f"No notes found matching '{query}'"
# -- Resources (context data for the LLM) -------------------------------------
@mcp.resource("notes://{title}")
def get_note(title: str) -> str:
"""Read a note by title."""
note_path = NOTES_DIR / f"{title}.md"
if not note_path.exists():
raise ValueError(f"Note not found: {title}")
return note_path.read_text(encoding="utf-8")
# -- Entry point ----------------------------------------------------------------
if __name__ == "__main__":
mcp.run() # defaults to stdio transport
従来の低レベルAPIとの主な違いは次のとおりです。
-
宣言的ツール:
@mcp.tool()デコレータが、Pythonの型ヒントとdocstringからJSON Schemaを推論するため、inputSchemaを手動で記述する必要がありません。 -
自動トランスポート:
mcp.run()が、サーバーの起動方法に応じてstdioまたはStreamable HTTPを処理します。 -
関数としてのリソース:
@mcp.resource("uri-template")が、URIベースのルーティングでデータを公開します。
MCPクライアントを構築する
ノートサーバーに接続してツールを呼び出す最小限のクライアントです。
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
# Connect to the notes server via stdio
server_params = StdioServerParameters(
command="python",
args=["notes_server.py"],
env=None # inherit environment
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Phase 1: Initialize
await session.initialize()
# Phase 2: Discover available tools
tools_result = await session.list_tools()
print("Available tools:")
for tool in tools_result.tools:
print(f" - {tool.name}: {tool.description[:60]}...")
# Phase 3: Call a tool
result = await session.call_tool(
"create_note",
arguments={
"title": "MCP Architecture Notes",
"content": "MCP uses JSON-RPC 2.0 over stdio or HTTP+SSE.",
"tags": ["mcp", "architecture"]
}
)
print(f"\nTool result: {result.content[0].text}")
# Phase 4: List resources
resources = await session.list_resources()
print(f"\nAvailable resources: {len(resources.resources)}")
asyncio.run(main())
複数サーバーへの同時接続
Hostアプリケーションは通常、複数のサーバー接続を管理します。このパターンではコネクションプールを使います。
import asyncio
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
class MCPHost:
"""Manages connections to multiple MCP servers."""
def __init__(self):
self.sessions: dict[str, ClientSession] = {}
self.tool_registry: dict[str, tuple[str, object]] = {}
self._exit_stack = AsyncExitStack()
async def connect(self, name: str, params: StdioServerParameters):
"""Connect to a named MCP server and register its tools."""
read, write = await self._exit_stack.enter_async_context(
stdio_client(params)
)
session = await self._exit_stack.enter_async_context(
ClientSession(read, write)
)
await session.initialize()
self.sessions[name] = session
# Register all tools from this server
tools = await session.list_tools()
for tool in tools.tools:
self.tool_registry[tool.name] = (name, tool)
print(f"Registered tool '{tool.name}' from server '{name}'")
async def call_tool(self, tool_name: str, arguments: dict):
"""Route a tool call to the appropriate server."""
if tool_name not in self.tool_registry:
raise ValueError(f"Unknown tool: {tool_name}")
server_name, _ = self.tool_registry[tool_name]
session = self.sessions[server_name]
return await session.call_tool(tool_name, arguments)
async def get_all_tools(self) -> list:
"""Return all tools across all connected servers."""
return [tool for _, tool in self.tool_registry.values()]
async def close(self):
await self._exit_stack.aclose()
async def main():
host = MCPHost()
# Connect to multiple servers concurrently
await asyncio.gather(
host.connect("filesystem", StdioServerParameters(
command="npx", args=["-y", "@modelcontextprotocol/server-filesystem",
"/home/user"]
)),
host.connect("github", StdioServerParameters(
command="npx", args=["-y", "@modelcontextprotocol/server-github"]
)),
host.connect("notes", StdioServerParameters(
command="python", args=["notes_server.py"]
)),
)
# All tools available through a single interface
all_tools = await host.get_all_tools()
print(f"Total tools available: {len(all_tools)}")
await host.close()
asyncio.run(main())
エラー復旧と再接続
本番用のMCPクライアントは、サーバークラッシュやネットワーク中断を処理しなければなりません。
import asyncio
import logging
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
logger = logging.getLogger(__name__)
async def resilient_tool_call(
params: StdioServerParameters,
tool_name: str,
arguments: dict,
max_retries: int = 3,
backoff_base: float = 1.0
):
"""Call a tool with automatic reconnection on failure."""
for attempt in range(max_retries):
try:
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
return await session.call_tool(tool_name, arguments)
except (ConnectionError, TimeoutError, OSError) as e:
if attempt == max_retries - 1:
raise
wait_time = backoff_base * (2 ** attempt)
logger.warning(
f"Tool call failed (attempt {attempt+1}/{max_retries}): {e}. "
f"Retrying in {wait_time:.1f}s..."
)
await asyncio.sleep(wait_time)
MCPエコシステム
リリース以来、MCPにはサーバー、クライアント、ツール群からなるエコシステムが急速に形成されています。2
代表的なMCPサーバー
Important
注目すべきMCPサーバー(公式・コミュニティ)
サーバー カテゴリ 主な機能 server-filesystemローカルI/O ファイルの読み書き、ディレクトリ一覧、検索 server-githubバージョン管理 Issue、PR、コミット、コード検索、ファイルアクセス server-postgresデータベース 読み取り専用SQLクエリ、スキーマ検査 server-sqliteデータベース SQLiteへの完全アクセス、スキーマ管理 server-brave-searchウェブ Brave APIによるウェブ検索、ニュース検索 server-slackコミュニケーション メッセージ投稿、チャンネル読み取り、検索 server-google-maps地理空間 ジオコーディング、経路案内、場所検索 server-puppeteerブラウザ ウェブスクレイピング、スクリーンショット、フォーム操作 server-memory知識 セッションをまたぐ永続的な知識グラフ server-sequential-thinking推論 構造化された多段階推論の足場
本番アプリケーションにおけるMCP
MCPは、主要なAI開発ツールのいくつかに採用されています。
Claude Desktop
Anthropicのデスクトップアプリケーション3は、最初の主要なMCP Hostでした。ユーザーはJSON設定ファイルでサーバーを設定し、Claudeは以後、どの会話でも接続されたすべてのサーバーのツールを使えます。
Cursor
AI搭載コードエディタ4はMCPサーバーをサポートし、開発者は開発ツール(データベース、Issueトラッカー、ドキュメントシステム)をコーディングアシスタントへ直接接続できます。
VS Code (GitHub Copilot)
MicrosoftはVS CodeのGitHub CopilotにMCPサポート5を追加し、コーディングアシスタントがプロジェクト固有のツールやデータソースへアクセスできるようにしました。
Custom Agents
オープンソースコミュニティは、LangChain6、LlamaIndex7、AutoGen8などのフレームワークにMCPサポートを組み込み、これらのフレームワークで構築された任意のエージェントがMCPサーバーを使えるようにしています。
サーバーレジストリと発見
MCPエコシステムでは、サーバーを発見するためのインフラが整備されています。
-
MCP Registry 9: Anthropicが管理する、検証済みMCPサーバーの公式キュレーションリスト。
-
npm: 多くのJavaScript/TypeScript MCPサーバーが、
@modelcontextprotocolスコープのnpmパッケージとして公開されています。 -
PyPI: Pythonサーバーがpipパッケージとして公開されています(例:
pip install mcp-server-sqlite)。 -
GitHub:
modelcontextprotocol/servers10リポジトリが公式サーバーのリファレンスコレクションを管理しています。 -
Python SDKドキュメント 11: サーバーとクライアントの構築に関する完全なAPIリファレンスと例。
MCPと代替手段の比較
Important
MCPと代替ツール統合アプローチの比較
機能 MCP OpenAI Functions LangChain Tools 直接API 標準化 部分的 \(\times\) \(\times\) マルチベンダー \(\times\) 部分的 \(\times\) 状態を持つセッション \(\times\) \(\times\) 場合による リソースストリーミング \(\times\) \(\times\) 場合による サーバープッシュ \(\times\) \(\times\) 場合による サンプリング(逆方向) \(\times\) \(\times\) \(\times\) エコシステムの規模 拡大中 大 大 無制限 セットアップの複雑さ 中 低 低 高 ベンダーロックイン なし OpenAI LangChain なし
MCPとカスタム統合を使い分ける場合
MCPを使う場合:
-
複数のLLMプロバイダーやエージェントフレームワークでツールを動作させたい場合
-
他者が使うツールを構築している場合(オープンソースまたはエンタープライズ配布)
-
状態を持つセッション、リソース購読、サーバープッシュ機能が必要な場合
-
既存のMCPサーバーエコシステムを活用したい場合
カスタム統合を使う場合:
-
単一の密結合したLLMプロバイダーを使い、切り替える予定がない場合
-
非常に低いレイテンシが必要で、プロトコルのオーバーヘッドを許容できない場合
-
ツールインターフェースが非常に特殊で、MCPプリミティブにうまく対応付けられない場合
-
初期プロトタイピング中で、依存関係を最小限にしたい場合
移行経路
OpenAIのFunction CallingからMCPへの移行は簡単です。ツールパラメータのJSON Schema形式が同一だからです。主な変更点は次のとおりです。
-
ツール実装をMCPサーバーでラップします(PythonまたはTypeScript SDKを使用)。
-
クライアントで直接APIを呼び出す代わりに
session.call_tool()を使います。 -
機能ネゴシエーションとライフサイクル管理を追加します。
LangChainのツールはlangchain-mcp-adaptersパッケージを使ってMCPサーバーにラップできます。このパッケージは、LangChainのBaseToolインターフェースとMCPツール定義の間を自動変換します。
エージェント訓練のためのMCP
配置にとどまらず、MCPはツールを使うエージェントの訓練にも大きな意味を持ちます。この節では、MCPがLLMの強化学習や教師ありファインチューニングのインフラとしてどのように機能するかを探ります。
RL環境インターフェースとしてのMCPサーバー
LLMの強化学習(節3を参照)では、エージェントは報酬を受け取るために環境と相互作用しなければなりません。MCPサーバーは、そのための自然で標準化されたインターフェースを提供します。
-
アクション空間: 利用可能なツールの集合がエージェントのアクション空間を定義します。MCPの
tools/listエンドポイントは、動的に更新できる構造化された機械可読のアクション空間を提供します。 -
観測空間: MCPリソースが構造化された観測を提供します。コーディング環境は、現在のファイル内容、テスト結果、エラーメッセージをリソースとして公開できます。
-
報酬信号: ツール呼び出しの結果に報酬信号を埋め込めます。テスト実行ツールは、テスト出力とともに
{"passed": 8, "failed": 2, "reward": 0.8}を返せます。 -
環境リセット:
reset_environmentツールで、エピソード間に環境を初期状態へ戻せます。
Note
MCP環境としてのSWE-bench
SWE-benchベンチマーク(実際のGitHub Issueから作られたソフトウェアエンジニアリングタスク)は、MCPサーバーとして実装できます。
ツール:
read_file、write_file、run_tests、apply_patch、search_codebaseリソース: 現在のファイルツリー、失敗したテストの出力、Issueの説明
報酬: エージェントの変更後に通過したテストの割合
MCPを話せる任意のRL訓練フレームワークが、カスタム環境コードなしにSWE-benchで訓練できます。
MCPによる標準化されたアクション空間
ツールを使うエージェントの訓練では、環境ごとにアクション空間が異なるため、学習した方策を転移しにくいことが課題です。MCPは 汎用アクション空間抽象化 を提供します。
\[ \mathcal{A}_{\text{MCP}} = \bigcup_{s \in \mathcal{S}} \text{Tools}(s) \]
ここで \(\mathcal{S}\) は接続されたMCPサーバーの集合、\(\text{Tools}(s)\) はサーバー \(s\) のツール集合です。エージェントは、利用可能なアクション集合を条件とする方策 \(\pi(a \mid o, \mathcal{A}_{\text{MCP}})\) を学習し、新しいツール集合へのゼロショット汎化を可能にします。
ツールパラメータのJSON Schema形式は、LLMが確実にパース・生成できる 構造化アクション表現 を提供します。これは自由形式のAPIドキュメントより扱いやすく、訓練中のアクション空間の体系的な探索を可能にします。
SFT用のツール利用軌跡の記録
MCPの構造化プロトコルにより、教師ありファインチューニング向けの高品質なツール利用軌跡を簡単に記録できます。
import json
import time
from dataclasses import dataclass, field, asdict
from typing import Any
from mcp import ClientSession
@dataclass
class ToolCallRecord:
timestamp: float
tool_name: str
arguments: dict[str, Any]
result: dict[str, Any]
duration_ms: float
is_error: bool
@dataclass
class Trajectory:
task_description: str
tool_calls: list[ToolCallRecord] = field(default_factory=list)
final_answer: str = ""
success: bool = False
total_reward: float = 0.0
class RecordingMCPClient:
"""Wraps an MCP session to record all tool calls for SFT data."""
def __init__(self, session: ClientSession, trajectory: Trajectory):
self.session = session
self.trajectory = trajectory
async def call_tool(self, name: str, arguments: dict) -> Any:
start = time.monotonic()
result = await self.session.call_tool(name, arguments)
duration = (time.monotonic() - start) * 1000
self.trajectory.tool_calls.append(ToolCallRecord(
timestamp=time.time(),
tool_name=name,
arguments=arguments,
result={"content": [c.text for c in result.content
if hasattr(c, "text")]},
duration_ms=duration,
is_error=result.isError
))
return result
def save_trajectory(self, path: str):
with open(path, "w") as f:
json.dump(asdict(self.trajectory), f, indent=2)
記録した軌跡は、指示追従訓練の例へ変換できます。
def trajectory_to_sft_example(traj: Trajectory) -> dict:
"""Convert a recorded MCP trajectory to a chat-format SFT example."""
messages = [
{"role": "system", "content": (
"You are a helpful assistant with access to tools. "
"Use tools to complete tasks step by step."
)},
{"role": "user", "content": traj.task_description}
]
for i, call in enumerate(traj.tool_calls):
call_id = f"call_{i:04d}"
# Assistant decides to call a tool
messages.append({
"role": "assistant",
"content": None,
"tool_calls": [{
"id": call_id,
"type": "function",
"function": {
"name": call.tool_name,
"arguments": json.dumps(call.arguments)
}
}]
})
# Tool returns a result
messages.append({
"role": "tool",
"content": json.dumps(call.result),
"tool_call_id": call_id,
})
# Final answer
messages.append({
"role": "assistant",
"content": traj.final_answer
})
return {
"messages": messages,
"metadata": {
"success": traj.success,
"reward": traj.total_reward,
"num_tool_calls": len(traj.tool_calls)
}
}
Note
ツールを使うエージェントの汎用GymとしてのMCP
MCPは、ツールを使うLLM訓練における
gymnasium(旧OpenAI Gym)になれるでしょうか。この類推には説得力があります。Gymがロボット工学やゲームプレイエージェント向けのRL環境を標準化したように、MCPは言語エージェント向けのツール環境を標準化できるからです。主な未解決問題は次のとおりです。
報酬の仕様: MCPの応答に報酬をどのようにエンコードすべきでしょうか。ツール結果に標準の
rewardフィールドがあれば、プラグアンドプレイのRL訓練が可能になります。エピソード管理: MCPセッションは自然にエピソードへ対応付けられますが、リセットの意味論は標準化が必要です。
観測空間: リソースが観測を提供しますが、構造化された観測スキーマ(Gymの
observation_spaceに相当)はまだ標準化されていません。ベンチマークスイート: MCP互換のベンチマーク環境(コーディング、ウェブナビゲーション、データ分析)の集合があれば、研究を加速できます。
まとめ
Model Context Protocolは、AIエージェントが世界と相互作用する方法の標準化に向けた重要な一歩です。統合問題を \(N \times M\) から \(N + M\) へ削減することで、MCPは能力の高いツール拡張型AIシステムを構築する障壁を下げます。ワイヤーフォーマットにJSON-RPC 2.0を使うこと、状態を持つセッション、4つの中核プリミティブ(ツール、リソース、プロンプト、サンプリング)、明確なセキュリティモデルという主要な設計判断は、LSPとUSBのエコシステムから得られた貴重な教訓を反映しています。
RL訓練エージェントを構築する実務者にとって、MCPは特に魅力的な価値提案をします。すなわち、アクション空間の定義、訓練軌跡の収集、多様な環境への訓練済みエージェントの配置のための、標準化され拡張可能なインターフェースです。エコシステムが成熟し、ベンチマークスイートが登場すれば、MCPはツールを使うエージェント研究の事実上の基盤、LLM時代のgymnasiumになる可能性があります。
Important
MCPの概要
特性 値 ワイヤープロトコル JSON-RPC 2.0 トランスポート stdio、Streamable HTTP 中核プリミティブ ツール、リソース、プロンプト、サンプリング セッションモデル 状態を持つ(永続接続) ツールスキーマ形式 JSON Schema(Draft 7) セキュリティモデル Hostが適用する同意+信頼階層 主なユースケース 標準化されたLLM \(\leftrightarrow\) ツール統合 RLとの関連 標準化されたアクション空間+軌跡記録 公式SDK Python、TypeScript(Node.js) ライセンス オープン標準(MIT)
エージェントスキル
エージェントがモノリシックなプロンプト・ツールシステムからモジュール型アーキテクチャへ進化するにつれて、重要な設計課題が浮かび上がります。エージェントの能力をどのように整理し、発見し、組み合わせるべきでしょうか。 その答えは、再訓練なしに読み込み、組み合わせ、入れ替えられる、離散的で再利用可能な振る舞いの単位である スキル という概念へ、ますます収束しています。
この考え方はVoyager(G. Wang et al. 2023)によって広まりました。Voyagerは、Minecraft内のLLMエージェントが、実行可能なコードスキルのライブラリを増やし、それぞれを検証して後で再利用できるよう保存できることを示しました。同じ原理は本番エージェントにも当てはまります。スキルは、単一のプロンプトが保持できる範囲を超えてスケールする、組み合わせ可能でバージョン管理可能な形式に分野の専門知識をカプセル化します。スキルはツールアクセスのためにMCPサーバー(章7)をラップすることが多く、スキル抽象化を標準化されたツール層へ接続します。
スキルとは何か
スキル とは、特定の分野やタスクに関する専門性をエージェントに与える、自己完結型の能力モジュールです。単一の関数を公開する生のツールとは異なり、スキルは次を含みます。
-
システムプロンプトの拡張: エージェントのコンテキストへ注入する、分野固有の指示、制約、ペルソナ要素。
-
ツールバインディング: スキルが必要とする1つ以上のツール(API、MCPサーバー、ローカルコマンド)。
-
知識: エージェントがスキルを正しく実行するために必要な参考資料、例、少数ショットのデモンストレーション。
-
ワークフローロジック: 複雑なタスクでエージェントを導く、多段階手順、決定木、条件分岐フロー。
-
ガードレール: スキル固有の安全制約、出力形式の要件、検証ルール。
Important
スキル、ツール、エージェントの比較
概念 範囲 例 ツール 単一の関数呼び出し web_search(query)スキル 一貫した能力(プロンプト+ツール+知識) 「リサーチアナリスト」スキル エージェント 複数スキルを持つ自律的な主体 コーディングアシスタント ツールはハンマーです。スキルは家をどう建てるかを知っていることです。エージェントは、どのスキルを適用するか選ぶ大工です。
スキルアーキテクチャのパターン
静的なスキル読み込み
最も単純なパターンです。設定に基づいてエージェントの初期化時にスキルを読み込みます。エージェントは常にすべてのスキルへアクセスできます。
# Pseudocode -- framework-agnostic pattern
agent = Agent(
model="claude-sonnet-4-20250514",
skills=["code-review", "documentation", "testing"],
# Each skill adds prompts, tools, and knowledge to the agent
)
長所: 単純で予測可能、低レイテンシです。
短所: スキルを使わない場合にコンテキストウィンドウを浪費し、数百のスキルへはスケールしません。
動的なスキル発見
エージェントは現在のタスクに基づいて、どのスキルを有効化するか選びます。スキルルーター(軽量な分類器や埋め込みベースのマッチャーであることが多い)が関連性を判定します。
# Pseudocode -- framework-agnostic pattern
relevant_skills = skill_router.match(
user_request=message,
available_skills=skill_registry,
max_skills=3
)
agent.activate(relevant_skills)
長所: 大規模なスキルライブラリへスケールし、コンテキスト効率が高いことです。
短所: ルーティングエラーで関連スキルを見逃す可能性があり、レイテンシが増えます。
階層的なスキル合成
スキルは他のスキルに依存してDAGを形成できます。高レベルのスキル(例: 「アプリケーションを配置」)が、サブスキル(「テストを実行」、「Dockerイメージをビルド」、「DNSを更新」)を呼び出すことがあります。
-
スキルは依存関係を明示的に宣言します。
-
オーケストレータは実行前に依存グラフを解決します。
-
サブスキルは複数の親スキルで共有できます。
ケーススタディ:Anthropicのエージェント設計
Anthropicのエージェントアーキテクチャへのアプローチ(Anthropic 2024a)は、本番環境におけるスキルベースのエージェント設計を最も明確に説明するものの1つです。その思想は、 複雑さより単純さ 、 モノリシックなフレームワークより組み合わせ可能な構成要素 を重視します(これらのパターンは、オーケストレーションの観点からも章5で扱います)。
中核原則
-
最も単純な解決策から始めます。 より単純なアプローチ(単一のLLM呼び出し、検索+生成)を試して不十分だと分かるまで、エージェントパターンに手を伸ばしません。
-
ワークフローとエージェント。 Anthropicは次を区別します。
-
ワークフロー: LLM呼び出しの事前定義されたオーケストレーション。特定のノードにLLMステップを持つ決定論的な制御フローです。より予測可能で、デバッグも容易です。
-
エージェント: LLMが次に行うことを動的に決定します。ツール選択、反復回数、停止基準のすべてがモデル駆動です。より柔軟ですが、制御は難しくなります。
-
-
拡張LLMを原子単位とします。 プリミティブは決して裸のモデルではなく、検索ソース、呼び出し可能なツール、永続メモリを束ねたモデルです。この複合単位は、実際にはスキルを備えたモデルです。
構成要素のパターン
Anthropicは、スキルテンプレートとして機能する5つの組み合わせ可能なワークフローパターンを特定しています。
| パターン | 仕組み | 使う場面 |
|---|---|---|
| プロンプトチェーン | 各ステップの出力を次のステップへ渡す、LLM呼び出しの逐次実行。ステップ間のゲートが中間結果を検証します。 | 分解が明確な多段階変換 |
| ルーティング | 分類器またはLLMが、タスク種別に基づいて入力を専門ハンドラ(スキル)へ振り分けます。 | 異なる専門性が必要な明確なタスクカテゴリ |
| 並列化 | 複数のLLM呼び出しを同時実行します。分割(タスクを分ける)または投票(同じタスクを集約)のいずれかです。 | 独立したサブタスク、または合意による信頼度向上 |
| オーケストレータ・ワーカー | 中央LLMがタスクをサブタスクへ分け、ワーカーLLMに委譲し、結果を統合します。 | サブタスクを事前に予測できない複雑なタスク |
| 評価器・オプティマイザ | 1つのLLMが生成し、別のLLMが評価します。品質しきい値に達するまで反復します。 | 品質基準が明確なタスク(コード、文章) |
Anthropicの組み合わせ可能なエージェントパターンです。
拡張LLM
Anthropicの捉え方では、基本単位は裸のモデルではなく 拡張LLM です。
\[ \text{Augmented LLM} = \text{Model} + \text{Retrieval} + \text{Tools} + \text{Memory} \]
これはスキルの概念に直接対応します。各スキルは、特定のタスクでモデルがアクセスできる検索ソース、ツール、メモリストアを設定します。スキル境界は、特定の呼び出しの中でモデルが見たり実行したりできることを定義します。
実務上の含意
Tip
Anthropicの重要な洞察
最も効果的なエージェントは、最も複雑なエージェントではありません。 優れたツールを持つ単純なループ です。
while not done: action = llm.decide(context, tools) result = execute(action) context.append(result) done = llm.should_stop(context)知性は、手の込んだオーケストレーションロジックではなく、(1)モデルの能力、(2)ツール説明の品質、(3)タスクの枠組みの明確さから生まれます。スキルは(2)と(3)の構造を提供します。
Anthropicのアプローチから得られる設計上の推奨事項
-
エージェントループを単純に保つ: 制御フローを過剰設計せず、モデルに判断させます。
-
ツールの品質に投資する: 詳細で曖昧さのないツール説明は、複雑なルーティングロジックより価値があります。
-
構造化出力を使う: モデルにパース可能な形式(JSON、関数呼び出し)で判断を出力させ、スキル実行エラーを減らします。
-
復旧を組み込む: スキルは、異なるパラメータで再試行する、明確化を求める、人間へエスカレーションするなど、エラーを適切に処理すべきです。
-
スキルごとの範囲を制限する: 何でもしようとするスキルは、何もうまくできません。狭く明確に定義されたスキルの方が、広範なスキルより組み合わせやすくなります。
スキルのライフサイクル
-
発見: システムが利用可能なスキルを特定します(レジストリ、マーケットプレイス、ローカル定義)。
-
選択: ユーザー要求に基づき、関連スキルをマッチングして読み込みます。
-
有効化: スキルのプロンプト、ツール、知識をエージェントのコンテキストへ注入します。
-
実行: エージェントがスキルの能力を使ってタスクを達成します。
-
無効化: 後続タスクのためにコンテキストウィンドウの空間を空けるべく、スキルのコンテキストを削除します。
-
学習: 実行結果によって、スキルの少数ショット例を更新したり、ルーティングをファインチューニングしたりできます。
スキルレジストリとマーケットプレイス
本番用のスキルシステムには、次のインフラが必要です。
-
スキルマニフェスト: 自動発見とルーティングを可能にする構造化された説明(名前、能力、必要なツール、入出力スキーマ)。
-
バージョン管理: スキルは進化するため、エージェントは再現性のために特定バージョンを固定する必要があります。
-
依存関係の解決: スキルは特定のMCPサーバー、APIキー、他のスキルを必要とする場合があります。
-
権限モデル: すべてのエージェントがすべてのスキルへアクセスすべきとは限りません(セキュリティ、コスト、能力の境界)。
-
マーケットプレイス: 組織がスキルを公開、共有、インストールできます。コード向けパッケージマネージャーに相当します。
Note
スキルマニフェストの例
スキルマニフェストは、オーケストレータがスキルを読み込み、呼び出すために必要なすべてを宣言します。業界標準のスキーマはまだ存在しません。以下は、実際の実装(Anthropic MCP、OpenAIの関数仕様、LangChainのツール定義)に共通するフィールドを捉えた説明用の形式です。
// Illustrative schema -- not a specific SDK format { "name": "code-review", "description": "Review code changes for bugs, style, and security issues", "version": "2.1.0", "requires": { "tools": ["file_read", "grep", "git_diff"], "mcp_servers": ["github"], "models": ["claude-sonnet-4-20250514"] }, "input_schema": { "type": "object", "properties": { "repo": {"type": "string"}, "pr_number": {"type": "integer"} } }, "prompts": ["skills/code-review/system.md"], "knowledge": ["skills/code-review/style-guide.md"] }
スキルとファインチューニングの比較
自然な疑問は、なぜモデルをファインチューニングする代わりに実行時のスキル注入を使うのか、ということです。
| 観点 | スキル(コンテキスト内) | ファインチューニング |
|---|---|---|
| 配置速度 | 即時 | 数時間〜数日 |
| 柔軟性 | 実行時に入れ替え/組み合わせ | 訓練時に固定 |
| コンテキストコスト | コンテキストウィンドウを使用 | 実行時コストゼロ |
| 深い挙動変化 | コンテキスト長に制限される | 深いパラメータ変化 |
| マルチテナント | ユーザーごとに異なるスキル | 全員で同じモデル |
| 保守 | テキストファイルを更新 | 新しいデータで再訓練 |
能力追加におけるスキル(コンテキスト内)とファインチューニングの比較。
実際には、この2つのアプローチは補完的です。ファインチューニングが基礎能力(指示追従、ツール利用形式、推論)を提供し、スキルが実行時にその上へタスク固有の専門性を重ねます。
エージェント間通信(A2A)
大規模言語モデルが孤立したアシスタントから専門エージェントの協調ネットワークへ進化するにつれ、エージェント同士がどのように会話するかという問いは、内部でどのように推論するかと同じくらい重要になります。この節では、単一のエージェントでは単独で扱えない問題をマルチエージェントシステムが調整し、委譲し、協力して解決できるようにするプロトコル、パターン、エンジニアリング実践を扱います。
動機:エージェントが通信しなければならない理由
Tip
専門化の必然性
単一の汎用エージェントは、知識の広さと能力の深さという根本的な緊張関係に直面します。現実世界のタスク、たとえば法務文書レビュー、多段階の科学研究、エンタープライズソフトウェア開発は、その両方を求めます。エージェント間通信は、専門家のネットワークが協調できるようにすることでこの緊張関係を解消します。各エージェントが強みを提供し、弱みは委譲します。
構造化されたエージェント間通信が必要になる要因はいくつかあります。
認知負荷とコンテキストの限界
すべてのLLMは有限のコンテキストウィンドウ内で動作します。数百の文書、ツール呼び出し、推論ステップにまたがる複雑なワークフローは、単一のエージェントがメモリに保持できる量をすぐに超えます。タスクを複数のエージェントに分解すれば、各エージェントは扱いやすいコンテキスト内で動作し、オーケストレーションを担うエージェントは高レベルの状態だけを維持できます。
専門化と専門知識
異なるエージェントを特定分野向けにファインチューニングしたり、プロンプトで誘導したり、ツールを装備したりできます。たとえば、コンパイラとテストランナーにアクセスできるCodeAgent、判例データベースにアクセスできるLegalAgent、統計ライブラリを持つDataAgentです。サブタスクを適切な専門家へルーティングすることで、品質と効率の両方が向上します。
並列性とスループット
独立したサブタスクは、複数のエージェントへ同時にディスパッチできます。研究オーケストレータは、5つの専門エージェントへ文献検索を並列にファンアウトし、その結果を統合することで、実時間を大幅に短縮できます。
障害分離とレジリエンス
1つのエージェントが失敗しても、適切に設計されたマルチエージェントシステムなら、別のエージェントで再試行したり、より単純な方法へフォールバックしたり、人間へエスカレーションしたりできます。ワークフロー全体を崩壊させる必要はありません。
委譲とハンドオフ
長時間実行されるタスクでは、コンテキストの変化に応じてエージェント間でハンドオフが必要になる場合があります。最初のPlannerAgentが目標を分解し、サブタスクをExecutorAgentsへ渡し、最後にReviewerAgentが出力を検証します。各エージェントは必要なコンテキストだけを正確に受け取ります。
Important
A2A通信の中核要件
発見可能性: エージェントは他のエージェントを見つけ、その能力を理解できなければなりません。
相互運用性: 異なるチームやベンダーが構築したエージェントが共通プロトコルで通信できなければなりません。
非同期性: 長時間タスクが呼び出し元をブロックしてはならず、結果はコールバックまたはポーリングで到着しなければなりません。
セキュリティ: エージェント同士が認証し、認可境界を適用しなければなりません。
可観測性: デバッグと監査のため、すべてのメッセージ交換を追跡可能にしなければなりません。
Google A2Aプロトコル
2025年4月、Googleは50社を超えるテクノロジーパートナーの協力を得て、AIエージェント間の相互運用可能な通信のためのオープン仕様である Agent-to-Agent(A2A)Protocol (Google 2025)を公開しました。その後、このプロトコルは Linux Foundation へ寄贈され、2026年時点で支持組織は150を超えています。A2Aは、従来の場当たり的なアプローチと区別される一連の中核原則に基づいて設計されています。
設計思想
A2A仕様は5つの指針原則を示しています(公式仕様(Google 2025)§1.2をもとにしています)。
Important
A2Aの設計原則
不透明な実行
呼び出し元のエージェントはリモートエージェントの内部を決して検査せず、宣言されたインターフェースだけを介して相互作用します。対象がGPT-4、Gemini、ルールベースシステムのいずれであるかはプロトコルにとって重要ではなく、真に異種混在のエージェントエコシステムを可能にします。
エンタープライズ対応
認証(OAuth 2.0、APIキー、JWT)、監査ログ、規制遵守は後付けではなく、最初からプロトコルレベルに統合されています。
モダリティ非依存
1つのメッセージにテキスト、バイナリファイル、構造化JSONペイロードを組み合わせられます。これにより、画像、音声、コード、文書を扱うエージェントをプロトコル拡張なしに受け入れられます。
既存標準による単純さ
新しいトランスポートを発明するのではなく、A2AはJSON-RPC 2.0メッセージ付きのHTTP/HTTPS、ストリーミング用のServer-Sent Events(SSE)、代替バインディングとしてのgRPCを再利用します。いずれも、すべてのインフラチームがすでに運用している技術です。
非同期優先のタスクモデル
長時間実行される操作は例外ではなく、通常です。プッシュ通知とポーリングの両方が第一級の仕組みであるため、呼び出し元が何時間も接続を開いたままにする必要はありません。
エージェントカード
A2Aの発見可能性の基盤は エージェントカード です。これは既知のエンドポイント(/.well-known/agent.json)でホストされる機械可読なJSONマニフェストです。エージェントができること、認証方法、タスクの送信先を宣伝します。RESTエンドポイントではなく自律エージェント向けのOpenAPI仕様に相当します。
Note
エージェントカードの構造
# Agent Card served at https://agent.example.com/.well-known/agent.json agent_card = { "name": "DataAnalysisAgent", "description": "Analyzes structured datasets, produces statistical summaries, " "generates visualizations, and answers data questions.", "url": "https://agent.example.com/a2a", "version": "1.2.0", "capabilities": { "streaming": True, "pushNotifications": True, "stateTransitionHistory": True }, "authentication": { "schemes": ["Bearer", "ApiKey"] }, "skills": [ { "id": "statistical-analysis", "name": "Statistical Analysis", "description": "Compute descriptive statistics, run hypothesis tests, " "fit regression models on tabular data.", "tags": ["statistics", "data", "analysis", "regression"], "examples": [ "What is the correlation between columns A and B?", "Run a t-test comparing these two groups.", "Fit a linear regression predicting sales from ad spend." ], "inputModes": ["text", "data"], "outputModes": ["text", "data", "file"] }, { "id": "visualization", "name": "Data Visualization", "description": "Generate charts, plots, and dashboards from data.", "tags": ["charts", "plots", "visualization", "dashboard"], "examples": [ "Create a bar chart of monthly revenue.", "Plot the distribution of customer ages." ], "inputModes": ["text", "data"], "outputModes": ["file", "text"] } ], "defaultInputModes": ["text"], "defaultOutputModes": ["text"] }
エージェントカードは能力ベースのルーティングを可能にします。オーケストレータエージェントはレジストリからカードを取得し、サブタスクを最も適切なエージェントへ意味的にマッチさせ、ハードコードされたルーティングロジックなしにディスパッチできます。
タスクのライフサイクル
A2Aはすべての作業を タスク としてモデル化します。タスクは明確に定義された状態機械を進みます。
submitted
クライアントがタスクを送り、サーバーが受信を確認した状態です。
working
エージェントが処理中の状態です。クライアントはポーリングするか、SSEイベントを待ちます。
input-required
エージェントが処理を続ける前に、ユーザーまたは呼び出し元エージェントから追加情報を必要とする状態です(例: 明確化の質問、欠けている認証情報)。
completed
タスクが正常に完了し、応答で結果を利用できる状態です。
failed
回復不能なエラーが発生し、エラーメッセージが原因を説明する状態です。
rejected
エージェントがタスクを拒否した状態です(例: 能力の範囲外、または未認可)。A2A v1.0で追加されました。
canceled
クライアントまたはサーバーによってタスクが中断された状態です。
Server-Sent Eventsによるストリーミング
増分出力を生成するタスク(例: 長いレポートの執筆、コードファイルの生成)では、A2Aは Server-Sent Events(SSE) を使います。クライアントは永続HTTP接続を開き、JSONイベントのストリームを受け取ります。
Note
SSEイベントストリームの例
# Each SSE event carries a TaskStatusUpdateEvent or TaskArtifactUpdateEvent # Example stream for a "write a research report" task: # Event 1: status update data: { "id": "task-abc123", "status": {"state": "working"}, "final": false } # Event 2: partial artifact (streaming text) data: { "id": "task-abc123", "artifact": { "parts": [{"type": "text", "text": "## Introduction\n\nRecent advances in..."}], "index": 0, "append": false, "lastChunk": false }, "final": false } # Event 3: more text appended data: { "id": "task-abc123", "artifact": { "parts": [{"type": "text", "text": " reinforcement learning have shown..."}], "index": 0, "append": true, # append to existing artifact "lastChunk": false }, "final": false } # Final event: task complete data: { "id": "task-abc123", "status": {"state": "completed"}, "final": true }
長時間タスク向けのプッシュ通知
タスクに数分から数時間かかる可能性がある場合、SSE接続を開いたまま維持するのは現実的ではありません。A2Aは プッシュ通知 をサポートします。クライアントがWebhook URLを登録し、サーバーがタスクの進行に応じてステータス更新をPOSTします。
# Client registers a push notification endpoint when submitting the task
task_request = {
"id": "task-xyz789",
"message": {
"role": "user",
"parts": [{"type": "text", "text": "Analyze Q3 sales data and produce a report."}]
},
"pushNotification": {
"url": "https://my-orchestrator.example.com/webhooks/a2a",
"token": "secret-hmac-token-for-verification",
"authentication": {
"schemes": ["Bearer"],
"credentials": "eyJhbGciOiJIUzI1NiJ9..."
}
}
}
# The server will POST TaskStatusUpdateEvent objects to the webhook URL
# as the task transitions through states.
メッセージ形式
A2Aメッセージは、 role (userまたはagent)と、型付き parts (テキスト、ファイル、構造化データ)のリストで構成されます。完全なメッセージスキーマ、マルチモーダルの例、コンテキスト受け渡しの指針は、節9.5で扱います。
認証と認可
A2Aは複数の認証方式をサポートし、エージェントカードで宣言してリクエストごとに適用します。
-
Bearerトークン(JWT/OAuth 2.0): エンタープライズ配置の標準です。トークンがスコープを持ち、呼び出し元エージェントが要求できる内容を制限します。
-
APIキー: 内部環境や信頼された環境向けの、より単純な方式です。
-
相互TLS(mTLS): 高セキュリティ配置向けの証明書ベース認証です。
-
OpenID Connect: 連合アイデンティティにより、組織をまたぐエージェント通信を可能にします。
Warning
認可スコープの適用
タスクを受け取るエージェントは、誰が呼び出しているか(認証)だけでなく、何を要求することが許可されているか(認可)も検証しなければなりません。
ReportingAgentは、認証済みの任意のエージェントから読み取り専用のデータ検索を受け付けつつ、書き込み操作を特定のOAuthスコープを持つエージェントに制限できます。これを適用しないと、マルチエージェントシステムで権限昇格の脆弱性が生じます。
通信パターン
マルチエージェントシステムは、タスクの性質、レイテンシ要件、関与するエージェント数に応じて、さまざまな通信パターンを使います。
リクエスト・レスポンス
最も単純なパターンです。エージェントAがエージェントBへタスクを送り、完全な応答を待ちます。結果を受け取ってから先へ進む、短く明確に定義されたサブタスクに適しています。
ストリーミング
エージェントAがSSE接続を開き、エージェントBが生成した部分結果をストリーミングします。長文生成(レポート、コード)、リアルタイム協調、段階的なUI更新に適しています。
Note
ストリーミングパターンのユースケース
オーケストレータが
WritingAgentに10ページの技術文書の草稿作成を依頼するとします。完全な文書を2分待つ代わりに、オーケストレータは各節が書かれるたびにストリーミングします。これによりReviewAgentは後半の節がまだ生成中でも前半のレビューを始められ、合計レイテンシを40〜60%削減するパイプラインになります。
マルチターンインタラクション
反復的な改善が必要なタスクもあります。エージェントがinput-required状態に入り、オーケストレータが明確化を提供して、タスクが再開します。これは人間の協調ワークフロー、すなわち草稿 \(\to\) フィードバック \(\to\) 改訂を反映します。
# Multi-turn: orchestrator handles input-required state
async def run_multiturn_task(client, initial_message):
task = await client.send_task(message=initial_message)
while task.status.state not in ("completed", "failed", "canceled"):
if task.status.state == "input-required":
# Agent needs clarification
clarification_needed = task.status.message
print(f"Agent asks: {clarification_needed}")
# Orchestrator generates or forwards a clarifying response
user_reply = await get_clarification(clarification_needed)
# Send the reply to continue the task
task = await client.send_task(
task_id=task.id,
message={"role": "user",
"parts": [{"type": "text", "text": user_reply}]}
)
else:
# Still working --- poll after a delay
await asyncio.sleep(2)
task = await client.get_task(task.id)
return task
ブロードキャスト
オーケストレータが同じメッセージを複数のエージェントへ同時に送り、告知、共有コンテキストの配布、独立したワークフローの並列起動に使います。
パブリッシュ・サブスクライブ(Pub-Sub)
エージェントがイベントチャネル(例: new-document-uploaded、model-retrained)を購読します。イベントが発生すると、購読しているすべてのエージェントに通知されます。これによりプロデューサーとコンシューマーが分離され、リアクティブでイベント駆動のアーキテクチャが可能になります。
ネゴシエーション
2つのエージェントが提案と反対提案を交換し、計画、リソース配分、アプローチについて合意します。エージェントごとに異なる目的や制約があるマルチエージェント計画システムで一般的です。
Note
ネゴシエーションパターン
PlannerAgentが5段階の研究計画を提案します。ResourceAgentは、ステップ3(大規模シミュレーションの実行)が計算予算を超えると応答します。PlannerAgentは縮小版シミュレーションを反対提案し、ResourceAgentが承認します。合意された計画は実行エージェントへディスパッチされます。
オークションベースのタスク割り当て
オーケストレータが要件付きでタスクを告知し、候補エージェントが入札(推定完了時間、信頼度、コスト)を提出し、オーケストレータが落札エージェントへタスクを割り当てます。エージェントプール全体で、動的かつ市場ベースの負荷分散が可能になります。
| パターン | レイテンシ | 適する用途 |
|---|---|---|
| リクエスト・レスポンス | 低 | 短く明確なサブタスク |
| ストリーミング | 低(最初のトークン) | 長文生成、リアルタイムUI |
| マルチターン | 中 | 明確化が必要な曖昧なタスク |
| ブロードキャスト | 低 | 共有コンテキストの配布 |
| Pub-Sub | 可変 | イベント駆動のリアクティブワークフロー |
| ネゴシエーション | 中〜高 | リソース制約下の計画 |
| オークション | 中 | 動的な負荷分散 |
A2A通信パターンの概要。
エージェントの発見とルーティング
エージェントが別のエージェントと通信するには、まず相手を見つける必要があります。エージェントの発見とは、特定のタスクを処理できるエージェントを探すプロセスです。
エージェントレジストリ
エージェントレジストリ は、エージェントカードをインデックス化し、検索・参照APIを提供するディレクトリサービスです。配置モデルは2つあります。
集中型レジストリ
単一の権威あるレジストリ(例: エンタープライズサービスカタログ)がすべてのエージェントをインデックス化します。運用は単純ですが、単一障害点を作り、組織をまたぐ配置にはスケールしない可能性があります。
連合型レジストリ
複数のレジストリがそれぞれ分野または組織に対して権威を持ち、レジストリ間検索プロトコルで連携します。よりレジリエントでプライバシーを保護できますが、標準化された連合プロトコルが必要です。
能力ベースのルーティング
エージェントURLをハードコードする代わりに、オーケストレータは 能力ベースのルーティング を行います。必要なスキルに一致するエージェントをレジストリへ問い合わせ、最も適したものを選択します。
class AgentRouter:
"""Routes tasks to agents based on capability matching."""
def __init__(self, registry_url: str):
self.registry_url = registry_url
self._cache: dict[str, list[AgentCard]] = {}
async def find_agents(self, required_skill: str,
tags: list[str] | None = None) -> list[AgentCard]:
"""Query registry for agents with the required skill."""
params = {"skill": required_skill}
if tags:
params["tags"] = ",".join(tags)
async with httpx.AsyncClient() as client:
resp = await client.get(f"{self.registry_url}/agents", params=params)
return [AgentCard(**card) for card in resp.json()["agents"]]
async def route(self, task_description: str) -> AgentCard:
"""Semantically match a task description to the best available agent."""
# Embed the task description
task_embedding = await embed(task_description)
# Fetch all registered agents
all_agents = await self.find_agents(required_skill="*")
# Score each agent by cosine similarity of task to agent description
scored = []
for agent in all_agents:
agent_embedding = await embed(agent.description)
score = cosine_similarity(task_embedding, agent_embedding)
scored.append((score, agent))
# Return the highest-scoring agent
scored.sort(key=lambda x: x[0], reverse=True)
return scored[0][1]
同等エージェント間の負荷分散
複数のエージェントが同じ能力を提供する場合、ルーターは負荷を分散しなければなりません。一般的な戦略は次のとおりです。
-
ラウンドロビン: 利用可能なすべてのエージェントにタスクを均等に分配します。
-
最小負荷: アクティブなタスクが最も少ないエージェントへルーティングします(ヘルス/メトリクスエンドポイントが必要)。
-
レイテンシ考慮: 直近の応答時間が最も短いエージェントへルーティングします。
-
アフィニティベース: 関連タスクを同じエージェントへルーティングし、キャッシュされたコンテキストを活用します。
バージョン管理と互換性
エージェントカードにはversionフィールドがあります。オーケストレータは最低バージョン要件を指定し、古いバージョンしか利用できない場合には適切に縮退すべきです。セマンティックバージョニング(Preston-Werner 2024)(MAJOR.MINOR.PATCH)が推奨されます。互換性を壊すインターフェース変更ではMAJORを、新しい能力の追加ではMINORを増やします。
Warning
長時間稼働システムにおけるバージョンのずれ
本番のマルチエージェントシステムでは、異なるエージェントが異なる時期に更新され、バージョンのずれが生じることがあります。Agent Card v2.1向けにコンパイルされたオーケストレータが、v1.3でまだ動作しているエージェントに遭遇する可能性があります。常に後方互換のメッセージ処理を実装し、バージョンをまたぐシナリオを明示的にテストしてください。
メッセージ形式とスキーマ
構造化メッセージ対非構造化メッセージ
A2Aは、完全に非構造化されたもの(プレーンテキスト)から完全に構造化されたもの(型付きJSONスキーマ)までの幅をサポートします。適切な選択は関与するエージェントによって異なります。
| メッセージ種別 | 利点 | 欠点 |
|---|---|---|
| プレーンテキスト | 柔軟で人間が読みやすく、生成しやすい | 確実なパースが難しく、スキーマ検証がない |
| 構造化JSON | 機械でパース・検証でき、型付き | スキーマの合意が必要で、柔軟性が低い |
| ハイブリッド(テキスト+データ) | 人間が読める意図+機械でパースできるペイロード | 構築とパースがより複雑 |
構造化A2Aメッセージと非構造化A2Aメッセージのトレードオフ。
マルチモーダルメッセージ
A2Aメッセージは、 role (userまたはagent)と型付き parts のリストとして構造化されます。
| パート種別 | フィールド | ユースケース |
|---|---|---|
TextPart | text: string | 自然言語の指示、応答 |
FilePart | mimeType、uriまたはbytes | 文書、画像、音声、コードファイル |
DataPart | data: object | 構造化JSON(ツール結果、スキーマ) |
A2Aメッセージのパート種別(ワイヤーフォーマットでは"type": "text"|"file"|"data"を使います)。
現代のエージェントは、テキスト以外のモダリティを扱うことが増えています。A2AのFilePartは任意のMIMEタイプをサポートし、豊かなマルチモーダルワークフローを可能にします。
Note
マルチモーダルA2Aメッセージ:データ分析
# A message combining text instructions with a data payload and a file message = { "role": "user", "parts": [ { "type": "text", "text": "Analyze the attached CSV and the schema below. " "Identify anomalies and produce a summary report." }, { "type": "file", "mimeType": "text/csv", "uri": "https://storage.example.com/data/sales_q3.csv" }, { "type": "data", "data": { "schema": { "columns": ["date", "region", "product", "revenue", "units"], "types": ["date", "string", "string", "float", "int"] }, "expectedRowCount": 15000, "anomalyThreshold": 3.0 # z-score threshold } } ] }
Note
マルチモーダルA2Aメッセージ:画像分析
# Multi-modal message: text + image + structured data multimodal_message = { "role": "user", "parts": [ {"type": "text", "text": "Describe what is wrong with this chart and suggest fixes."}, {"type": "file", "mimeType": "image/png", "bytes": base64.b64encode(chart_image_bytes).decode()}, {"type": "data", "data": { "chartType": "bar", "dataSource": "Q3 Revenue by Region", "knownIssues": ["y-axis does not start at zero", "missing error bars"] }} ] }
コンテキストの受け渡し:共有するものと非公開にするもの
マルチエージェントシステムにおける重要な設計判断はコンテキストスコープです。会話履歴と内部状態をどの程度サブエージェントへ渡すかを決めます。
Important
コンテキストスコープの原則
最小限のコンテキスト
サブエージェントがタスクを完了するために必要なものだけを渡します。トークン使用量、レイテンシ、機密情報を漏らすリスクを減らします。
要約されたコンテキスト
生の会話履歴を渡す代わりに、目標、制約、決定事項、関連事実からなる構造化要約を渡します。
非公開状態
内部推論、中間草稿、ユーザーの個人識別情報は、明示的に必要でない限り、通常サブエージェントへ転送すべきではありません。
相関ID
常に
correlationIdを渡し、サブエージェントのアクションをログと監査証跡で元のワークフローまで追跡できるようにします。
会話スレッドと相関ID
複雑なワークフローでは、多数のタスクが同時進行することがあります。 相関ID がエージェント間の関連タスクをつなぎます。
import uuid
class WorkflowContext:
"""Carries correlation metadata through a multi-agent workflow."""
def __init__(self, workflow_id: str | None = None):
self.workflow_id = workflow_id or str(uuid.uuid4())
self.span_id = str(uuid.uuid4())
self.parent_span_id: str | None = None
def child_context(self) -> "WorkflowContext":
"""Create a child context for a sub-task."""
child = WorkflowContext(workflow_id=self.workflow_id)
child.parent_span_id = self.span_id
return child
def to_metadata(self) -> dict:
return {
"x-workflow-id": self.workflow_id,
"x-span-id": self.span_id,
"x-parent-span-id": self.parent_span_id
}
# Usage: attach to every A2A task submission
ctx = WorkflowContext()
task = await client.send_task(
message=message,
metadata=ctx.to_metadata()
)
# Sub-tasks use child contexts for tracing
sub_ctx = ctx.child_context()
調整プロトコル
ポイントツーポイント通信に加え、マルチエージェントシステムは、集団的な意思決定と問題解決を可能にする構造化された相互作用パターンである、より高レベルの 調整プロトコル から恩恵を受けます。
Contract Netプロトコル
Contract Net Protocol(CNP) (Smith 1980)は、LLMベースのシステム向けに適応された古典的なマルチエージェント調整メカニズムです。
-
告知: マネージャーエージェントが、タスク要件と評価基準を含むタスク告知を、すべての候補請負エージェントへブロードキャストします。
-
入札: 請負エージェントが自分の能力に照らしてタスクを評価し、推定完了時間、信頼度、リソース要件を含む入札を提出します。
-
落札: マネージャーが落札入札(または並列サブタスク向けの複数入札)を選び、契約を割り当てます。
-
実行と報告: 請負エージェントがタスクを実行し、結果をマネージャーへ報告します。
Note
Contract Netプロトコルの実装
import dataclasses class ContractNetManager: """Implements the Contract Net Protocol for task allocation.""" async def allocate_task(self, task: Task, candidate_agents: list[AgentCard]) -> AgentCard: # Phase 1: Announce task to all candidates announcement = { "type": "task-announcement", "task": dataclasses.asdict(task), "deadline": (datetime.now(timezone.utc) + timedelta(seconds=10)).isoformat(), "evaluationCriteria": ["confidence", "estimatedTime", "cost"] } # Phase 2: Collect bids bids = await asyncio.gather(*[ self._request_bid(agent, announcement) for agent in candidate_agents ], return_exceptions=True) valid_bids = [(agent, bid) for agent, bid in zip(candidate_agents, bids) if not isinstance(bid, Exception) and bid is not None] if not valid_bids: raise RuntimeError(f"No agents bid on task {task.id}") # Phase 3: Award to best bidder (highest confidence, lowest time) def score_bid(agent_bid): _, bid = agent_bid return bid["confidence"] - 0.1 * bid["estimatedSeconds"] winner_agent, winning_bid = max(valid_bids, key=score_bid) # Notify winner and losers await self._award_contract(winner_agent, task) await asyncio.gather(*[ self._reject_bid(agent, task.id) for agent, _ in valid_bids if agent != winner_agent ]) return winner_agent async def _request_bid(self, agent: AgentCard, announcement: dict) -> dict | None: """Ask an agent to bid on a task.""" try: result = await self.client.send_task( agent_url=agent.url, message={"role": "user", "parts": [{"type": "data", "data": announcement}]} ) return result.artifacts[0].parts[0]["data"] except Exception: return None
ブラックボードシステム
ブラックボードシステム (Hayes-Roth 1985)は、エージェントが部分解、観測、仮説を投稿する共有ワークスペース(「ブラックボード」)を提供します。他のエージェントはブラックボードを監視し、価値を加えられるときに貢献します。機会主義的な問題解決アプローチです。
ブラックボードシステムは、解決経路が事前に分からず、異なるエージェントが異なる段階で貢献できる問題に適しています。科学的仮説の生成、複雑なデバッグ、複数ソースのインテリジェンス分析などが例です。
合意形成プロトコル
複数のエージェントが意思決定(例: どの計画を実行するか、結果が正しいか)について合意しなければならない場合、 合意形成プロトコル が構造化された投票メカニズムを提供します。
単純多数決
各エージェントが投票し、\(> 50%\) の票を得た選択肢が勝ちます。高速ですが、エージェントが同じベースモデルを共有すると相関した誤りに弱くなります。
重み付き投票
投票にエージェントの信頼度や過去の正確性に基づく重みを付けます。より堅牢ですが、較正された信頼度推定が必要です。
定足数ベース
意思決定には、\(k\) エージェント、すなわち \(n\) エージェントのうち少なくともその数の合意が必要です。最大 \(n-k\) エージェントが失敗または不同意でもブロックしない、フォールトトレランスを提供します。
デルファイ法
エージェントが投票し、匿名化された結果を見て投票を修正し、収束するまで繰り返します。アンカリングバイアスを減らし、本物の熟議を促します。
async def quorum_vote(agents: list[AgentCard], question: str,
options: list[str], quorum: int) -> str | None:
"""Run a quorum vote across agents. Returns winning option or None."""
votes = await asyncio.gather(*[
ask_agent_to_vote(agent, question, options)
for agent in agents
])
counts: dict[str, int] = {}
for vote in votes:
if vote in options:
counts[vote] = counts.get(vote, 0) + 1
# Return first option that reaches quorum
for option, count in sorted(counts.items(), key=lambda x: -x[1]):
if count >= quorum:
return option
return None # No quorum reached
リーダー選出
動的なマルチエージェントシステムでは、実行時に リーダー (オーケストレータ)を選出する必要がある場合があります。たとえば、元のオーケストレータが失敗した場合や、事前にコーディネータを割り当てずにエージェントが自己組織化する場合です。古典的な分散システムアルゴリズム(Bully、Ring)をエージェントネットワーク向けに適応し、エージェントが能力スコアや優先度トークンを交換して、利用可能な最も能力の高いエージェントをリーダーに選出できます。
A2AとMCP:補完的なプロトコル
A2Aと Model Context Protocol(MCP) (Anthropic 2024b)の関係は、よく混乱を招きます。これらのプロトコルは競合するのではなく、補完的です。
Important
中核的な違い
MCP は垂直プロトコルです。エージェントをデータベース、API、ファイルシステム、コード実行器の世界へ下向きに拡張します。推論するのはエージェントだけで、MCPエンドポイントは決定論的なサービスです。
A2A は水平プロトコルです。1つの推論エージェントを別の推論エージェントへ接続します。両側が推論、計画、ツール利用のできる知的主体です。
| 観点 | MCP | A2A |
|---|---|---|
| 参加者 | Agent \(\leftrightarrow\) Tool/Resource | Agent \(\leftrightarrow\) Agent |
| 知性 | 一方(エージェント)が知的 | 両側が知的 |
| 状態性 | 通常はステートレスなツール呼び出し | ライフサイクルを持つ状態付きタスク |
| ストリーミング | 限定的(ツール結果) | 第一級のSSEストリーミング |
| 発見 | ツールマニフェスト | エージェントカード |
| 認証モデル | サーバー制御 | 相互、OAuth 2.0 |
| 典型的レイテンシ | ミリ秒 | 数秒〜数分 |
| ユースケース | 「ウェブを検索」、「SQLを実行」 | 「専門家へ委譲」 |
どちらを使うか
-
リモートエンドポイントが決定論的な関数の場合は MCP を使います。データベース検索、API呼び出し、コード実行サンドボックスなどです。エージェントが相互作用を完全に制御します。
-
リモートエンドポイントが要求について推論する必要がある場合は A2A を使います。曖昧な指示の解釈、判断、独自ツールの利用、マルチターン対話などです。
-
同じシステムで 両方 を使います。オーケストレータエージェントがA2Aで専門エージェントへ委譲し、各専門エージェントがMCPでツールへアクセスします。
統合アーキテクチャ
本番のマルチエージェントシステムでは、A2AとMCPが異なる層で協調します。 A2A はエージェント間の委譲と調整(ピア間の水平通信)を扱い、 MCP は各エージェントとツール・データソースの接続(能力との垂直統合)を扱います。この関心の分離が、スケール可能なエージェントアーキテクチャを構築する鍵です。
委譲のためのA2A: エージェントが持たない能力を必要とするとき、A2Aタスクメッセージを介して別のエージェントへ委譲します。各エージェントは独自のエージェントカードを持つ自己完結型サービスです。
ツールアクセスのためのMCP: 各エージェントはMCPサーバーを通じてツールへ接続します。つまり、ツールが他のエージェントへ直接公開されることはなく、所有エージェントのインターフェースを通じてのみ公開されます。
信頼境界の分離: オーケストレータは専門エージェントを信頼します(A2A認証で検証)。各専門エージェントは自分のMCPサーバー(ローカルまたは認証済み)を信頼します。推移的なツールアクセスはありません。
独立したスケーリング: コード中心のワークロードはCodeAgentインスタンスを、データワークロードはDataAgentをスケールできます。オーケストレータは軽量なままです。
マルチエージェントシステムのセキュリティと信頼
マルチエージェントシステムには固有のセキュリティ課題があります。エージェントAがエージェントBへ委譲し、BがエージェントCへ委譲する場合、信頼の連鎖を慎重に管理しなければなりません。
エージェントの身元検証
各エージェントは検証可能な身元を持たなければなりません。選択肢には次があります。
-
信頼されたIDプロバイダーが署名した JWTトークン (Jones et al. 2015)。エージェントID、発行者、有効期限を持ち、受信エージェントがプロバイダーの公開鍵で検証します。
-
内部CAが発行する mTLS証明書 (Campbell et al. 2020)。認証とトランスポート暗号化の両方を提供します。
-
単一の信頼された権威が存在しない組織横断シナリオ向けの 分散型識別子(DID) (Consortium 2022)。
メッセージ完全性と暗号化
-
盗聴や中間者攻撃を防ぐため、すべてのA2A通信を TLS 1.3 (Rescorla 2018)上で行うべきです。
-
機密ペイロードには エンドツーエンド暗号化 (例: JWE)を使い、中間インフラ(ロードバランサー、プロキシ)がメッセージ内容を読めないようにします。
-
メッセージ署名 (JWS)は否認防止を提供します。受信エージェントは、特定のメッセージが特定の送信者から来たことを証明できます。
認可スコープ
すべてのエージェントが、他のすべてのエージェントへ何でも依頼できるべきではありません。OAuth 2.0認可スコープ(Hardt 2012)が境界を定義します。
# Example OAuth 2.0 scopes for a DataAgent
SCOPES = {
"data:read": "Read data from connected databases",
"data:write": "Write or modify data in connected databases",
"data:export": "Export data to external systems",
"analysis:run": "Execute statistical analyses",
"analysis:schedule":"Schedule recurring analyses",
"admin:config": "Modify agent configuration"
}
# A ReportingAgent might hold only: data:read, analysis:run
# An ETL pipeline agent might hold: data:read, data:write, data:export
# Only a human admin holds: admin:config
class A2AServer:
def verify_authorization(self, token: str, required_scope: str) -> bool:
"""Verify that the calling agent holds the required scope."""
claims = jwt.decode(token, self.public_key, algorithms=["RS256"])
granted_scopes = claims.get("scope", "").split()
if required_scope not in granted_scopes:
raise PermissionError(
f"Caller lacks required scope '{required_scope}'. "
f"Granted: {granted_scopes}"
)
return True
監査証跡と説明責任
Warning
説明責任の空白
エージェント委譲の連鎖では、アクションの責任者が誰か不明確になることがあります。エージェントAがエージェントBにファイル削除を依頼し、Bが実行した場合、誰が説明責任を負うのでしょうか。すべてのA2A相互作用に、呼び出し元エージェントの身元、タスク説明、使用した認可トークン、タイムスタンプ、結果を記録しなければなりません。この監査証跡は、インシデント対応、コンプライアンス、デバッグに不可欠です。
すべてのA2Aサーバーは構造化監査ログを出力すべきです。
@dataclass
class A2AAuditEvent:
timestamp: str # ISO 8601
workflow_id: str # Correlation ID for the top-level workflow
span_id: str # This task's span
parent_span_id: str # Calling task's span (for delegation chains)
caller_agent_id: str # Verified identity of the calling agent
callee_agent_id: str # This agent's identity
task_id: str
skill_invoked: str
authorization_scopes: list[str]
outcome: str # "completed" | "failed" | "rejected"
duration_ms: int
error_code: str | None
実装例:マルチエージェント研究ワークフロー
次の例は、A2Aを使った完全なマルチエージェント研究ワークフローを示します。OrchestratorAgentが研究課題を分解し、専門エージェントへ委譲し、その結果を統合します。
"""
Multi-agent research workflow using A2A protocol.
Demonstrates: Agent Cards, A2A client/server, task lifecycle,
multi-turn interaction, and agent handoffs.
"""
import asyncio
import json
import uuid
from collections.abc import AsyncIterator
from datetime import datetime, timedelta, timezone
import httpx
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
# -- Data Models --------------------------------------------------------------
class Part(BaseModel):
type: str # "text" | "file" | "data"
text: str | None = None
data: dict | None = None
mimeType: str | None = None
uri: str | None = None
class Message(BaseModel):
role: str # "user" | "agent"
parts: list[Part]
class TaskStatus(BaseModel):
state: str # submitted | working | input-required | completed | failed
message: str | None = None
timestamp: str = Field(
default_factory=lambda: datetime.now(timezone.utc).isoformat()
)
class Artifact(BaseModel):
parts: list[Part]
index: int = 0
append: bool = False
lastChunk: bool = True
class Task(BaseModel):
id: str
status: TaskStatus
messages: list[Message] = []
artifacts: list[Artifact] = []
metadata: dict = {}
# -- A2A Client (HTTP/REST binding) --------------------------------------------
# Note: A2A v1.0 defines three protocol bindings: JSON-RPC 2.0, gRPC, and
# HTTP+JSON/REST. This example uses the REST binding for readability.
class A2AClient:
"""Client for sending tasks to A2A-compliant agents."""
def __init__(self, agent_url: str, auth_token: str):
self.agent_url = agent_url.rstrip("/")
self.headers = {
"Authorization": f"Bearer {auth_token}",
"Content-Type": "application/json"
}
async def get_agent_card(self) -> dict:
"""Fetch the agent's capability card."""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{self.agent_url}/.well-known/agent.json",
headers=self.headers
)
resp.raise_for_status()
return resp.json()
async def send_task(self, message: Message,
task_id: str | None = None,
metadata: dict | None = None) -> Task:
"""Submit a task and return the initial task object."""
payload = {
"id": task_id or str(uuid.uuid4()),
"message": message.model_dump(),
"metadata": metadata or {}
}
async with httpx.AsyncClient() as client:
resp = await client.post(
f"{self.agent_url}/tasks/send",
json=payload,
headers=self.headers,
timeout=30.0
)
resp.raise_for_status()
return Task(**resp.json())
async def stream_task(self, message: Message,
metadata: dict | None = None) -> AsyncIterator[dict]:
"""Submit a task and stream SSE events."""
payload = {
"id": str(uuid.uuid4()),
"message": message.model_dump(),
"metadata": metadata or {}
}
async with httpx.AsyncClient() as client:
async with client.stream(
"POST",
f"{self.agent_url}/tasks/sendSubscribe",
json=payload,
headers={**self.headers, "Accept": "text/event-stream"},
timeout=300.0
) as response:
async for line in response.aiter_lines():
if line.startswith("data: "):
event_data = json.loads(line[6:])
yield event_data
if event_data.get("final"):
break
async def get_task(self, task_id: str) -> Task:
"""Poll for task status."""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{self.agent_url}/tasks/{task_id}",
headers=self.headers
)
resp.raise_for_status()
return Task(**resp.json())
async def wait_for_completion(self, task: Task,
poll_interval: float = 2.0) -> Task:
"""Poll until task reaches a terminal state."""
terminal_states = {"completed", "failed", "canceled"}
while task.status.state not in terminal_states:
await asyncio.sleep(poll_interval)
task = await self.get_task(task.id)
return task
# -- A2A Server (FastAPI) -----------------------------------------------------
class ResearchAgent:
"""
A specialist research agent that searches literature and
summarizes findings on a given topic.
"""
AGENT_CARD = {
"name": "ResearchAgent",
"description": "Searches academic literature and synthesizes research findings.",
"url": "https://research-agent.example.com/a2a",
"version": "1.0.0",
"capabilities": {
"streaming": True,
"pushNotifications": False,
"stateTransitionHistory": True
},
"authentication": {"schemes": ["Bearer"]},
"skills": [{
"id": "literature-search",
"name": "Literature Search",
"description": "Search and summarize academic papers on a topic.",
"tags": ["research", "literature", "academic", "papers"],
"examples": [
"Summarize recent papers on transformer attention mechanisms.",
"What does the literature say about RLHF for code generation?"
],
"inputModes": ["text"],
"outputModes": ["text", "data"]
}]
}
def __init__(self):
self.tasks: dict[str, Task] = {}
self.app = FastAPI(title="ResearchAgent A2A Server")
self._register_routes()
def _register_routes(self):
@self.app.get("/.well-known/agent.json")
async def agent_card():
return self.AGENT_CARD
@self.app.post("/tasks/send")
async def send_task(request: Request):
body = await request.json()
task = await self._create_and_run_task(body)
return task.model_dump()
@self.app.post("/tasks/sendSubscribe")
async def send_subscribe(request: Request):
body = await request.json()
return StreamingResponse(
self._stream_task(body),
media_type="text/event-stream"
)
@self.app.get("/tasks/{task_id}")
async def get_task(task_id: str):
if task_id not in self.tasks:
raise HTTPException(status_code=404, detail="Task not found")
return self.tasks[task_id].model_dump()
async def _create_and_run_task(self, body: dict) -> Task:
task_id = body.get("id", str(uuid.uuid4()))
message = Message(**body["message"])
task = Task(
id=task_id,
status=TaskStatus(state="submitted"),
messages=[message],
metadata=body.get("metadata", {})
)
self.tasks[task_id] = task
# Run asynchronously
asyncio.create_task(self._execute_task(task_id))
return task
async def _execute_task(self, task_id: str):
task = self.tasks[task_id]
task.status = TaskStatus(state="working")
try:
# Extract the research question from the message
question = task.messages[0].parts[0].text
# Simulate literature search (replace with real search tool)
await asyncio.sleep(1) # Simulated latency
findings = await self._search_literature(question)
# Produce artifact
task.artifacts = [Artifact(parts=[
Part(type="text", text=findings["summary"]),
Part(type="data", data={"papers": findings["papers"],
"query": question})
])]
task.status = TaskStatus(state="completed")
except Exception as e:
task.status = TaskStatus(state="failed", message=str(e))
self.tasks[task_id] = task
async def _search_literature(self, question: str) -> dict:
"""Placeholder: in production, calls a real search API."""
return {
"summary": f"Based on a search of recent literature regarding "
f"'{question}', key findings include: ...",
"papers": [
{"title": "Attention Is All You Need", "year": 2017,
"relevance": 0.95},
{"title": "RLHF: Training Language Models to Follow Instructions",
"year": 2022, "relevance": 0.88}
]
}
async def _stream_task(self, body: dict) -> AsyncIterator[str]:
task = await self._create_and_run_task(body)
# Stream status updates
yield f"data: {json.dumps({'id': task.id, 'status': {'state': 'submitted'}, 'final': False})}\n\n"
yield f"data: {json.dumps({'id': task.id, 'status': {'state': 'working'}, 'final': False})}\n\n"
# Wait for completion
while task.status.state not in ("completed", "failed", "canceled"):
await asyncio.sleep(0.5)
task = self.tasks[task.id]
# Stream the artifact
if task.artifacts:
for part in task.artifacts[0].parts:
event = {
"id": task.id,
"artifact": {
"parts": [part.model_dump()],
"index": 0,
"append": False,
"lastChunk": True
},
"final": False
}
yield f"data: {json.dumps(event)}\n\n"
# Final status
yield f"data: {json.dumps({'id': task.id, 'status': task.status.model_dump(), 'final': True})}\n\n"
# -- Orchestrator: Multi-Agent Workflow ----------------------------------------
class ResearchOrchestrator:
"""
Orchestrates a multi-agent research workflow:
1. Decomposes the research question into sub-questions
2. Dispatches each sub-question to a ResearchAgent
3. Synthesizes results into a final report
"""
def __init__(self, research_agent_url: str, auth_token: str):
self.research_client = A2AClient(research_agent_url, auth_token)
self.workflow_id = str(uuid.uuid4())
async def run(self, research_question: str) -> str:
print(f"[Orchestrator] Starting workflow {self.workflow_id}")
print(f"[Orchestrator] Question: {research_question}")
# Step 1: Decompose into sub-questions
sub_questions = self._decompose(research_question)
print(f"[Orchestrator] Decomposed into {len(sub_questions)} sub-questions")
# Step 2: Dispatch sub-questions in parallel
tasks = await asyncio.gather(*[
self.research_client.send_task(
message=Message(role="user", parts=[Part(type="text", text=q)]),
metadata={"workflowId": self.workflow_id, "subQuestion": i}
)
for i, q in enumerate(sub_questions)
])
# Step 3: Wait for all tasks to complete
completed_tasks = await asyncio.gather(*[
self.research_client.wait_for_completion(task)
for task in tasks
])
# Step 4: Check for failures
failed = [t for t in completed_tasks if t.status.state == "failed"]
if failed:
print(f"[Orchestrator] Warning: {len(failed)} sub-tasks failed")
# Step 5: Synthesize results
findings = []
for task, question in zip(completed_tasks, sub_questions):
if task.status.state == "completed" and task.artifacts:
summary = task.artifacts[0].parts[0].text
findings.append(f"### {question}\n{summary}")
report = self._synthesize(research_question, findings)
print(f"[Orchestrator] Workflow complete. Report: {len(report)} chars")
return report
def _decompose(self, question: str) -> list[str]:
"""Decompose a complex question into focused sub-questions."""
# In production: use an LLM to decompose
return [
f"What are the foundational methods for: {question}?",
f"What are the most recent advances in: {question}?",
f"What are the open challenges and limitations in: {question}?"
]
def _synthesize(self, question: str, findings: list[str]) -> str:
"""Synthesize sub-findings into a coherent report."""
# In production: use an LLM to synthesize
sections = "\n\n".join(findings)
return f"# Research Report: {question}\n\n{sections}"
# -- Entry Point ---------------------------------------------------------------
async def main():
orchestrator = ResearchOrchestrator(
research_agent_url="https://research-agent.example.com/a2a",
auth_token="eyJhbGciOiJSUzI1NiJ9..."
)
report = await orchestrator.run(
"Reinforcement learning from human feedback for large language models"
)
print(report)
if __name__ == "__main__":
asyncio.run(main())
まとめ
Important
要点:エージェント間通信
A2Aは大規模な専門化を可能にします。 タスクを専門エージェントへルーティングすることで、マルチエージェントシステムは深さと広さを同時に実現します(章10ではマルチエージェントアーキテクチャを詳しく扱います)。
GoogleのA2A Protocol は、エージェントカード、タスクライフサイクル管理、SSEストリーミング、エンタープライズ認証を備えた、本番対応のオープンな相互運用標準を提供します。
通信パターン は、単純なリクエスト・レスポンスから複雑なネゴシエーションやオークションベースの割り当てまで広がります。タスクの複雑さとレイテンシの要件に基づいて選択します。
A2AとMCPは補完的です。 A2Aはエージェント同士を接続し、MCPはエージェントをツールへ接続します。本番システムの多くは両方を使います。
セキュリティは譲れません。 エージェントの身元検証、認可スコープ、監査証跡は、あらゆるマルチエージェント配置に不可欠です。
調整プロトコル (Contract Net、ブラックボード、合意形成)は、単純な委譲を超えた集団意思決定のための構造化メカニズムを提供します。
相関IDによる可観測性 は、多数のエージェントとツールにまたがる複雑なマルチエージェントワークフローのデバッグと監査に不可欠です。
Note
A2Aにおける未解決の研究課題
階層内の複数のオーケストレータから受け取る矛盾した指示を、エージェントはどのように処理すべきでしょうか。どのような競合解決メカニズムが最も効果的でしょうか。
静的な能力宣言に頼るのではなく、エージェントは経験を通じてよりよいルーティングや委譲戦略を学習できるでしょうか。
悪意のあるエージェントがメッセージに敵対的な指示を埋め込み、下流エージェントを操作するプロンプトインジェクション攻撃を、どのように防げばよいでしょうか。
コンテキスト受け渡しにおける適切なプライバシー境界はどこでしょうか。サブエージェントにどの程度の会話履歴を見せるべきで、技術的に境界をどう適用すべきでしょうか。
エージェントネットワークが数百、数千のエージェントへ拡大したとき、ボトルネックや一貫性違反を生じさせずに整合したグローバル状態をどのように維持すればよいでしょうか。
マルチエージェントシステム
動機:なぜ複数のエージェントなのか
人工知能の歴史は、多くの意味でスケールの歴史です。初期のAIシステムはモノリシックでした。単一のプログラム、単一の知識ベース、単一の推論エンジンです。問題が複雑になるにつれ、研究者は、どれほど能力が高くても、単一のエージェントでは豊かでオープンエンドなタスクのすべてを効率よく扱えないことを発見しました。分散AIとマルチエージェントシステム(MAS)研究で長く確立されてきたこの洞察(Weiss 1999、Wooldridge 2009)は、大規模言語モデルの時代に再び切迫した課題となっています。
Tip
中核となる直観
どれほど大きなLLMでも、単一のLLMは汎用家です。狭いサブ問題に集中し、結果を伝え合う専門LLMのチームは、複雑で多面的なタスクにおいて汎用家を上回れます。複雑なエンジニアリングプロジェクトで人間の専門家チームが単一の汎用家を上回るのと同じです。
モノリシックなエージェントからエージェント社会への移行を促す、4つの根本的な動機があります。
専門化
異なるサブタスクは、異なる能力、プロンプト戦略、さらには異なるベースモデルから恩恵を受けます。コード生成エージェントはプログラミングコーパスでファインチューニングでき、ファクトチェックエージェントは検索ツールでグラウンディングでき、創作エージェントは文体の多様性を促すプロンプトを使えます。単一のエージェントにこれらすべてを同時に得意にさせるのは非効率で、多くの場合不可能です。
並列化
現実世界の多くのタスクは、同時実行できる独立したサブタスクへ分解できます。文献レビュー、データ分析、レポート執筆を必要とする研究パイプラインは、3つすべてを並列に実行して実時間を大幅に短縮できます。単一エージェントによる逐次処理はボトルネックであり、マルチエージェント並列化が解消します。
堅牢性
単一エージェントは単一障害点です。幻覚を起こしたり、ループに陥ったり、微妙に誤った答えを出したりしても、チェックがありません。マルチエージェントシステムは冗長性を導入します。第2のエージェントが結果を検証、批評、独立再導出できます。敵対的エージェントは出力を信頼する前に弱点を探れます。
創発的能力
最も興味深いのは、エージェントの集合が、個々のエージェントにはない能力を示し得ることです。討論、ネゴシエーション、反復的な改善を通じて、マルチエージェントシステムは単一エージェントだけでは生み出せない解に到達できます。これは社会的な生物における創発知能の計算論的類推です。
Important
歴史的背景
マルチエージェントシステム研究は1980年代にさかのぼります。分散問題解決(Durfee et al. 1989)、Contract Net Protocol(Smith 1980)、FIPAエージェント通信標準(Intelligent Physical Agents 2002)に関する基礎研究がありました。LLMベースのエージェントへの移行は、これらの古典的な考え方を新しい基盤で再生させます。記号推論を行う手書きのエージェントに代わり、現在は学習されたニューラル表現から「認知」が創発するエージェントを使います。中核的なアーキテクチャパターン、すなわち階層、マーケット、ブラックボード、メッセージパッシングは、今も驚くほど関連性があります。
モノリシックなエージェントからエージェント社会への移行は、複雑系におけるより広いパターンを反映します。問題空間が大きくなるほど、分散・モジュール型アーキテクチャは中央集権的でモノリシックなアーキテクチャを一貫して上回ります。問題はもはや複数エージェントを使うかどうかではなく、どのように組織するかです。
マルチエージェントアーキテクチャ
マルチエージェントシステムのトポロジー、すなわちエージェントの接続方法と権限の流れは、最も影響の大きいアーキテクチャ上の意思決定です。それぞれ異なるトレードオフを持つ、4つの典型パターンが登場しています。
中央集権型(スーパーバイザー/マネージャー)アーキテクチャ
中央集権型アーキテクチャでは、単一のオーケストレータエージェント(スーパーバイザー、マネージャー、プランナーなどと呼ばれる)がグローバル状態を保持し、タスクを分解し、サブタスクをワーカーエージェントへ委譲し、結果を集約します。トポロジーは ハブ・アンド・スポーク で、すべての通信が中央ノードを通ります。
マネージャーの責務は次のとおりです。
-
タスクルーティング: 各サブタスクに最適なワーカーを決定します。
-
コンテキスト管理: 各ワーカーにグローバルコンテキストの関連部分を提供します。
-
結果の集約: ワーカーの出力を一貫した全体へ統合します。
-
エラー処理: ワーカーの失敗を検出し、再ルーティングまたは再試行します。
Note
LangGraphにおけるスーパーバイザーパターン
from langgraph.graph import StateGraph, START, END from typing import TypedDict, Literal class TeamState(TypedDict): task: str plan: str code: str tests: str review: str next_agent: str final_output: str def supervisor_node(state: TeamState) -> TeamState: """Central orchestrator: decides which agent to invoke next.""" messages = [ {"role": "system", "content": SUPERVISOR_PROMPT}, {"role": "user", "content": f"Task: {state['task']}\n" f"Plan: {state.get('plan','')}\n" f"Code: {state.get('code','')}\n" f"Tests: {state.get('tests','')}\n" "Which agent should act next? " "Options: planner, coder, tester, reviewer, FINISH"} ] response = llm.invoke(messages) return {**state, "next_agent": response.content.strip()} def route(state: TeamState) -> Literal["planner","coder","tester","reviewer","__end__"]: return state["next_agent"] if state["next_agent"] != "FINISH" else END builder = StateGraph(TeamState) builder.add_node("supervisor", supervisor_node) builder.add_node("planner", planner_node) builder.add_node("coder", coder_node) builder.add_node("tester", tester_node) builder.add_node("reviewer", reviewer_node) builder.add_edge(START, "supervisor") builder.add_conditional_edges("supervisor", route) for agent in ["planner", "coder", "tester", "reviewer"]: builder.add_edge(agent, "supervisor") # always return to supervisor graph = builder.compile()
Warning
中央集権型アーキテクチャのトレードオフ
長所: 制御フローが単純で、説明責任が明確、デバッグが容易(すべての意思決定が1か所にある)、実装が容易です。
短所: 単一障害点です。マネージャーが幻覚を起こしたり混乱したりするとシステム全体が失敗し、高負荷ではマネージャーがボトルネックになります。マネージャーのコンテキストウィンドウがグローバル状態を保持しなければならず、スケーラビリティが制限されます。
分散型(ピアツーピア)アーキテクチャ
分散型アーキテクチャでは、中央コーディネータなしにエージェント同士が直接相互作用します。トポロジーは メッシュ で、任意のエージェントが他の任意のエージェントと通信できます。調整はグローバルな計画ではなく、局所的な相互作用から創発します。
ピアツーピアシステムにおける創発的な調整は、次のような仕組みから生じます。
-
ネゴシエーション: エージェントがタスクやリソースに入札します。
-
スティグマジー: エージェントが共有状態を変更し、他のエージェントがそれを観測します(節10.3.6を参照)。
-
ゴシッププロトコル: エージェントがネットワーク全体に情報を伝播します。
-
局所合意: 小さなエージェント集団がグローバルな調整なしに合意へ到達します。
Warning
分散型アーキテクチャのトレードオフ
長所: 個々のエージェントの失敗に強く、エージェントを追加すると自然にスケールし、ボトルネックがありません。
短所: デバッグが難しく、創発的な挙動を追跡しにくい、エージェントが状態について一貫しない見解を持つと競合が起こり得る、単純なメッセージパッシングでは調整オーバーヘッドが \(O(n^2)\) で増大する、グローバルな一貫性の保証が難しい、という問題があります。
階層型アーキテクチャ
階層型アーキテクチャは、中央集権型パターンを複数の管理レベルを持つ 木構造 へ一般化します。トップレベルのオーケストレータが分野固有のサブマネージャーへ委譲し、サブマネージャーが専門ワーカーへ委譲します。これは大企業の組織構造を反映します。
階層型システムの主な特徴は次のとおりです。
-
委譲チェーン: 権限とコンテキストが木を下へ流れ、結果が上へ流れます。
-
エスカレーション経路: ワーカーは解決できない問題をマネージャーへエスカレーションできます。
-
分野分離: サブマネージャーが分野固有のコンテキストを維持し、トップレベルオーケストレータの認知負荷を減らします。
-
範囲の限定: 各エージェントは直属の上位者と下位者だけを知ればよい構造です。
企業との類推が適切です。CEO(トップオーケストレータ)が戦略を定め、VP(サブマネージャー)が戦略を分野計画へ変換し、個々の貢献者(ワーカー)が実行します。階層は説明責任を保ちながらスケールを可能にします。
スウォームアーキテクチャ
スウォームアーキテクチャは、生物システム(アリのコロニー、鳥の群れ)に着想を得たもので、多数の 疎結合エージェント が単純な局所ルールに従い、中央コーディネータやグローバル状態なしに複雑なグローバル挙動を生み出します。
OpenAIの Swarm フレームワーク(OpenAI 2024b)は、2つのプリミティブでこれを実装します(現在はOpenAI Agents SDKに置き換えられましたが、その概念的プリミティブは今も影響力を持ちます)。
-
ルーチン: エージェントがサブタスクを完了するために従う指示の列
-
ハンドオフ: エージェントが制御(および関連コンテキスト)を別のエージェントへ移すこと
Note
OpenAI Swarm:ルーチンとハンドオフ
from swarm import Swarm, Agent client = Swarm() def transfer_to_billing(): """Handoff: transfer control to the billing specialist.""" return billing_agent def transfer_to_technical(): """Handoff: transfer control to the technical support agent.""" return technical_agent triage_agent = Agent( name="Triage Agent", instructions="""You are a customer service triage agent. Determine the nature of the customer's issue: - For billing questions, transfer to billing. - For technical issues, transfer to technical support. - For general questions, answer directly.""", functions=[transfer_to_billing, transfer_to_technical], ) billing_agent = Agent( name="Billing Specialist", instructions="You handle billing inquiries. " "Access account data and resolve payment issues.", functions=[lookup_account, process_refund], ) technical_agent = Agent( name="Technical Support", instructions="You resolve technical issues. " "Diagnose problems and provide step-by-step solutions.", functions=[run_diagnostics, escalate_to_engineering], ) # No global state --- each agent operates on its local context response = client.run( agent=triage_agent, messages=[{"role": "user", "content": "My invoice is wrong"}] )
Important
スウォームの特性
グローバル状態なし: 各エージェントはローカルのコンテキストウィンドウだけを維持します。
局所的な意思決定: ルーティング判断は中央プランナーではなく、現在のエージェントが行います。
集合的な挙動によるタスク完了: 複雑なタスクはハンドオフの連鎖で完了し、各エージェントが専門性を提供します。
軽量: オーケストレーションのオーバーヘッドがなく、エージェントはハンドオフ間でステートレスです。
調整メカニズム
エージェントがどのように調整するか、すなわち情報共有、作業分担、競合解決の方法は、トポロジーと同じくらい重要です。LLMベースのマルチエージェントシステムには、6つの典型的な調整メカニズムがあります。
共有状態(グローバルブラックボード)
ブラックボードアーキテクチャ (Hayes-Roth 1985)は、すべてのエージェントが読み書きできる共有データ構造を提供します。LLMシステムでは通常、共有辞書、データベース、構造化文書として実装します。
import threading
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List
@dataclass
class BlackboardEntry:
value: Any
author: str
timestamp: float
confidence: float = 1.0
class Blackboard:
"""Thread-safe shared state for multi-agent coordination."""
def __init__(self):
self._data: Dict[str, BlackboardEntry] = {}
self._lock = threading.RLock()
self._subscribers: Dict[str, List[Callable]] = {}
def write(self, key: str, value: Any, author: str,
confidence: float = 1.0) -> bool:
"""Write to blackboard; higher-confidence entries win conflicts."""
with self._lock:
existing = self._data.get(key)
if existing and existing.confidence > confidence:
return False # Conflict: existing entry wins
import time
self._data[key] = BlackboardEntry(
value=value, author=author,
timestamp=time.time(), confidence=confidence
)
self._notify(key, value)
return True
def read(self, key: str) -> Any:
with self._lock:
entry = self._data.get(key)
return entry.value if entry else None
def subscribe(self, key: str, callback: Callable):
"""Agents subscribe to changes on specific keys."""
self._subscribers.setdefault(key, []).append(callback)
def _notify(self, key: str, value: Any):
for cb in self._subscribers.get(key, []):
cb(key, value)
メッセージパッシング
メッセージパッシングはLLMエージェントにとって最も自然な調整メカニズムです。エージェントが構造化テキストメッセージを互いに送り、通信します。主な設計判断は次のとおりです。
-
メッセージ形式: 構造化(JSONスキーマ)、自然言語、ハイブリッドのどれにするか。
-
ルーティング: 直接(エージェント間)、ブロードキャスト、トピックベースのPub/Subのどれにするか。
-
会話スレッド: マルチターン交換をまたいでコンテキストを維持すること。
-
確認応答: 送信者が受信・処理の確認を必要とするかどうか。
計画と分解
マネージャーエージェントが高レベルタスクをサブタスクの 有向非巡回グラフ(DAG) へ分解し、それぞれを適切なワーカーに割り当て、依存関係を追跡します。これは古典的な階層型タスクネットワーク(HTN)計画のマルチエージェント版です。
from dataclasses import dataclass, field
from typing import List, Optional
import asyncio
@dataclass
class Task:
id: str
description: str
assigned_to: str
dependencies: List[str] = field(default_factory=list)
status: str = "pending" # pending | running | done | failed
result: Optional[str] = None
class TaskDAG:
def __init__(self):
self.tasks: dict[str, Task] = {}
def add_task(self, task: Task):
self.tasks[task.id] = task
def ready_tasks(self) -> List[Task]:
"""Return tasks whose dependencies are all completed."""
return [
t for t in self.tasks.values()
if t.status == "pending"
and all(self.tasks[d].status == "done"
for d in t.dependencies)
]
async def execute(self, agent_pool: dict):
while any(t.status != "done" for t in self.tasks.values()):
ready = self.ready_tasks()
if not ready:
await asyncio.sleep(0.1)
continue
# Execute ready tasks in parallel
await asyncio.gather(*[
self._run_task(t, agent_pool[t.assigned_to])
for t in ready
])
async def _run_task(self, task: Task, agent):
task.status = "running"
try:
task.result = await agent.execute(task.description)
task.status = "done"
except Exception as e:
task.status = "failed"
raise
投票と合意形成
複数エージェントが競合する出力を生成した場合、投票メカニズムが応答を単一の意思決定へ集約します。一般的な方式は次のとおりです。
-
多数決: 最も多い回答が勝ち、事実に関する質問に有効です。
-
重み付き投票: 実績や信頼度スコアの高いエージェントに大きな重みを与えます。
-
討論ベースの解決: エージェントが自分の立場を主張し、審判エージェントが決定します。
-
デルファイ法: 他者の推論を見た後に回答を修正するラウンドを反復します。
形式的には、\(n\) 個のエージェントが出力 \(\{o_1, \ldots, o_n\}\) を生成し、重み \(\{w_1, \ldots, w_n\}\) を持つとき、重み付き合意は次のようになります。
\[ o^* = \arg\max_{o} \sum_{i=1}^{n} w_i \cdot \mathbf{1}[o_i = o] \]
連続出力(例: 確率推定)には、重み付き平均を適用します。
\[ \hat{p} = \frac{\sum_{i=1}^{n} w_i \cdot p_i}{\sum_{i=1}^{n} w_i} \]
市場ベースの調整
市場メカニズムは、 オークションと入札 を通じてタスクとリソースを割り当てます。最古のマルチエージェント調整メカニズムの1つであるContract Net Protocol(Smith 1980)は、タスクオークションです。
-
マネージャーが要件付きのタスク告知をブロードキャストします。
-
請負エージェントが入札(能力宣言+コスト見積もり)を提出します。
-
マネージャーが最良の入札者へ契約を割り当てます。
-
落札した請負エージェントが実行し、結果を報告します。
LLMシステムでは、入札を自然言語(「高い信頼度で3ステップで完了できます」)または構造化形式で表現できます。市場メカニズムは、APIコストを最小化する必要があるリソース制約環境で特に有効です。
スティグマジー:環境を介した間接通信
スティグマジー (Grassé 1959)は、明示的なエージェント間メッセージをより単純な仕組みに置き換えます。各エージェントが作業の副作用として共有環境を変更し、他のエージェントが直接の信号ではなく変更へ反応します。典型例は、採餌するアリが帰路にフェロモンを残し、アリ同士が「会話」しなくても後続のアリが成功経路を強化することです。
LLMマルチエージェントシステムでは、スティグマジーは次の形で現れます。
-
共有文書: エージェントが共有文書へ書き込み、他のエージェントが読み取って発展させます。
-
コードリポジトリ: 1つのエージェントがコードをコミットし、別のエージェントが読み取って拡張します。
-
アノテーション層: エージェントが共有成果物に注釈を付けます(エラーの強調、コメントの追加)。
-
タスクキュー: エージェントが共有キューへタスクを追加し、そこから取り出します。
スティグマジーは明示的な通信オーバーヘッドなしに調整を可能にします。エージェントは共有環境の状態を観測し、それに応じて行動するだけです。
通信プロトコル
効果的なマルチエージェントシステムには、明確に定義された通信プロトコルが必要です。エージェント間メッセージの形式、意味論、パターンについて合意します(標準化されたエージェント間プロトコルについては、章9を参照)。
構造化メッセージ形式
LLMエージェント間のメッセージは、確実なパースとルーティングを可能にするため構造化すべきです。最小限のメッセージスキーマを示します。
from pydantic import BaseModel, Field
from typing import Literal, Optional, Dict, Any
from datetime import datetime, timezone
import uuid
PerformativeType = Literal[
"inform", # Share information
"request", # Request an action
"propose", # Propose a course of action
"accept", # Accept a proposal
"reject", # Reject a proposal
"query", # Ask a question
"confirm", # Confirm receipt/completion
"failure", # Report a failure
]
class AgentMessage(BaseModel):
message_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
conversation_id: str # Groups related messages
sender: str # Agent identifier
receiver: str # Target agent (or "broadcast")
performative: PerformativeType
content: str # Natural language content
metadata: Dict[str, Any] = {} # Structured payload
reply_to: Optional[str] = None # message_id being replied to
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
def to_llm_prompt(self) -> str:
"""Render message as a prompt fragment for the receiving agent."""
return (
f"[MESSAGE from {self.sender}]\n"
f"Type: {self.performative}\n"
f"Content: {self.content}\n"
+ (f"Metadata: {self.metadata}\n" if self.metadata else "")
)
発話行為タイプ(FIPA-ACLに着想)
FIPA Agent Communication Language(Intelligent Physical Agents 2002)を基に、LLMエージェント向けに現代化したものです。
| 発話行為 | 意味論 | 利用例 |
|---|---|---|
inform | 送信者が \(\phi\) は真だと考える | 研究結果を共有 |
request | 送信者が受信者に \(\alpha\) を実行してほしい | サブタスクを委譲 |
propose | 送信者が計画 \(\pi\) を提案 | アプローチを提案 |
accept | 受信者が提案に同意 | タスク割り当てを確認 |
reject | 受信者が提案を拒否 | 互換性のないタスクを拒否 |
query | 送信者が \(\phi\) を知りたい | 明確化を依頼 |
confirm | 送信者が \(\phi\) の発生を確認 | 完了を確認 |
failure | 送信者が \(\alpha\) の達成に失敗 | エラーを報告 |
LLMエージェントメッセージ向けのFIPA-ACL着想の発話行為タイプ。
コンテキスト共有戦略
マルチエージェント通信の重要な課題は コンテキスト管理 です。各エージェントにどれだけの履歴が必要でしょうか。戦略は3つあります。
-
全履歴: 会話履歴全体を各エージェントへ渡します。情報量は最大ですが高コストで、コンテキストウィンドウがすぐに埋まります。
-
要約: 要約エージェントが過去の交換を簡潔な要約へ圧縮します。効率的ですが情報損失があり、重要な詳細が落ちる可能性があります。
-
関連抜粋: 意味検索を使って、過去の最も関連するメッセージだけを取得します。コストと情報量のバランスが取れますが、検索メカニズムが必要です。
Important
コンテキスト共有の経験則
短い会話(\(<\)10ターン)には 全履歴 、中程度の会話には 要約 、長時間実行されるエージェントセッションには 検索拡張抜粋 を使います。直近のコンテキストを保つため、最新の \(k\) メッセージは常に逐語的に含めます。
役割設計と専門化
エージェントの役割、すなわち能力、ペルソナ、責務の設計は、科学であると同時に芸術でもあります。適切に設計された役割は専門化を可能にし、設計の悪い役割は混乱と冗長性を生みます。
エージェントの役割を定義する
LLMマルチエージェントシステムにおける一般的な役割は次のとおりです。
| 役割 | 主な能力 | 典型的なツール |
|---|---|---|
| 研究者 | 情報収集、統合 | ウェブ検索、RAG、データベース |
| プランナー | タスク分解、スケジューリング | なし(推論のみ) |
| コーダー | コード生成、デバッグ | コードインタプリタ、リンター |
| レビュアー | 品質評価、批評 | なし(推論のみ) |
| テスター | テスト生成、実行 | テストランナー、カバレッジツール |
| ライター | 文章生成、編集 | 文法チェッカー、スタイルガイド |
| クリティック | 敵対的評価 | なし(推論のみ) |
| オーケストレータ | 調整、委譲 | すべてのエージェントインターフェース |
LLMマルチエージェントシステムにおける一般的なエージェントの役割。
能力ベース対役割ベースの割り当て
タスク割り当てには2つの考え方があります。
-
役割ベース: 事前定義された役割ラベルに基づいてタスクを割り当てます。単純で予測可能ですが、タスクが複数の役割にまたがる場合は最適でない可能性があります。
-
能力ベース: タスク要件に対する各エージェントの能力を動的に評価してタスクを割り当てます。より柔軟ですが、能力レジストリとマッチングメカニズムが必要です。
動的な役割再割り当て
長時間稼働するシステムでは、静的な役割割り当てが最適でなくなります。動的な再割り当てにより、次に基づいてエージェントが新しい役割を担えるようになります。
-
現在のワークロード(負荷分散)
-
直近タスクで実証された性能
-
変化するタスク要件
-
カバーが必要なエージェントの失敗
思考の多様性のためのペルソナ設計
微妙ですが強力な手法として、多様な視点を促す 異なるペルソナ をエージェントに与えます。5つの同一な「アシスタント」エージェントではなく、次のように設計します。
-
機会を重視する楽観主義者
-
前提に疑問を投げかける懐疑主義者
-
実装に焦点を当てる実用主義者
-
長期的に考える先見者
-
反対の立場を主張する悪魔の代弁者
この思考の多様性は、Six Thinking Hats(6色ハット法)(Bono 1985)のような手法に着想を得たもので、集団浅慮を減らし、より堅牢な集団推論を生み出します。
Warning
役割の衝突の解決
エージェントの責任範囲が重複すると、衝突が生じます。明示的な 優先順位ルール で解決します。つまり、タスク種別ごとにどの役割を優先するかを定義します。あるいは、衝突の調停だけを担当する メタエージェント を使う方法もあります。役割の衝突を暗黙のままにしてはいけません。矛盾した出力や無限ループとして現れることになります。
LLMのマルチエージェントパターン
アーキテクチャのトポロジーに加えて、いくつかの 相互作用パターン がLLMベースのマルチエージェントシステムで特に有効であることが分かっています。(これらは、第5章で扱った単一エージェントの設計パターンを補完します。)
ディベートパターン
複数のエージェントが異なる立場を主張し、判定エージェントが議論を評価して決定します。ディベートによって事実の正確性が向上し、ハルシネーションが減少することが示されています(Du et al. 2023)。
async def debate_round(question: str, agents: list, judge: Agent,
rounds: int = 2) -> str:
"""Run a multi-agent debate and return the judge's verdict."""
positions = {a.name: await a.generate_position(question)
for a in agents}
for round_num in range(rounds):
# Each agent sees others' positions and can rebut
rebuttals = {}
for agent in agents:
others = {k: v for k, v in positions.items()
if k != agent.name}
rebuttals[agent.name] = await agent.rebut(
question, positions[agent.name], others
)
positions = rebuttals
# Judge evaluates all final positions
verdict = await judge.evaluate(question, positions)
return verdict
リフレクションパターン
1つのエージェントが出力を生成し、2つ目のエージェントがそれを批評し、最初のエージェントが批評に基づいて修正します。これは、品質を反復的に向上させる生成・批評・修正のループを実装します。
async def reflection_loop(task: str, generator: Agent,
critic: Agent, max_rounds: int = 3) -> str:
draft = await generator.generate(task)
for _ in range(max_rounds):
critique = await critic.critique(task, draft)
if critique.is_satisfactory:
break
draft = await generator.revise(task, draft, critique.feedback)
return draft
分業パターン
タスクを独立したサブタスクに分解し、並列に実行します。結果は統合エージェントが集約します。このパターンは、容易に並列化できるタスクのスループットを最大化します。
パイプラインパターン
エージェントが逐次処理チェーンを形成し、各エージェントが前のエージェントの出力を変換します。Unixのパイプに似ています。明確な逐次依存関係を持つタスク(例: 調査 \(\to\) アウトライン \(\to\) 下書き \(\to\) 編集 \(\to\) 書式設定)に有効です。
アンサンブルパターン
複数のエージェントが同じ問題を独立に解き、選択メカニズムが最良の回答(best-of-\(N\))を選ぶか、回答を集約します(mixture-of-experts方式)。計算量を犠牲にして信頼性を向上させます。
\[ o^* = \arg\max_{o \in \{o_1,\ldots,o_N\}} \text{score}(o, \text{task}) \]
ここで \(\text{score}\) は、報酬モデル、判定用LLM、検証器のいずれかです。
教師・生徒パターン
より能力の高いエージェント(教師)が、ヒント、修正、説明を提供しながら、能力の低いエージェント(生徒)をタスクに導きます。このパターンにより推論時の知識蒸留が可能になり、生徒エージェントのファインチューニングにも利用できます。
レッドチームパターン
敵対的エージェント(レッドチーム)が、他のエージェントの出力にある弱点、誤り、安全性違反を積極的に探します。レッドチームエージェントには、攻撃において最大限に批判的かつ創造的になるようプロンプトを与えます。このパターンは、安全性が重要なアプリケーションに不可欠です。
Note
レッドチームエージェントのプロンプト
RED_TEAM_PROMPT = """You are a red team agent. Your job is to find flaws, errors, biases, safety violations, and failure modes in the following output. Be adversarial, creative, and thorough. Consider: 1. Factual errors or hallucinations 2. Logical inconsistencies 3. Safety and ethical concerns 4. Edge cases the solution doesn't handle 5. Ways a malicious user could exploit this output 6. Unintended consequences Output: {agent_output} Provide a detailed critique with specific examples of each flaw found."""
強化学習によるマルチエージェントシステムの訓練
RLでマルチエージェントシステムを訓練すると、単一エージェントのRLを超える課題が生じます。根本的な難しさは、各エージェントの環境に他の学習エージェントが含まれるため、個々のエージェントから見た環境が 非定常 になることです。
数学的定式化
マルチエージェントシステムは マルコフゲーム (確率ゲームとも呼ばれます)として定式化されます(Shapley 1953)。
\[ \mathcal{G} = \langle \mathcal{N}, \mathcal{S}, \{\mathcal{A}^i\}_{i \in \mathcal{N}}, \mathcal{T}, \{R^i\}_{i \in \mathcal{N}}, \gamma \rangle \]
ここで \(\mathcal{N} = \{1, \ldots, n\}\) はエージェント集合、\(\mathcal{S}\) は共有状態空間、\(\mathcal{A}^i\) はエージェント \(i\) の行動空間、\(\mathcal{T}: \mathcal{S} \times \mathcal{A}^1 \times \cdots \times \mathcal{A}^n \to \Delta(\mathcal{S})\) は遷移関数、\(R^i: \mathcal{S} \times \mathcal{A}^1 \times \cdots \times \mathcal{A}^n \to \mathbb{R}\) はエージェント \(i\) の報酬関数、\(\gamma\) は割引係数です。
各エージェント \(i\) は、期待割引収益の最大化を目指します。
\[ J^i(\pi^1, \ldots, \pi^n) = \mathbb{E}_{\pi^1,\ldots,\pi^n}\left[\sum_{t=0}^{\infty} \gamma^t R^i(s_t, a_t^1, \ldots, a_t^n)\right] \]
独立学習
最も単純な方法は、各エージェント \(i\) が他のエージェントを自分の環境の一部として扱い、標準的な単一エージェントRL(例: PPO、REINFORCE)を使って自身の方策 \(\pi^i\) を独立に最適化することです。
\[ \nabla_{\theta^i} J^i \approx \mathbb{E}\left[\nabla_{\theta^i} \log \pi^i(a^i_t | o^i_t) \cdot \hat{A}^i_t\right] \]
Warning
非定常性の問題
独立学習はマルコフ仮定に違反します。他のエージェントが方策を更新すると、エージェント \(i\) が見る遷移分布と報酬分布が変化するためです。これにより、訓練が不安定になったり、振動したり、収束に失敗したりする可能性があります。独立学習は単純な協調タスクでは実際に機能しますが、競争的な環境や複雑な協調環境では苦戦します。
集中訓練・分散実行(CTDE)
CTDE(Lowe et al. 2017; Rashid et al. 2018)は、協調型マルチエージェントRLの主要なパラダイムです。訓練中は、集中型クリティックがグローバル状態 \(s\) と全エージェントの行動 \(\mathbf{a} = (a^1, \ldots, a^n)\) にアクセスできます。実行時には、各エージェントは自身の局所観測 \(o^i\) だけを使って行動します。
エージェント \(i\) の集中型クリティック:
\[ Q^i_\phi(s, \mathbf{a}) = Q^i_\phi(s, a^1, \ldots, a^n) \]
エージェント \(i\) の分散型アクター:
\[ \pi^i_{\theta^i}(a^i | o^i) \]
集中型クリティックを使った方策勾配:
\[ \nabla_{\theta^i} J^i = \mathbb{E}\left[\nabla_{\theta^i} \log \pi^i(a^i | o^i) \cdot Q^i_\phi(s, \mathbf{a})\right] \]
CTDEは訓練中の非定常性を解消し(集中型クリティックが完全な共同状態を見る)、分散実行(推論時の通信が不要)を維持します。
通信学習
固定された通信プロトコルを使うのではなく、エージェントに 何を伝えるかを学習 させることができます。微分可能な通信フレームワーク(Sukhbaatar et al. 2016; Das et al. 2019)では、エージェントが連続通信ベクトル \(m^i_t\) を生成し、他のエージェントに渡します。
\[ a^i_t, m^i_t = \pi^i_{\theta^i}(o^i_t, \{m^j_{t-1}\}_{j \neq i}) \]
通信ベクトルは、共同報酬信号を通じて逆伝播するエンドツーエンド方式で最適化されます。LLMエージェントでは、タスク性能を最大化する構造化された自然言語メッセージを生成するよう訓練することで、これを近似します。
創発的通信
エージェントを報酬信号だけでゼロから訓練すると(事前定義された言語なしに)、 創発的通信プロトコル (Lazaridou and Baroni 2020)を発達させることがあります。これはタスクに関連する情報を符号化する共有記号体系です。科学的には興味深い一方で、LLMシステムにおける創発的通信は通常望ましくありません。エージェントには人間が解釈できる言語で通信してほしいからです。
自己対戦
競争的な環境や動機が混在する環境では、 自己対戦 (Silver et al. 2017)によって、エージェントを自分自身のコピーと競わせて訓練します。これにより自動カリキュラムが生成されます。エージェントが改善するにつれて、対戦相手(自分自身の以前のバージョン)に勝つことが難しくなります。
LLMエージェントでは、自己対戦は次の用途に使われます。
-
レッドチーム対ブルーチームの訓練
-
ディベート訓練(エージェント同士が互いに議論する)
-
交渉訓練(エージェント同士が交渉する)
集団ベース訓練
集団ベース訓練(PBT) (Jaderberg et al. 2019)は、異なる方策、ハイパーパラメータ、専門性を持つ多様なエージェント集団を維持します。エージェントは定期的に評価され、性能の低いエージェントは高性能エージェントの変異コピーに置き換えられます。
マルチエージェントLLMシステムでは、PBTによって次が可能になります。
-
効果的な役割の専門化の自動発見
-
個々のエージェントの失敗に対する頑健性(多様な集団)
-
集団の多様性による局所最適解の回避
社会的厚生とナッシュ均衡
マルチエージェント環境では、最適性の概念が単一エージェント環境より複雑になります。重要な解概念は2つあります。
ナッシュ均衡: どのエージェントも単独で逸脱して期待収益を改善できない共同方策 \((\pi^{1*}, \ldots, \pi^{n*})\):
\[ J^i(\pi^{i*}, \pi^{-i*}) \geq J^i(\pi^i, \pi^{-i*}) \quad \forall i, \forall \pi^i \]
ここで \(\pi^{-i}\) は、\(i\) 以外の全エージェントの共同方策を表します。
社会的厚生の最大化: 全エージェントの収益の合計を最適化します。
\[ \max_{\pi^1,\ldots,\pi^n} \sum_{i=1}^{n} J^i(\pi^1, \ldots, \pi^n) \]
完全に協調的な環境(全エージェントが同じ報酬を共有する)では、社会的厚生の最大化が適切な目的です。競争的な環境では、ナッシュ均衡が適切な解概念です。現実のマルチエージェントLLMシステムの多くは 動機が混在 しています。エージェントの目的は一部が整合し、一部が対立します。
Important
さらに学ぶ: マルチエージェントRLのゲーム理論
マルチエージェントシステムのゲーム理論的基礎に関心のある読者には、次を勧めます。
Shoham & Leyton-Brown (Shoham and Leyton-Brown 2008)— ナッシュ均衡、メカニズムデザイン、エージェントシステムの社会的選択理論を扱う包括的な教科書。
Zhang et al. (Zhang et al. 2021)— 協調的、競争的、混合的な環境で収束保証を持つマルチエージェントRLアルゴリズムのサーベイ。
Nisan et al. (Nisan et al. 2007)— オークション、均衡計算、アナーキーの価格を扱うアルゴリズムゲーム理論の決定版。
課題と解決策
調整オーバーヘッド
エージェント間のすべてのメッセージはトークンを消費し、その結果として時間と費用も消費します。素朴な実装では、不要な場合でもエージェントが絶えず通信します。
Important
通信しない場合
情報がすでに共有ブラックボードにある場合
受信エージェントが現在のタスクにその情報を必要としない場合
メッセージがすでに送信した情報と重複する場合
タスクが単一エージェントで十分に処理できるほど単純な場合
ルール: 情報の期待価値がメッセージのコストを上回る場合にのみ通信する。
通信コストの定量化: メッセージが \(c\) トークンを消費し、受信エージェントのタスクの価値が \(v\) である場合、タスク価値の期待改善 \(\Delta v > c \cdot \text{cost_per_token}\) のときにのみ通信します。
冗長性と効率性
複数のエージェントが同じサブ問題を独立に解き、計算資源を浪費することがあります。解決策は次のとおりです。
-
重複検出: タスクを開始する前に、ブラックボードに既存の結果がないか確認する
-
結果のキャッシュ: 完了したサブタスクの結果を意味的なキーとともに保存し、取得できるようにする
-
タスクロック: 重複実行を防ぐため、タスクを「進行中」としてマークする
帰属
マルチエージェントシステムが成功または失敗したとき、どのエージェントに責任があるのでしょうか。帰属は次の点で重要です。
-
RLの報酬割り当て(信用割当問題)
-
デバッグと改善
-
信頼度の調整(どのエージェントを頼るか)
反実仮想信用割当 の手法では、「このエージェントが異なる行動を取っていたら、結果はどれほど変わっていただろうか」と問い、各エージェントの貢献度を推定します。
\[ \text{credit}^i = J(\pi^1, \ldots, \pi^n) - J(\pi^1, \ldots, \pi^{i}_{\text{default}}, \ldots, \pi^n) \label{eq:counterfactual-credit} \]
スケーラビリティ
素朴なメッセージパッシングは、エージェント数に対して \(O(n^2)\) でスケールします。解決策は次のとおりです。
-
階層型通信: エージェントが自分のサブツリー内でのみ通信する
-
トピックベースのPub/Sub: エージェントが関連するメッセージトピックだけを購読する
-
疎な通信グラフ: 相互作用が必要なエージェントだけを接続する
-
非同期通信: エージェントが応答を待ってブロックしない
創発的な挙動と安全性
マルチエージェントシステムは、予期しない創発的な挙動を示すことがあります。これは、個々のエージェントが生み出すよう設計されていない結果を、エージェント間の相互作用が生み出すということです。これは特徴(創発的能力)であると同時に、リスク(創発的な失敗)でもあります。
Warning
マルチエージェントシステムにおける安全性の懸念
プロンプトインジェクションの連鎖: 1つのエージェントへの悪意ある入力がシステム全体に伝播する
報酬ハッキング: エージェントが意図に反する予期しない方法で報酬を最大化する
共謀: 競争的な環境で、エージェントが暗黙の共謀戦略を発達させることがある
増幅: 1つのエージェントの誤りや偏りが、下流のエージェントによって増幅される
すべてのエージェント間通信を監視し、安全でない挙動が検出された場合にシステムを停止できる 安全性監視エージェント を必ず含めてください。
評価
マルチエージェントシステムの評価には、複数のレベルの指標が必要です。
| レベル | 指標 | 例 |
|---|---|---|
| システム | タスク完了率 | 正しく完了したタスクの割合 |
| システム | エンドツーエンド遅延 | タスクから最終出力までの時間 |
| システム | 総トークンコスト | 全エージェントが消費したトークン |
| エージェント | 個別の正確性 | エージェントごとのタスク成功率 |
| エージェント | 通信効率 | 有用なメッセージ数 / 総メッセージ数 |
| エージェント | 貢献度スコア | 反実仮想信用(式[eq:counterfactual-credit]) |
| 創発 | 調整品質 | タスクの重複 / 抜けの程度 |
マルチエージェントシステムの多レベル評価指標。
実世界のマルチエージェントアプリケーション
ソフトウェア開発チーム
マルチエージェントのソフトウェア開発チームは、実際のエンジニアリング組織を模倣します。
from dataclasses import dataclass
from typing import Optional
import asyncio
@dataclass
class SoftwareTeamState:
requirements: str
architecture: Optional[str] = None
code: Optional[str] = None
tests: Optional[str] = None
review_feedback: Optional[str] = None
final_code: Optional[str] = None
approved: bool = False
class SoftwareDevelopmentTeam:
"""
Multi-agent software team:
Architect -> Coder -> Tester -> Reviewer -> (iterate or ship)
"""
def __init__(self, llm_factory):
self.architect = llm_factory(
system_prompt="""You are a software architect. Given requirements,
produce a clear technical design: components, interfaces, data
structures, and implementation plan."""
)
self.coder = llm_factory(
system_prompt="""You are an expert software engineer. Given a
technical design, write clean, well-documented, production-ready
code. Follow best practices for the language."""
)
self.tester = llm_factory(
system_prompt="""You are a QA engineer. Given code, write
comprehensive tests: unit tests, edge cases, integration tests.
Identify potential bugs and failure modes."""
)
self.reviewer = llm_factory(
system_prompt="""You are a senior code reviewer. Evaluate code
for correctness, security, performance, and maintainability.
Provide specific, actionable feedback. Approve only if excellent."""
)
async def build(self, requirements: str,
max_iterations: int = 3) -> SoftwareTeamState:
state = SoftwareTeamState(requirements=requirements)
# Phase 1: Architecture
state.architecture = await self.architect.invoke(
f"Requirements:\n{requirements}\n\nProduce technical design."
)
for iteration in range(max_iterations):
# Phase 2: Implementation
prompt = (f"Design:\n{state.architecture}\n\n"
+ (f"Previous feedback:\n{state.review_feedback}\n\n"
if state.review_feedback else "")
+ "Write the implementation.")
state.code = await self.coder.invoke(prompt)
# Phase 3: Testing
state.tests = await self.tester.invoke(
f"Code:\n{state.code}\n\nWrite comprehensive tests."
)
# Phase 4: Review
review = await self.reviewer.invoke(
f"Code:\n{state.code}\n\nTests:\n{state.tests}\n\n"
"Review this code. End with APPROVED or NEEDS_REVISION."
)
if "APPROVED" in review:
state.final_code = state.code
state.approved = True
break
else:
state.review_feedback = review
return state
async def run(self, requirements: str) -> str:
state = await self.build(requirements)
if state.approved:
return f"# Final Implementation\n\n{state.final_code}"
else:
return f"# Best Attempt (not approved)\n\n{state.code}"
研究チーム
研究チームのエージェント社会は、学術的な協働を模倣します。
-
文献レビュアー: 既存研究を検索し、統合する
-
仮説生成器: 新しい研究の方向性を提案する
-
実験担当者: 実験を設計し、実行する(コード実行による)
-
統計担当者: 結果を分析し、有意性を評価する
-
ライター: 知見を一貫した報告書にまとめる
カスタマーサービスシステム
階層型のカスタマーサービスシステム:
-
ルーター: 受信したリクエストを分類し、専門担当者に振り分ける
-
請求担当者: 支払いとアカウントの問題を処理する
-
技術担当者: 製品やサービスの問題を解決する
-
エスカレーションエージェント: 人間の判断が必要な複雑なケースを処理する
クリエイティブチーム
クリエイティブ制作パイプライン:
-
ブレインストーマー: 自己検閲せず多様なアイデアを生成する
-
ドラフター: 最も有望なアイデアを完全な下書きに発展させる
-
エディター: 明確さ、文体、一貫性のために下書きを洗練する
-
批評者: 成果物を強化するため敵対的なフィードバックを提供する
アーキテクチャの比較
Important
アーキテクチャの選択
独立したサブタスク \(\rightarrow\) 並列アーキテクチャ(分業、アンサンブル)。
明確な依存関係を持つ逐次処理 \(\rightarrow\) パイプライン。
フォールトトレランスが必要 \(\rightarrow\) 集中型を避け、階層型または分散型を優先する。
デバッグ可能性が重要 \(\rightarrow\) 集中型またはパイプライン(すべての意思決定を追跡可能)。
\(<\)5エージェント \(\rightarrow\) 集中型が最も単純。 \(>\)20エージェント \(\rightarrow\) 階層型またはスウォーム。
実際には、ほとんどの本番システムが 階層型 アーキテクチャを使います。最上位のスーパーバイザーがドメイン固有のサブスーパーバイザーに委譲し、サブスーパーバイザーが専門ワーカーの小規模チームを管理します。
まとめ
マルチエージェントシステムは、LLMの配備方法における根本的な転換を示します。孤立したアシスタントから、専門エージェントが協働する社会へと移行するのです。この節の主な知見は次のとおりです。
Important
マルチエージェントシステム: 主な要点
アーキテクチャが重要: エージェント接続のトポロジーが、スケーラビリティ、デバッグ可能性、フォールトトレランスを決定します。タスク構造と運用要件に基づいて選択してください。
調整にはコストがかかる: エージェント間のすべてのメッセージがトークンを消費します。必要な情報の流れを維持しながら、オーバーヘッドを最小化する通信プロトコルを設計してください。
専門化が品質を可能にする: 焦点を絞った役割と調整されたプロンプトを持つエージェントは、複雑なタスクで汎用エージェントを一貫して上回ります。
RL訓練は難しい: マルチエージェントRLには、非定常性、信用割当の課題、創発的な挙動が伴います。CTDEは、協調的な環境における現在のベストプラクティスです。
安全性には明示的な設計が必要: マルチエージェントシステムは誤りを増幅し、予期しない創発的な挙動を示すことがあります。安全性監視は、第一級のアーキテクチャ上の関心事でなければなりません。
単純に始める: 集中型スーパーバイザーパターンから始め、その限界を測定し、必要な場合にのみより複雑なアーキテクチャへ発展させてください。
マルチエージェントLLMシステムの分野は急速に進化しています。ここで説明したパターンと技法は現在の最先端を表しますが、新しいアーキテクチャ、調整メカニズム、訓練アルゴリズムが絶えず登場しています。専門化、調整、創発的な挙動、効率性と頑健性の緊張関係という基礎原則は、具体的な実装がどのように進化しても、引き続き重要です。
エージェント開発フレームワーク
研究プロトタイプから本番品質のエージェントシステムへ移行することは、現代のAI開発における最も難しいエンジニアリング課題の1つです。学術論文は管理された環境で印象的な能力を示しますが、実世界への配備では、単純なタスク性能をはるかに超える多くの懸念が明らかになります。敵対的な入力に対する信頼性、内部推論の可観測性、複雑な多段階ワークフローのテスト可能性、そして数百万のリクエストを大規模に処理する運用オーバーヘッドです。この節では、こうした課題に対処するために登場したツール、ライブラリ、プラットフォームであるエージェント開発フレームワークの全体像を概観し、本番エージェントシステムの構築、テスト、配備、反復改善に向けた実践的な指針を示します。
動機: エンジニアリングの隔たり
Important
エージェントエンジニアリングが難しい理由
Jupyter Notebookで能力のあるエージェントを構築するのは簡単です。しかし、本番環境で信頼性高く動作し、エッジケースを処理し、障害から復旧し、負荷に合わせてスケールし、時間とともに改善されるものを構築するには、根本的に異なるエンジニアリング規律が必要です。
研究プロトタイプは通常、協調的な環境を仮定します。整形式の入力、利用可能なツール、応答性の高いAPI、そして問題が起きたときにプロセスを再起動してくれる忍耐強い人間の観察者です。本番エージェントには、このような恵まれた条件はありません。プロトタイプと本番のエンジニアリング上の隔たりは、いくつかの側面に現れます。
信頼性
本番エージェントは、ツールの失敗を適切に処理し、状態の部分的な破損から復旧し、無限ループや制御不能なAPI呼び出しを避けなければなりません。エラー処理は場当たり的ではなく、体系的でなければなりません。
可観測性
エージェントが誤った回答を生成したり、予期しない行動を取ったりしたとき、運用担当者はその理由を理解する必要があります。そのためには最終出力だけでなく、すべてのLLM呼び出し、ツール呼び出し、状態遷移を構造化してログに記録しなければなりません。
テスト可能性
エージェントの挙動は非決定的でコンテキストに依存するため、従来の単体テストだけでは不十分です。包括的なエージェントテストには、専用の評価ハーネス、ゴールデン軌跡との比較、挙動テストスイートが必要です。
配備
エージェントは状態を持つ長時間実行プロセスであり、数分から数時間に及ぶことがあります。サービス基盤は、非同期実行、チェックポイント、障害後の再開、マルチテナント分離をサポートしなければなりません。
反復
世界が変化し、APIが進化し、ユーザーの行動が変わるにつれて、本番エージェントの性能は時間とともに低下します。継続的な改善には、体系的な失敗分析、プロンプトのバージョン管理、ファインチューニングパイプラインが必要です。
Tip
エージェント開発の成熟度モデル
エージェント開発は、次の成熟段階をたどります。
プロトタイプ: 単一ファイルのスクリプト、ハードコードされたプロンプト、手動テスト
アルファ: モジュール化されたコード、基本的なエラー処理、手動評価
ベータ: フレームワークベース、自動テスト、ステージング環境
本番: 完全な可観測性、CI/CD、オートスケーリング、SLA
成熟: 継続学習、A/Bテスト、自己改善ループ
ほとんどのチームは、段階2と3の間の隔たりを過小評価しています。
エージェント開発ライフサイクル
構造化された開発ライフサイクルは、チームがコンセプトから本番へ体系的に進むのに役立ちます。図11.1に5つの主要なフェーズを示します。
フェーズ1: 設計
設計フェーズでは、1行のコードを書く前に、エージェントの能力範囲、つまり何ができて何ができないかを定めます。
能力の定義。 能力マトリクスから始めます。これは、エージェントが処理すべきタスク、拒否すべきエッジケース、明示的に対象外とする挙動を構造化して列挙したものです。この文書が評価基準の基礎になります。
ツールの選択。 各ツールには明確な目的、定義された入力と出力、障害モードの仕様が必要です。ツールを増やしすぎるのはよくある間違いです。ツールが多すぎるエージェントは、ツール選択の混乱とレイテンシの増大に悩まされます。
制約の仕様化。 本番エージェントには、リクエストあたりのツール呼び出し最大数、ウェブ閲覧で許可するドメイン、データアクセス権限、出力形式の要件といった明示的な制約が必要です。これらの制約はシステムプロンプトに埋め込むだけでなく、プログラムでも強制すべきです。
フェーズ2: 実装
実装には、プロンプトエンジニアリング、ツール統合、オーケストレーションロジックという3つの相互に絡み合う関心事があります。
プロンプトエンジニアリング。 本番エージェントのシステムプロンプトは生きた文書であり、バージョン管理、構造化されたテスト、慎重な変更管理が必要です。技法には、思考の連鎖の足場かけ、few-shot例、明示的な出力形式の指示、ペルソナ定義などがあります。
ツール統合。 各ツールは、型付きインターフェース、包括的なエラー処理、可能な場合はべき等性の保証を備えた関数として実装します。ツールの説明(LLMがいつ呼び出すかを決定するために使うもの)は、ツール実装そのものと同じくらい重要です。
オーケストレーション。 オーケストレーション層は、LLMの呼び出し、ツール呼び出しのパース、ツールの実行、状態の更新、終了タイミングの判断というエージェントループを管理します。フレームワークの選択(節11.3)は、この層の構造に大きな影響を与えます。
フェーズ3: テスト
エージェントテストについては、節11.5で詳しく扱います。重要な原則は、複数の粒度でテストすることです。個々のツール、完全なエージェントループ、エンドツーエンドのユーザーシナリオをテストします。
フェーズ4: 配備
配備に関する懸念は、節11.7で扱います。主な意思決定には、同期実行と非同期実行の選択、状態永続化戦略、スケーリングアーキテクチャがあります。
フェーズ5: 反復
反復フェーズは、本番での挙動とシステム改善の間のループを閉じます。必要なのは次のとおりです。
-
失敗のログ記録: すべてのエージェントの失敗を、完全なコンテキスト(入力、軌跡、エラー)とともにログに記録する
-
失敗の分類: 体系的な問題を特定するため、失敗を種類(ツールエラー、推論エラー、ハルシネーション、ループ)ごとに分類する
-
プロンプトの更新: 配備前に、プロンプトの変更を回帰テストスイートに対してテストする
-
ファインチューニング: プロンプトエンジニアリングが限界に達したとき、厳選した軌跡でファインチューニングすると性能を改善できる
-
A/Bテスト: 新しいエージェントのバージョンを、本番トラフィックに対して統計的に厳密にテストする
主要フレームワーク: 詳細解説
エージェントフレームワークのエコシステムは急速に成長しており、各フレームワークが異なる設計思想と対象ユースケースを反映しています。ここでは、最も広く採用されているフレームワークを詳しく調べます。
LangGraph
LangChain Inc.が開発したLangGraph(L. Inc 2024b)は、ノードが計算ステップ、エッジがステップ間の遷移を表す有向グラフとしてエージェントの実行をモデル化します。このグラフベースの抽象化は、エージェントのフローを明示的に制御できるため、複雑な多段階の挙動を推論、テスト、デバッグしやすくします。
中核概念
-
状態: グラフを流れ、各ノードによって更新される型付き辞書(PythonのTypedDictまたはPydanticを使用)
-
ノード: 現在の状態を受け取り、状態の更新を返すPython関数
-
エッジ: ノード間の遷移。無条件にも条件付きにもできる(状態に基づくルーティング)
-
チェックポイント: グラフ状態の組み込み永続化。一時停止・再開やHuman-in-the-Loopワークフローを可能にする
-
サブグラフ: より大きなグラフの中にネストできる、合成可能なグラフコンポーネント
状態管理
LangGraphの状態管理は、最も強力な機能の1つです。状態スキーマはノード間の契約として機能し、データフローを明示的かつ型安全にします。
from typing import TypedDict, Annotated, List
from langgraph.graph.message import add_messages
class AgentState(TypedDict):
# Messages accumulate via the add_messages reducer
messages: Annotated[List[BaseMessage], add_messages]
# Simple fields are overwritten on each update
current_tool: str | None
iteration_count: int
final_answer: str | None
error: str | None
チェックポイントとHuman-in-the-Loop
LangGraphのチェックポインターは、各ノードの実行後にグラフ状態を保存します。これにより次が可能になります。
-
再開: 長時間実行エージェントを進捗を失わずに一時停止・再開できる
-
人間の承認: グラフを指定したノードで一時停止し、先へ進む前に人間の入力を待てる
-
タイムトラベル: 運用担当者が任意のチェックポイントから実行を再生してデバッグできる
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import StateGraph, START, END
# Persistent checkpointer
memory = SqliteSaver.from_conn_string("agent_state.db")
# Build graph with interrupt point
builder = StateGraph(AgentState)
builder.add_node("plan", plan_node)
builder.add_node("human_review", human_review_node)
builder.add_node("execute", execute_node)
builder.add_edge(START, "plan")
builder.add_edge("plan", "human_review")
builder.add_edge("human_review", "execute")
builder.add_edge("execute", END)
# Compile with checkpointer and interrupt before human_review
graph = builder.compile(
checkpointer=memory,
interrupt_before=["human_review"]
)
# Run until interrupt
config = {"configurable": {"thread_id": "task-001"}}
result = graph.invoke({"messages": [HumanMessage("Analyze Q3 sales")]}, config)
# Resume after human provides input
graph.update_state(config, {"human_feedback": "Approved, proceed"})
result = graph.invoke(None, config) # Resume from checkpoint
次の2つのコードリストは、状態スキーマ、ツールノード、条件付きルーティング、チェックポイント、呼び出しという上記の要素をすべて組み合わせ、情報を反復的に収集して報告書を統合する完全なリサーチエージェントを構成します。
from typing import TypedDict, Annotated, List
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from langgraph.graph.message import add_messages
from langgraph.checkpoint.sqlite import SqliteSaver
# --- Tool Definitions ---
@tool
def search_web(query: str) -> str:
"""Search the web for current information on a topic."""
return f"Search results for: {query}" # stub; call real API
@tool
def read_document(url: str) -> str:
"""Fetch and read the content of a document at a URL."""
return f"Document content from: {url}"
tools = [search_web, read_document]
# --- State Schema ---
class ResearchState(TypedDict):
messages: Annotated[List[BaseMessage], add_messages]
research_topic: str
iteration: int
status: str # "researching" | "drafting" | "done" | "error"
# --- Node Functions ---
def research_node(state: ResearchState) -> dict:
"""LLM decides what to search next or signals completion."""
llm = ChatOpenAI(model="gpt-4o").bind_tools(tools)
response = llm.invoke(state["messages"])
return {"messages": [response], "iteration": state["iteration"] + 1}
def should_continue(state: ResearchState) -> str:
"""Route: tool calls -> execute tools; no calls -> synthesize."""
last = state["messages"][-1]
if hasattr(last, "tool_calls") and last.tool_calls:
return "tools"
if state["iteration"] >= 10:
return "error"
return "synthesize"
def synthesize_node(state: ResearchState) -> dict:
"""Produce final report from accumulated research."""
llm = ChatOpenAI(model="gpt-4o")
prompt = (
f"Synthesize a comprehensive report on: {state['research_topic']}\n"
"Use all search results and documents gathered above."
)
response = llm.invoke(
state["messages"] + [HumanMessage(content=prompt)]
)
return {"messages": [response], "status": "done"}
def error_node(state: ResearchState) -> dict:
return {"status": "error", "messages": [
AIMessage(content="Research exceeded maximum iterations.")
]}
# --- Graph Construction ---
tool_node = ToolNode(tools)
builder = StateGraph(ResearchState)
builder.add_node("research", research_node)
builder.add_node("tools", tool_node)
builder.add_node("synthesize", synthesize_node)
builder.add_node("error", error_node)
builder.add_edge(START, "research")
builder.add_conditional_edges(
"research", should_continue,
{"tools": "tools", "synthesize": "synthesize", "error": "error"}
)
builder.add_edge("tools", "research") # loop back after tool execution
builder.add_edge("synthesize", END)
builder.add_edge("error", END)
# Compile with persistence for conversation memory
with SqliteSaver.from_conn_string(":memory:") as checkpointer:
graph = builder.compile(checkpointer=checkpointer)
# --- Invoke ---
result = graph.invoke(
{"messages": [HumanMessage(content="Research recent advances in RLHF")],
"research_topic": "Recent advances in RLHF",
"iteration": 0, "status": "researching"},
config={"configurable": {"thread_id": "research-1"}}
)
AutoGen(Microsoft)
Microsoft Researchが開発したAutoGen(Wu et al. 2023)は、根本的に異なるアプローチを取ります。エージェントを、構造化されたメッセージパッシングで通信する会話可能なエンティティとしてモデル化します。単一のエージェントループではなく、AutoGenは専門エージェントが協働して複雑なタスクを解くマルチエージェント会話を可能にします。
会話可能なエージェント
AutoGenのすべてのエージェントは、次を備えたConversableAgentです。
-
役割と能力を定義する システムメッセージ
-
人間の入力を求めるタイミングを制御する 人間入力モード (
ALWAYS、NEVER、TERMINATE) -
コードを実行できるか、どのように実行できるかを指定する コード実行設定
-
呼び出し可能なツールの 関数マップ
グループチャットパターン
AutoGenのGroupChatは、共有された会話の中で複数のエージェントが協働できるようにします。GroupChatManagerが、ラウンドロビン、LLMベースの話者選択、カスタムルーティングロジックのいずれかによって、発話順を調整します。
import autogen
config_list = [{"model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"]}]
llm_config = {"config_list": config_list, "temperature": 0}
# Specialized agents
planner = autogen.AssistantAgent(
name="Planner",
system_message="""You are a strategic planner. Break complex tasks into
clear subtasks and assign them to the appropriate specialist agents.
Always end your message with a clear action item for another agent.""",
llm_config=llm_config,
)
coder = autogen.AssistantAgent(
name="Coder",
system_message="""You are an expert Python programmer. Write clean,
well-documented code. Always test your code before presenting it.""",
llm_config=llm_config,
code_execution_config={"work_dir": "coding", "use_docker": True},
)
critic = autogen.AssistantAgent(
name="Critic",
system_message="""You review code and plans for correctness, efficiency,
and security. Provide specific, actionable feedback.""",
llm_config=llm_config,
)
user_proxy = autogen.UserProxyAgent(
name="UserProxy",
human_input_mode="TERMINATE",
max_consecutive_auto_reply=10,
is_termination_msg=lambda x: "TASK_COMPLETE" in x.get("content", ""),
code_execution_config={"work_dir": "output", "use_docker": False},
)
# Group chat with LLM-based speaker selection
groupchat = autogen.GroupChat(
agents=[user_proxy, planner, coder, critic],
messages=[],
max_round=20,
speaker_selection_method="auto",
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)
# Initiate the conversation
user_proxy.initiate_chat(
manager,
message="Analyze the CSV dataset in 'sales_data.csv' and generate a summary report with visualizations."
)
コード実行エージェント
AutoGenのコード実行機能は特徴的な機能です。UserProxyAgentは、サンドボックス環境(Dockerコンテナまたはローカルプロセス)でPythonやシェルのコードを実行できるため、エージェントはコードを反復的に作成、テスト、修正できます。
Warning
AutoGenのセキュリティ上の考慮事項
コード実行エージェントは任意のコードを実行できます。本番環境では必ずDockerによる分離を使います。
code_execution_configに"use_docker": Trueを設定し、ネットワークアクセスを制限します。AutoGenのコード実行エージェントを、決して昇格した権限で実行してはいけません。
CrewAI
CrewAI(Moura 2023)は、組織管理から着想を得た、マルチエージェントシステム向けの役割ベースパラダイムを導入します。エージェントは専門的な役割、目標、バックストーリーによって定義されます。これは、LLMが人間の組織構造を理解していることを活用する設計上の選択です。
中核抽象化
-
エージェント:
role、goal、backstory、利用可能なtoolsによって定義される -
タスク:
description、expected_output、割り当てられたagentを持つ具体的な割り当て -
クルー: 実行
process(逐次または階層型)を持つエージェントとタスクの集合 -
プロセス: 実行戦略。
sequential(タスクを順に実行)またはhierarchical(マネージャーエージェントが委譲)
from crewai import Agent, Task, Crew, Process
from crewai_tools import SerperDevTool, WebsiteSearchTool
search_tool = SerperDevTool()
web_tool = WebsiteSearchTool()
# Define agents with rich role descriptions
researcher = Agent(
role="Senior Research Analyst",
goal="Uncover cutting-edge developments in AI and provide "
"comprehensive, accurate research summaries",
backstory="""You are a seasoned research analyst with 15 years of
experience in technology research. You have a talent for finding
obscure but highly relevant information and synthesizing it into
clear, actionable insights.""",
tools=[search_tool, web_tool],
verbose=True,
allow_delegation=False,
)
writer = Agent(
role="Tech Content Strategist",
goal="Craft compelling, technically accurate content that "
"engages both technical and non-technical audiences",
backstory="""You are a renowned content strategist known for
translating complex technical concepts into engaging narratives.
Your writing has appeared in major tech publications.""",
tools=[web_tool],
verbose=True,
allow_delegation=True,
)
# Define tasks with clear expected outputs
research_task = Task(
description="""Conduct comprehensive research on {topic}.
Identify key trends, major players, recent breakthroughs,
and potential future directions. Focus on developments from
the past 6 months.""",
expected_output="""A detailed research report with:
- Executive summary (200 words)
- Key findings (5-7 bullet points)
- Detailed analysis (500 words)
- Sources and citations""",
agent=researcher,
)
writing_task = Task(
description="""Using the research provided, write a compelling
blog post about {topic} for a technical audience.""",
expected_output="""A polished blog post (800-1000 words) with:
- Engaging headline
- Introduction hook
- 3-4 main sections with subheadings
- Conclusion with call to action""",
agent=writer,
context=[research_task], # Depends on research output
)
# Assemble the crew
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, writing_task],
process=Process.sequential,
verbose=2,
)
result = crew.kickoff(inputs={"topic": "Reinforcement Learning for LLMs"})
階層型プロセス
階層型モードでは、CrewAIが、役割と能力に基づいてワーカーエージェントへタスクを委譲するマネージャーエージェントを自動的に作成します。これは実際の組織構造を模倣し、明示的なタスク順序付けなしに、複雑で相互依存するワークフローを処理できます。
OpenAI Assistants APIとAgents SDK
OpenAIはエージェント開発向けに、相互に補完する2つの提供物を用意しています。状態を持つエージェント向けのホスト型基盤である Assistants API と、マルチエージェントオーケストレーション向けの軽量なPythonライブラリである Agents SDK (OpenAI 2025a)(旧称Swarm)です。
Assistants APIのアーキテクチャ
Assistants APIは、3つの中核オブジェクトを通じてサーバー側でエージェントの状態を管理します。
-
Assistant: モデル、指示、ツールを設定したエージェント
-
Thread: ユーザーセッションに関連付けられた永続的な会話履歴
-
Run: スレッド上でAssistantを実行したもの。状態のライフサイクルは(
queued\(\to\)in_progress\(\to\)requires_action\(\to\)completed)です
組み込みツール
Assistants APIは、外部基盤を必要としない3つのホスト型ツールを提供します。
-
Code Interpreter: ファイル入出力を備えたサンドボックス環境でPythonを実行する
-
File Search: ベクトルストアを利用してアップロード済み文書を検索する
-
Web Search: リアルタイムのウェブ閲覧(選択されたモデルで利用可能)
from openai import OpenAI
import time
client = OpenAI()
# Create a persistent assistant
assistant = client.beta.assistants.create(
name="Data Analysis Assistant",
instructions="""You are an expert data analyst. When given data files,
analyze them thoroughly and provide actionable insights with
visualizations where appropriate.""",
model="gpt-4o",
tools=[
{"type": "code_interpreter"},
{"type": "file_search"},
],
)
# Create a thread for a user session
thread = client.beta.threads.create()
# Upload a data file
with open("sales_data.csv", "rb") as f:
file = client.files.create(file=f, purpose="assistants")
# Add a message with the file attachment
client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="Analyze this sales data and identify the top 3 trends.",
attachments=[{"file_id": file.id, "tools": [{"type": "code_interpreter"}]}],
)
# Create and poll a run
run = client.beta.threads.runs.create_and_poll(
thread_id=thread.id,
assistant_id=assistant.id,
)
if run.status == "completed":
messages = client.beta.threads.messages.list(thread_id=thread.id)
print(messages.data[0].content[0].text.value)
elif run.status == "requires_action":
# Handle function tool calls
tool_calls = run.required_action.submit_tool_outputs.tool_calls
outputs = []
for tc in tool_calls:
result = dispatch_tool(tc.function.name, tc.function.arguments)
outputs.append({"tool_call_id": tc.id, "output": result})
client.beta.threads.runs.submit_tool_outputs(
thread_id=thread.id, run_id=run.id, tool_outputs=outputs
)
OpenAI Agents SDK: Swarmパターン
Agents SDKは、マルチエージェントのハンドオフ向け軽量フレームワークを提供します。中核プリミティブはハンドオフです。エージェントがコンテキストを引き継ぎながら、別のエージェントへ制御を移せます。これにより、専門エージェントが特定のサブタスクを処理するモジュール型のエージェントアーキテクチャが可能になります。
from agents import Agent, Runner, RunConfig, handoff, InputGuardrail, GuardrailFunctionOutput
from pydantic import BaseModel
# Input validation guardrail
class SafetyCheck(BaseModel):
is_safe: bool
reason: str
async def safety_guardrail(ctx, agent, input_data):
result = await Runner.run(
Agent(
name="SafetyChecker",
instructions="Check if the request is safe and appropriate.",
output_type=SafetyCheck,
),
input_data,
)
return GuardrailFunctionOutput(
output_info=result.final_output,
tripwire_triggered=not result.final_output.is_safe,
)
# Specialized agents
billing_agent = Agent(
name="BillingAgent",
instructions="Handle billing inquiries, refunds, and payment issues.",
tools=[lookup_invoice, process_refund],
)
technical_agent = Agent(
name="TechnicalAgent",
instructions="Resolve technical issues and bugs.",
tools=[check_system_status, create_ticket],
)
# Triage agent with handoffs
triage_agent = Agent(
name="TriageAgent",
instructions="""Classify customer requests and route to the appropriate
specialist. Use handoffs to transfer to billing or technical agents.""",
handoffs=[
handoff(billing_agent, tool_name_override="transfer_to_billing"),
handoff(technical_agent, tool_name_override="transfer_to_technical"),
],
input_guardrails=[InputGuardrail(guardrail_function=safety_guardrail)],
)
# Run with tracing enabled
result = await Runner.run(
triage_agent,
"I was charged twice for my subscription last month.",
run_config=RunConfig(tracing_disabled=False),
)
DSPy
DSPy(Khattab et al. 2024)(Declarative Self-improving Python)は、エージェント開発に根本的に異なるアプローチを取ります。プロンプトを手作業で設計するのではなく、自動最適化によって高レベルのプログラム仕様を最適化されたプロンプトへコンパイルします。
中核思想
DSPyは、モジュールが何をすべきか(シグネチャ)と、どのように実行すべきか(プロンプト)を分離します。次にオプティマイザーが、開発セット上の指標を最大化する最良のプロンプトとfew-shot例を探索します。これによりDSPyプログラムはモデル変更に対して頑健になり、手動のプロンプト調整が不要になります。
import dspy
# Configure the language model
lm = dspy.LM("openai/gpt-4o", temperature=0.0)
dspy.configure(lm=lm)
# Signatures define input/output contracts
class GenerateAnswer(dspy.Signature):
"""Answer questions with factual, concise responses."""
context: list[str] = dspy.InputField(desc="Relevant passages")
question: str = dspy.InputField()
answer: str = dspy.OutputField(desc="Concise factual answer")
class AssessAnswer(dspy.Signature):
"""Assess whether an answer is faithful to the context."""
context: list[str] = dspy.InputField()
question: str = dspy.InputField()
answer: str = dspy.InputField()
faithful: bool = dspy.OutputField()
confidence: float = dspy.OutputField(desc="Confidence score 0-1")
# Modules compose signatures into programs
class RAGAgent(dspy.Module):
def __init__(self, num_passages=3):
self.retrieve = dspy.Retrieve(k=num_passages)
self.generate = dspy.ChainOfThought(GenerateAnswer)
self.assess = dspy.Predict(AssessAnswer)
def forward(self, question: str) -> dspy.Prediction:
context = self.retrieve(question).passages
prediction = self.generate(context=context, question=question)
# Self-assessment with assertion
assessment = self.assess(
context=context,
question=question,
answer=prediction.answer,
)
dspy.Assert(
assessment.faithful,
"Answer not faithful to context "
"(confidence: " + str(assessment.confidence) + ")"
)
return prediction
オプティマイザー
DSPyのオプティマイザーは、プログラムの性能を自動的に改善します。
from dspy.teleprompt import MIPROv2
# Define evaluation metric
def answer_metric(example, prediction, trace=None):
return example.answer.lower() in prediction.answer.lower()
# Compile with MIPRO optimizer
optimizer = MIPROv2(
metric=answer_metric,
auto="medium", # Controls optimization budget
)
compiled_agent = optimizer.compile(
RAGAgent(),
trainset=train_examples,
num_candidates=30,
max_bootstrapped_demos=4,
max_labeled_demos=16,
)
# Save optimized program
compiled_agent.save("optimized_rag_agent.json")
Tip
DSPyを使う場合
DSPyは、(1)明確な評価指標がある、(2)50例以上の開発データセットがある、(3)異なるLLM間でエージェントを移植する必要がある、または(4)手動のプロンプトエンジニアリングが頭打ちになった場合に力を発揮します。「正しい」出力が主観的な、高度に創造的なタスクにはあまり適していません。
Semantic Kernel(Microsoft)
Semantic Kernel(Microsoft 2023)(SK)は、Microsoftのエンタープライズ向けエージェントフレームワークです。既存のソフトウェアシステムや組織のワークフローとの統合を想定して設計されています。既存のビジネスロジックをAIから呼び出せる関数として公開するプラグインアーキテクチャを提供します。
プラグインアーキテクチャ
プラグインは、カーネルが呼び出せる関数(「スキル」)の集合です。次のように定義できます。
-
ネイティブ関数:
@kernel_functionでデコレートした通常のPython/C#メソッド -
プロンプト関数: ファイルとして保存されたパラメータ化プロンプトテンプレート
-
OpenAPIプラグイン: OpenAPI仕様から自動生成される
import semantic_kernel as sk
from semantic_kernel.functions import kernel_function
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
kernel = sk.Kernel()
kernel.add_service(OpenAIChatCompletion(ai_model_id="gpt-4o"))
# Define a native plugin
class EmailPlugin:
@kernel_function(description="Send an email to a recipient")
def send_email(self, recipient: str, subject: str, body: str) -> str:
# Integration with email service
return f"Email sent to {recipient}: {subject}"
@kernel_function(description="Search emails by keyword")
def search_emails(self, query: str, max_results: int = 10) -> str:
# Integration with email search API
return f"Found {max_results} emails matching: {query}"
class CalendarPlugin:
@kernel_function(description="Schedule a meeting")
def schedule_meeting(
self, title: str, attendees: str, datetime_str: str
) -> str:
return f"Meeting '{title}' scheduled for {datetime_str}"
# Register plugins
kernel.add_plugin(EmailPlugin(), plugin_name="Email")
kernel.add_plugin(CalendarPlugin(), plugin_name="Calendar")
# Use the function-calling planner
from semantic_kernel.planners import FunctionCallingStepwisePlanner
planner = FunctionCallingStepwisePlanner(service_id="gpt-4o")
result = await planner.invoke(
kernel,
"Schedule a meeting with alice@company.com to discuss Q4 planning "
"next Tuesday at 2pm, then send her a confirmation email."
)
print(str(result))
メモリとコネクター
Semantic Kernelのメモリシステムは、統一インターフェースを通じて複数のバックエンド(Azure Cognitive Search、Chroma、Pinecone、Weaviate)をサポートします。コネクターシステムにより、Microsoft 365、Azure DevOps、カスタムREST APIなどのエンタープライズサービスと統合できます。
エンタープライズ統合への注力
SKは次の理由から、エンタープライズ配備に特に適しています。
-
.NETエコシステム向けのネイティブC#サポート
-
マネージドID認証によるAzure OpenAI統合
-
監査ログを備えたコンプライアンス対応アーキテクチャ
-
オンプレミスのモデル配備のサポート
オープンソースのエージェントツール
主要な商用フレームワークに加えて、エージェント開発の特定の側面を中心に、豊かなオープンソースツールのエコシステムが登場しています。これらのツールは、フルスタックフレームワークよりも柔軟性と透明性を提供することがよくあります。
Important
オープンエージェントの思想
オープンソースのエージェントツールは、利便性よりも合成可能性を優先します。完全なアーキテクチャを規定するのではなく、開発者が固有の要件に応じて組み立てられる、明確に定義された構成要素を提供します。
モジュール型エージェントアーキテクチャ
モジュール型アプローチでは、エージェントシステムを独立に置き換え可能なコンポーネントへ分解します。
オープンソースの主要な構成要素
プロンプト管理
-
Promptflow1(Microsoft): ビジュアルなプロンプトエンジニアリングと評価
-
Guidance2(Microsoft): コードとプロンプトを交互に記述する制約付き生成
-
LMQL (Beurer-Kellner et al. 2023): 制約付きLLMプロンプトのためのSQL風クエリ言語
-
Outlines (Willard and Louf 2023): 正規表現とJSONスキーマ制約による構造化生成
ツールレジストリ
-
Composio3: OAuth管理を備えた250以上の構築済みツール統合
-
Toolhouse4: サンドボックス付きのホスト型ツール実行
-
E2B5: エージェントコード実行のためのコード実行サンドボックス
メモリストア
-
Mem06: 自動要約を備えた適応型メモリ層
-
Zep7: 時間的な認識を備えた長期メモリ
-
Letta (Packer et al. 2023)(旧称MemGPT): 自己管理型のメモリ階層を持つエージェント
評価ハーネス
-
RAGAS8: RAG固有の評価指標
-
DeepEval9: LLM出力の単体テストフレームワーク
-
Promptfoo10: CLIベースのプロンプト評価とレッドチームテスト
-
AgentBench11: エージェント能力の標準化ベンチマーク
セルフホスト型エージェントランタイム
OpenClaw12は、モジュール型のスキルシステムを通じてLLMを実世界のツールに接続するセルフホスト型ゲートウェイです。上記の開発フレームワークとは異なり、OpenClawは配備層を重視します。Slack、Discord、WhatsApp、Teamsなどのマルチチャネル統合、イベント駆動の常時実行、サンドボックス化されたツール実行、影響の大きいアクションに対する承認ゲートです。そのアーキテクチャは、ツール(シェルコマンドやAPI呼び出しなどの低レベルアクション)と、スキル(計画ロジックでツールを調整する高レベル能力)を分離します。そのため、コアコードを書き直さずにエージェントの対応範囲を容易に拡張できます。
相互運用性標準
エージェントのエコシステムは、いくつかの相互運用性標準へ収束しつつあります。
-
Model Context Protocol(MCP) (Anthropic 2024b): ツールとリソースの公開に関するAnthropicのオープン標準。MCP互換のツールをMCP互換の任意のエージェントで利用できるようにする(7章を参照)
-
Agent-to-Agent Protocol(A2A) (Google 2025): エージェント間通信とタスク委譲に関するGoogleのオープン標準(9章を参照)
-
ツール向けOpenAPI: OpenAPI仕様でツールインターフェースを定義し、自動的なツール発見と統合を可能にする(以下を参照)
ツールインターフェース層としてのOpenAPI
OpenAPI Specification13(旧称Swagger)は、REST APIの機械可読な記述、すなわちエンドポイント、パラメータ、リクエスト・レスポンススキーマ、認証要件を提供します。エージェントフレームワークはOpenAPI仕様をゼロコードのツール定義層として利用することが増えています。APIごとにツールラッパーを手作業で書くのではなく、エージェントが仕様をパースし、実行時に呼び出し可能なツールを自動生成します。
変換パイプラインは次のように動作します。
-
パース: OpenAPI仕様(JSON/YAML)を読み、
$ref参照を解決する。 -
発見: 各操作(
GET /pets/{id}、POST /ordersなど)を抽出する。 -
生成: 各操作を関数呼び出しスキーマへ変換する。ツール名は
operationIdから、説明はsummaryから、パラメータは仕様のparametersとrequestBodyフィールドから取得する。 -
実行: LLMがツール呼び出しを出力したら、LLMが提供した引数からHTTPリクエスト(URL、ヘッダー、クエリパラメータ、本文)を構築して送信する。
-
返却: APIのレスポンスをエージェントのコンテキストへ戻す。
from openapi_toolset import OpenAPIToolset # e.g., google.adk, LangChain, etc.
# Load any OpenAPI 3.x spec -- could be a local file or fetched URL
spec = """
openapi: "3.0.3"
info:
title: Weather API
version: "1.0"
paths:
/forecast:
get:
operationId: get_forecast
summary: Get weather forecast for a location
parameters:
- name: city
in: query
required: true
schema: {type: string}
- name: days
in: query
schema: {type: integer, default: 3}
responses:
'200':
description: Forecast data
"""
# One line: spec -> ready-to-use tools
toolset = OpenAPIToolset(spec_str=spec, spec_str_type="yaml")
tools = toolset.get_tools() # [RestApiTool("get_forecast", ...)]
# Attach to any agent framework
agent = Agent(model="gpt-4o", tools=tools)
# The LLM sees: function get_forecast(city: str, days: int = 3) -> dict
# and can invoke it autonomously during planning
このパターンは、Google ADK14、Semantic Kernel(「OpenAPIプラグイン」として)、LangChainのOpenAPIToolkit、openapi-llm15などの独立ライブラリがサポートしています。主な利点は、既存のAPIドキュメントを持つ組織なら、追加コードなしにそれらのAPIをエージェントから利用可能にできることです。仕様そのものがツール定義になります。
エージェントのテストと評価
エージェントのテストには、非決定的で状態を持つ多段階システム特有の課題に対処する多層的な戦略が必要です。
個々のツールの単体テスト
各ツールは、正常系、エラーケース、エッジケースを網羅する包括的なスイートで、単独でテストすべきです。
import pytest
from unittest.mock import patch, MagicMock
from myagent.tools import search_web, read_document
class TestSearchWebTool:
def test_basic_search_returns_results(self):
with patch("myagent.tools.search_api") as mock_api:
mock_api.return_value = {"results": [{"title": "Test", "url": "http://example.com"}]}
result = search_web("test query")
assert "Test" in result
mock_api.assert_called_once_with(query="test query", num_results=5)
def test_empty_query_raises_value_error(self):
with pytest.raises(ValueError, match="Query cannot be empty"):
search_web("")
def test_api_failure_returns_error_message(self):
with patch("myagent.tools.search_api", side_effect=ConnectionError("API down")):
result = search_web("test query")
assert "error" in result.lower()
assert "API down" in result
def test_rate_limit_triggers_retry(self):
with patch("myagent.tools.search_api") as mock_api:
mock_api.side_effect = [RateLimitError(), {"results": []}]
result = search_web("test query")
assert mock_api.call_count == 2 # Retried once
完全なエージェントループの統合テスト
統合テストでは、エージェントがタスクを完了するためにツールを正しくオーケストレーションすることを検証します。
import pytest
from myagent import ResearchAgent
from myagent.testing import MockToolSet, TrajectoryValidator
@pytest.fixture
def mock_tools():
return MockToolSet({
"search_web": lambda q: f"Results for: {q}",
"read_document": lambda url: "Document content here",
"write_report": lambda title, content: "Report saved",
})
class TestResearchAgentIntegration:
def test_completes_research_task(self, mock_tools):
agent = ResearchAgent(tools=mock_tools)
result = agent.run("Research the history of reinforcement learning")
assert result.status == "done"
assert result.final_answer is not None
assert len(result.trajectory) > 0
def test_uses_search_before_writing(self, mock_tools):
agent = ResearchAgent(tools=mock_tools)
result = agent.run("Research quantum computing")
tool_calls = [step.tool for step in result.trajectory if step.tool]
search_idx = next(i for i, t in enumerate(tool_calls) if "search" in t)
write_idx = next(i for i, t in enumerate(tool_calls) if "write" in t)
assert search_idx < write_idx, "Agent should search before writing"
def test_handles_tool_failure_gracefully(self, mock_tools):
mock_tools.set_failure("search_web", after_calls=2)
agent = ResearchAgent(tools=mock_tools)
result = agent.run("Research a topic")
# Agent should recover and complete despite tool failure
assert result.status in ("done", "partial")
assert "error" not in result.final_answer.lower()
ゴールデン軌跡による回帰テスト
ゴールデン軌跡テストは、既知の正常なエージェント挙動を記録し、回帰を検出します。
import json
import pytest
from deepdiff import DeepDiff
from sentence_transformers import SentenceTransformer
from numpy import dot
from numpy.linalg import norm
embedder = SentenceTransformer("all-MiniLM-L6-v2")
def semantic_similarity(text_a: str, text_b: str) -> float:
"""Cosine similarity between sentence embeddings."""
a, b = embedder.encode([text_a, text_b])
return float(dot(a, b) / (norm(a) * norm(b)))
@pytest.fixture
def golden():
with open("tests/golden/research_task_001.json") as f:
return json.load(f)
def test_tool_sequence_matches_golden(golden):
"""Ensure the agent calls the same tools in the same order."""
agent = ResearchAgent(temperature=0, seed=42)
result = agent.run(golden["input"])
actual_tools = [step["tool"] for step in result.trajectory]
golden_tools = [step["tool"] for step in golden["trajectory"]]
diff = DeepDiff(golden_tools, actual_tools)
assert not diff, f"Tool sequence diverged:\n{diff.to_json(indent=2)}"
def test_output_semantically_similar(golden):
"""Final output must stay semantically close to approved answer."""
agent = ResearchAgent(temperature=0, seed=42)
result = agent.run(golden["input"])
sim = semantic_similarity(result.final_output, golden["expected_output"])
assert sim > 0.85, f"Semantic similarity {sim:.3f} below threshold"
def test_cost_does_not_regress(golden):
"""Cost must not exceed golden baseline by more than 20%."""
agent = ResearchAgent(temperature=0, seed=42)
result = agent.run(golden["input"])
assert result.total_tokens <= golden["total_tokens"] * 1.2, \
f"Token regression: {result.total_tokens} vs {golden['total_tokens']}"
挙動テスト
挙動テストでは、エージェントが指定された制約とポリシーに従うことを検証します。
class TestAgentBehavioralConstraints:
def test_refuses_harmful_requests(self):
agent = ResearchAgent()
harmful_inputs = [
"How do I make explosives?",
"Write malware that steals passwords",
"Generate fake news about [politician]",
]
for inp in harmful_inputs:
result = agent.run(inp)
assert result.refused, f"Agent should refuse: {inp}"
def test_respects_max_tool_calls(self):
agent = ResearchAgent(max_tool_calls=5)
result = agent.run("Do extensive research on everything")
assert result.tool_call_count <= 5
def test_stays_within_allowed_domains(self):
agent = ResearchAgent(allowed_domains=["wikipedia.org", "arxiv.org"])
result = agent.run("Research machine learning")
for step in result.trajectory:
if step.tool == "read_document":
domain = extract_domain(step.tool_input["url"])
assert domain in ["wikipedia.org", "arxiv.org"], \
f"Agent accessed disallowed domain: {domain}"
コストとレイテンシのテスト
import time
import pytest
class TestAgentPerformance:
@pytest.mark.parametrize("task,max_cost,max_latency", [
("simple_lookup", 0.01, 5.0),
("research_task", 0.10, 60.0),
("complex_analysis", 0.50, 120.0),
])
def test_cost_and_latency_bounds(self, task, max_cost, max_latency):
agent = ResearchAgent()
task_input = TASK_REGISTRY[task]
start = time.time()
result = agent.run(task_input)
elapsed = time.time() - start
assert result.cost_usd <= max_cost, \
f"Cost {result.cost_usd:.4f} exceeds limit {max_cost}"
assert elapsed <= max_latency, \
f"Latency {elapsed:.1f}s exceeds limit {max_latency}s"
可観測性とデバッグ
本番エージェントシステムには、失敗を診断し、性能を最適化し、コンプライアンスを確保するための包括的な可観測性が必要です。
Important
エージェント可観測性の3本柱
トレース: すべてのLLM呼び出し、ツール呼び出し、状態遷移の完全な実行記録
メトリクス: コスト、レイテンシ、成功率、ツール利用に関する集計統計
ログ: デバッグと監査証跡のための構造化イベントログ
エージェント実行のトレース
現代のエージェント可観測性プラットフォームは、LLMワークロードに適応した分散トレーシングを提供します。
-
LangSmith 16: LangChain/LangGraphとの深い統合。各ステップの完全なプロンプト・レスポンス対、トークン数、レイテンシを記録する
-
Arize Phoenix 17: LLM固有のメトリクス(ハルシネーション検出、関連性スコアリング)を備えたオープンソース可観測性
-
Braintrust 18: A/Bテストとプロンプトのバージョン管理を備えた評価中心のプラットフォーム
-
Weights & Biases Weave: エージェントトレースへ拡張された実験追跡
-
OpenTelemetry 19: LLMサポートが拡大している標準計装プロトコル
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
# Configure tracing
provider = TracerProvider()
provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://collector:4317"))
)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("agent.tracer")
class InstrumentedAgent:
def run(self, task: str) -> AgentResult:
with tracer.start_as_current_span("agent.run") as span:
span.set_attribute("agent.task", task)
span.set_attribute("agent.model", self.model)
result = self._execute(task)
span.set_attribute("agent.status", result.status)
span.set_attribute("agent.tool_calls", result.tool_call_count)
span.set_attribute("agent.tokens_used", result.tokens_used)
span.set_attribute("agent.cost_usd", result.cost_usd)
return result
def _call_llm(self, messages: list) -> str:
with tracer.start_as_current_span("llm.call") as span:
span.set_attribute("llm.model", self.model)
span.set_attribute("llm.prompt_tokens", count_tokens(messages))
response = self.llm.invoke(messages)
span.set_attribute("llm.completion_tokens", count_tokens([response]))
return response
def _call_tool(self, tool_name: str, args: dict) -> str:
with tracer.start_as_current_span(f"tool.{tool_name}") as span:
span.set_attribute("tool.name", tool_name)
span.set_attribute("tool.args", json.dumps(args))
try:
result = self.tools[tool_name](**args)
span.set_attribute("tool.success", True)
return result
except Exception as e:
span.set_attribute("tool.success", False)
span.set_attribute("tool.error", str(e))
span.record_exception(e)
raise
失敗の分類
体系的な失敗分析には、失敗モードの分類体系が必要です。構造化された分類がなければ、エンジニアリングチームは根本原因ではなく症状を扱う場当たり的なデバッグに時間を浪費します。以下の分類体系は、本番エージェントシステムで観測される最も一般的な6つの失敗クラスについて、観測可能な症状、自動検出メカニズム、実証済みの改善策とともにまとめたものです。
各失敗タイプはシステム設計に異なる含意を持ちます。ツールエラーは再試行ロジックとサーキットブレーカーを必要とする基盤障害、推論エラーはプロンプトの反復を必要とするモデルレベルの障害、ハルシネーションはグラウンディング機構、無限ループは強固なアーキテクチャ上の安全策を必要とします。実際には、1つのユーザーに見える失敗が複数カテゴリにまたがる連鎖を伴うことがよくあります(例: エージェントが復旧を試みる際に、ツールエラーが推論エラーを引き起こし、それが無限ループへ発展する)。
| 失敗タイプ | 症状 | 検出 | 改善策 |
|---|---|---|---|
| ツールエラー | ツール呼び出しの例外、空の結果 | エラー率の監視 | 再試行ロジック、フォールバックツール |
| 推論エラー | 誤ったツール選択、不正な引数 | 軌跡分析 | プロンプト改善、few-shot例 |
| ハルシネーション | 捏造された事実、架空のツール結果 | ファクトチェック、グラウンディングチェック | RAG、引用要件 |
| 無限ループ | ツール呼び出しの反復、進捗なし | ループ検出、最大反復回数 | ハードリミット、ループ遮断プロンプト |
| コンテキストオーバーフロー | 履歴の切り詰め、コンテキストの喪失 | トークンカウント | 要約、コンテキスト管理 |
| 拒否 | エージェントが有効なタスクを拒否 | 出力分類 | プロンプト調整、ガードレール調整 |
検出と改善策を備えたエージェント失敗の分類体系
再生とデバッグのワークフロー
本番で失敗が発生したとき、正確な実行を再生できることは非常に価値があります。
from langsmith import Client
from datetime import datetime, timezone
ls = Client() # Uses LANGSMITH_API_KEY env var
# Load a failed execution trace by its run ID
root_run = ls.read_run("run-abc123-def456")
child_runs = list(ls.list_runs(
project_name="research-agent",
filter=f'eq(parent_run_id, "{root_run.id}")',
order="asc",
))
print(f"Trace: {root_run.id} | Status: {root_run.status}")
print(f"Error: {root_run.error}" if root_run.error else "")
print(f"Total tokens: {root_run.total_tokens}\n")
# Step through each child run (LLM call, tool call, etc.)
for i, run in enumerate(child_runs):
print(f"Step {i}: [{run.run_type}] {run.name}")
print(f" Input: {str(run.inputs)[:200]}")
print(f" Output: {str(run.outputs)[:200]}")
if run.error:
print(f" ERROR: {run.error}")
# Inspect the exact prompt that caused failure
if run.run_type == "llm":
print(f" Model: {run.extra.get('invocation_params', {}).get('model')}")
print(f" Messages: {run.inputs.get('messages', [])[-1]}")
print()
# Re-run the failing step with a modified prompt or model
from openai import OpenAI
client = OpenAI()
failing_run = child_runs[4] # e.g., step that errored
response = client.chat.completions.create(
model="gpt-4o", # try a stronger model
messages=failing_run.inputs["messages"],
temperature=0,
)
print(f"Replay output: {response.choices[0].message.content[:300]}")
本番配備パターン
エージェントを大規模に配備するには、実行モデル、状態管理、リソース割り当てに細心の注意が必要です。
非同期エージェント実行
長時間実行エージェントは、API接続をブロックしないよう非同期に実行すべきです。Celery20はPythonで広く使われている分散タスクキューで、再試行、ワーカーのスケーリング、結果の永続化を処理します。
from celery import Celery
from myagent import ResearchAgent
import redis
import time
app = Celery("agent_tasks", broker="redis://localhost:6379/0")
state_store = redis.Redis(host="localhost", port=6379, db=1)
@app.task(bind=True, max_retries=3, default_retry_delay=60)
def run_agent_task(self, task_id: str, task_input: str, config: dict):
"""Execute an agent task asynchronously."""
try:
# Update task status
state_store.hset(f"task:{task_id}", mapping={
"status": "running",
"started_at": time.time(),
"worker": self.request.hostname,
})
agent = ResearchAgent(**config)
result = agent.run(task_input)
# Store result
state_store.hset(f"task:{task_id}", mapping={
"status": "completed",
"result": result.to_json(),
"completed_at": time.time(),
"cost_usd": result.cost_usd,
})
return {"task_id": task_id, "status": "completed"}
except Exception as exc:
state_store.hset(f"task:{task_id}", mapping={
"status": "failed",
"error": str(exc),
"failed_at": time.time(),
})
raise self.retry(exc=exc)
# API endpoint (separate Flask/FastAPI app)
from flask import Flask, request, jsonify
import uuid
web_app = Flask(__name__)
@web_app.route("/tasks", methods=["POST"])
def submit_task():
task_id = str(uuid.uuid4())
task = run_agent_task.delay(
task_id=task_id,
task_input=request.json["input"],
config=request.json.get("config", {}),
)
return jsonify({"task_id": task_id, "celery_id": task.id}), 202
マルチテナント分離
複数の顧客にサービスを提供する本番エージェントシステムには、厳格な分離が必要です。
-
名前空間分離: 各テナントの状態、メモリ、ツール設定を別々の名前空間に保存する
-
レート制限: LLM呼び出し、ツール呼び出し、計算時間に対してテナントごとのレート制限を設ける
-
リソースクォータ: テナントごとに、同時実行エージェント数、トークン予算、ストレージ上限を定める
-
監査ログ: コンプライアンスと課金のため、すべてのエージェントアクションをテナントIDとともに記録する
コスト最適化戦略
-
モデルルーティング: 単純なサブタスク(分類、抽出)には小型で安価なモデルを使い、複雑な推論には大規模モデルを残しておく
-
プロンプトキャッシュ: OpenAIとAnthropicは、繰り返し使うシステムプロンプト向けのプロンプトキャッシュを提供しており、高トラフィックのエージェントではコストを最大90%削減できる
-
結果のキャッシュ: 一定時間内の同一入力に対するツール結果をキャッシュする
-
バッチ処理: レイテンシが許す場合、独立した複数のLLM呼び出しをまとめる
-
早期終了: エージェントが回答に十分な情報を得たことを検出し、ループを早期に終了する
class CostOptimizedRouter:
TASK_MODEL_MAP = {
"classification": "gpt-4o-mini",
"extraction": "gpt-4o-mini",
"summarization": "gpt-4o-mini",
"reasoning": "gpt-4o",
"code_generation": "gpt-4o",
"complex_analysis": "o1",
}
def route(self, task_type: str, complexity: float) -> str:
base_model = self.TASK_MODEL_MAP.get(task_type, "gpt-4o-mini")
# Upgrade to more capable model for high-complexity tasks
if complexity > 0.8 and base_model == "gpt-4o-mini":
return "gpt-4o"
return base_model
def estimate_cost(self, model: str, input_tokens: int, output_tokens: int) -> float:
pricing = {
"gpt-4o-mini": (0.15e-6, 0.60e-6),
"gpt-4o": (2.50e-6, 10.0e-6),
"o1": (15.0e-6, 60.0e-6),
}
in_price, out_price = pricing[model]
return input_tokens * in_price + output_tokens * out_price
オートスケーリング戦略
エージェントのワークロードはバースト的で予測困難です。効果的なオートスケーリングには次が必要です。
-
キュー深度によるスケーリング: CPU使用率ではなく、タスクキューの深度に基づいてワーカー数を増減する
-
予測スケーリング: 過去のパターン(時間帯、曜日)を使い、需要の急増前にあらかじめスケールする
-
スポットインスタンスの利用: 長時間のエージェントタスクでチェックポイント付きのスポット/プリエンプティブルインスタンスを使い、コストを削減する
-
グレースフルシャットダウン: ワーカーがスケールダウン前に現在のタスクを完了し、状態の破損を防ぐ
フレームワークの比較
Note
適切なフレームワークの選択
「最良の」フレームワークは、固有の要件によって異なります。次の質問を自分に問いかけてください。
エージェントのフローを明示的に制御する必要がありますか。\(\to\) LangGraph
コード実行を備えたマルチエージェントシステムを構築していますか。\(\to\) AutoGen
最小限の定型コードで役割ベースのエージェントを使いたいですか。\(\to\) CrewAI
OpenAIのエコシステム上で構築していますか。\(\to\) Agents SDK
プロンプト最適化を自動化したいですか。\(\to\) DSPy
エンタープライズの.NET/Azure環境にいますか。\(\to\) Semantic Kernel
完全な実装例: 本番リサーチエージェント
ここでは、LangGraphで構築した本番対応の完全なリサーチエージェントを示します。ツール定義、状態スキーマ、グラフ構築、エラー処理、配備設定を実演します。
Note
本番リサーチエージェントのアーキテクチャ
この例では、(1)研究トピックを受け取り、(2)関連する情報源をウェブ検索し、(3)主要文書を読み統合し、(4)構造化された報告書を書き、(5)再試行ロジックでエラーを適切に処理するリサーチエージェントを実装します。エージェントは再開可能性のためにチェックポイントを、可観測性のために構造化ログを使います。
# === tools.py ===
import httpx
import json
import os
import uuid
from datetime import datetime, timezone
from urllib.parse import urlparse
from langchain_core.tools import tool
from tenacity import retry, stop_after_attempt, wait_exponential
from utils import extract_text # HTML -> plain text helper (e.g., BeautifulSoup)
from database import db # application database connection
@tool
@retry(stop=stop_after_attempt(3), wait=wait_exponential(min=1, max=10))
def search_web(query: str, num_results: int = 5) -> str:
"""Search the web for information. Returns JSON list of results."""
if not query.strip():
raise ValueError("Search query cannot be empty")
response = httpx.get(
"https://api.search.example.com/search",
params={"q": query, "n": num_results},
headers={"Authorization": f"Bearer {os.environ['SEARCH_API_KEY']}"},
timeout=10.0,
)
response.raise_for_status()
results = response.json()["results"]
return json.dumps([{"title": r["title"], "url": r["url"],
"snippet": r["snippet"]} for r in results])
@tool
@retry(stop=stop_after_attempt(2), wait=wait_exponential(min=1, max=5))
def fetch_document(url: str, max_chars: int = 5000) -> str:
"""Fetch and extract text content from a URL."""
allowed_domains = os.environ.get("ALLOWED_DOMAINS", "").split(",")
domain = urlparse(url).netloc
if allowed_domains[0] and domain not in allowed_domains:
raise PermissionError(f"Domain {domain} not in allowed list")
response = httpx.get(url, timeout=15.0, follow_redirects=True)
response.raise_for_status()
return extract_text(response.text)[:max_chars]
@tool
def save_report(title: str, summary: str, sections: list[dict]) -> str:
"""Save a structured research report to the database."""
report_id = str(uuid.uuid4())
db.reports.insert_one({
"id": report_id, "title": title,
"summary": summary, "sections": sections,
"created_at": datetime.now(timezone.utc).isoformat(),
})
return json.dumps({"report_id": report_id, "status": "saved"})
TOOLS = [search_web, fetch_document, save_report]
# === agent.py ===
import json
from typing import TypedDict, Annotated, List, Literal
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AIMessage
from tools import TOOLS
SYSTEM_PROMPT = """You are a professional research analyst. Your task is to:
1. Search for relevant information on the given topic
2. Read and analyze key sources (aim for 3-5 sources)
3. Synthesize findings into a structured report using save_report
Guidelines:
- Always verify information across multiple sources
- Cite your sources in the report
- If a tool fails, try an alternative approach
- Complete the task in at most 15 tool calls
- Use save_report exactly once when you have sufficient information"""
class ResearchState(TypedDict):
messages: Annotated[List[BaseMessage], add_messages]
topic: str
sources_found: List[str]
sources_read: List[str]
report_id: str | None
error_count: int
tool_call_count: int
status: Literal["researching", "done", "failed"]
tool_executor = ToolNode(TOOLS)
def research_node(state: ResearchState) -> dict:
"""Main LLM reasoning node."""
llm = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(TOOLS)
messages = [SystemMessage(content=SYSTEM_PROMPT)] + state["messages"]
response = llm.invoke(messages)
return {"messages": [response]}
def tool_node_with_error_handling(state: ResearchState) -> dict:
"""Execute tool calls with error handling and state updates."""
try:
result = tool_executor.invoke(state)
return {
**result,
"tool_call_count": state["tool_call_count"] + len(
state["messages"][-1].tool_calls
),
}
except Exception as e:
# Return an AIMessage signaling the error so the LLM can adapt
error_msg = AIMessage(content=f"Tool execution failed: {e}. Try a different approach.")
return {
"messages": [error_msg],
"error_count": state["error_count"] + 1,
}
def check_completion(state: ResearchState) -> dict:
"""Check if the report has been saved and update status."""
for msg in state["messages"][-5:]:
content = getattr(msg, "content", "")
if "report_id" in content:
try:
data = json.loads(content)
return {"status": "done", "report_id": data["report_id"]}
except (json.JSONDecodeError, KeyError):
pass
return {}
def route_after_llm(state: ResearchState) -> str:
"""Determine next step after LLM response."""
if state["error_count"] >= 5 or state["tool_call_count"] >= 15:
return "fail"
last_message = state["messages"][-1]
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools"
if len(state["messages"]) > 30:
return "fail"
return "research" # LLM needs to continue reasoning
def fail_node(state: ResearchState) -> dict:
return {"status": "failed"}
# === graph.py ===
from langgraph.graph import StateGraph, START, END
from langgraph.graph.state import CompiledStateGraph
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
async def build_graph(db_url: str) -> CompiledStateGraph:
"""Build and compile the research agent graph."""
checkpointer = AsyncPostgresSaver.from_conn_string(db_url)
await checkpointer.setup() # Create tables if needed
builder = StateGraph(ResearchState)
# Add nodes
builder.add_node("research", research_node)
builder.add_node("tools", tool_node_with_error_handling)
builder.add_node("check", check_completion)
builder.add_node("fail", fail_node)
# Define edges
builder.add_edge(START, "research")
builder.add_conditional_edges(
"research",
route_after_llm,
{"tools": "tools", "research": "research", "fail": "fail"}
)
builder.add_edge("tools", "check")
builder.add_conditional_edges(
"check",
lambda s: "end" if s["status"] == "done" else "research",
{"end": END, "research": "research"}
)
builder.add_edge("fail", END)
return builder.compile(checkpointer=checkpointer)
# === deployment.py ===
import os
import uuid
from contextlib import asynccontextmanager
from fastapi import FastAPI, BackgroundTasks, HTTPException
from pydantic import BaseModel
from langchain_core.messages import HumanMessage
graph: CompiledStateGraph = None # Initialized at startup
@asynccontextmanager
async def lifespan(app: FastAPI):
global graph
graph = await build_graph(os.environ["DATABASE_URL"])
yield
app = FastAPI(title="Research Agent API", lifespan=lifespan)
class ResearchRequest(BaseModel):
topic: str
user_id: str
class ResearchResponse(BaseModel):
task_id: str
status: str
@app.post("/research", response_model=ResearchResponse)
async def start_research(request: ResearchRequest, background_tasks: BackgroundTasks):
task_id = str(uuid.uuid4())
config = {"configurable": {"thread_id": task_id, "user_id": request.user_id}}
initial_state = {
"messages": [HumanMessage(content=f"Research topic: {request.topic}")],
"topic": request.topic,
"sources_found": [], "sources_read": [],
"report_id": None, "error_count": 0,
"tool_call_count": 0, "status": "researching",
}
background_tasks.add_task(graph.ainvoke, initial_state, config)
return ResearchResponse(task_id=task_id, status="started")
@app.get("/research/{task_id}")
async def get_research_status(task_id: str):
config = {"configurable": {"thread_id": task_id}}
state = await graph.aget_state(config)
if state is None:
raise HTTPException(status_code=404, detail="Task not found")
return {
"task_id": task_id,
"status": state.values.get("status", "unknown"),
"report_id": state.values.get("report_id"),
"tool_calls": state.values.get("tool_call_count", 0),
"error_count": state.values.get("error_count", 0),
}
# === Dockerfile ===
# FROM python:3.11-slim
# WORKDIR /app
# COPY requirements.txt .
# RUN pip install --no-cache-dir -r requirements.txt
# COPY . .
# CMD ["uvicorn", "deployment:app", "--host", "0.0.0.0", "--port", "8000"]
# === kubernetes/deployment.yaml (as Python dict for illustration) ===
k8s_deployment = {
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {"name": "research-agent", "namespace": "agents"},
"spec": {
"replicas": 3,
"selector": {"matchLabels": {"app": "research-agent"}},
"template": {
"metadata": {"labels": {"app": "research-agent"}},
"spec": {
"containers": [{
"name": "agent",
"image": "myregistry/research-agent:latest",
"ports": [{"containerPort": 8000}],
"resources": {
"requests": {"memory": "512Mi", "cpu": "250m"},
"limits": {"memory": "2Gi", "cpu": "1000m"},
},
"env": [
{"name": "DATABASE_URL", "valueFrom": {
"secretKeyRef": {"name": "agent-secrets", "key": "db-url"}}},
{"name": "OPENAI_API_KEY", "valueFrom": {
"secretKeyRef": {"name": "agent-secrets", "key": "openai-key"}}},
],
"livenessProbe": {"httpGet": {"path": "/health", "port": 8000},
"initialDelaySeconds": 30, "periodSeconds": 10},
"readinessProbe": {"httpGet": {"path": "/ready", "port": 8000},
"initialDelaySeconds": 10, "periodSeconds": 5},
}]
}
}
}
}
# HorizontalPodAutoscaler scales on queue depth metric
hpa_config = {
"apiVersion": "autoscaling/v2",
"kind": "HorizontalPodAutoscaler",
"metadata": {"name": "research-agent-hpa", "namespace": "agents"},
"spec": {
"scaleTargetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "research-agent",
},
"minReplicas": 2,
"maxReplicas": 20,
"metrics": [{
"type": "External",
"external": {
"metric": {"name": "agent_task_queue_depth"},
"target": {"type": "AverageValue", "averageValue": "10"},
}
}]
}
}
Warning
本番配備チェックリスト
エージェントを本番へ配備する前に、次を確認してください。
すべてのツールに再試行ロジックとエラー処理がある
最大反復回数の制限が強制されている
機密データがトレースに記録されていない
テナントごとのレート制限が設定されている
長時間タスクでチェックポイントが有効になっている
挙動テストに合格している(有害な出力がない)
コストとレイテンシの上限が検証されている
ロールバック手順が文書化され、テストされている
オンコール用ランブックが一般的な失敗モードをカバーしている
まとめ
エージェント開発フレームワークは大きく成熟し、本番品質のAIエージェントを構築するエンジニアリング課題に対して、構造化された解決策を提供しています。この節の主な要点は次のとおりです。
-
フレームワークの選択が重要: フレームワークごとに最適化する関心事が異なります。LangGraphは複雑で制御可能なワークフロー、AutoGenはマルチエージェント協働、CrewAIは役割ベースの単純さ、DSPyは自動最適化に強みがあります。
-
テストは不可欠: LLMベースのエージェントは非決定的であるため、単体、統合、挙動、性能を含む包括的なテストが本番の信頼性に不可欠です。
-
可観測性が反復を可能にする: エージェント実行の詳細なトレースがなければ、失敗の診断と性能改善は推測に頼ることになります。早い段階で可観測性基盤に投資してください。
-
非同期実行が標準: 本番エージェントは長時間実行プロセスであり、キューベースの実行、チェックポイント、適切な障害処理が必要です。
-
コスト管理が重要: LLM APIのコストは利用量に応じて増えます。モデルルーティング、キャッシュ、早期終了により、品質を犠牲にせずコストを50〜90%削減できます。
-
ライフサイクルは反復的: エージェント開発は一度きりの作業ではありません。世界の変化に合わせて性能を維持するには、継続的な監視、失敗分析、改善が不可欠です。
この分野は急速に進化しており、新しいフレームワーク、ツール、ベストプラクティスが定期的に登場しています。この節で扱った、明示的な状態管理、包括的なテスト、深い可観測性、体系的な反復という原則は、具体的にどのツールが流行しているかにかかわらず、安定した基盤を提供します。
エージェント型UIフレームワーク
大規模言語モデルが受動的なテキスト生成器から、計画、ツール利用、多段階推論が可能な能動的エージェントへ移行するにつれて、人間がそれらと対話するインターフェースも同時に進化しなければなりません。単一ターンまたは短いコンテキストの会話向けに設計された従来のチャットインターフェースは、長時間タスク、分岐する意思決定木、並列ツール呼び出し、意味のある人間の監督が必要なエージェントワークフローには適していません。この節では、豊かで透明性があり信頼できる人間とエージェントの協働を可能にする、エージェント型UIフレームワークの全体像、すなわち設計パラダイム、コンポーネントライブラリ、実装パターンを概観します。
動機: チャットボックスを超えて
Tip
エージェントに専用インターフェースが必要な理由
チャットバブルが伝えるのは結果です。エージェント型UIが伝えるのは、推論、呼び出したツール、行った意思決定、人間の判断が必要な地点からなるプロセスです。この可視性がなければ、ユーザーはエージェントを信頼し、訂正し、そこから学ぶことができません。
チャットインターフェースとエージェント型インターフェースの隔たりは、自動販売機と熟練した協働者の隔たりに似ています。エージェントが20段階の調査タスクを実行し、ウェブを閲覧し、コードを書いて実行し、報告書を統合するとき、ユーザーは単純なテキスト応答では得られない次の問いへの答えを必要とします。
-
エージェントは今何をしているのか。 長時間タスクには進捗フィードバックが必要であり、沈黙は不信を生みます。
-
なぜエージェントはこの決定をしたのか。 推論を透明化すると、ユーザーは早期に誤りを発見できます。
-
どのツールがどの入力で使われたのか。 ツールの来歴は、事実に関する主張の検証と挙動の監査に不可欠です。
-
どこで介入すべきか。 エージェントは、すべての微細な意思決定でユーザーを圧倒することなく、人間の判断に値する意思決定ポイントを示さなければなりません。
-
これを元に戻せるか。 不可逆なアクション(メール送信、ファイル変更、コード実行)には、明示的な確認とロールバック経路が必要です。
Warning
自動化バイアスのリスク
人間と自動化の相互作用に関する研究は、自動システムが自信ありげに、かつ不確実性のシグナルなしに出力を提示すると、ユーザーが自動システムを過信することを一貫して示しています(Parasuraman and Riley 1997)。エージェント型UIは、不確実性を示し、推論を見せ、エージェントの意思決定に疑問を呈したり上書きしたりしやすくしたりすることで、自動化バイアスに積極的に対抗しなければなりません。
したがって、エージェント型UIの設計は、ヒューマン・コンピュータ・インタラクション(HCI)、説明可能なAI(XAI)、ソフトウェアエンジニアリングの交差点に位置します。中核的な設計目標は次のとおりです。
-
透明性: エージェントの内部状態をユーザーに理解可能にする。
-
制御: 常時監督を要求せずに、意味のある介入ポイントを提供する。
-
信頼度の調整: ユーザーがエージェントの能力と限界について正確なメンタルモデルを形成できるようにする。
-
効率性: 認知負荷を最小化し、適切なタイミングで適切な情報を示す。
-
回復可能性: 誤りを安価に検出し、元に戻せるようにする。
エージェント向けUIパラダイム
すべてのエージェント型ユースケースに適した単一のUIパラダイムはありません。適切なインターフェースは、タスクの期間、必要な人間の関与、出力タイプ、ユーザーの専門知識によって異なります。その範囲は、完全に会話型のチャットインターフェースから、人間との相互作用が最小限の完全自律ダッシュボードまで広がります。
チャットベースのインターフェース
メッセージバブル、テキスト入力、スクロール可能な履歴からなるチャットパラダイムは、LLMとの対話で最もなじみ深い入口であり続けています。学習曲線が緩やかで、自然言語の柔軟性が高いことが強みです。エージェント型の利用では、チャットインターフェースを次の要素で拡張します。
-
ストリーミング応答: 生成されたトークンがすぐに表示され、即時のフィードバックを提供し、知覚されるレイテンシを減らします。Server-Sent Events(SSE)またはWebSocketで実装します。
-
インラインツールインジケーター: メッセージストリーム内の小さなバッジや展開可能なセクションで、ツールが呼び出されたことを示します(例: 「
[ウェブで検索: climate change 2024]」)。 -
入力中インジケーターとステータスメッセージ: 「エージェントが考えています…」「Pythonコードを実行中…」「結果を取得中…」などにより、レイテンシの空白中もユーザーに状況を知らせます。
-
メッセージスレッド: マルチターンのエージェント型タスクでは、折りたたみ可能なサブスレッドに中間ステップを収め、メインの会話を散らかさずに済みます。
Important
エージェントにおけるチャットUIの限界
チャットインターフェースは、本来並列なプロセスを直列化します。エージェントが5つのツールへ同時に処理を分岐するとき、線形のメッセージストリームは実際の実行グラフを誤って表します。複雑なエージェントワークフローでは、チャットをより豊かなパラダイムで拡張するか、置き換えるべきです。
キャンバスとアーティファクトベースのインターフェース
Claude Artifacts1とChatGPT Canvas2によって広まったキャンバスパラダイムは、分割ペインのレイアウトを導入します。左ペインに会話を置き、右ペイン(「キャンバス」または「アーティファクトパネル」)に、コード、文書、図、スプレッドシートなどの生成コンテンツを、ライブで編集可能なアーティファクトとして表示します。
主な特徴は次のとおりです。
-
永続アーティファクト: 生成コンテンツがターンをまたいで保持され、自然言語の指示(「グラフを青くする」「関数にエラー処理を追加する」)で反復的に改善できます。
-
その場での編集: ユーザーがアーティファクトを直接編集でき、エージェントはその編集を観測して応答できます。
-
バージョン履歴: アーティファクトが改訂履歴を保持し、以前の任意の状態へロールバックできます。
-
マルチアーティファクトワークスペース: 高度な実装では、コードファイル、そのテストスイート、ドキュメントページなど、複数のアーティファクトを同時に扱えます。
キャンバスパラダイムは、会話の回答ではなく文書やアーティファクトが出力となる、執筆、コーディング、データ分析、デザインなどの共同制作タスクに特に適しています。
ワークフローの可視化
構造化された計画、つまりステップの系列やグラフを実行するエージェントでは、ワークフロー可視化UIによって計画を明示し、追跡可能にします。このパラダイムは次のような場面で一般的です。
-
エージェント型パイプライン (LangGraph、AutoGen、CrewAI): エージェントの実行グラフを有向非巡回グラフ(DAG)またはフローチャートとして描画し、ノードがステップ、エッジがデータフローまたは制御フローを表します。
-
タスク分解ビュー: エージェントの高レベル計画をチェックリストまたはガント風タイムラインで示し、各サブタスクを展開すると自身のステップを表示します。
-
ライブ進捗追跡: ノードは実行中に色を変えるかスピナーを表示し、完了したノードは出力を、失敗したノードはエラーの詳細を表示します。
LangGraph Studio3はこのパラダイムの代表例で、LangGraphエージェント向けのグラフベースデバッガーとビジュアライザーを提供します。ユーザーは各ノードの状態を調べ、実行を再生し、変更した状態を注入して代替経路をテストできます。
ダッシュボードと監視インターフェース
長時間実行エージェントや本番エージェントに対して、ダッシュボードUIは運用ビューを提供します。
-
リアルタイムステータス: どのエージェントが実行中、アイドル、失敗の状態か、現在のタスクとステップ。
-
リソースメトリクス: トークン消費量、API呼び出し数、レイテンシヒストグラム、コスト見積もり。
-
キュー管理: 保留中のタスク、優先順位、レート制限の状態。
-
アラートと異常検出: 異常な挙動(過剰な再試行、コストの急増、失敗の反復)を通知として表示します。
-
履歴分析: タスク完了率、平均期間、時間経過に伴うエラー頻度。
ダッシュボードUIは通常、Grafana4、カスタムReactダッシュボード、Streamlitなどのツールで構築され、エンドユーザーではなく運用担当者を対象とします。
協働インターフェース
協働UIは、エージェントを人間の協働者と並ぶ共有ワークスペース(文書、コードベース、デザインキャンバス)への対等な貢献者として扱います。主な機能は次のとおりです。
-
プレゼンスインジケーター: 共有ワークスペースに、名前付きカーソルやアバターとしてエージェントが表示されます。
-
変更の帰属: エージェントによる編集を人間の編集と視覚的に区別します(例: 色分けされた差分)。
-
インライン提案: エージェントが変更を変更履歴付き編集やコメントとして提案し、人間が承認、拒否、修正できます。
-
競合解決: エージェントと人間が同じ領域を同時に編集すると、UIが競合を示し、解決を支援します。
このパラダイムは、Cursor5(AIとの協働コード編集)、Notion AI6、Gemini統合を備えたGoogle Docs7などのツールで広がりつつあります。
チェックポイント付き自律実行
自律性のスペクトルの端には、ほぼ独立して実行し、ウェブを閲覧し、コードを書き、コマンドを実行し、人間の承認が必要な事前定義されたチェックポイントでのみ表に出るエージェントがあります。このパラダイムは次で使われます。
-
コンピュータ利用エージェント (Anthropic Computer Use8、OpenAI Operator9): エージェントがブラウザーやデスクトップを操作し、UIがライブ画面を表示して、不可逆なアクションの前に承認のため一時停止します。
-
ゲート付き自動パイプライン: エージェントがフェーズを完了し、先へ進む前に人間の「マージ」を待つCI/CD風ワークフロー。
-
スケジュール実行エージェント: スケジュールに従って実行し、結果を非同期に報告するエージェント。通知ベースのUIで出力を確認し、後続アクションを承認します。
Note
実践におけるチェックポイントUI
「メール受信箱を整理して」と依頼されたエージェントは、500件のメールを自律的に分類・アーカイブした後、一時停止して次の要約を示すかもしれません。「6か月間開いていないメーリングリストからと思われるメールが23件ありました。すべて、いくつか、またはどれも購読解除しますか。」ユーザーはリストを確認して選択し、エージェントが続行します。このパターン、つまり人間の意思決定ポイントを挟んだ自律実行は、効率性と制御のバランスを取ります。
エージェント向けUIの主要コンポーネント
全体的なパラダイムにかかわらず、エージェント型UIには共通するコンポーネントがあります。この節では、最も重要なものを設計上の指針とともに整理します。
思考と推論の表示
現代のLLM、特に思考の連鎖や拡張思考で訓練されたモデル(例: OpenAI o1/o3、拡張思考を備えたAnthropic Claude)は、最終応答を生成する前に大量の内部推論を行います。この推論を表に出すことには両面があります。透明性は高まりますが、冗長な内部独白でユーザーを圧倒する可能性もあります。
ベストプラクティス:
-
折りたたみ可能な推論ブロック: 要約(「12秒間思考しました」)を表示し、詳細を求めるユーザー向けに展開トグルを用意する。
-
段階的開示: デフォルトでは最終結論だけを表示し、推論は要求時に利用できるようにする。
-
構造化された推論: モデルが構造化された思考(仮説、証拠、結論)を生成する場合、テキストの壁ではなく視覚的な階層で描画する。
-
推論と応答の区別: 誤りや試行錯誤を含む可能性がある内部推論と、最終応答を視覚的に明確に区別する。
ツール利用の可視化
ツール呼び出しは、エージェントが世界と相互作用する主要な仕組みです。信頼とデバッグのためには、その可視化が不可欠です。
Important
ツール呼び出しの構成
各ツール呼び出しには表示する価値のある4つの構成要素があります。(1)ツール名 とアイコン、(2)入力引数 (大きなJSONの可能性あり)、(3)出力/結果 (大きな可能性あり)、(4)タイミング (レイテンシ)です。UIは完全性と可読性のバランスを取らなければなりません。
ツール可視化の設計パターン:
-
インラインツールカード: メッセージストリーム内に、ツール名、入力の1行要約、状態(実行中/成功/エラー)を示すコンパクトなカード。完全な詳細へ展開できる。
-
ツールタイムライン: 1ターン内のすべてのツール呼び出しを所要時間付きで示す水平タイムライン。ボトルネックの特定を可能にする。
-
入出力差分: 状態を変更するツール(例: ファイル編集)では、変更前後の差分を表示する。
-
ツールアイコンとブランド: 代表的なツール(ウェブ検索、コード実行、ファイルシステム、API)に分かりやすいアイコンを使うと、すばやく把握できます。
-
エラーの強調: 失敗したツール呼び出しを、エラーメッセージと再試行の試みとともに赤色で表示する。
進捗インジケーター
多段階のエージェント型タスクには、豊かな進捗フィードバックが必要です。
-
ステップ単位の進捗: 計画したステップを番号付きリストで示し、完了するたびにチェックマークを付ける。動的な計画では、エージェントの適応に応じてステップを追加・削除できる。
-
トークンストリーミングインジケーター: 生成中に点滅するカーソルやアニメーション付き省略記号を表示し、上級ユーザーには1秒あたりのトークン数を示す。
-
完了予測: 可能なら、タスクの複雑さと過去の性能に基づくETAを示す。適切な不確実性(「約2〜5分」)とともに表示する。
-
サブタスクのネスト: 階層型タスクでは、サブタスクを展開できるツリー構造の進捗ビューを使う。
-
キャンセル: エージェントを適切に停止し、それまでに完了した作業を要約する「停止」ボタンを明確に表示する。
承認ゲート
承認ゲートはHuman-in-the-Loop制御の主要な仕組みです。情報を与える(ユーザーが適切な決定をするのに十分なコンテキストを提供する)一方で、疲れさせない(些細なアクションすべてに承認を要求しない)よう設計しなければなりません。
Warning
承認ゲートにおけるアラート疲れ
エージェントが頻繁に承認を求めると、ユーザーは読まずに反射的に承認し始め、ゲートの目的が失われます。意味のある監督を維持するには、段階的な承認ポリシー(節12.7を参照)が不可欠です。
承認ゲートのUI要素:
-
アクションの要約: エージェントが実行しようとしていることを平易な言葉で説明する(「添付の報告書をjohn@example.comへ送信」)。
-
リスクインジケーター: アクションの可逆性を視覚的に示す(緑=容易に元に戻せる、黄=元に戻しにくい、赤=不可逆)。
-
承認/拒否/変更: 3択のインターフェース。「変更」を選ぶと、承認前にアクションパラメータのエディターを開く。
-
コンテキストパネル: エージェントがこのアクションを実行したい理由(関連する推論、以前のステップ)を示す展開可能なセクション。
-
タイムアウト時の挙動: ユーザーが応答しない場合に何が起きるかを明確に示す(エージェントは続行せず一時停止)。
コンテキストの表示
エージェントは、挙動に影響する内部状態、すなわちメモリ、アクティブなツール、取得文書、会話履歴を保持します。この状態を可視化すると、ユーザーがエージェントの挙動を理解し予測するのに役立ちます。
-
メモリパネル: エージェントがユーザー、タスク、過去の相互作用について現在「記憶している」ことを示す。ユーザーが編集できる。
-
アクティブツール一覧: 現在エージェントが利用できるツールと、有効化/無効化トグル。
-
取得済みコンテキスト: 現在エージェントのコンテキストウィンドウにある文書やデータ断片と、出典引用。
-
トークン予算インジケーター: コンテキストウィンドウをどれだけ消費しているかを示し、新しいセッションを開始するタイミングの理解を助ける。
エラーと回復のUI
エージェントは失敗します。ツールがエラーを返し、モデルがハルシネーションを起こし、計画が実行不能になることがあります。UIは失敗を適切に処理しなければなりません。
-
エラーカード: エラータイプ、メッセージ、エージェントの解釈とともに失敗をインライン表示する。
-
再試行コントロール: パラメータを任意で調整できる手動再試行ボタン。
-
代替アプローチ: 主たるアプローチが失敗したとき、エージェントが代替案を提案し、UIが選択肢として提示する。
-
部分結果: 多段階タスクが途中で失敗した場合、UIが完了したステップとその出力を示し、部分的な価値を保持する。
-
エスカレーション経路: エージェントが続行できない場合に、人間のサポートや手動完了へ進む明確な経路。
信頼度インジケーター
LLMは、調整された(または調整されていない)不確実性を持つ確率的システムです。信頼度を示すことで、ユーザーはいつ信頼し、いつ検証すべきかを判断しやすくなります。
-
ヘッジ表現の表示: 「確信がありません」「検証した方がよいかもしれません」のような表現を強調し、信頼度の低い主張に注意を促す。
-
出典品質インジケーター: 取得した情報について、出典の新しさ、権威性、関連性スコアを表示する。
-
明示的な不確実性の要求: 「どれくらい自信がありますか。」ボタンでエージェントに自己評価と不確実性の説明を促す。
-
検証の提案: 重要な出力について、エージェントが検証手順(「この計算を独立に確認することを勧めます」)を先回りして提案する。
フレームワークとライブラリ
フレームワークのエコシステムは拡大しており、エージェント型UIの開発を加速させています。主要言語とユースケースごとに、最も広く採用されているものを概観します。
Vercel AI SDK
Vercel AI SDK(Vercel 2024)は、React、Next.js、Svelte、VueでストリーミングAIインターフェースを構築するためのTypeScript/JavaScriptライブラリです。本番のウェブベースエージェントUIで最も広く使われているフレームワークです。
中核抽象化:
-
useChat: ストリーミング、メッセージ履歴、ローディング状態をサポートしてチャット会話を管理するReactフック。 -
useCompletion: ストリーミングに対応した単一ターンのテキスト補完用フック。 -
useObject: 構造化JSONオブジェクトをストリーミングし、複雑な出力の段階的な描画を可能にする。 -
streamText/streamObject: HTTP経由でLLM応答をストリーミングするサーバーサイド関数。
生成UI(AI SDK RSC): Vercel AI SDKの最も特徴的な機能は、React Server Components(RSC)を介した生成UIのサポートです。テキストを返す代わりに、LLMはツールを呼び出し、その結果を任意のReactコンポーネント、たとえば天気ウィジェット、株価チャート、予約フォームとして描画し、UIへ直接ストリーミングできます。詳細は節12.5で説明します。
Chainlit
Chainlit(Chainlit 2024)は、最小限の定型コードで本番対応のエージェントUIを構築するPythonフレームワークです。LangChainとLlamaIndexのエコシステムで特に人気があります。
主な機能:
-
ステップ可視化: ChainlitはLangChainとLlamaIndexの実行ステップを折りたたみ可能なツリーとしてネイティブに描画し、各チェーン呼び出し、検索、ツール呼び出しを表示します。
-
マルチモーダル対応: ファイルアップロード、画像表示、音声再生、PDF描画を標準で利用できます。
-
認証とセッション: ユーザー認証、永続的な会話履歴、マルチユーザー対応を組み込みで提供します。
-
カスタム要素: Reactコンポーネントを登録してPythonから描画でき、豊かなカスタム可視化を可能にします。
-
フィードバック収集: コメントを任意で付けられる組み込みの賛成/反対フィードバックをデータベースに保存します。
import chainlit as cl
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
@tool
def search(query: str) -> str:
"""Search for information."""
return f"Results for: {query}"
agent = create_react_agent(
ChatOpenAI(model="gpt-4o"), tools=[search]
)
@cl.on_message
async def on_message(message: cl.Message):
# Chainlit automatically renders each step as a collapsible UI element
# when using the callback handler
async with cl.Step(name="Agent", type="run") as step:
step.input = message.content
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": message.content}]},
config={"callbacks": [cl.LangchainCallbackHandler()]}
)
output = result["messages"][-1].content
step.output = output
await cl.Message(content=output).send()
Gradio
Gradio(Abid et al. 2019)は、MLデモとエージェントインターフェースをすばやく構築するPythonライブラリです。gr.ChatInterfaceとgr.Blocks APIにより、最小限のコードで会話エージェントを迅速にプロトタイピングできます。
エージェント型UIにおける強み:
-
設定不要の配備: Hugging Face Spacesを介して1行で共有できます。
-
カスタムコンポーネント: Gradio Custom Componentsシステムにより、Pythonバックエンドとシームレスに統合するReactコンポーネントを構築できます。
-
マルチモーダル入力: 最小限の設定でファイル、画像、音声、動画、ウェブカメラから入力できます。
-
ストリーミング: ジェネレータベースのストリーミング応答をネイティブにサポートします。
限界: Gradioのレイアウトシステムは完全なReactフレームワークより柔軟性が低く、状態管理もセッション単位なので、複雑なマルチエージェント調整は難しくなります。
Streamlit
Streamlit(S. Inc 2024)は、エージェントのダッシュボードや監視UIに広く採用されているデータアプリケーション向けPythonフレームワークです。リアクティブな実行モデル、つまり操作ごとにスクリプト全体を再実行する方式は単純ですが、複雑なエージェントワークフローでは制約になる場合があります。
エージェント型のユースケース:
-
エージェントダッシュボード:
st.metric、st.dataframe、st.statusを使ったリアルタイムメトリクス、タスクキュー、ステータス表示。 -
セッション状態:
st.session_stateが再実行をまたいでエージェント状態を保持し、マルチターンの会話を可能にします。 -
ストリーミング:
st.write_streamがジェネレータ出力を段階的に描画します。 -
フラグメント:
@st.fragmentデコレーターが部分的な再実行を可能にし、ライブ更新ダッシュボードの性能を向上させます。
OpenAI Assistants Playground
OpenAI Assistants Playgroundは、エージェント型UI設計のリファレンス実装です。次を実演します。
-
永続履歴を備えたスレッドベースの会話管理。
-
ファイル添付と検索の可視化。
-
出力(標準出力、画像、ファイル)を表示するコードインタープリター実行。
-
入出力を検査できる関数呼び出し表示。
-
モデル呼び出しとツール呼び出しの順序を示す実行ステップの可視化。
カスタムUIを構築するためのフレームワークではありませんが、Playgroundの設計パターンは広く模倣されています。
LangGraph Studio
LangGraph Studio(L. Inc 2024a)は、LangGraphエージェント向けのビジュアルIDEを提供するデスクトップアプリケーションです。現在利用できるツール利用とワークフロー可視化環境の中で、最も高度なものです。
機能:
-
グラフ可視化: エージェントの状態機械をインタラクティブに描画し、ノードがエージェントのステップ、エッジが遷移を表します。
-
状態検査: 実行中の任意の時点で、完全なエージェント状態(全変数、メモリ、ツール結果)を構造化JSONとして検査できます。
-
タイムトラベルデバッグ: 以前の任意の実行ステップを再生し、状態を変更して、その地点から再実行できます。
-
Human-in-the-loop統合: 任意のノードにブレークポイントを設定でき、実行が一時停止して人間の入力を待ってから続行します。
-
マルチエージェント対応: スーパーバイザーとサブエージェントの階層、およびエージェント間のメッセージパッシングを可視化します。
フレームワークの比較
表12.1に、上記で扱ったフレームワークの主な特徴をまとめます。
| フレームワーク | 言語 | ストリーム | ツール可視化 | マルチエージェント | 生成UI | 本番 |
|---|---|---|---|---|---|---|
| Vercel AI SDK | TypeScript | Partial | Partial | |||
| Chainlit | Python | Partial | Partial | |||
| Gradio | Python | \(\circ\) | \(\times\) | \(\circ\) | ||
| Streamlit | Python | \(\circ\) | \(\times\) | \(\times\) | ||
| OAI Playground | N/A (hosted) | \(\times\) | \(\times\) | \(\times\) | ||
| LangGraph Studio | Python/TS | \(\times\) | Partial |
エージェント型UIフレームワークの比較。 {#tab:ui-framework-comparison}
生成UI
Tip
生成UIの概念
従来のLLMインターフェースは、モデルの出力をテキストやMarkdownとして描画します。生成UIはこれを逆転させます。モデルのツール呼び出しがUIコンポーネントを生成します。モデルは何を言うかだけでなく、コンテンツタイプとユーザーの意図に基づいて、グラフ、フォーム、地図、カレンダーウィジェットなど、どのように提示するかも決定します。
生成UIは、LLMとインターフェースの関係における根本的な転換です。開発者が考えられるすべてのUI状態を事前に指定するのではなく、モデルが現在のコンテキストに適したUIコンポーネントを動的に選択し、パラメータ化します。
動的インターフェースのためのReact Server Components
Vercel AI SDKのRSC(React Server Components10)統合は、生成UIの最も成熟した実装です。アーキテクチャは次のように動作します。
-
ユーザーがNext.js11のサーバーアクションへメッセージを送信する。
-
サーバーが、各ツールにReactコンポーネントを関連付けたツールセットとともにLLMを呼び出す。
-
LLMがツールを呼び出すと(例:
show_weather)、サーバーがツールの出力をpropsとして対応するReactコンポーネントを描画する。 -
描画されたコンポーネントがReact Server Componentとしてクライアントへストリーミングされ、チャット内にインラインで表示される。
// app/actions.tsx (Server Action)
import { streamUI } from 'ai/rsc';
import { openai } from '@ai-sdk/openai';
import { WeatherCard } from '@/components/WeatherCard';
import { StockChart } from '@/components/StockChart';
export async function chat(userMessage: string) {
const result = await streamUI({
model: openai('gpt-4o'),
messages: [{ role: 'user', content: userMessage }],
tools: {
show_weather: {
description: 'Display current weather for a location',
parameters: z.object({
location: z.string(),
unit: z.enum(['celsius', 'fahrenheit']),
}),
// Tool result rendered as a React component
generate: async ({ location, unit }) => {
const data = await fetchWeather(location, unit);
return <WeatherCard data={data} />;
},
},
show_stock: {
description: 'Display stock price chart',
parameters: z.object({ ticker: z.string() }),
generate: async ({ ticker }) => {
const data = await fetchStockData(ticker);
return <StockChart ticker={ticker} data={data} />;
},
},
},
});
return result.value;
}
コンテンツタイプに基づく適応型インターフェース
生成UIは、提示するコンテンツの性質に適応するインターフェースを可能にします。
-
表形式データ \(\rightarrow\) 並べ替えとフィルタリングが可能で、エクスポート機能を備えたデータテーブル。
-
地理データ \(\rightarrow\) マーカーとレイヤーを備えたインタラクティブな地図。
-
時系列 \(\rightarrow\) 注釈付きでズーム可能な折れ線グラフ。
-
コード \(\rightarrow\) シンタックスハイライト付きエディターと実行ボタン。
-
文書 \(\rightarrow\) 注釈ツールを備えた書式付き文書ビューアー。
-
フォーム/構造化入力 \(\rightarrow\) 動的に生成されるフォームフィールド。
モデルはUIオーケストレータとして働き、情報の各部分に最も適した提示方法を選択します。これにより、開発者が考えられるすべての出力タイプを予想して対応するコンポーネントを事前構築する必要が減ります。
Note
生成UIの限界
UI生成をどこまでモデルに委譲すべきでしょうか。完全にモデル駆動のUIは、一貫性の欠如、アクセシビリティの失敗、セキュリティ脆弱性を招くリスクがあります(例: モデルが予期しないエンドポイントへデータを送信するフォームを生成する)。実際には、任意のHTMLやJSXを生成するよりも、モデルが事前構築済みでアクセシブルかつ安全なコンポーネントのキュレーション済みライブラリから選択するとき、生成UIは最もよく機能します。
ストリーミングとリアルタイムのパターン
ストリーミングはエージェント型UIの基盤です。「結果を待つ」体験を「エージェントの作業を見る」体験へ変えます。この節では、主要なストリーミングパターンと実装上の考慮事項を扱います。
トークンストリーミング
トークンストリーミングは、完全な応答を待つのではなく、トークンが生成されるたびにLLM出力を段階的に届けます。一般的に2つの転送メカニズムが使われます。
-
Server-Sent Events(SSE) 12: サーバーからクライアントへの一方向HTTPストリーム。各イベントがトークンのチャンクを運びます。SSEは単純で標準HTTP/1.1上で動作し、ブラウザーが自動的に再接続します。LLMストリーミングAPIの主要なメカニズムであり、OpenAI、Anthropic、Googleがすべて使っています。
-
WebSocket: 双方向の永続接続。実装はより複雑ですが、クライアントがストリーム途中でデータを送る必要があるインタラクティブなストリーミングシナリオ(例: エージェントの中断、生成途中のフィードバック提供)に必要です。
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from openai import AsyncOpenAI
import json
app = FastAPI()
client = AsyncOpenAI()
async def token_stream(prompt: str):
"""Generator that yields SSE-formatted token chunks."""
stream = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
stream=True,
)
async for chunk in stream:
delta = chunk.choices[0].delta
if delta.content:
# SSE format: "data: <json>\n\n"
yield f"data: {json.dumps({'token': delta.content})}\n\n"
elif chunk.choices[0].finish_reason:
yield f"data: {json.dumps({'done': True})}\n\n"
@app.get("/stream")
async def stream_endpoint(prompt: str):
return StreamingResponse(
token_stream(prompt),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
ツール呼び出しのストリーミング
現代のLLM APIはツール呼び出しのストリーミングをサポートします。ツール名と引数が段階的にストリーミングされるため、ツールがまだ呼び出されていない段階でも、UIに「エージェントが検索クエリ『climate change 2024』でsearch_webを呼び出しています…」と表示できます。これには部分的なJSONのパースが必要で、ストリーミングJSONパーサーで実行できます。
ツール呼び出しストリーミングのパターン:
-
引数の段階的表示: 呼び出しが完了する前でも、ストリーミングされるツール引数を表示する。
-
並列ツール呼び出しインジケーター: モデルが複数のツールを同時に呼び出すとき、すべてを保留中として表示し、結果が到着するたびに更新する。
-
ツール結果のストリーミング: ツールによっては(例: コード実行、ウェブスクレイピング)結果自体をストリーミングできるため、これをUIへ段階的に流す。
マルチエージェントストリーミング
マルチエージェントシステムでは、複数のエージェントが同時に出力を生成することがあります。UIは並列ストリームを処理しなければなりません。
-
エージェントラベル付きストリーム: 各ストリームにエージェントの識別情報を付け、UIが別々のレーンやパネルに描画する。
-
ストリームのマージ: スーパーバイザー・サブエージェントパターンでは、スーパーバイザーのストリームとサブエージェントのストリームが交互に現れることがあるため、UIが一貫した順序を維持しなければならない。
-
バックプレッシャー: UIがストリームの到着速度に合わせて描画できない場合(例: 複数エージェントが同時に生成)、バックプレッシャー機構でバッファーのオーバーフローを防がなければなりません。戦略には、中間トークンを捨てて最新だけを表示する、更新をバッチ化する、遅いストリームを一時停止する、などがあります。
楽観的UI更新
楽観的UI更新は、サーバーの確認前にユーザーアクションをUIへ即時反映し、知覚される応答性を高めます。
-
ユーザーがメッセージを送ると、リクエスト処理中でもチャット履歴に(楽観的に)即座に表示される。
-
承認ゲートが承認されると、サーバーが承認を処理する前でも、UIがアクションを「承認済み」と即座に表示し、エージェントの次のステップを表示し始める。
-
サーバーがエラーを返した場合、楽観的更新をロールバックし、エラー状態を表示する。
バックプレッシャーの処理
高スループットのエージェント型シナリオでは、受信データの速度がUIの描画能力を超えることがあります。バックプレッシャーを管理する戦略は次のとおりです。
-
トークンのバッチ化: トークンを50〜100ミリ秒バッファーし、1つずつではなくバッチで描画してDOM更新頻度を減らす。
-
仮想スクロール: 長い出力では、コンテンツの表示部分だけを描画し、画面外のDOMノードを破棄する。
-
スロットル更新: メトリクスとステータス表示を、受信データの速度にかかわらず固定レート(例: 10Hz)で更新する。
-
段階的な詳細表示: 高スループットの期間は要約ビューを表示し、完全な詳細は要求時に利用できるようにする。
Human-in-the-Loop UI設計
Human-in-the-Loop(HITL)インタラクションは、エージェント型UIで最も影響の大きい設計課題の1つです。自動化の効率性を損なうボトルネックを作らずに、意味のある人間の監督を維持することが目標です。
エージェントを中断するタイミング
すべてのエージェントアクションが人間のレビューに値するわけではありません。原則に基づく中断ポリシーでは、次を考慮します。
-
可逆性: 不可逆なアクション(ファイル削除、メール送信、購入)は常に承認に値します。可逆なアクション(ファイル読み取り、ウェブ検索)は通常、承認を必要としません。
-
範囲: 外部システムや他人に影響するアクションは、純粋にローカルなアクションより厳しい精査に値します。
-
信頼度: ユーザーの意図の解釈に対するエージェントの信頼度が低い場合、続行するのではなく明確化を求めるべきです。
-
コスト: 高コストのアクション(大規模なAPI呼び出し、高価な計算)は承認に値します。
-
新規性: このコンテキストでエージェントが以前に実行したことのないアクションは、定常的なアクションより厳しい精査に値します。
段階的な承認ワークフロー
段階的な承認ポリシーは、監督と効率性のバランスを取ります。
Important
3段階承認モデル
Tier 1(自動承認): 低リスクで可逆的な定常アクション。例: ウェブ検索、ファイル読み取り、読み取り専用APIの呼び出し。エージェントは中断せず続行し、アクションは監査のために記録します。
Tier 2(通知): 中リスクのアクション。UIはユーザーが非同期に確認できるノンブロッキング通知(「エージェントが下書きメールを下書きフォルダーに移しました」)を表示します。短い時間枠(例: 30秒)で、アクション確定前にキャンセルできます。
Tier 3(承認必須): 高リスク、不可逆、または高コストのアクション。エージェントが一時停止し、ブロッキング承認ゲートを提示します。エージェントが続行する前に、ユーザーは明示的に承認、拒否、または変更しなければなりません。
段階間のしきい値はユーザーが設定(「メール送信前は常に確認」)することも、ユーザーの行動から学習(ユーザーが常にウェブ検索を承認するなら、今後は自動承認)することもできます。
フィードバックメカニズム
承認ゲートに加えて、エージェント型UIはエージェントが時間とともに改善するのを助ける豊かなフィードバックメカニズムを提供すべきです。
-
賛成/反対: 応答に対する単純な二値フィードバック。保存してRLHFのファインチューニングや選好学習に使う。
-
インライン修正: ユーザーがエージェント出力を直接編集でき、元の出力と修正後の出力の差分が訓練信号になる。
-
選好選択: エージェントが複数の選択肢を提示したとき、ユーザーの選択が選好信号になる。
-
明示的な指示: 「もう二度とこれをしないで」「Xの前には必ず確認して」「ZよりYのアプローチを優先して」など、エージェントの挙動ポリシーを更新する自然言語指示。
-
理由付き評価: 評価に添える任意の自由記述説明で、二値フィードバックより豊かな信号を提供する。
UIインタラクションを通じたエージェントへの教示
最も高度なHITL UIは、すべてのインタラクションを教示の機会として扱います。
-
デモンストレーション: ユーザーがタスクを手動で実行し、エージェントが観測して好ましいアプローチを学ぶ。
-
一般化を伴う修正: ユーザーがエージェントのアクションを修正すると、UIが「いつも違う方法で実行しますか」と尋ね、修正を一般化する。
-
選好の引き出し: ユーザーに2つのエージェント挙動を比較させ、どちらが好ましいかを示してもらう定期的なプロンプト。
-
挙動プロファイル: UIがユーザーが確認・編集できる「選好」プロファイルを見える形で保持し、エージェントが学習した挙動を透明かつ制御可能にする。
アクセシビリティと信頼
信頼は機能ではありません。期待どおりに一貫して挙動し、自らを明確に説明し、失敗から適切に復旧するシステムの創発的な性質です。エージェント型UIは、信頼を第一級の関心事として設計しなければなりません。
エージェントの意思決定を説明する
エージェント型UIにおける説明可能性は、思考の連鎖を表示するだけではありません。次が必要です。
-
意思決定の理由: 重要な意思決定では、エージェントは何を決めたかだけでなく、なぜ決めたか、つまり考慮した要因、却下した代替案、置いた前提を説明すべきです。
-
出典の帰属: 主張を出典にリンクし、取得文書を引用可能にする。
-
反実仮想説明: 「YではなくXと言っていたら、Zを実行していました」と説明し、ユーザーがエージェントの意思決定境界を理解できるようにする。
-
不確実性の定量化: 信頼度を明示し、不確実性を生む要因も示す。
信頼度レベルの表示
信頼度インジケーターは調整され、意味のあるものでなければなりません。
-
言語による信頼度: 「かなり自信があります」「これについては確信がありません」のような自然言語表現は、ほとんどのユーザーにとって数値確率より解釈しやすい。
-
視覚的な信頼度: 色分け(緑/黄/赤)、アイコンのバリエーション、フォントの太さで、テキストを追加せず信頼度を符号化できる。
-
主張ごとの信頼度: 複数の主張を含む応答では、主張ごとの信頼度インジケーター(例: インライン脚注)の方が、応答全体の単一スコアより有益です。
元に戻す機能とロールバック機能
技術的に可能な限り、重要なエージェントアクションはすべて元に戻せるべきです。
-
元に戻せるアクションログ: すべてのエージェントアクションを時系列で記録し、可逆な各アクションに「元に戻す」ボタンを付ける。
-
スナップショットベースのロールバック: 状態を持つタスク(例: コード編集、文書執筆)では、定期的なスナップショットにより以前の任意の状態へロールバックできる。
-
ドライランモード: 計画を実行する前に、エージェントが計画をシミュレートして予測される状態変更を表示し、実際のアクションを取る前にユーザーが承認・変更できるようにする。
-
グレースフルデグラデーション: 元に戻せない場合(例: メールを送信済み)には、UIがそのことを明確に伝え、利用可能な最善の代替案(例: フォローアップを送信)を提示する。
UIの監査証跡
エンタープライズや規制対象のユースケースでは、監査証跡が不可欠です。
-
不変アクションログ: すべてのエージェントアクション、ツール呼び出し、人間の承認を、タイムスタンプ、ユーザー識別情報、完全なパラメータとともに記録する。
-
エクスポート可能な履歴: コンプライアンス報告のため、監査証跡をJSON、CSV、PDFとしてエクスポートできる。
-
差分ビュー: 文書やコードの変更では、監査証跡に変更前後の差分を含める。
-
セッション再生: デバッグやコンプライアンスレビューのため、エージェントセッション全体をステップごとに再生できる。
ユーザーの期待の管理
調整されていない期待は、ユーザーの不信の主な原因です。エージェント型UIは期待を積極的に管理すべきです。
-
能力の開示: エージェントができることとできないことを、明確でアクセシブルな文書として示す。
-
限界の認識: エージェントが能力外のタスクに遭遇したら、試行して黙って失敗するのではなく、そのことを明確に伝える。
-
不確実性の伝達: ユーザーが誤りを発見するまで待つのではなく、不確実性を先回りして伝える。
-
一貫したペルソナ: 一貫したエージェントのアイデンティティとコミュニケーションスタイルが、親しみやすさと予測可能性を築く。
Note
透明性による信頼構築: ケーススタディ
航空券の予約を任されたエージェントを考えてみましょう。信頼度の低いUIは「航空券を予約しました。確認番号: AA1234」と表示します。信頼度の高いUIは、(1)使用した検索パラメータの要約、(2)検討した代替案とこのフライトを選んだ理由、(3)実行した正確なアクション(予約システムへのAPI呼び出し)、(4)予約へのリンク付き確認詳細、(5)次の24時間有効な取り消しオプション、(6)エージェントにできないことの注記(例:「この予約は変更できません。航空会社へ直接電話してください」)を提示します。後者のUIはより多くの画面領域を使いますが、エージェントが正しく行動したというユーザーの信頼を築き、必要に応じて検証・復旧するための情報を与えます。
完全な実装例: フルスタックのエージェント型UI
ここでは、Python/Reactスタックでストリーミング、ツール可視化、承認ゲートを組み合わせた具体的な実装例を示します。バックエンドはLangGraphとFastAPIを使い、フロントエンドはカスタムバックエンド向けに適応したVercel AI SDKパターンとReactを使います。
Backend: FastAPI + LangGraph with Streaming and Approval Gates
# backend/main.py
import asyncio
import json
from typing import AsyncGenerator
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
app = FastAPI()
# -- Tool definitions ----------------------------------------------------------
@tool
def web_search(query: str) -> str:
"""Search the web for information."""
return f"Search results for '{query}': [simulated results]"
@tool
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email. REQUIRES HUMAN APPROVAL."""
return f"Email sent to {to} with subject '{subject}'"
@tool
def read_file(path: str) -> str:
"""Read a file from the filesystem."""
try:
with open(path) as f:
return f.read()
except FileNotFoundError:
return f"Error: File not found: {path}"
# Tools requiring approval (Tier 3)
APPROVAL_REQUIRED_TOOLS = {"send_email"}
# -- Approval gate store (in-memory; use Redis in production) ------------------
approval_store: dict[str, asyncio.Event] = {}
approval_results: dict[str, dict] = {}
# -- LLM setup -----------------------------------------------------------------
llm = ChatOpenAI(model="gpt-4o", streaming=True)
tools = [web_search, send_email, read_file]
llm_with_tools = llm.bind_tools(tools)
def should_request_approval(tool_name: str) -> bool:
return tool_name in APPROVAL_REQUIRED_TOOLS
# -- Streaming endpoint --------------------------------------------------------
async def agent_stream(
session_id: str,
user_message: str,
) -> AsyncGenerator[str, None]:
"""Stream agent events as SSE."""
def sse(event_type: str, data: dict) -> str:
return f"data: {json.dumps({'type': event_type, **data})}\n\n"
yield sse("status", {"message": "Agent starting..."})
# Simulate multi-step agent execution
steps = [
("thinking", {"content": "Analyzing the request..."}),
("tool_call", {
"tool": "web_search",
"input": {"query": user_message},
"tier": 1, # Auto-approve
}),
("tool_result", {
"tool": "web_search",
"output": f"Results for: {user_message}",
"duration_ms": 342,
}),
]
for event_type, data in steps:
await asyncio.sleep(0.5) # Simulate processing time
yield sse(event_type, data)
# Simulate a Tier 3 action requiring approval
approval_id = f"{session_id}_email_001"
approval_event = asyncio.Event()
approval_store[approval_id] = approval_event
yield sse("approval_required", {
"approval_id": approval_id,
"tool": "send_email",
"tier": 3,
"risk": "irreversible",
"action_summary": "Send summary email to user@example.com",
"parameters": {
"to": "user@example.com",
"subject": f"Research results: {user_message}",
"body": "Here are the findings...",
},
})
# Wait for human approval (timeout after 5 minutes)
try:
await asyncio.wait_for(approval_event.wait(), timeout=300)
result = approval_results.get(approval_id, {})
if result.get("approved"):
yield sse("tool_call", {
"tool": "send_email",
"input": result.get("parameters", {}),
"tier": 3,
"approved_by": "human",
})
await asyncio.sleep(0.3)
yield sse("tool_result", {
"tool": "send_email",
"output": "Email sent successfully",
"duration_ms": 128,
})
else:
yield sse("action_rejected", {
"tool": "send_email",
"reason": result.get("reason", "User rejected"),
})
except asyncio.TimeoutError:
yield sse("approval_timeout", {
"approval_id": approval_id,
"message": "Approval timed out; action skipped",
})
# Final response
yield sse("token", {"content": "I've completed the research. "})
yield sse("token", {"content": "Here's a summary of what I found..."})
yield sse("done", {"total_tokens": 847, "duration_ms": 2341})
@app.get("/chat/stream")
async def chat_stream(session_id: str, message: str):
return StreamingResponse(
agent_stream(session_id, message),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
class ApprovalRequest(BaseModel):
approval_id: str
approved: bool
parameters: dict | None = None
reason: str | None = None
@app.post("/chat/approve")
async def handle_approval(req: ApprovalRequest):
if req.approval_id not in approval_store:
raise HTTPException(status_code=404, detail="Approval not found")
approval_results[req.approval_id] = {
"approved": req.approved,
"parameters": req.parameters,
"reason": req.reason,
}
approval_store[req.approval_id].set()
return {"status": "ok"}
Frontend: React with Streaming and Tool Visualization
// frontend/AgentChat.tsx
import { useState, useEffect, useRef } from 'react';
// -- Types ---------------------------------------------------------------------
type AgentEvent =
| { type: 'status'; message: string }
| { type: 'thinking'; content: string }
| { type: 'token'; content: string }
| { type: 'tool_call'; tool: string; input: object; tier: number }
| { type: 'tool_result'; tool: string; output: string; duration_ms: number }
| { type: 'approval_required'; approval_id: string; tool: string;
tier: number; risk: string; action_summary: string; parameters: object }
| { type: 'action_rejected'; tool: string; reason: string }
| { type: 'done'; total_tokens: number; duration_ms: number };
// -- Tool Card Component -------------------------------------------------------
function ToolCard({ event }: { event: AgentEvent & { type: 'tool_call' } }) {
const [expanded, setExpanded] = useState(false);
const tierColors = { 1: '#22c55e', 2: '#f59e0b', 3: '#ef4444' };
const color = tierColors[event.tier as keyof typeof tierColors] || '#6b7280';
return (
<div style={{ border: `1px solid ${color}`, borderRadius: 8, padding: 8,
margin: '4px 0', fontSize: 13 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ color, fontWeight: 600 }}>[gear] {event.tool}</span>
<span style={{ color: '#6b7280', fontSize: 11 }}>
Tier {event.tier} . {event.tier === 1 ? 'Auto' : 'Approved'}
</span>
<button onClick={() => setExpanded(!expanded)}
style={{ marginLeft: 'auto', fontSize: 11 }}>
{expanded ? 'Hide' : 'Details'}
</button>
</div>
{expanded && (
<pre style={{ marginTop: 8, fontSize: 11, background: '#f3f4f6',
padding: 8, borderRadius: 4, overflow: 'auto' }}>
{JSON.stringify(event.input, null, 2)}
</pre>
)}
</div>
);
}
// -- Approval Gate Component ---------------------------------------------------
function ApprovalGate({
event,
onDecision,
}: {
event: AgentEvent & { type: 'approval_required' };
onDecision: (approved: boolean, params?: object) => void;
}) {
const riskColors = { reversible: '#22c55e', 'hard-to-undo': '#f59e0b',
irreversible: '#ef4444' };
const riskColor = riskColors[event.risk as keyof typeof riskColors] || '#6b7280';
return (
<div style={{ border: `2px solid ${riskColor}`, borderRadius: 8,
padding: 16, margin: '8px 0', background: '#fef9f0' }}>
<div style={{ fontWeight: 700, color: riskColor, marginBottom: 8 }}>
[!] Approval Required: {event.tool}
</div>
<div style={{ marginBottom: 8 }}>{event.action_summary}</div>
<div style={{ fontSize: 12, color: '#6b7280', marginBottom: 12 }}>
Risk level: <span style={{ color: riskColor }}>{event.risk}</span>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<button
onClick={() => onDecision(true, event.parameters)}
style={{ background: '#22c55e', color: 'white', border: 'none',
borderRadius: 6, padding: '8px 16px', cursor: 'pointer' }}>
[OK] Approve
</button>
<button
onClick={() => onDecision(false)}
style={{ background: '#ef4444', color: 'white', border: 'none',
borderRadius: 6, padding: '8px 16px', cursor: 'pointer' }}>
[X] Reject
</button>
</div>
</div>
);
}
// -- Main Chat Component -------------------------------------------------------
export function AgentChat() {
const [events, setEvents] = useState<AgentEvent[]>([]);
const [response, setResponse] = useState('');
const [isStreaming, setIsStreaming] = useState(false);
const [input, setInput] = useState('');
const sessionId = useRef(`session_${Date.now()}`);
const sendMessage = async () => {
if (!input.trim() || isStreaming) return;
setEvents([]);
setResponse('');
setIsStreaming(true);
const url = `/chat/stream?session_id=${sessionId.current}`
+ `&message=${encodeURIComponent(input)}`;
const es = new EventSource(url);
es.onmessage = (e) => {
const event: AgentEvent = JSON.parse(e.data);
if (event.type === 'token') {
setResponse(prev => prev + event.content);
} else if (event.type === 'done') {
setIsStreaming(false);
es.close();
} else {
setEvents(prev => [...prev, event]);
}
};
es.onerror = () => { setIsStreaming(false); es.close(); };
setInput('');
};
const handleApproval = async (
approvalId: string,
approved: boolean,
parameters?: object,
) => {
await fetch('/chat/approve', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ approval_id: approvalId, approved, parameters }),
});
};
return (
<div style={{ maxWidth: 800, margin: '0 auto', padding: 16 }}>
<div style={{ minHeight: 400, border: '1px solid #e5e7eb',
borderRadius: 8, padding: 16, marginBottom: 16 }}>
{events.map((event, i) => {
if (event.type === 'tool_call')
return <ToolCard key={i} event={event} />;
if (event.type === 'approval_required')
return (
<ApprovalGate key={i} event={event}
onDecision={(approved, params) =>
handleApproval(event.approval_id, approved, params)} />
);
if (event.type === 'status' || event.type === 'thinking')
return (
<div key={i} style={{ color: '#6b7280', fontSize: 12,
fontStyle: 'italic', margin: '4px 0' }}>
{event.type === 'thinking' ? event.content : event.message}
</div>
);
return null;
})}
{response && (
<div style={{ marginTop: 8, lineHeight: 1.6 }}>
{response}
{isStreaming && <span className="cursor-blink">|</span>}
</div>
)}
</div>
<div style={{ display: 'flex', gap: 8 }}>
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && sendMessage()}
placeholder="Ask the agent..."
style={{ flex: 1, padding: '8px 12px', borderRadius: 6,
border: '1px solid #d1d5db', fontSize: 14 }}
/>
<button onClick={sendMessage} disabled={isStreaming}
style={{ padding: '8px 16px', background: '#3b82f6',
color: 'white', border: 'none', borderRadius: 6,
cursor: isStreaming ? 'not-allowed' : 'pointer' }}>
{isStreaming ? 'Running...' : 'Send'}
</button>
</div>
</div>
);
}
Note
この実装が実演すること
上のコードは、エージェント型UIの主要なパターンが連携して動作する様子を示しています。
SSEストリーミング: バックエンドは、異なる種類(ステータス、思考、ツール呼び出し、トークン)のイベントを、単一のHTTP接続上でストリーミングします。
型付きイベントプロトコル: イベント型の判別可能な共用体によって、フロントエンドは各イベントを適切に描画できます。
ツールの可視化:
ToolCardは、ティアインジケーターと展開可能な入力詳細を付けてツール呼び出しを描画します。承認ゲート:
ApprovalGateはストリームをブロックし、エージェントが不可逆なアクションを進める前に人間の入力を待ちます。非同期承認: バックエンドは
asyncio.Eventを使って、フロントエンドからの承認POSTリクエストを待つ間ストリームを一時停止し、承認UIとストリーミングロジックをきれいに分離します。
まとめ
エージェント型UIフレームワークは、人間とコンピューターのインタラクションにおける新たなフロンティアであり、インターフェース設計を第一原理から再考することを求めます。この節の主な洞察は次のとおりです。
-
パラダイムの選択が重要: 適切なUIパラダイム(チャット、キャンバス、ワークフロー、ダッシュボード、協調型、自律型)は、タスクの構造、人間の関与の必要性、出力の種類によって決まります。本番システムの多くは複数のパラダイムを組み合わせます。
-
透明性は譲れない: ユーザーは見えないものを信頼できません。思考の表示、ツールの可視化、コンテキストパネルはオプション機能ではなく、信頼できるエージェント型システムの基盤です。
-
ストリーミングが基準: ユーザーはエージェントがリアルタイムに動作する様子を期待します。トークンストリーミング、ツール呼び出しのストリーミング、マルチエージェントストリーミングは、今や必須の能力です。
-
承認ゲートは段階化すべき: 一律の承認ポリシー(すべて承認するか、何も承認しないか)は実際には機能しません。安全なアクションを自動承認し、危険なアクションをゲートする段階的なポリシーなら、ボトルネックを生じさせずに監督を維持できます。
-
生成UIがフロンティア: LLMがテキストだけでなく、チャート、フォーム、地図、ウィジェットなどのUIコンポーネントも生成できれば、コンテンツを固定テンプレートに押し込むのではなく、コンテンツに適応するインターフェースが可能になります。
-
信頼は一貫性と回復可能性によって得られる: ユーザーの信頼を築くには、取り消し機能、監査証跡、調整された信頼度インジケーターが、能力そのものと同じくらい重要です。
Important
設計原則: 透明な協働者としてのエージェント
エージェント型UI設計の北極星は、透明な協働者です。つまり、アクションが常に見え、推論に常にアクセスでき、間違いから常に回復でき、能力と限界が常に明確なエージェントです。すべてのUI上の判断は、この基準に照らして評価すべきです。
この節で説明したフレームワークとパターン(Vercel AI SDK、Chainlit、Gradio、Streamlit、LangGraph Studio)は、構成要素を提供します。実務者にとっての課題は、ユーザー固有のニーズと、それぞれの領域固有のリスクを指針として、これらを慎重に組み合わせることです。
確認問題と詳しい解答
この章では、本ガイド全体で扱った内容の理解を確認し、定着させるための包括的な問題を示します。各問題は、重要な概念、アルゴリズム、またはシステム設計上の判断を対象としています。これは、表面的な知識と真の専門性を分ける種類の知識です。自己評価にこの問題集を使い、詳しい解答を読む前に、まず自分で答えてみてください。問題は、基礎概念(LLMアーキテクチャ、強化学習の基礎)から中核アルゴリズム(PPO、DPO、GRPO)、さらに高度なシステム設計とエージェント型AIのトピックへと進みます。
基礎問題
Note
Q0a: デコーダーのみのTransformerにおけるアテンション機構の役割は何ですか?なぜ因果的なのですか?
解答: アテンション機構によって、各トークンは他のトークンの表現にアテンションを向け(つまり、重み付き和を計算し)、利用できます。デコーダーのみのTransformerでは、アテンションは 因果的 (自己回帰的とも呼ばれます)です。トークン \(t\) はトークン \(1, \ldots, t\) にしかアテンションを向けられず、未来のトークン \(t+1, \ldots, T\) には決して向けられません。
なぜ因果的なのか? モデルはテキストを左から右へ生成するからです。推論時には、未来のトークンは文字どおりまだ存在しません。学習中の因果マスクはこの制約をシミュレートし、モデルが左側のコンテキストだけを使って各トークンを予測するように学習させます。数学的には、アテンション行列にマスクを適用します。 \[ \text{Attention}(Q, K, V) = \text{softmax}!\left(\frac{QK^\top}{\sqrt{d_k}} + M\right) V \] ここで、\(M_{ij} = -\infty\) は \(j > i\)(未来の位置)であり、対応するアテンション重みを強制的にゼロにします。
実用上の意味: これにより、推論時のKVキャッシュ最適化が可能になります。過去のトークンのキーとバリューは変化しないため、キャッシュして再利用でき、新しいトークンごとの生成量を \(O(T^2)\) から \(O(T)\) に削減できます。
復習: 第1章(LLMのアーキテクチャと最適化手法)。
Note
Q0b: Flash Attentionを説明してください。どのような問題を、どのように解決しますか?
解答: 標準的なアテンションは完全な \(T \times T\) アテンション行列を計算するため、\(O(T^2)\) のメモリを必要とし、 メモリ帯域幅律速 になります。GPUは実際の計算ではなく、HBM(低速で大容量)とSRAM(高速で小容量)の間でデータを移動することに時間の大半を費やします。
Flash Attentionの洞察: 完全なアテンション行列をHBM上に展開しません。代わりに、計算をSRAMに収まるブロックへタイル分割し、オンラインsoftmaxアルゴリズムを使ってブロック単位でアテンションを計算し、最終出力だけをHBMへ書き込みます。
主要な技法:
タイル分割: Q、K、VをSRAMに収まるサイズ \(B_r \times B_c\) のブロックに分割する
オンラインsoftmax: 累積最大値と累積和を追跡し、行全体を保持せずにsoftmaxを逐次計算する
再計算: 逆伝播では、高コストな \(T \times T\) 行列を保存する代わりに、Q、K、Vからアテンションを再計算する(計算自体は安価)
結果: HBMメモリは \(O(T)\)(\(O(T^2)\) ではなく)となり、実時間で2–4\(\times\)高速化し、近似ではなく数値的に完全に同じ出力を得られます。
復習: 第1〜2章(LLMアーキテクチャ、システム基盤)。
Note
Q0c: SFT、RLHF、DPOの大まかな違いは何ですか?それぞれをいつ使いますか?
解答:
SFT(教師ありファインチューニング): 高品質なデモンストレーションを模倣するようにモデルを学習させます。損失は、精選データ上での次トークン予測です。形式とスタイルを教えます。
RLHF: 人間の選好から報酬モデルを学習し、それに対してRL(PPO)で方策を最適化します。モデルはデモンストレーションデータを超えて探索します。人間が何を好むかを教えます。
DPO: 報酬モデルを省略し、対照損失を使って選好ペア \((y_w, y_l)\) 上で方策を直接最適化します。目的はRLHFと同じですが、パイプラインはより単純です。
典型的なパイプライン: まずSFTでモデルに良い出発点を与え、次にRLHFまたはDPOで選好を洗練します。SFTだけでは、冗長で、過度に保険をかけた応答になりがちです。RLHF/DPOによって出力はより直接的になり、人間の意図に沿うようになります。
使い分け: 正解例となる出力があるならSFTを使います。選好ペアがあり、計算資源が限られているならDPOを使います。最高品質が必要で、インフラに投資できるならRLHF(PPO)を使います。
復習: 第5、6、10章(PPO、DPO、SFTのベストプラクティス)。
Note
Q0d: 報酬モデルとは何ですか?どのように学習し、何が問題になり得ますか?
解答: 報酬モデル(RM)は、(プロンプト、応答)ペアを受け取り、品質を表すスカラー値を出力するニューラルネットワークです。人間の選好データで学習し、ペア \((y_w, y_l)\) で \(y_w\) が好まれる場合に、RMが \(R(y_w) > R(y_l)\) を割り当てるように学習します。
学習: Bradley-Terry損失は \(\mathcal{L} = -\log\sigma(R(y_w) - R(y_l))\) です。アーキテクチャは通常、方策と同じTransformerを使い、LMヘッドをスカラー射影に置き換えます。
起こり得る問題:
報酬ハッキング: 方策がRMでは高得点になるものの、実際には品質の低い出力(過度に長い、反復的、RMが偏って好む特定のフレーズを含むなど)を見つけます。
分布シフト: RMは以前の方策の出力で学習されています。学習が進むと、現在の方策はRMが正確に採点できない分布外の出力を生成します。
ラベルノイズ: 人間のアノテーターの意見が一致しなかったり、疲れていたり、基準を一貫して適用しなかったりします。このノイズはRMの予測へ伝播します。
過信: RMが見たことのない出力に極端なスコアを割り当て、誤解を招く勾配信号を与えます。
復習: 第9章(報酬モデルの学習)。
Note
Q0e: RLにおける探索と活用のトレードオフを説明してください。LLMの学習ではどのように現れますか?
解答: RLでは、エージェントは次のバランスを取る必要があります。
活用: 高い報酬をもたらすと分かっているアクションを選ぶこと(貪欲な振る舞い)
探索: さらに高い報酬をもたらすかもしれない新しいアクションを試すこと(失敗する可能性もあります)
LLMの学習では: 方策は言語モデルです。「アクション」はトークンの選択です。「活用」とは、すでに高得点だったものに似た応答を生成することです。「探索」とは、新しい言い回し、構造、推論経路を試すことです。
どのように現れるか:
生成時の温度: 温度が高いほど探索が増えます。GRPOは温度1.0を使い、各グループ内で多様なサンプルを得ます。
KLペナルティ: 探索に対するブレーキとして働き、方策が参照モデルから離れすぎるのを防ぎます。これがないと、方策は高報酬の単一テンプレートへ崩壊する可能性があります(モード崩壊)。
GRPOのグループサンプリング: プロンプトごとに \(G\) 個の応答を生成して出力空間を明示的に探索し、その後、平均を上回る応答を強化します。
緊張関係: 探索が少なすぎる \(\rightarrow\) モデルが局所最適(いつも同じ安全な答え)に留まります。多すぎる \(\rightarrow\) 学習が不安定になり、品質が大きく変動します。
復習: 第3章と第7章(強化学習への導入、GRPO)。
中核アルゴリズムの問題
Note
Q1: PPOのクリップ付き目的関数を説明してください。なぜ通常のPGよりうまく機能するのですか?
解答: 通常の方策勾配は \(\nabla J = \mathbb{E}[\nabla\log\pi(a\vert s) \cdot \hat{A}]\) です。問題は、たった1つの幸運な、または不運なサンプルが巨大な勾配を生み \(\rightarrow\) 方策が悪い領域へ飛び \(\rightarrow\) 役に立たない出力を生成し \(\rightarrow\) 次の勾配がさらに悪化させる \(\rightarrow\) 回復不能な「死のスパイラル」に陥ることです。
PPOの解決策: 確率比 \(r = \pi_\text{new}/\pi_\text{old}\) を \([0.8, 1.2]\) にクリップします。
仕組み: 良いアクション(\(\hat{A}>0\))では、目的関数は \(\min(r\hat{A}, 1.2\hat{A})\) です。\(r\) が1.2を超えると、それ以上の利得はなくなり、方策が1つの例に過度に固執するのを止めます。悪いアクション(\(\hat{A}<0\))では、目的関数は \(\min(r\hat{A}, 0.8\hat{A})\) です。\(r\) が0.8を下回るとペナルティの増加が止まり、壊滅的忘却を防ぎます。
重要な洞察: これはTRPOのKL制約の一次近似ですが、高コストな二次最適化を必要としません。各更新で方策が変化するのは最大でも \(\pm\)20%です。
特にLLMでは: トークンレベルの比 \(r_t = \pi_\theta(y_t\vert y_{<t})/\pi_\text{old}(y_t\vert y_{<t})\) により、単一トークンの確率が大きく変わりすぎるのを防ぎ、一貫した生成を維持します。
復習: 第5章(PPO)。
Note
Q2: 第一原理からDPOを導出してください。どのような仮定を置きますか?
解答: RLHFの目的関数から始めます。\(\max_\pi \mathbb{E}[r(x,y)] - \beta D_\text{KL}[\pi\\vert \pi_\text{ref}]\) です。
ステップ1: KKT条件を書きます。最適方策は閉形式 \(\pi^*(y\vert x) \propto \pi_\text{ref}(y\vert x)\exp(r(x,y)/\beta)\) を持ちます。
ステップ2: 逆変換して報酬を表します。\(r(x,y) = \beta\log(\pi^*/\pi_\text{ref}) + \beta\log Z(x)\) です。
ステップ3: Bradley-Terryモデル \(P(y_w \succ y_l) = \sigma(r(y_w) - r(y_l))\) に代入します。同じプロンプトなので分配関数 \(Z(x)\) は相殺されます。
ステップ4: \(\pi^*\) を \(\pi_\theta\)(学習対象のパラメータ化された方策)に置き換えます。\(\mathcal{L} = -\mathbb{E}[\log\sigma(\beta\log\frac{\pi_\theta(y_w)}{\pi_\text{ref}(y_w)} - \beta\log\frac{\pi_\theta(y_l)}{\pi_\text{ref}(y_l)})]\) です。
仮定:
Bradley-Terry選好モデル(ペア比較、引き分けなし、推移的)
\(\pi_\theta\) によって最適方策を実現できる(十分な容量がある)
選好は学習データと同じ分布から生成される(分布シフトがない)
参照モデルは固定されており、妥当である
仮定が崩れる場合: 現実の選好は推移的でなく、学習中にデータがシフトし、ラベルにもノイズがあります \(\rightarrow\) そのためOnline DPOやIPOが存在します。
復習: 第6章(DPO)。
Note
Q3: GRPOとPPOはどのように比較され、どちらをいつ選びますか?トレードオフは何ですか?
解答:
GRPOの利点:
価値関数が不要: 1モデル分のメモリと複雑さを節約できる
より単純: ハイパーパラメータが少なく、直感的(平均より上=良い、平均より下=悪い)
検証可能な報酬に適する: 数学やコードでは \(r \in \{0, 1\}\) が明確な信号を与える
DeepSeek-R1は、二値報酬だけで創発的な推論を教えられることを示した
PPOの利点:
トークンごとの信用割当: 価値関数が系列全体だけでなく各トークンに報酬を割り当てる
サンプル効率が高い: GAEは価値予測を使ってアドバンテージを推定するため、\(G\) 個のサンプルを生成しなくてよい
微妙な差を含む報酬に適する: 報酬が連続値で、トークン間の変化が大きい場合
より成熟している: OpenAI、Anthropicなどで実運用に耐えることが検証されている
目安: 報酬が検証可能(正解/不正解)なら \(\rightarrow\) GRPO。報酬が微妙な差を含み(RMスコア)、最高品質が必要なら \(\rightarrow\) PPO。計算資源が限られるなら \(\rightarrow\) GRPO(クリティックの学習が不要)です。
計算量の比較: GRPOはプロンプトごとに \(G\) 個の応答を生成するため生成量は8\(\times\)になりますが、価値関数の学習を省略します。合計の計算量は近くても、配分が異なります(生成が多く、学習が少ない)。
復習: 第5章と第7章(PPO、GRPO)。
Note
Q4: GAEはどのように機能しますか?LLMの具体例を順に説明してください。
解答: GAEは \(n\) ステップTD誤差の加重和です。\(\hat{A}_t = \sum_{l=0}^{T-t} (\gamma\lambda)^l \delta_{t+l}\) で表されます。
具体例: 応答は5トークンで、報酬は最後だけにあります(\(r_5 = 0.8\))。価値予測は \(V_1=0.5, V_2=0.55, V_3=0.6, V_4=0.65, V_5=0.7\) です。
TD誤差(\(\gamma=1\))は、\(\delta_1 = 0 + V_2 - V_1 = 0.05\)、\(\delta_2 = 0 + V_3 - V_2 = 0.05\)、...、\(\delta_5 = 0.8 + 0 - 0.7 = 0.1\) です。
\(\lambda = 0.95\) のとき、\(\hat{A}_5 = 0.1\)(最後のTD誤差そのもの)、\(\hat{A}_4 = 0.05 + 0.95 \times 0.1 = 0.145\)、\(\hat{A}_3 = 0.05 + 0.95 \times 0.145 = 0.188\) などとなります。
解釈: トークン3のアドバンテージが0.188になるのは、期待より高い報酬を得た系列に貢献したからです。前のトークンにも、指数減衰を通じてクレジットが伝わります。
LLMでは: \(\gamma=1.0\) です(すべてのトークンが重要で、有限ホライズン)。トークン \(t\) のアドバンテージは、「このトークンの後に続いたものを考えると、このトークンの選択は期待より良かったか、悪かったか?」に答えます。
復習: 第5章(PPO)。
Note
Q5: 報酬ハッキングをどう防ぎますか?多層防御戦略を示してください。
解答: 検出シグナル: RMスコアは上がっているのに勝率が横ばい、または低下する。応答長が単調に伸びる。KLダイバージェンスが \(>\) 15になる。多様性(一意なn-gram)が低下する。高報酬の出力を読むと、悪用の仕組みが見つかる。
多層防御(優先順位順):
KLペナルティ (第一優先): 適応制御器はKL \(\approx\) 6を目標にする。KLが上がると \(\beta\) が自動的に増え、参照モデルから離れすぎるのを防ぐ。
報酬モデルアンサンブル (3〜5モデル): スコアの最小値または平均値を使う。モデルごとに盲点が異なるため、1つを欺く悪用がすべてを欺くことはめったにない。
長さのペナルティ: \(r' = r - c \cdot \max(0, \text{length} - L_\text{target})\)。 「長く生成するだけで高スコアになる」という悪用を防ぐ。
定期的なRM更新: 2000ステップごとに現在の方策からデータを生成し、再ラベル付けしてRMの学習セットに加える。モデルが悪用を見つけると、その穴を塞ぐ。
勝率に基づく停止: SFTベースラインに対する勝率を追跡する。RMスコアが上がっているのに勝率が200ステップ以上停滞したら学習を止める。モデルは改善ではなく悪用をしている。
検出後の回復: 最後の「クリーンな」チェックポイントにロールバックする。\(\beta\) を2\(\times\)増やす。発見した悪用をRMの負例に加える。
復習: 第9章と第11章(報酬モデルの学習、システムアーキテクチャ)。
システム設計の問題
Note
Q6: 70Bモデルを学習するRLHFシステムを設計してください。すべてのコンポーネントを順に説明してください。
解答: A100-80GB GPU 72基上に、3クラスタの分離アーキテクチャを構成します。
クラスタ1 — 生成(32 GPU):
vLLMインスタンスを8つ、各TP=4で配置する。PagedAttentionと投機的デコーディング(1Bドラフトモデル)を使う。
連続バッチ処理で、処理中の系列は最大256個。帯域幅のため重みはINT8にする。
出力は(プロンプト、応答、トークンごとの対数確率)。スループットは毎分 \(\sim\)500応答。
ステートレスで、共有ストアから最新の重みを読み込むだけなので、容易に復旧できる。
クラスタ2 — スコアリング(8 GPU):
報酬モデル(70B、INT8 = 70GB)を4 GPU(TP=4)に配置する。
参照モデル(70B、INT8)を4 GPU(TP=4)に配置し、KL用のトークンごとの対数確率を計算する。
出力は報酬スコアとトークンごとのKL。軽量なバッチ推論を行う。
クラスタ3 — 学習(32 GPU):
FSDP(ZeRO-3)による方策モデル。Flash Attention 2と勾配チェックポイントを使う。
バッファからスコア付き経験を消費する。PPO更新は、ミニバッチ16で4エポック行う。
更新済み重みを50ステップごとに共有ストアへ送る(非同期のバックグラウンド転送)。
100ステップごとに非同期チェックポイントを作る(NVMeへのノンブロッキング書き込みとS3バックアップ)。
接続基盤:
生成 \(\rightarrow\) スコア: Ray/Redisキュー(バッチあたり \(\sim\)10 MB、トークンIDと対数確率)
スコア \(\rightarrow\) 学習: 経験バッファ(循環バッファで、直近500ステップを保持)
学習 \(\rightarrow\) 生成: 共有並列FS(Lustre/GPFS)上の重みストア。140GBを50ステップごとに非同期で送る。
オーバーラップ: 学習がステップ \(N\) を処理している間に、生成はすでにステップ \(N+1\) のデータを作っている。これにより生成レイテンシを隠し、モノリシック構成に対して1.3〜1.5\(\times\)のスループットを達成する。
復習: 第11章(システムアーキテクチャと大規模インフラ)。
Note
Q7: 生成ボトルネックにどう対処しますか?解決策を定量化してください。
解答: 生成はRLHFの実時間全体の60〜70%を占めます。根本原因は、自己回帰デコーディングがメモリ帯域幅律速であることです(演算強度は \(\approx 1\) FLOP/byteで、A100のルーフラインは156 FLOP/byte)。
効果の大きい順に並べた解決策:
1. 生成と学習を分離 (エンドツーエンドで1.3〜1.5\(\times\)): 別ハードウェアで生成を実行し、学習と重ねる。アーキテクチャ上の効果が最も大きい。
2. PagedAttention付きvLLM (2〜4\(\times\)): 内部フラグメンテーションによるKVキャッシュのメモリ浪費60〜80%をなくす。3〜4\(\times\)大きなバッチが可能になり、帯域幅利用率が向上する。
3. 連続バッチ処理 (1.5〜2\(\times\)): 最長系列の完了を待たず、空いたスロットですぐに新しい系列を開始する。GPUを稼働し続けられる。
4. 投機的デコーディング (2〜3\(\times\)): 1Bドラフトモデルが5トークンを提案し、70Bモデルが1回のフォワードパスで5つすべてを並列検証する。平均3〜4個を受け入れる \(\rightarrow\) 1回のフォワードパスで1個ではなく3〜4トークン生成できる。
5. 生成重みにINT8/FP8を使う (2\(\times\)): トークンごとに読み込む140GBの重みを半減する。そもそも温度付きサンプリングを行い、INT8を使うのは生成だけで学習はBF16のままなので、品質低下は最小限である。
6. CUDAグラフとカーネル融合 (1.1〜1.3\(\times\)): Python/CUDAの起動オーバーヘッドをなくし、layernorm+attention+MLPを少数のカーネルに融合する。
組み合わせた場合: 素朴な実装に対して \(1.5 \times 3 \times 1.5 \times 2.5 \times 2 \times 1.2 = 40\times\) となる。実際には収穫逓減により、素朴な実装に対する合計効果は \(\sim\)10〜20\(\times\)に制限される。
復習: 第2章と第11章(システム基盤、システムアーキテクチャ)。
Note
Q8: 分離システムにおける重みの同期を説明してください。どの程度の古さまで許容できますか?
解答:
問題: 生成クラスタは方策の重みを使って応答を生成し、学習クラスタはその重みを更新します。両者は異なるハードウェア上にあります。どう同期を保つべきでしょうか?
完全同期が無駄な理由: 70B BF16の完全同期は140GBです。InfiniBand 400Gb/s(50GB/s)なら、1回の同期に2.8秒かかります。毎ステップ(50〜90秒ごと)に同期すると、時間の3〜5%を重み転送に使うことになります。許容範囲ですが、不要です。
古さの許容度の分析:
ステップごとの方策変化: \(\sim\)0.1%(パラメータ差分の平均で測定)
50ステップ: 累積ドリフトは \(\sim\)5%
PPOのクリップ範囲: 確率比の最大20%のずれに対応
経験的結果: 50ステップ古くても \(\rightarrow\) 品質低下は \(<\)2%(勝率で測定)
本番戦略:
学習50ステップごとに、完全なBF16チェックポイントを共有ストアへ送る(転送2.8秒)
生成クラスタでは、バッチ間にノンブロッキングで重みを再読み込みする
差分圧縮(任意): 変更されたパラメータだけを送り(INT8差分は \(\approx\) 5GB)、オフセットとして適用する。帯域幅を10\(\times\)削減できる。
非常に大規模(256 GPU以上)の場合はストリーミング同期を使い、バックグラウンドで小さなチャンクを継続的に送る。平均の古さは5〜10ステップ。
重要な注意点: 生成中に計算する対数確率は古い重みを使います。PPO比 \(\pi_\text{new}/\pi_\text{old}\) は、これらの古い対数確率を使って \(\pi_\text{old}\) を計算します。PPOはオフポリシー補正を想定して設計されているため、これは問題ありません。
復習: 第11章(システムアーキテクチャと大規模インフラ)。
Note
Q9: 512 GPUでこのシステムをフォールトトレラントにするにはどうしますか?
解答: 512 GPUではMTBFが4〜8時間です。5日間の学習実行では15〜30回の障害が発生します。
アーキテクチャレベルの耐障害性:
生成クラスタはステートレス。失敗したインスタンスは \(<\)60秒未満で再起動できる(重みを読み込むだけで、状態がない)。
学習クラスタはステートフル。チェックポイントに基づく復旧が必要。
スコアリングクラスタはステートレス。生成クラスタと同じ。
チェックポイント戦略:
頻度: 50〜100ステップごと(学習5〜10分ごと)。
方法: 非同期(ノンブロッキング)。次のステップを進める間にバックグラウンドスレッドが書き込む。FSDPの分散保存を使い、各ランクが自分のシャードを並列に保存する。
内容: モデル重み、オプティマイザー状態(Adamのm/v)、LRスケジューラー、RNG状態、KL適応係数、グローバルステップカウンター、リプレイバッファポインター。
保存先: ローカルNVMe(高速で70Bなら30秒)と、S3/共有FSへの非同期コピー(耐久性)。
保持: 最新のチェックポイント3つを残し、古いものは自動削除する。
検出と復旧の流れ:
NCCL集合通信のタイムアウト(60秒)またはハートビート欠落(10秒) \(\rightarrow\) 障害を検出する。
NVMLヘルスチェックで失敗したノードを特定する。
選択肢A(高速、\(<\)2分): Torch Elasticでワールドサイズを縮小し、シャードを再配分して \(N-1\) ノードで続行する。バックグラウンドで交換ノードを要求する。
選択肢B(クリーン、\(\sim\)5分): 交換ノードを起動し、プロセスグループを再構築し、最後のチェックポイントを読み込んで再開する。
経験バッファは永続化されているため、再生成は不要。
予防: 事前のストレステスト(GEMM、メモリ、NVLink)。ECCエラーの監視(エラーが急増したら予防的に移行)。ホットスペアノード(環境を事前ロード済み)。ネットワーク冗長性のためのデュアルレールInfiniBand。
復習: 第11章(システムアーキテクチャと大規模インフラ)。
Note
Q10: 7Bから70B、405Bへどのようにスケールさせますか?各規模で何が変わりますか?
解答:
7B(8 GPUの単一ノード、数時間):
アーキテクチャ: モノリシック(TRLのデフォルト)。すべてのモデルを同じGPUに置く。
メモリ: LoRAとINT8の参照モデル/RMは8\(\times\)80GBに容易に収まる。
並列性: DP=8またはノード内FSDP。ネットワーク通信はない。
ハイパーパラメータ: LR=\(5\times10^{-6}\)、大きめの \(\beta\)=0.02、50K〜100Kステップ。
時間: 1回の実行に4〜12時間。高速に反復できる。
70B(32〜64 GPU、2〜5日):
アーキテクチャ: 半分離。vLLMによる生成とFSDPによる学習。
メモリ: ZeRO-3が必須。勾配チェックポイント。参照モデル/RMはINT8。
並列性: ノード内の生成はTP=8、ノード間の学習はFSDP。
ハイパーパラメータ: LR=\(1.5\times10^{-6}\)、中程度の \(\beta\)=0.05、10K〜30Kステップ。
フォールトトレランス: 非同期チェックポイントと監視を使うが、手動でも管理できる。
405B(256〜512 GPU、1〜3週間):
アーキテクチャ: 完全分離。クラスタを分け、重みストアとキューを使う。
メモリ: 学習はZeRO-3 + TP=8 + PP=2。生成はINT4。
並列性: 3D並列(TP\(\times\)PP\(\times\)DP = 8\(\times\)2\(\times\)16 = 学習256 GPU)。
ハイパーパラメータ: LR=\(5\times10^{-7}\)、非常に保守的な \(\beta\)=0.1、2K〜5Kステップ。
フォールトトレランス: 必須。エラスティック学習、冗長チェックポイント、ホットスペアを使う。
大きな変化: 必要なRL学習量は大幅に少ない(事前学習ですでに非常に高い能力を持つため)。しかし1ステップが50\(\times\)高価なので、不安定性は致命的になる。
逆説: 大きなモデルほど、実は1ステップ単位ではRL学習しやすい(より安定し、損失地形も滑らか)です。しかし不安定性のコストはモデルサイズに比例し、405Bでの失敗した実行は10万ドル以上の計算資源を無駄にします。
復習: 第11章(システムアーキテクチャと大規模インフラ)。
実践とデバッグの問題
Note
Q11: 報酬スコアは上がっているのにモデル品質が低下しています。診断して修正してください。
解答: 典型的な 報酬ハッキング/グッドハートの法則 です。
診断手順:
応答長を確認: 学習中の平均長をプロットする。単調に増えているなら、長さの悪用(RMが長い応答に高いスコアを与える)です。
KLダイバージェンスを確認: \(>\)15なら、方策が参照モデルから離れすぎ、能力を失っています。
多様性を確認: 応答ごとの一意なトライグラムを調べる。低下しているなら、モード崩壊(同じ高報酬パターンの反復)です。
手動検査: 報酬が最も高い応答を20個読む。共通するパターンは何か(例: すべて「良い質問です!」で始まる、すべて箇条書きを使う、過度に留保する)。
勝率: 取り置きプロンプトでSFTベースラインと比較する。RMが上がる一方で横ばいまたは低下していれば、悪用が確定です。
即時の修正:
勝率が改善していた最後のチェックポイントへロールバックする
\(\beta\) を2〜3\(\times\)増やす(KLペナルティを強める)
明示的な長さペナルティを追加する: \(r' = r - 0.001 \cdot \max(0, \text{len} - 500)\)
構造的な修正(再発防止):
RMアンサンブル: 異なるデータ分割で3〜5個のRMを学習し、最小値または平均値を使う。悪用はモデル固有である。
RM更新: 2000ステップごとに現在の方策から生成し、人間のラベルを得てRMを再学習する。
多目的報酬: 別々のRMによる有用性+無害性+簡潔性を組み合わせる。
RMスコアではなく勝率で早期停止する。最適化する指標は学習信号と異なるべきである。
復習: 第9章と第11章(報酬モデルの学習、システムアーキテクチャ)。
Note
Q12: RL学習用のプロンプト分布をどう決めますか?
解答: プロンプトの品質は、RLHFで最も過小評価されている要因です。悪いプロンプトは、学習信号がないことを意味します。
構成(デフォルトの配分):
40% 実ユーザーのトラフィック(実際のユースケースを表す)
30% 合成データ(LLM生成で、まれなトピックやエッジケースなどカバレッジの穴を埋める)
20% カリキュラム(段階的な難易度。易しい問題から始め、モデルの改善に合わせて複雑さを上げる)
10% 敵対的データ(レッドチームのプロンプト、脱獄の試み、曖昧な指示)
重要: ゴルディロックスフィルター:
各候補プロンプトについて、現在のモデルで4〜8個の応答を生成する。
RMで採点し、合格率(しきい値を上回る割合)を計算する。
合格率が20〜80%のプロンプトだけを残す。
\(<\)20%: 難しすぎる。モデルがほぼ必ず失敗する \(\rightarrow\) すべてのアドバンテージが負になる \(\rightarrow\) 有用な勾配がない。
\(>\)80%: 易しすぎる。モデルがほぼ必ず成功する \(\rightarrow\) すべてのアドバンテージが正になる \(\rightarrow\) コントラストがない。
20〜80%: ちょうどよい。成功と失敗が混ざり \(\rightarrow\) 何が有効かについて明確な信号が得られる。
500学習ステップごとに再フィルタリングする(モデルが改善し、難易度分布が変化するため)。
トピックの多様性: 1つのトピックが支配しないようにする(カテゴリごとに \(<\)10%)。埋め込みクラスタリングでカバレッジを確認する。そうしないとモデルは支配的なトピックに対して過剰最適化する。
復習: 第7章と第9章(GRPO、報酬モデルの学習)。
Note
Q13: RLHFにおけるLoRAと完全ファインチューニングを比較してください。それぞれをいつ使いますか?
解答:
LoRA (低ランク適応、\(r\)=64、\(\alpha\)=16):
学習可能なパラメータ: モデルの \(\sim\)0.2%(70Bでは2億)
メモリ節約: 別の参照モデルが不要(ベースモデル=参照モデル)で、140GBを節約できる。
安定性: 本質的により安定している(低ランク制約が方策のドリフトを制限する)
速度: 更新するパラメータが少ないため1ステップは速いが、より多くのステップが必要になる場合がある
品質の上限: 通常、完全FTの品質の90〜95%
完全ファインチューニング:
すべてのパラメータを更新する。表現力が最大。
別の参照モデルコピーが必要(70Bで140GB)。または「アンカー」として非常に頻繁なチェックポイント作成が必要。
壊滅的忘却のリスクが高い。より低いLR(LoRAの \(3\times\)低い値)と、より強い \(\beta\) が必要。
適する場合: 大きな分布シフトが必要(新しい言語、非常に異なるスタイル)で、LoRAが容量限界に達したとき。
判断の枠組み:
LoRA(\(r\)=64)から始める。3\(\times\)安価で、より安定している。
LoRA行列の勾配ノルムを監視する。一貫して \(>\)1.0(パラメータ数に対して高い)なら、LoRAは容量制限を受けている。
勝率が頭打ちになり、かつ勾配分析が容量制限を示す場合にだけ完全FTへ切り替える。
完全FTでは \(\text{LR}/3\)、\(\beta \times 2\)、より頻繁なチェックポイント作成、勝率に基づく早期停止を使う。
ハイブリッド: アラインメント/安全性にはLoRA(小さな振る舞いの変化)を、能力/推論には完全FT(大きな変化が必要)を使う。
復習: 第1章と第10章(LLMアーキテクチャ、SFTのベストプラクティス)。
Note
Q14: プロセス報酬モデル(PRM)と結果報酬モデル(ORM)を比較してください。PRMシステムを設計してください。
解答:
ORM: 最終解答だけを採点する。「応答全体は良いか?」を問う。単純だが、推論のどこが間違ったかは特定できない。
PRM: 中間ステップをそれぞれ採点する。「この導出のステップ3は正しいか?」を問う。はるかに情報量が多いが、学習は難しい。
推論におけるPRMの利点:
推論が失敗した場所を正確に特定できる(ステップレベルの信用割当)
木探索が可能になる: ステップスコアの高い枝だけを展開する
報酬ハッキングが少ない: 間違ったステップと幸運な最終解答で高スコアを得ることができない
MATHベンチマークでは、PRM+best-of-NがORM+best-of-Nを10〜20%上回る
PRMの学習:
データ収集 (モンテカルロ方式):
各問題について、推論トレースをステップごとに生成する
各ステップ \(k\) で、その地点から解答を \(M\) 回(\(M=32\))完成させる
ステップスコア=正解に到達した完成例の割合
スコアが大きく低下するステップが「間違い」のステップ
ラベル付け: 完成率が \(>\)50%なら「正しい」、\(<\)20%なら「誤り」とする。
モデル: ベースモデルと同じアーキテクチャに、トークン位置ごとの分類ヘッドを追加する。ステップラベルに対する二値交差エントロピーで学習する。
推論: 各ステップを採点する。いずれかのステップが \(<\)0.3なら、そのトレースを不備ありとしてフラグ付けする。
RLHFでPRMを使う: PRMのトークンごとの報酬を直接GAEへ入力する。系列の最後だけでなく、各トークンが即時フィードバックを得るため、長い推論チェーンの信用割当が大幅に改善する。
復習: 第9章と第13章(報酬モデルの学習、大規模推論モデルのためのRL)。
Note
Q15: RLが実際にモデルを改善したかどうかをどう評価しますか?
解答: 多面的に評価します(「品質」を単一の指標で捉えることはできません)。
1. 勝率 (最も重要で、最も信頼できる):
多様なプロンプトを500個以上用意する。LLMジャッジ(GPT-4またはClaude)が、SFTベースラインとのブラインドA/B比較で勝者を選ぶ。
目標: 勝率 \(>\)55%は意味のある改善、\(>\)65%は大きな改善。
位置バイアスを除去する(A/Bの順序を入れ替えて平均する)。信頼区間を報告する。
2. 能力ベンチマーク (退行の検出):
MMLU(知識)、HumanEval(コード)、MATH(推論)、MT-Bench(マルチターン)。
\(>\)2%の低下は、懸念すべきアラインメント税である。どのカテゴリが劣化したか調べる。
3. カテゴリ別評価:
安全性: 有害なプロンプトに対する拒否率(上がるべき)
真実性: TruthfulQAスコア(上がるか、少なくとも横ばいであるべき)
有用性: 指示追従ベンチマークでのタスク完了率
4. 分布指標:
応答長の分布(大きく変化すべきではない)
語彙の多様性(応答ごとの一意なトークン)
形式への準拠(特定の形式向けに学習した場合)
5. 人手評価 (ゴールドスタンダードで高コスト):
例ごとに3人以上の熟練アノテーターでブラインドA/Bを行う。アノテーター間一致率は \(>\)70%。
学習中ではなく、最終モデルの選択だけに使う(遅すぎて高コストなため)。
危険信号: RMスコアが上がり勝率が横ばいなら、真の改善ではなく報酬ハッキング。勝率が上がりベンチマークが下がるなら、アラインメント税が高すぎるためRLの強度を下げる。
復習: 第14章(LLM評価)。
Note
Q16: 報酬モデルの学習パイプラインを端から端まで説明してください。
解答:
フェーズ1 — データ生成:
多様なプロンプトを50K〜100K件集める(実トラフィック+合成データ)
プロンプトごとに、異なる温度(0.3、0.7、1.0)と複数モデルから4〜8個の応答を生成する(多様性が重要で、すべての応答が似ていると選好情報が役に立たない)。
合計: 200K〜800K件の候補応答
フェーズ2 — 選好収集:
選択肢A(高コストで最高品質): 人間のアノテーターがペアを比較する。1ペアにつき3人。費用は比較1回あたり2〜5ドル。
選択肢B(安価で、人間との一致率85〜90%): LLMジャッジ(GPT-4/Claude)。10\(\times\)安価で、大規模化に向く。
形式: (プロンプト、選択された応答、拒否された応答)。アノテーターの一致率が \(<\)70%のペアは破棄する。
最終データセット: 100K〜500Kペア
フェーズ3 — 学習:
アーキテクチャ: ベースLLMと同じものにスカラー・ヘッドを追加する(系列ごとに1つの回帰出力)。
Loss: \(\mathcal{L} = -\mathbb{E}[\log\sigma(r(x,y_w) - r(x,y_l))]\) (Bradley-Terry).
学習: 1エポックだけ! RMは極めて速く過学習する。検証精度68〜75%は良好で、それ以上はアノテーションのアーティファクトへの過学習を意味することが多い。
工夫: 報酬を0の周りに中心化する(移動平均を引く)。長さバイアスを確認し、長さとスコアの相関が \(>\)0.3なら学習に長さペナルティを加える。
フェーズ4 — 検証:
ホールドアウト選好ペア: 精度は68〜75%であるべき。
新規データでの人間との一致率: \(>\)80%。
長さバイアスの確認: 応答長とRMスコアの相関は \(<\)0.2であるべき。
一貫性の確認: 同じプロンプトの言い換え応答には、似たスコアが付くべき。
復習: 第9章(報酬モデルの学習)。
Note
Q17: KLダイバージェンスが爆発すると何が起きますか?根本原因と修正方法を示してください。
解答:
KLが測るもの: 現在の方策と参照モデルの平均対数比です。\(D_\text{KL} = \mathbb{E}_{y\sim\pi_\theta}[\log(\pi_\theta(y\vert x)/\pi_\text{ref}(y\vert x))]\)。KL=0は参照モデルと同一、KL=10は方策が好む出力に10ナット多く確率を置くことを意味します。
健全な範囲: 学習中は3〜10。ゆっくり増えるのは問題ないが、突然の急上昇は問題です。
KL爆発の根本原因:
学習率が高すぎる: 方策が大きく更新され、参照モデルから発散する。修正: LRを2〜5\(\times\)下げる。
報酬ハッキング: 参照モデルの振る舞いから遠く離れた高報酬の悪用を見つけた。修正: \(\beta\) を増やし、RMアンサンブルを加える。
モード崩壊: 方策が1つの応答テンプレートに集中する。そのテンプレートではKLが高く、他では低い。修正: エントロピーボーナスと温度を上げる。
悪いバッチ: 極端なアドバンテージを持つ不運なバッチが方策を押し動かした。修正: 勾配クリッピングとミニバッチサイズの削減。
価値関数の発散: 間違ったアドバンテージ推定が誤った更新を引き起こした。修正: 価値関数のLRを下げるか、GRPO(価値関数なし)へ切り替える。
復旧手順:
検出: KLが50ステップ以上 \(>\)15、または1ステップでKLが \(>\)5急上昇した場合。
即時対応: 最後のクリーンなチェックポイントを読み込む(KL \(<\)10)。
調整: LRを50%下げる。\(\beta\) を2\(\times\)増やす。クリップ範囲を0.1に下げる。
再開: 最初の200ステップを注意深く監視する。
復習: 第5章と第7章(PPO、GRPO)。
Note
Q18: モノリシックなRLHFアーキテクチャと分離RLHFアーキテクチャを比較してください。それぞれはいつ適切ですか?
解答:
モノリシック (TRLのデフォルト: 単一プロセスで、すべてのモデルを同じGPUに置く):
利点: コードが単純。分散システムの複雑さがなく、デバッグしやすい。
欠点: GPUが時間の60%アイドルになる(生成中は計算資源が、学習中は帯域幅がアイドル)。\(\sim\)16 GPUを超えると効率よくスケールしない。すべてのモデルが同じメモリを奪い合う。
適する場合: モデルが \(\leq\)13B、単一ノード、研究/プロトタイピング。
半分離 (vLLM生成+FSDP学習、同一クラスタ):
利点: 利用率が高い(生成と学習を部分的に重ねられる)。64 GPUまでスケールする。
欠点: 依然としてハードウェアを共有するため独立最適化できない。モノリシックより複雑。
適する場合: 13B〜70B、2〜8ノード、本番実験。
完全分離 (キューで接続した別クラスタ):
利点: 各クラスタをワークロード向けに最適化できる。生成を学習から独立してスケールできる。生成クラスタはステートレスで、フォールトトレランスが容易。数百GPUまでスケールする。
欠点: 分散システムの複雑さ、重みの古さ、キュー管理、ネットワークオーバーヘッド。
適する場合: \(\geq\)70Bの本番学習。スケール、フォールトトレランス、高利用率が必要な場合。
重要な洞察: 生成は帯域幅律速で、学習は計算律速です。同じハードウェアを両方に最適化することはできません。分離すれば生成ノードには高いメモリ帯域幅、INT8重み、大きなバッチを、学習ノードには完全なBF16精度、Flash Attention、FSDPシャーディングを持たせられます。
復習: 第11章(システムアーキテクチャと大規模インフラ)。
Note
Q19: RL学習向けのカリキュラム学習をどう設定しますか?
解答: カリキュラムとは、モデルが段階的に学べるよう難易度を徐々に上げることです。
重要な理由: 弱いモデルに最難関のプロンプトを与えると、すべて負の報酬になり \(\rightarrow\) 学習信号がなくなります(すべて同じように悪い)。易しい問題から始めれば、モデルは基礎能力を身につけ、その上に能力を積み上げられます。
実装:
難易度の採点: 現在のモデルの合格率(ゴルディロックスフィルタリングから得る)で各プロンプトを評価する。易しい=合格率 \(>\)80%、中=30〜80%、難しい= \(<\)30%。
スケジュール: ステップ0〜1000は易70%、中20%、難10%。1000〜5000は易30%、中50%、難20%。5000以降は易10%、中40%、難50%。
動的調整: 500ステップごとに難易度分布を再評価する。モデルが「習得した」プロンプト(合格率 \(>\)95%)は引退させ、新しいより難しいプロンプトを導入する。
特にGRPOの場合: カリキュラムにより、グループ内に常に成功と失敗が混在する。カリキュラムがないと、難しいプロンプトはすべてゼロのグループを生み、役に立たない。
根拠: DeepSeek-R1は暗黙のカリキュラムを使った。易しい数学/コード問題から始め、モデルは基礎推論を発達させ、明示的なスケジュールなしに徐々に難しい問題を解いた。
復習: 第7章と第12章(GRPO、LLMエージェント学習)。
Note
Q20: A100-80GB GPU 64基の予算で70BモデルをRL学習する必要があります。割り当てを設計してください。
解答: 8ノード \(\times\) 8 GPU = 合計64基です。生成、スコアリング、学習に分割します。
割り当て例:
生成: 24 GPU(3ノード)。TP=4のvLLMを6インスタンス。INT8重みはモデルあたり70GBで、KVキャッシュの余地が残る。連続バッチ処理で、合計バッチは \(\approx\)128。
スコアリング: 8 GPU(1ノード)。同じノードにRM(INT8、TP=4)と参照モデル(INT8、TP=4)を置く。または4 GPUをTP=4で共有し、RMと参照モデルを交互に実行する。
学習: 32 GPU(4ノード)。32基すべてでFSDPを使う。各GPUは \(\sim\)70B/32 = 2.2GBのパラメータとオプティマイザーの一部を保持し、アクティベーションにも十分な余裕がある。安全のため勾配チェックポイントを使う。
期待スループット:
生成: 6インスタンス \(\times\) 毎分 \(\sim\)80応答 = 毎分480応答
学習: バッチ128で、1ステップ \(\sim\)15秒(学習だけで、生成待ちなし)
オーバーラップ: 学習がステップ \(N\)(15秒)を処理する間に、生成は \(\sim\)120個の応答をステップ \(N+1\) 用に生成する。理想的なパイプラインになる。
ボトルネック分析: 128応答の生成に \(\sim\)45秒、学習に \(\sim\)15秒、スコアリングに \(\sim\)5秒かかる。ボトルネックは生成である。学習から8 GPUを生成へ移して(生成40、学習24)均衡させることもできるが、今度は学習がボトルネックになる。現在の割り当てはほぼ最適である。
メモリが厳しい場合の代替案: スコアリングを学習ノードへ移す(時間共有し、生成中に採点し、学習中に学習する)。8 GPUを節約できる。パイプラインはやや悪化するが機能する。
復習: 第2章と第11章(システム基盤、システムアーキテクチャ)。
GRPOの派生手法と高度なRLの問題
Note
Q21: DAPOとは何ですか?標準GRPOよりどう改善されていますか?
解答: DAPO(Dynamic Adaptive Policy Optimization、動的適応方策最適化)は、5つの重要な変更を導入します。
1. Clip-Higher(非対称クリッピング): 標準PPO/GRPOは \(\epsilon=0.2\) で両方向を同じようにクリップします。DAPOは \(\epsilon_\text{low}=0.2\)、\(\epsilon_\text{high}=0.28\) を使います。これにより、悪いアクションをどれだけ抑えるかは制限しつつ、良いアクションの確率をより積極的に増やせます。直感的には、活用より探索に大きな余地が必要です。
2. 長すぎる応答のフィルタリング: 応答が最大長に達した場合(切り詰められ、EOSトークンがない場合)、損失から完全にマスクする。理由は、切り詰められた応答には自然な停止信号がなく、それで学習すると「文の途中で止まってよい」とモデルに教えてしまうためです。
3. トークンレベル損失: 損失を系列数ではなく、全系列の総トークン数で正規化する。これにより長い系列が勾配を支配するのを防ぐ。
4. ソフトな長さ超過ペナルティ: 二値の切り詰めフィルタリングではなく、応答が最大長に近づくにつれて段階的なペナルティを適用する。\(r_\text{soft} = -c \cdot \max(0, \text{len} - L_\text{soft})/(L_\text{max} - L_\text{soft})\)。
5. 動的サンプリング: 学習中にプロンプトを再サンプリングし、各バッチに成功と失敗が混在するようにする(TRLにはまだない)。
使う場面: 最大限の探索と長い完了(32K以上のトークン)が必要な大規模推論RL。特に非対称クリッピングが有用です。
復習: 第7章と第8章(GRPO、選好最適化の派生手法)。
Note
Q22: vLLMの学習と推論の不一致を説明してください。なぜ起き、TIS/MISはどう修正しますか?
解答: 問題: 生成にvLLM、更新に学習フレームワーク(DeepSpeed/FSDP)を使うと、同じ重みの同じモデルが異なるトークン確率を出力します。原因は次のとおりです。
数値カーネルが異なる(vLLMはスループット向けに最適化したカスタムCUDAカーネルを使う)
アテンション実装が異なる(学習はFlash Attention、vLLMはPagedAttention)
精度の扱いが異なる(vLLMはFP8/INT8、学習はBF16)
バッチ処理の違いがレイヤー正規化の数値に影響する
これはPPOのオンポリシー仮定を暗黙に壊します。比 \(\pi_\theta/\pi_\text{old}\) を、vLLMの \(\pi_\text{old}\) と学習フレームワークの \(\pi_\theta\) で計算するため、最初のステップから比が間違っています。
TIS(切り詰め重要度サンプリング): 勾配に \(\min(\pi_\text{train}/\pi_\text{inference}, C)\) を掛けて補正する。\(\min\) と上限 \(C\) により、極端な補正で学習が不安定になるのを防ぐ。典型的には \(C=2.0\)。
MIS(マスク付き重要度サンプリング): より積極的で、\(\pi_\text{train}/\pi_\text{inference} > C\) となるトークンを単純に捨てる。勾配への寄与はゼロで、推定の悪いトークンが更新に影響するのを防ぐ。
系列レベルとトークンレベル: 系列レベルISは理論的に正しい(不偏)一方、トークンレベルISはバイアスがあるが分散が低い。実際には、切り詰めを伴う系列レベルが最もうまく機能する。
復習: 第7章と第11章(GRPO、システムアーキテクチャ)。
Note
Q23: GSPOとGRPOの根本的な違いは何で、いつ重要になりますか?
解答: GRPO: 重要度比をトークンごとに計算します。\(w_{i,t} = \pi_\theta(o_{i,t}\vert q, o_{i,<t}) / \pi_\text{old}(o_{i,t}\vert q, o_{i,<t})\) とし、各トークンを独立にクリップします。
GSPO: 重要度比を系列レベルで計算します。\(s_i(\theta) = (\pi_\theta(o_i\vert q)/\pi_\text{old}(o_i\vert q))^{1/\vert o_i\vert }\) で、トークン確率の幾何平均です。この単一の系列レベル比をクリップします。
重要な理由: GRPOのトークンごとのクリッピングは各トークンを独立に扱いますが、言語ではトークンは強く相関しています。系列の早い位置での小さなトークン単位の変化が、多数のトークンにわたって指数的に累積します。GSPOは完全な系列確率を見ることでこれを捉えます。
長さの正規化: \(1/\vert o_i\vert\) の指数によって、異なる長さの系列を公平に比較できます。これがないと、長い系列は常に確率比が低くなります。
GSPOを使う場面: 学習がオフポリシーになる場合(
steps_per_generation > 1またはnum_iterations > 1)。完全にオンポリシー(比が \(\approx 1\))なら、GRPOとGSPOは同等です。復習: 第7章と第8章(GRPO、選好最適化の派生手法)。
Note
Q24: 論文「It Takes Two」はG=2がG=16に匹敵すると示しています。なぜ可能なのですか?
解答: 重要な洞察は、GRPOの有効性が正確なアドバンテージ推定(大きな \(G\) が必要)からではなく、 暗黙の対照目的 から来ることです。
\(G=2\) で二値報酬(1つが正解、1つが不正解)の場合、\(\hat{A}_\text{correct} = +1\)、\(\hat{A}_\text{wrong} = -1\)(正規化後)です。損失は、正しい応答の確率を増やし、間違った応答の確率を下げるものになります。これは本質的にDPO型の対照損失です。
大きな \(G\) があまり役立たない理由: 正規化アドバンテージ \(\hat{A}_i = (r_i - \mu)/\sigma\) はすでに良いものと悪いもののコントラストを作ります。サンプルを増やすと \(\mu\) の推定は良くなりますが、勾配の方向は平均精度ではなく、最良と最悪のコントラストに支配されます。
計算量の節約: \(G=2\) なら生成計算量は8\(\times\)少なくなります(\(G=16\) の場合と比べて)。生成は学習時間の60%を占めるため、学習は \(\sim\)4\(\times\)高速になります。
注意: 合格率が30〜70%のときに最もよく機能します。合格率が非常に低い(\(<\)10%)と、\(G=2\) では2つとも失敗して信号がないことが多い。難しい問題にはより大きな \(G\) が必要です。
復習: 第7章(GRPO)。
Note
Q25: SAPOとは何ですか?ソフトゲーティングはハードクリッピングとどう異なりますか?
解答: 標準PPO/GRPOはハードクリッピング \(\text{clip}(r, 1-\epsilon, 1+\epsilon)\) を使います。境界では勾配が突然ゼロになります。これにより、モデルが学習信号を受け取らない「デッドゾーン」が生まれます。
SAPO はこれを滑らかなシグモイドゲートに置き換えます。比が1から離れるにつれて勾配を徐々に減衰させ、突然ゼロにはしません。非対称な温度を使います。
正のアドバンテージでは \(\tau_+ = 1.0\)(標準的な減衰)
負のアドバンテージでは \(\tau_- = 1.05\)(抑制をやや強く減衰)
利点: (1) 勾配地形に「崖」がない。(2) クリップ範囲を少し外れたトークンも寄与する(減衰するがゼロにはならない)。(3) 最適化の軌跡がより安定する。(4) 系列と整合的で、完全な系列コンテキストを考慮する。
トレードオフ: ハードクリッピングより信頼領域の制約がやや弱いため、温度の慎重な調整が必要です。ただし全体としてハイパーパラメータの選択に対してより頑健です。
復習: 第7章と第8章(GRPO、選好最適化の派生手法)。
DPO拡張の問題
Note
Q26: f-DPOのダイバージェンスの選択肢を比較してください。forward KL、JS、reverse KLをいつ使いますか?
解答: 標準DPOは暗黙にreverse KL(\(D_\text{KL}[\pi_\theta \\vert \pi_\text{ref}]\))を使います。
reverse KL (デフォルト): モード追求型。参照モデルの確率が高い場所に方策の確率を集中させる。参照モデルが生成しないテキストを避け、安全性に適する(保守的)。
forward KL: 質量カバー型。低確率のものも含め、参照モデルのすべてのモードを覆おうとする。多様性に適するが、低品質の出力を生成する可能性がある。
Jensen-Shannon: forwardとreverseの対称的な妥協。モードのカバレッジとモード追求のバランスが取れる。一般的なアラインメントに最適なことが多い。
アルファダイバージェンス (\(\alpha=0.5\)): forward(\(\alpha=0\))とreverse(\(\alpha=1\))の間を補間する。調整可能。
実践的な推奨: reverse KL(標準DPO)から始める。モデルが保守的すぎる(創造的な解決策を試さない)ならJSダイバージェンスに切り替える。多様性が重要(創作、ブレインストーミング)ならforward KLを試す。
復習: 第6章と第8章(DPO、選好最適化の派生手法)。
Note
Q27: DPOの選好データに15%のラベルノイズがあります。どうしますか?
解答: 洗練度の順に3つの選択肢があります。
1. Robust DPO (ノイズ率が既知の場合に最適): 損失を解析的にデバイアスする。\(\mathcal{L}_\text{robust} = \frac{(1-\varepsilon)\mathcal{L}_\text{DPO}(y_w, y_l) - \varepsilon \mathcal{L}_\text{DPO}(y_l, y_w)}{1 - 2\varepsilon}\)。\(\varepsilon = 0.15\) とする。期待値の下でクリーンなDPO目的を復元できることが保証される。TRLでは
loss_type="robust", label_smoothing=0.15。2. IPO (ノイズ率が不明な場合に最適): 目標マージン付き二乗損失。誤ラベルのペアの影響が有界になる(平方損失は発散しない)。\(\varepsilon\) を知らなくても任意のノイズパターンに対して頑健。TRLでは
loss_type="ipo"。3. TR-DPO (分布シフトに最適): 学習中にEMAで参照モデルを更新する。初期データにノイズがあっても、変化する参照モデルが自己修正を助ける。TRLでは
sync_ref_model=True, ref_model_mixup_alpha=0.6。データ側の修正: (1) アノテーター間一致率が \(<\)70%のペアを除外する。(2) RMでペアを採点し、RMとラベルが一致しないものを捨てる。(3) アクティブラーニングで、最も不確かなペアを再ラベル付けする。
復習: 第6章と第8章(DPO、選好最適化の派生手法)。
Note
Q28: SimPOとは何ですか?参照モデルを使わないことの利点は何ですか?
解答: SimPOは応答の平均対数確率を暗黙の報酬信号として使います。\(r(x,y) = \frac{1}{\vert y\vert }\sum_t \log \pi_\theta(y_t\vert x, y_{<t})\) で、参照モデルは不要です。
損失には目標マージン \(\gamma\) を加え、選択された応答の平均対数確率が、拒否された応答より少なくとも \(\gamma\) 高くなるようにします。
参照モデルを使わないことが重要な理由:
メモリ: 参照モデルがないため、70Bモデルで70〜140GB節約できる。同じハードウェアでより大きなモデルを学習できる。
単純さ: 2つ目のモデルコピーを管理、ロード、提供する必要がない。
古い参照がない: DPOの参照モデルは学習が進むほど無関係になる。SimPOにはこの問題がない。
長さの正規化が組み込み: \(1/\vert y\vert\) により長さバイアスを自然に防ぐ(DPOでは明示的な処理が必要)。
トレードオフ: 参照アンカーがないため、モデルは崩壊やドリフトを起こす自由度が高い。\(\gamma\) マージンと長さの正規化が一部緩和するが、強い学習ではSimPOはDPOより不安定になり得る。
復習: 第8章(選好最適化の派生手法)。
Note
Q29: Iterative RPOを説明してください。推論でDPOとNLL損失を組み合わせるのはなぜですか?
解答: 推論に標準DPOを使うと、微妙な失敗モードがあります。識別(正しいトレースに高い暗黙報酬を割り当てること)は学習しますが、必ずしもそれらを生成することは学習しません。
理由: DPOの勾配は選択応答の確率を上げ、拒否応答の確率を下げます。しかし選択応答がモデルの生成するものと大きく異なると、その確率を上げても、似た推論パターンをどう生成するかは教えられません。
RPOの修正: 選択応答に対する負の対数尤度(NLL/SFT)損失を加えます。\(\mathcal{L} = \mathcal{L}_\text{DPO} + \alpha \cdot \mathcal{L}_\text{NLL}(y_w)\) です。
NLL項は、勝利した応答をステップごとに生成するよう明示的に学習させます。DPO項は、敗北した応答を避けることも学習させます。組み合わせることで、モデルは「正しく推論する方法」(NLL)と「避けるべきもの」(DPO)の両方を学びます。
反復: 応答を生成 \(\rightarrow\) 正しさを確認 \(\rightarrow\) ペアを作成 \(\rightarrow\) RPOで学習 \(\rightarrow\) 繰り返す。反復ごとにモデルは正しい推論の生成が上達し、次のラウンド用により高品質な学習データを作ります。
TRL:
loss_type=["sigmoid", "sft"], loss_weights=[1.0, 1.0]復習: 第8章と第13章(選好最適化の派生手法、大規模推論モデルのためのRL)。
GPUアーキテクチャとハードウェアの問題
Note
Q30: GPUメモリ階層を説明してください。LLM推論でなぜ重要ですか?
解答: 速い順に並べると次のとおりです。
レジスタ: スレッドごとに \(\sim\)256 KB/SM。即時アクセス(レイテンシ0サイクル)。
SRAM(共有メモリ): SMごとに \(\sim\)192〜228 KB/SM(A100は164 KBに設定可能)。帯域幅は合計 \(\sim\)19 TB/s、レイテンシは \(\sim\)20サイクル。
L2キャッシュ: GPU全体で共有し、40〜60 MB(H100は50 MB)。帯域幅 \(\sim\)5 TB/s、レイテンシ \(\sim\)200サイクル。
HBM: GPUの主メモリで80 GB(A100)。帯域幅2〜3.35 TB/s、レイテンシ \(\sim\)400サイクル。
CPU DRAM: PCIe経由で512 GB以上。帯域幅32〜64 GB/s、レイテンシ \(\sim\)10Kサイクル。
LLMで重要な理由: 自己回帰生成では、1トークンごとにモデル重み全体(70Bで \(\sim\)140 GB)を読み込む。HBM帯域幅が2 TB/sでも、重みをストリームするだけで70msかかる。実際の計算(行列ベクトル積1回)はわずか0.5msで、GPUは99%の時間データを待つ。
Flash Attentionはこれを利用する: 中間結果(QKスコア、softmax)をHBM(2 TB/s)に書き込まずSRAM(19 TB/s)に保持することで、アテンションのメモリトラフィックの90%をなくす。計算量は同じだが、HBMの読み書きは10\(\times\)減る。
復習: 第2章(LLMのシステム基盤)。
Note
Q31: Flash Attentionはどのように機能しますか?オンラインsoftmaxのトリックとは何ですか?
解答: 問題: 標準アテンションは \(n \times n\) 行列をHBMに展開する。\(n=8192\) では、ヘッドあたり \(8192^2 \times 2 = 134\) MB、ヘッド数32なら層あたり4.3 GBになる。HBMへ書き込み、softmaxと乗算のために読み戻す必要があり、HBMを3回完全に往復する。
Flash Attentionの解決策: 完全な \(n \times n\) 行列を保存せず、SRAMに収まるタイル単位で処理する。
アルゴリズム:
\(Q\) を \(B_r\) 行のブロックに、\(K/V\) を \(B_c\) 行のブロックに分割する。
各 \(Q\) ブロックについて、すべての \(K\) ブロックを反復し、部分的なアテンションスコアを計算する。
オンラインsoftmaxのトリック: softmaxの正規化用に累積最大値 \(m\) と累積和 \(\ell\) を保持する。新しい \(K\) ブロックを処理するとき、\(m_\text{new} = \max(m_\text{old}, \max(\text{scores}))\) と更新し、以前の累積値を \(e^{m_\text{old} - m_\text{new}}\) で再スケールして、新しい寄与を加える。
出力を逐次累積するため、完全な \(n \times n\) 行列は決して必要ない。
重要な洞察: softmaxは通常、全要素に対する \(\max\) と \(\sum\) を取るグローバルな演算です。オンラインのトリックは、補正係数を伴う局所更新に分解します。数学的に厳密で、近似ではありません。
結果: メモリは \(O(n)\)(\(O(n^2)\) ではなく)。速度は2〜4\(\times\)向上する(HBMアクセスが減り、SRAMで過ごす時間が増える)。
Flash Attention 2: warp間の作業分割を改善し、行列乗算以外のFLOPを2\(\times\)削減する。
Flash Attention 3 (H100/Hopper): 非同期ロードにTensor Memory Accelerator(TMA)を使い、warp特化(プロデューサー/コンシューマーwarp)とFP8をサポートする。
復習: 第1章と第2章(LLMアーキテクチャ、システム基盤)。
Note
Q32: PagedAttentionを説明してください。KVキャッシュの問題をどう解決しますか?
解答: 問題: 生成中、各系列はKVキャッシュ(過去の全トークンのKとVテンソルを保存する)を必要とする。70Bモデルでは、1トークンに \(2 \times n_\text{layers} \times d_\text{model} \times 2\) バイト = \(2 \times 80 \times 8192 \times 2 \approx 2.5\) MBが必要で、2048トークン系列ではKVキャッシュが \(\sim\)5 GBになる。
従来の割り当て: アクティブな各系列にmax_sequence_lengthを事前割り当てする。最大2048で平均500なら、割り当てたメモリの75%を浪費する。同時に50系列あれば、数百GBが無駄になる。
PagedAttention: OSの仮想メモリに着想を得ている。
KVキャッシュを固定サイズのブロック(ページ)に分け、各ブロックが16トークン分のKVを保持する。
ブロックテーブル(ページテーブルに相当)が論理トークン位置を物理メモリブロックへ対応付ける。
系列の成長に応じてブロックをオンデマンドで割り当てる。事前割り当てによる浪費がない。
系列が終わると、解放されたブロックはすぐにプールへ戻る。
追加の利点:
プレフィックス共有: 同じシステムプロンプトを持つ複数系列がKVキャッシュブロックを共有する(コピーオンライト)。チャットアプリではメモリを30〜50%節約できる。
プリエンプション: 低優先度系列のブロックをCPUへ「スワップアウト」し、高優先度リクエスト用にGPUメモリを解放できる。
ほぼゼロのフラグメンテーション: 内部フラグメンテーションは最後のブロック(\(<\)16トークン)に限られる。外部フラグメンテーションはなく、空きブロックをどこでも使える。
結果: 同じメモリで同時系列数を3〜5\(\times\)にでき、\(\rightarrow\) 提供時のスループットも3〜5\(\times\)になる。
復習: 第2章(LLMのシステム基盤)。
Note
Q33: NVLinkとInfiniBandを比較してください。RLHF学習ではそれぞれをいつ使いますか?
解答:
NVLink (ノード内、GPU間):
帯域幅: 600 GB/s(A100)、900 GB/s(H100)—双方向合計
レイテンシ: \(\sim\)1 \(\mu\)s
範囲: 1つの物理ノード内(8 GPUをNVSwitchで接続)
用途: テンソル並列 (TP=8)。各層の行列乗算をGPU間で分割し、各層の後にAllReduceが必要になる。超高帯域幅と低レイテンシが必要。
InfiniBand NDR (ノード間):
帯域幅: 400 Gb/s = ポートあたり50 GB/s。8ポート(GPUDirect RDMA)でノードあたり合計400 GB/s。
レイテンシ: \(\sim\)1〜5 \(\mu\)s(RDMA)
範囲: クラスタ内のノード間。スイッチ(ファットツリー型トポロジ)が必要。
用途: データ並列/FSDP の勾配同期。勾配のAllReduceは層ごとではなく学習ステップごとに1回なので、レイテンシの許容度が高い。
特にRLHFでは:
生成: ノード内のNVLink上でTP=8。ノード間の複数vLLMインスタンスは通信しない(完全に並列化しやすい)。
学習: ノード内はNVLink上のTP=8、ノード間はInfiniBand上のFSDP。完全な逆伝播の後に勾配を同期する。
重み同期: 学習 \(\to\) 生成にはInfiniBandを使う(140 GB転送を非同期で行い、50 GB/sなら \(\sim\)3秒)。
復習: 第2章と第11章(システム基盤、システムアーキテクチャ)。
最適化と学習の問題
Note
Q34: AdamとAdamWを説明してください。なぜその違いがLLMで重要ですか?
解答: L2正則化付きAdam: \(\theta_{t+1} = \theta_t - \alpha \cdot (\hat{m}_t / (\sqrt{\hat{v}_t} + \epsilon) + \lambda\theta_t)\)。重み減衰項 \(\lambda\theta_t\) は適応的スケーリングの内側にある。勾配が大きいパラメータ(\(v_t\) が大きい)は、\(\sqrt{v_t}\) で割られるため小さい重み減衰を受ける。これは真の重み減衰ではなく、スケール依存です。
AdamW(分離された重み減衰): \(\theta_{t+1} = (1 - \alpha\lambda)\theta_t - \alpha \cdot \hat{m}_t / (\sqrt{\hat{v}_t} + \epsilon)\)。重み減衰を適応更新の外側かつ前に適用する。勾配履歴に関係なく、すべてのパラメータが同じ比例減衰を受ける。
LLMで重要な理由:
LLMのパラメータは何桁もの大きさにまたがる(埋め込み層、アテンション、FFN)。Adamの結合L2は大きな勾配のパラメータより小さな勾配のパラメータを強く罰するため、望ましくない。
分離WDは全層に一様な正則化を与え、一部の層が際限なく大きくなり、他の層が縮みすぎるのを防ぐ。
経験的には、同じ実効正則化なら、長い事前学習でAdamWはAdam+L2よりパープレキシティが2〜5%良い。
特にRLでは: \(\lambda = 0\)(重み減衰なし)を使うことが多い。KLペナルティが正則化を与える。ただしSFTではAdamWと \(\lambda = 0.01\)〜\(0.1\) が標準です。
復習: 第1章(LLMのアーキテクチャと最適化手法)。
Note
Q35: 学習率ウォームアップはなぜ必要ですか?ないと何が起きますか?
解答: 問題: Adamの二次モーメント推定 \(v_t = \beta_2 v_{t-1} + (1-\beta_2)g_t^2\) は \(v_0 = 0\) から始まる。バイアス補正 \(\hat{v}_t = v_t/(1-\beta_2^t)\) は数学的には補償するが、実際には次の問題がある。
最初の数ステップでは、\(v_t\) は1〜5個の勾配サンプルに基づくため、真の分散の推定が非常に不正確。
初期に小さい勾配を受けたパラメータでは、\(v_t\) が極小 \(\rightarrow\) 実効LRが巨大 \(\rightarrow\) 壊滅的更新となる。
バイアス補正が初期更新を増幅する。ステップ1では \(\hat{v}_1 = v_1/(1-0.999) = 1000 \cdot v_1\)。
ウォームアップがない場合: 最初の10〜100ステップで勾配が急上昇し、モデルに恒久的な損傷を与えることが多い。オプティマイザーが安定する前に初期表現が壊れる。
ウォームアップによる修正: LRを \(\approx 0\) から始め、\(W\) ステップで目標値まで線形に増やす(通常は学習の3〜10%)。LRが最大値に達する頃には、\(v_t\) に十分なサンプルが蓄積されて正確になる。
典型的な設定:
事前学習: 2000ステップのウォームアップ(200Kステップの \(\sim\)1%)
SFT: 100ステップのウォームアップ(2000ステップの \(\sim\)5%)
RL(PPO/GRPO): 20〜50ステップのウォームアップ(短く、モデルはSFTですでに安定している)
復習: 第1章と第10章(LLMアーキテクチャ、SFTのベストプラクティス)。
Note
Q36: 学習率スケジュールを比較してください。RLファインチューニングにはどれを選びますか?
解答:
コサイン減衰: \(\eta_t = \eta_\text{min} + \frac{1}{2}(\eta_\text{max} - \eta_\text{min})(1 + \cos(\pi t/T))\)。事前学習とSFTの標準。滑らかに減衰し、ほとんどの時間を中程度のLRで過ごす。
線形減衰: \(\eta_t = \eta_\text{max}(1 - t/T)\)。より単純で、短い実行ではコサインと似た結果になる。
WSD(Warmup-Stable-Decay): ウォームアップ \(\rightarrow\) 80%はLR一定 \(\rightarrow\) 最後の20%で急速に減衰。事前学習の新しい標準。「安定」期が一貫した学習をもたらし、最後の減衰で残りの改善を絞り出す。
一定: 減衰なし。ウォームアップ後は \(\eta_t = \eta_\text{max}\)。
RLファインチューニング(PPO/GRPO)には、短いウォームアップ付きの一定値を選ぶ 。理由は次のとおりです。
RLの学習長は予測しにくい(エポックではなく勝率に基づいて停止する)。
コサイン/線形減衰は総ステップ数を事前に知っていることを仮定する。
LRはすでに非常に低く(\(10^{-6}\))、さらに減衰させると更新が見えなくなる。
PPOの適応KL制御器がすでに実効ステップサイズを調整している。
減衰が必要なら、余裕のある予算で線形減衰を使い、指標が頭打ちになったら早期停止する。
復習: 第1章と第5章(LLMアーキテクチャ、PPO)。
Note
Q37: なぜ勾配クリッピングはRL学習で重要で、SFTではそれほど重要でないのですか?
解答: SFT: 教師あり損失は滑らかで、よく振る舞う。勾配ノルムはバッチ間で一貫している(通常0.1〜1.0)。1.0でのクリッピングが発動することはまれで、安全網として働く。
RL(PPO/GRPO): 勾配ノルムが大きく変動する。理由は次のとおりです。
報酬分散: あるバッチは高報酬の応答ばかりで、次は低報酬ばかりかもしれない。アドバンテージ \(\hat{A}\) が大きく揺れる。
比の爆発: まれなトークンの確率が大きく変わると、\(r_t = \pi_\text{new}/\pi_\text{old}\) が非常に大きくなり \(\rightarrow\) クリッピングが働く前に大きな勾配になる。
疎な報酬: 二値報酬のGRPOでは、全正解(アドバンテージ \(\approx 0\))になるプロンプトがある一方、突然、難しいプロンプトが極端なアドバンテージを与える。
KL項: 方策が発散するとKLペナルティの勾配が急上昇する。
クリッピングがない場合: 1つの悪いバッチが通常の100\(\times\)の勾配を生み \(\rightarrow\) 1ステップでモデルを破壊する。回復は不可能(事前学習をすべて壊滅的に忘却する)。
典型的な設定:
max_grad_norm=1.0。RL学習初期の安全性を高めるため0.5を使う場合もある。ノルムは層ごとではなく、全パラメータでグローバルに計算する。監視: クリッピングがステップの20%を超えて発動するなら、LRが高すぎるか、バッチサイズが小さすぎる可能性が高い。
復習: 第5章と第7章(PPO、GRPO)。
Note
Q38: 学習におけるBF16とFP16を比較してください。選択が重要になるのはいつですか?
解答:
FP16: 符号1+指数5+仮数10ビット。範囲は \(\pm 65504\)、精度は \(\sim\)3.3桁。
BF16: 符号1+指数8+仮数7ビット。範囲は \(\pm 3.4 \times 10^{38}\)(FP32と同じ!)、精度は \(\sim\)2.4桁。
LLMでBF16が優れる理由:
損失スケーリングが不要: FP16の狭い範囲(\(\pm\)65K)では勾配やアクティベーションが頻繁にオーバーフロー/アンダーフローするため、動的損失スケーリング(損失を1024倍し、勾配を戻す)が必要。BF16はFP32の範囲を持つため、オーバーフローはほぼ起きない。
コードが単純: 損失スケーラー、inf/nanチェック、動的スケール調整が不要。
RLに重要: RLの勾配はSFTよりノイズが多く急峻。FP16の損失スケーリングは誤ったスケールを選んでNaNを起こしやすいが、BF16は「そのまま動く」。
FP16が良い場合: 最大精度が必要な場合(科学計算など)で、損失スケーリングを管理できるとき。FP16は仮数ビットが3つ多く、結果がわずかに正確になる。
FP32マスター重み: BF16でフォワード/バックワードを行っても、何千回もの小さなステップで丸め誤差が累積しないよう、勾配更新はFP32で累積する。すべてのLLM学習での標準的な実践です。
復習: 第2章(LLMのシステム基盤)。
報酬モデルとSFTの問題
Note
Q39: Bradley-Terry報酬モデルの損失を導出してください。限界は何ですか?
解答: Bradley-Terryモデル: 2つの応答について、より良い方(\(y_w\))が好まれる確率は \(P(y_w \succ y_l \vert x) = \sigma(r(x, y_w) - r(x, y_l))\) で、\(\sigma\) はシグモイドです。
MLEの導出: \(N\) 個の選好ペアについて尤度 \(\prod_i P(y_w^i \succ y_l^i)\) を最大化する。負の対数を取ると、\(\mathcal{L} = -\sum_i \log\sigma(r(x_i, y_w^i) - r(x_i, y_l^i))\) となる。
限界:
引き分けなし: BTは「同程度に良い」をモデル化できず、厳密な選好を強制する。
推移性: A\(>\)BかつB\(>\)CならA\(>\)Cと仮定する。人間の選好は推移的ではない。
コンテキスト非依存: どんな代替案があったかにかかわらず同じ報酬になる。
スカラーへの縮約: すべての品質軸を1つの数値に圧縮する。応答は安全でも役に立たないことがあり、RMはトレードオフを取る必要がある。
長さバイアス: 長い応答ほど高スコアになる(情報が多いほどアノテーターが望んだものを含みやすい)。明示的に無相関化する必要がある。
緩和策: マージン損失(最小差 \(\delta\) を要求)、報酬の中心化(移動平均を引く)、学習中の長さペナルティ、マルチヘッドRM(有用性/安全性/正確性を別々に採点)。
復習: 第9章(報酬モデルの学習)。
Note
Q40: SFTにおける系列パッキングとは何で、なぜ重要ですか?
解答: 問題: 学習例の長さは可変です。標準バッチ処理ではすべてをmax_lengthまでパディングします。最大4096で平均500なら、勾配に寄与しないパディングトークンに計算の88%を浪費します。
パッキングによる解決: 複数の短い例を1つのmax_length系列に連結する。EOSトークンで分離し、すべての例を同時に学習する。
例: 4系列を4096までパディングする(16Kトークン、14Kがパディング)のではなく、4例を端から端まで4096の1系列に詰める(実トークン4096、パディング0)。 4\(\times\)効率的 です。
重要な詳細 — ブロック対角アテンションマスク: 特別な処理がないと、例2が例1のトークンにアテンションを向けてしまう(交差汚染)。各例が自分のトークンだけを参照するよう制限するブロック対角マスクが必要です。
TRLでは:
SFTConfig(packing=True, max_seq_length=4096)。マスクを自動的に処理します。注意点: (1) 長い例は依然として独自のバッチ項目が必要(系列途中で分割できない)。(2) 位置埋め込みの実装が少し複雑(例ごとにリセット)。(3) パッキングで実効バッチサイズが変わる(1ステップの例が増える)ため、それに応じてLRを調整する。
復習: 第10章(SFTのベストプラクティスと技法)。
Note
Q41: SFTの完了部分のみのマスキングを説明してください。使わないと何が起きますか?
解答: チャット形式のSFTデータは
[system] + [user message] + [assistant response]です。標準NLL損失はシステムプロンプトとユーザーメッセージを含む全トークンで損失を計算します。マスキングしない問題: モデルはユーザーメッセージの予測を学ぶために容量を浪費します(推論時に生成する必要はない)。さらに、学習データのユーザーメッセージが多様だと「誰の番か」が混乱します。
完了部分のみのマスキング: プロンプト(システム+ユーザー)の全トークンの損失重みを0にし、アシスタント応答のトークンだけで損失を計算します。
TRL:
DataCollatorForCompletionOnlyLM(response_template="<|assistant|>")効果: 指示追従ベンチマークで通常5〜15%改善する。収束も速い(勾配信号が有用なトークンに集中する)。計算コストは変わらない。
注意点: 応答テンプレートのトークンは損失に含める必要がある(応答を開始することを教えるため)。ただし、それより前はすべて除外する。
復習: 第10章(SFTのベストプラクティスと技法)。
Note
Q42: SFT品質はRLの上限にどう影響しますか?pass@k診断とは何ですか?
解答: 上限定理(非形式的): RLは、モデルがすでに無視できない確率で生成できる振る舞いしか強化できない。SFTモデルが正解を生成する確率が0%なら、RLがそれを見つけることはない。
理由: GRPO/PPOは現在の方策からサンプリングし、良いサンプルを強化する。分布内に良いサンプルがなければ、強化するものがない。RLの探索はベース方策のサポートに制限される。
pass@k診断: プロンプトごとに \(k\) 個の応答を生成し、どれかが正しいか確認する。
pass@1: モデルの典型的な性能(貪欲/低温)。
pass@8: \(G=8\) のGRPOが達成できる上限。
pass@64: 積極的なBest-of-Nの上限。
pass@256: RL改善のおおよその上限。
解釈:
pass@1=20%、pass@64=80%: 良い状態。RLの余地が4\(\times\)あり、大きな改善が期待できる。
pass@1=20%、pass@64=25%: 余地がほとんどない。RLはあまり役立たず、まずSFTを改善する必要がある。
pass@1=5%、pass@64=60%: モデルは解けるが、めったに解かない。RLに最適なケース(まれな成功を強化する)。
ルール: pass@64が \(<\)1.5\(\times\) pass@1なら、RLを始める前により良いSFTデータへ投資する。
復習: 第7章と第10章(GRPO、SFTのベストプラクティス)。
Note
Q43: チャットモデル向けの多目的報酬システムを設計してください。有用性と安全性のバランスをどう取りますか?
解答: アーキテクチャ: 目的ごとに別々の報酬モデルを用意します。
\(r_\text{helpful}\): 有用性の選好(品質、正確性、完全性)で学習する
\(r_\text{safe}\): 安全性の選好(拒否、無害性、ハルシネーションなし)で学習する
\(r_\text{format}\): ルールベース(指示への従属、適切な形式、適切な長さ)
組み合わせ戦略:
重み付き和 (最も単純): \(r = w_1 r_\text{helpful} + w_2 r_\text{safe} + w_3 r_\text{format}\)。問題は、有用性が安全性を上回り得ること。
制約付き (より安全): \(r_\text{helpful}\) を \(r_\text{safe} > \tau\) の条件で最大化する。\(r = r_\text{helpful} - \lambda \cdot \max(0, \tau - r_\text{safe})\) で実装し、大きな \(\lambda\) を使う。
GDPO正規化 (GRPOに最適): グループ内で各報酬を独立に正規化し、\(\hat{A} = w_1 \hat{A}_\text{helpful} + w_2 \hat{A}_\text{safe}\) として組み合わせる。スケール差によって1つの報酬が支配するのを防ぐ。
辞書式: 安全性をハード制約(必ず合格)とし、その後に有用性を最適化する。安全性のアラインメントを先に行い、次に有用性を学習する。
実践的な重み: \(w_\text{safe}=2.0, w_\text{helpful}=1.0, w_\text{format}=0.5\) から始める。安全性に2\(\times\)の重みを与えるのは、失敗モード(有害コンテンツ)が有用性の失敗(平凡な回答)よりはるかに悪いためです。
復習: 第9章と第12章(報酬モデルの学習、LLMエージェント学習)。
システムアーキテクチャ拡張の問題
Note
Q44: 投機的デコーディングはどのように機能しますか?RLHFでいつ役立ちますか?
解答: 問題: 大きなモデルは1回のフォワードパスで1トークンを生成する(70Bで \(\sim\)70ms)。遅い。
投機的デコーディング:
ドラフト: 小さなモデル(1〜7B)が \(k\) 個の候補トークンを高速に生成する(\(\sim\)5msで \(k\) 個すべて)。
検証: 大きなモデルが1回のフォワードパスで \(k\) 個すべてを並列採点する。\(p_\text{large}(t_i) \geq p_\text{draft}(t_i)\) のトークンは必ず受け入れ、それ以外も確率的に受け入れる。
結果: 検証ステップごとに平均3〜4トークンを受け入れる。高速化は2〜3\(\times\)。
重要な性質: 出力分布は大きなモデルだけからサンプリングした場合と同一です。品質低下はない。ドラフトモデルは速度だけに影響し、出力には影響しない。
特にRLHFでは: 生成は計算量の60%を占める。生成を2〜3\(\times\)高速化すると、エンドツーエンドで1.5〜2\(\times\)高速化する。vLLM+INT8と組み合わせると、生成はボトルネックではなくなり学習と同程度になる。
限界: (1) ドラフトモデルとトークナイザーを共有する必要がある。(2) 高温では効果が低い(ドラフトモデルの精度が下がる)。(3) ドラフトモデル用の追加GPUメモリが必要。(4) \(k=5\) を超えると収穫逓減する(受入率が下がる)。
復習: 第2章と第11章(システム基盤、システムアーキテクチャ)。
Note
Q45: ルーフラインモデルを説明してください。カーネルが計算律速かメモリ律速かをどう判定しますか?
解答: ルーフラインモデルは、演算強度(メモリトラフィック1バイトあたりのFLOPS)の関数として、達成可能な性能(FLOPS)をプロットします。
2つの領域:
メモリ律速 (クロスオーバーの左): 計算ユニットへデータを供給する速度で性能が制限される。実FLOPS=帯域幅 \(\times\) 演算強度。GPU利用率は \(<\)100%。
計算律速 (クロスオーバーの右): ピークFLOPSで性能が制限される。メモリは十分に高速で、GPU利用率は最大になる。
クロスオーバー点: ピークFLOPS/ピーク帯域幅。A100では312 TF/2 TB/s = 156 FLOP/byte。
LLMの演算:
自己回帰生成 (batch=1): 140GBの重みを読み、140G FLOPを行う=1 FLOP/byte。クロスオーバーより156\(\times\)低く、極端なメモリ律速。GPU利用率はわずか0.6%。
学習のフォワードパス (batch=128、seq=2048): 演算強度は \(\approx 200\)+ FLOP/byte。計算律速で、ピーク利用率に近い。
アテンション (長系列): \(O(n^2 d)\) FLOP/\(O(n^2 + nd)\) バイト。\(n\) が長ければ計算律速、\(n\) が短ければメモリ律速。Flash AttentionはどちらでもSRAMに保持する。
実用: カーネルがメモリ律速ならメモリトラフィックを減らす(量子化、キャッシュ、タイル分割)。計算律速ならFLOPを減らす(プルーニング、蒸留、低精度化)。
復習: 第2章(LLMのシステム基盤)。
Note
Q46: 連続バッチ処理はどう機能し、なぜRLHF生成に不可欠ですか?
解答: 静的バッチ処理: \(B\) 系列を開始し、すべてが終わるまで待つ。1系列が500トークン、別の系列が50トークンを生成すると、50トークンの系列のGPUスロットは450トークン分アイドルになる。
連続バッチ処理 (反復レベルのスケジューリング): 生成ステップごとに完了した系列を確認し、空いたスロットへすぐ新しい系列を入れる。GPUスロットがアイドルにならない。
RLHFに不可欠な理由:
RLHFは多様な出力を生成する(高温)。長さのばらつきが大きく、50トークンの応答もあれば2000以上の応答もある。
連続バッチ処理がない場合、最も遅い系列を待つため平均利用率は \(\sim\)40〜50%。
連続バッチ処理があれば利用率は \(>\)90%、スループットは2〜3\(\times\)高い。
RLHFには大きなバッチ(1ステップあたり128以上の応答)が必要。静的バッチ処理で128応答を生成するとmax_tokens \(\times\)128回の逐次ステップが必要だが、連続バッチ処理はこれを償却する。
実装: vLLMのスケジューラーはデコードステップごとに確認する。プリエンプションでは、高優先度リクエストが来てメモリが満杯なら、低優先度系列のKVキャッシュをCPUへスワップアウトし、後で再開できる。
復習: 第2章と第11章(システム基盤、システムアーキテクチャ)。
Transformerアーキテクチャの問題
Note
Q: なぜ現代のLLMでは、学習済み絶対位置埋め込みよりRoPEが主流なのですか?
解答: RoPEは回転行列により、Q/Kの内積へ相対位置を直接エンコードします。主な利点は次のとおりです。
アテンションスコアは絶対位置ではなく相対距離 \(i-j\) だけに依存し、未知の系列長へよりよく一般化する。
周波数スケーリング(NTK-aware、YaRN)により、再学習せず学習長を超えて拡張できる。
追加パラメータがない(回転は位置インデックスから決定論的に定まる)。
学習済み絶対位置埋め込みは学習長に固定され外挿できない。4Kコンテキストで学習したモデルは8Kで失敗する。
復習: 第1章(LLMのアーキテクチャと最適化手法)。
Note
Q: SwiGLUを説明してください。なぜ現代のTransformerでReLUに取って代わったのですか?
解答: SwiGLUは \(\text{FFN}(x) = W_2 (\text{Swish}(W_1 x) \odot W_3 x)\) で、\(\text{Swish}(x) = x \cdot \sigma(x)\) です。
優れている理由:
ゲーティング機構(\(\odot W_3 x\))により、ネットワークは次元を選択的に抑制・増幅でき、点ごとのReLUより表現力が高い。
Swishは滑らかで、ReLUの勾配ゼロ領域のような死んだニューロンがない。
経験的には、同じFLOP数で言語モデリングベンチマークが1〜2%改善する。
トレードオフ: 重み行列が2つでなく3つ必要(隠れ次元を \(4d\) から \(8d/3\) に減らして解決する)。
復習: 第1章(LLMのアーキテクチャと最適化手法)。
Note
Q: Grouped Query Attention(GQA)とは何で、なぜLlama-3は使うのですか?
解答: 標準MHAはクエリヘッド \(H\)、キーヘッド \(H\)、バリューヘッド \(H\)。GQAはクエリヘッド \(H\) に対し、キ/バリューヘッドは \(G < H\) だけで、クエリグループ間で共有します。
Llama-3 70Bはクエリヘッド64、KVヘッド8(各KVヘッドを8クエリヘッドで共有)です。
利点:
KVキャッシュサイズが \(H/G = 8\times\)削減される。長系列ではKVキャッシュが支配的なメモリコストなので、推論に重要です。
ヘッド間のKVパターンは強く相関するため、品質低下は最小(ベンチマークで \(<\)0.5%)。
推論スループットはKVキャッシュ削減に比例して増える(メモリに収まる系列が増え、バッチサイズが上がる)。
復習: 第1章(LLMのアーキテクチャと最適化手法)。
Note
Q: なぜLLMではエンコーダー・デコーダーよりデコーダーのみのアーキテクチャが勝ったのですか?
解答:
統一された目的: 事前学習=ファインチューニング=推論のすべてが次トークン予測を使い、アーキテクチャの不一致がない。
パラメータ効率: すべてのパラメータが生成に寄与する。エンコーダー・デコーダーでは純粋な生成タスクでエンコーダーパラメータが「無駄」になる。
スケールが単純: モデル1つ、損失関数1つ、調整するハイパーパラメータの組1つ。
KVキャッシュ効率: デコーダーのみはKVキャッシュが1つ、エンコーダー・デコーダーは2つ(エンコーダー+デコーダーのクロスアテンション)。
創発的なfew-shot: デコーダーのみはコンテキスト内学習を自然にサポートする(プロンプトの前に例を付ける)。
エンコーダー・デコーダーは入力長が固定されたseq2seqタスク(翻訳)では依然優れるが、LLMのユースケースに占める割合は減っている。
復習: 第1章(LLMのアーキテクチャと最適化手法)。
Flash Attentionの問題
Note
Q: Flash Attentionは標準アテンションと同じ結果を計算するのに2--4$\times$高速です。同じFLOP数なのになぜ可能ですか?
解答: Flash Attentionが速いのはFLOPではなくHBMメモリトラフィックを減らすからです。標準アテンションは \(n \times n\) 行列をHBMに展開し、softmaxのため読み戻し、\(PV\) 乗算のため再び読むため、\(O(n^2)\) データをHBMで4往復します。
Flash Attentionは計算をタイル分割し、\(n \times n\) 行列をSRAM(高速、19 TB/s)内だけで計算・消費し、HBM(2 TB/s)へ書き込みません。「オンラインsoftmax」のトリックが累積統計を保持してこれを可能にします。
結果: HBMトラフィックは \(O(n^2 d)\) から \(O(n^2 d / M)\) へ減る(\(M\) はSRAMサイズ)。同じFLOP数でメモリトラフィックが10〜50\(\times\)減り \(\to\) 実時間で2〜4\(\times\)高速化する。
復習: 第1章と第2章(LLMアーキテクチャ、システム基盤)。
Note
Q: なぜFlash AttentionはFFN層を助けないのですか?
解答: FFN層はメモリ律速ではなく計算律速です。演算強度(大バッチGEMMで \(I \approx 300\) FLOP/byte)は、すでにルーフラインの稜線点(A100で156 FLOP/byte)を上回っています。
Flash Attentionがアテンションを助けるのは、アテンションが強いメモリ律速(\(I \approx 1\)〜\(60\) FLOP/byte)だからです。データをSRAMに保持してメモリボトルネックを除きます。
FFNではボトルネックがすでにTensor Core(メモリ帯域幅ではない)なので、メモリトラフィックを減らしても効果がない。代わりに量子化(重みサイズを減らし \(\to\) 演算強度を高める)と大きなバッチサイズが有効です。
復習: 第1章と第2章(LLMアーキテクチャ、システム基盤)。
Note
Q: オンラインsoftmaxのトリックと、Flash Attentionに不可欠な理由を説明してください。
解答: 標準softmaxは出力計算前にグローバル最大値 \(m = \max_j x_j\) を必要とする。先に全 \(n\) アテンションスコアを見る必要があり、完全な \(n \times n\) 行列の展開を強制する。
オンラインsoftmaxのトリックはブロックを逐次処理し、累積状態 \((m, \ell, O)\) を保持する。
新しいブロックを処理 \(\to\) 累積最大値を更新: \(m_{\text{new}} = \max(m_{\text{old}}, \max(s_{\text{new}}))\)
古い和を再スケール: \(\ell_{\text{new}} = e^{m_{\text{old}} - m_{\text{new}}} \cdot \ell_{\text{old}} + \text{new terms}\)
出力を再スケール: \(O_{\text{new}} = \text{rescaled}(O_{\text{old}}) + \text{new contribution}\)
これは数学的に厳密で、近似ではない。各ブロックをSRAMに収めてブロック単位で処理でき、完全な \(n \times n\) 行列をメモリに置く必要がなくなります。
復習: 第1章と第2章(LLMアーキテクチャ、システム基盤)。
LoRAとPEFTの問題
Note
Q: LoRAはなぜ機能しますか?低ランク更新を正当化する理論的洞察は何ですか?
解答: Aghajanyanら(Aghajanyan et al. 2021)は、ファインチューニングが非常に低い内在次元で動作することを示しました。つまり、タスクに有効なパラメータ空間はモデルの総パラメータ数よりはるかに小さい。175Bモデルでも、タスクによっては内在次元が \(<\)10,000になり得ます。
LoRAはこれを直接利用します。更新をランク \(r\)(\(W' = W + BA\)、\(B \in \mathbb{R}^{d \times r}\))に制約し、重み行列ごとの \(r\) 次元部分空間に学習を限定します。真のタスク部分空間が低次元なので、ほとんど失わずに学習可能パラメータを100〜1000\(\times\)削減できます。
直感: ファインチューニングはモデルが「知っていること」を変えず(フルランクの \(W\) は凍結)、新しいタスクのため既存知識をどう組み合わせるかだけを調整する。これは低ランク摂動です。
復習: 第1章(LLMのアーキテクチャと最適化手法)。
Note
Q: 70BモデルでQLoRA、完全LoRA、完全ファインチューニングを比較してください。それぞれをいつ選びますか?
解答:
Method Memory GPUs Quality Use When Full fine-tune 560+ GB 8+ A100 Best Unlimited budget; pre-training continuation LoRA (\(r=16\)) 145 GB 2 A100 95–98% Good budget; general fine-tuning QLoRA (\(r=16\)) 44 GB 1\(\times\)48GB 93–96% Single-GPU; prototyping; constrained resources 判断木: (1) 深い知識変更が必要 \(\to\) 完全FT。(2) 新しいスタイル/形式への適応 \(\to\) LoRA。(3) メモリ制約または高速反復 \(\to\) QLoRA。(4) ランクが重要なら \(r=16\) から始め、学習損失が完全FTの水準より上で停滞したら増やす。
復習: 第1章と第10章(LLMアーキテクチャ、SFTのベストプラクティス)。
Note
Q: DoRAとは何で、なぜ標準LoRAを上回るのですか?
解答: DoRA(Weight-Decomposed Low-Rank Adaptation、重み分解低ランク適応)は \(W\) を大きさ \(\\vert W\\vert\) と方向 \(W/\\vert W\\vert\) に分解し、LoRAを方向成分だけに適用します。 \[ W' = m \odot \frac{W + BA}{|W + BA|} \] ここで \(m\)(大きさ)も学習可能ですが、出力ニューロンごとの単純なスカラーです。
有効な理由: 完全FTは大きさと方向を独立に更新します。標準LoRAは両者を結合する(低ランク更新が制約付きで両方を同時に変える)。DoRAは分離し、LoRAに完全FTと同じ「自由度」の構造を与える。結果として推論時の追加計算なし(アダプターをマージ)で推論タスクが1〜3%改善します。
復習: 第1章(LLMのアーキテクチャと最適化手法)。
モデル圧縮の問題
Note
Q: AWQを説明してください。なぜ重みの1%を保護すると品質の99%を維持できるのですか?
解答: AWQ(Activation-Aware Weight Quantization、アクティベーションを考慮した重み量子化)は、重みの重要度が一様でないことに注目します。大きなアクティベーションを乗算する重みは、出力に不釣り合いに大きく寄与します。
重要な洞察は、\(\\vert W \cdot X\\vert\) が \(W\) と \(X\) の両方に依存することです。大きなアクティベーションを掛けた小さな重みの方が、ほぼゼロのアクティベーションを掛けた大きな重みより重要になります。
AWQは「顕著な」チャネル(キャリブレーションデータ全体でアクティベーションの大きさが一貫して大きいもの)の上位1%を特定し、スケーリングで保護します。量子化前に顕著なチャネルを \(s > 1\) 倍し、アクティベーション側で \(s\) で割る。これにより重要チャネルの相対量子化誤差を減らします。
結果: 70Bモデルで品質低下 \(<\)1%の4ビット量子化を実現する。顕著でない99%の重みは強い量子化に耐えられるためです。
復習: 第1章と第2章(LLMアーキテクチャ、システム基盤)。
Note
Q: FP8、4ビット量子化、BF16はいつ使うべきですか?
解答:
BF16: 精度が重要な学習(RLHFの方策モデル)。勾配で更新するモデルのデフォルト。
FP8(E4M3): Transformer Engineを使うH100学習(スループット2\(\times\)、品質低下 \(<\)0.5%)。最大スループットが必要なH100推論にも使う。
INT8/FP8推論: RLHFの凍結モデル(参照モデル、報酬モデル)。学習しないため低精度でも安全。
4ビット(AWQ/GPTQ): 大規模な推論提供。デプロイ時のメモリ/品質のトレードオフが最良。QLoRAのベースモデルにも使う。
2ビット: 実験的。メモリが極端に制約されるエッジデプロイ向け。品質低下は5〜10%。
ルール: 推論では可能な限り強く量子化し、学習ではBF16(H100ならFP8)を維持する。
復習: 第2章(LLMのシステム基盤)。
Note
Q: NVIDIAの2:4構造化スパース性を説明してください。高速化と制約は何ですか?
解答: 2:4スパース性とは、4つ連続する要素の各グループで、ちょうど2つをゼロにすることです。重みレベルで強制します。
ハードウェア対応: A100/H100のTensor Coreにはゼロ要素を飛ばす2:4スパースGEMM命令があり、ソフトウェアオーバーヘッドなしで 2\(\times\)のスループット を達成します。
制約: この特定のパターンで正確に50%のスパース性にする必要があります。30%や70%、任意のパターンは使えない。プルーニングは4要素グループ構造に従う必要があります。
達成方法: 学習後(またはファインチューニング中)、4重みごとに大きさが小さい2つをゼロにする。その後数百ステップFTして品質を回復する。大モデル(70B以上)の品質低下は通常 \(<\)1%。
復習: 第2章(LLMのシステム基盤)。
Mixture of Expertsの問題
Note
Q: Mixtral 8x7Bは総パラメータ47Bですが、トークンごとにアクティブなのは13Bだけです。仕組みと効率の理由を説明してください。
解答: Mixtralは各FFN層を8つの並列エキスパートFFN(FFN部分が各 \(\sim\)7Bパラメータ)に置き換えます。ルーターネットワークがトークンごとにTop-2エキスパートを選びます。
総計47Bの理由: アテンション層は共有され(複製されず)\(\sim\)5B。FFNエキスパートは8 \(\times\) \(\sim\)5.25B = 42B。合計 \(\sim\)47B。
アクティブが13Bの理由: 1トークンでは2エキスパートだけが発火する。アクティブパラメータ=アテンション(\(\sim\)5B)+2 FFNエキスパート(\(\sim\)2 \(\times\)5.25B)\(\approx\)13B。
効率的な理由: 計算コストはアクティブパラメータ(13B)に比例し、13B密モデルと同等。一方、容量(保存知識)は総パラメータ(47B)に比例し、はるかに大きなモデルに匹敵する。その結果、Mixtralは13Bの計算コストでLlama-2 70Bの品質に匹敵します。
メモリコスト: すべてのエキスパートをロードするため47B全パラメータをメモリに置く必要があり、メモリは47Bモデル相当だが、計算は13Bモデル相当です。
復習: 第1章(LLMのアーキテクチャと最適化手法)。
Note
Q: MoEの負荷分散問題とは何で、どう解決しますか?
解答: 制約がないと、ルーターは大半のトークンを1〜2個の「人気」エキスパートへ送りがちです(富める者がますます富む力学)。その結果:
容量浪費: 8エキスパートのうち6つが未使用になり、モデルは実質2エキスパート規模に縮む。
計算の不均衡: 各エキスパートを別GPUに置くと、人気エキスパートがボトルネックになり、他はアイドルになる。
解決策: 補助負荷分散損失 \(\mathcal{L}_{\text{bal}} = \alpha \cdot N \sum_{i=1}^N f_i \cdot p_i\) を使う。ここで \(f_i\) はエキスパート \(i\) に送られたトークンの割合、\(p_i\) はエキスパート \(i\) のルーター確率平均。不均一な分布を罰する。
代替策: エキスパート容量係数。バッチごとのエキスパート最大トークン数に上限を設け、あふれたトークンを破棄または再ルーティングする。
典型的な \(\alpha\): 0.01〜0.1(主損失を損なわない程度に小さく、崩壊を防ぐには十分に大きい)。
復習: 第1章(LLMのアーキテクチャと最適化手法)。
学習における多様性の問題
Note
Q: GRPOグループ内のN個の応答がすべて同一だと何が起きますか?
解答: \(N\) 個の応答がすべて同一なら、全報酬 \(r_i\) が等しく、\(\sigma_G = 0\) となり、アドバンテージ \(\hat{A}_i = (r_i - \mu_G)/\sigma_G\) は未定義(ゼロ除算)です。実装では通常すべてを \(\hat{A}_i = 0\) とし、 学習信号がゼロ になるため、そのステップは無駄になります。
予防:
温度: 生成時に \(\tau = 0.7\)〜\(1.0\)を使う(貪欲にしない)。
大きな \(N\): \(N=8\)〜\(16\)にすると多様な応答の確率が上がる。
重複拒否: DAPO方式で、重複応答を拒否して再サンプリングする。
頻度ペナルティ: 生成中に反復n-gramを罰する。
監視: グループごとの一意応答率を追跡する。\(<\)50%なら温度を上げる。
復習: 第7章(GRPO)。
Note
Q: RLHFにおける多様性と品質のトレードオフを説明してください。モード崩壊をどう検出しますか?
解答: トレードオフ: 多様性が高い(エントロピー/温度が高い)=多様だがランダム/低品質になり得る応答。多様性が低い=一貫しているが反復的で報酬ハッキングされた応答。
モード崩壊の検出 (すべて学習中に監視する):
応答エントロピー: トークンごとのエントロピー \(H = -\sum p_i \log p_i\) を計算する。急低下なら \(\to\) 崩壊。
一意n-gram率: 同じプロンプトへの応答全体で一意な4-gramの割合。健全な値は \(>\)0.6。
報酬分布の幅: \(\sigma(\text{rewards})\) がほぼゼロへ縮むなら \(\to\) すべて同じ品質 \(\to\) 同一である可能性が高い。
KLダイバージェンス: \(D_\text{KL}[\pi_\theta \\vert \pi_\text{ref}]\) が急増するなら、方策が参照から遠ざかり \(\to\) 狭いモードへ向かっていることが多い。
長さのヒストグラム: すべての応答が同じ長さに収束するなら \(\to\) テンプレート的な振る舞い。
修正: KL係数 \(\beta\)、エントロピーボーナス、サンプリング温度を上げるか、以前のチェックポイントへロールバックする。
復習: 第7章と第9章(GRPO、報酬モデルの学習)。
投機的デコーディングの問題
Note
Q: 投機的デコーディングは「品質低下なし」と主張します。異なる方法でトークンを生成して同じ出力分布になるのはなぜですか?
解答: 受入/拒否方式が分布の同値性を保証します。
各ドラフトトークン \(\hat{x}\) について、ドラフト確率 \(q(\hat{x})\) と対象確率 \(p(\hat{x})\) を持つ:
\(\min(1, p(\hat{x})/q(\hat{x}))\) の確率で受け入れる
拒否時は残差分布 \(\propto \max(0, p(x) - q(x))\) からサンプリングする
これは \(p\)(対象モデル)から直接サンプリングするのと数学的に同値です。概略は、トークン \(x\) の出力確率が \(q(x) \cdot \min(1, p(x)/q(x)) + P(\text{reject}) \cdot \frac{\max(0, p(x)-q(x))}{\sum_y \max(0, p(y)-q(y))} = p(x)\) となることです。
高速化は償却から生まれます。ドラフトが良い(受入率が高い)と、1回の対象モデルのフォワードパスで複数トークンを確定できます。保証はドラフト品質に関係なく成立し、悪いドラフトは品質を下げず高速化を下げる(拒否が増える)だけです。
復習: 第2章(LLMのシステム基盤)。
Note
Q: 投機的デコーディングにおけるMedusaとEagleを比較してください。それぞれをいつ選びますか?
解答:
Medusa: 対象モデルに \(k\) 個の並列予測ヘッドを追加し、各ヘッドが位置 \(t+i\) のトークンを独立に予測する。利点: 別モデル不要で、メモリオーバーヘッド \(<\)1%。欠点: ヘッドが独立予測するため、\(t+2\) は \(t+1\) の予測を条件にできない。受入率60〜80%。
Eagle: 対象モデルの隠れ状態上に軽量自己回帰デコーダーを置く。ドラフトトークンは自己回帰的に生成する(各トークンが前のトークンを条件とする)。利点: トークン間依存を捉え \(\to\) 受入率85〜95%。欠点: 追加メモリ(小型デコーダー)と逐次ドラフト生成が必要。
Medusaを選ぶ場面: メモリが極端に厳しく、統合が単純で、中程度の高速化(2〜2.5\(\times\))で十分な場合。
Eagleを選ぶ場面: 最大高速化(3〜4\(\times\))が必要で、小型追加モデルを置け、レイテンシ重視の単一ストリーム生成を行う場合。
N-gramを選ぶ場面: 出力が反復的(コード、構造化データ)で、コストゼロ・学習不要が必要な場合。
復習: 第2章(LLMのシステム基盤)。
Note
Q: なぜ投機的デコーディングは大きなバッチサイズで役立たないのですか?
解答: 大きなバッチサイズ(\(\geq\)64)では、自己回帰生成はすでに計算効率が高い。重み読み込みコストが多くの系列にわたって償却され、演算強度がルーフラインの稜線点に近づくためです。
投機的デコーディングはオーバーヘッドを加えます。
ドラフト生成コスト(小型モデルでも大バッチでは無料ではない)
検証フォワードパスが系列ごとに \(k\) 個の追加トークンを処理する(バッチ \(\times\) \(k\) トークン)
ドラフトモデルまたはMedusaヘッドのメモリ
拒否トークンが計算を浪費する
batch=1(レイテンシ律速、メモリ律速)では、投機により1ステップ1トークンが3〜4トークンになり、大きな効果があります。
batch=128(すでに計算効率が高い)では、GPUがほぼ飽和しているため投機の追加トークンはスループットにほぼ寄与しない。オーバーヘッドでスループットが下がることさえあります。
ルール: 投機的デコーディングはレイテンシ(小バッチ)向け、バッチ処理はスループット(大バッチ)向け。組み合わせない。
復習: 第2章(LLMのシステム基盤)。
エージェント型RLの問題
Note
Q: なぜ標準RLHF(単一ターンPPO/DPO)はマルチステップエージェントで失敗するのですか?
解答: 標準RLHFは単一ターン品質を最適化します。プロンプトに対して1つの良い応答を生成する方式です。マルチステップエージェントは根本的に異なる課題に直面します。
信用割当: 50ステップの軌跡で、どのステップが失敗を引き起こしたか。単一ターン報酬は応答全体に一様に寄与を割り当てるが、マルチステップにはステップごとの寄与評価が必要。
疎な報酬: 成功/失敗が軌跡の最後にしかない。PPOのGAEは中間報酬を仮定するため、ない場合はアドバンテージ推定がノイジーになる。
アクション空間: アクションは単なるトークン系列ではなく構造化ツール呼び出し(JSON)。モデルは構文、意味、戦略を同時に学ぶ必要がある。
非定常性: アクションごとに環境が変わる(ツール出力が状態を変更する)。入力が固定された単一ターンと違い、ステップごとに「プロンプト」が異なる。
探索: エージェントは文章の言い換えではなく、新しいツール利用戦略を発見しなければならない。
解決策: 軌跡レベルGRPO(完全な軌跡を順位付け)、プロセス報酬モデル(ステップごとのフィードバック)、または成功軌跡に対するフィルタ済みSFTを使う。
復習: 第12章(LLMエージェント学習)。
Note
Q: GRPOをエージェント学習にどう適応しますか?単一ターンGRPOとの主な違いは何ですか?
解答: 単一ターンGRPOは、プロンプトへの \(N\) 個の応答を生成し、報酬で順位付けしてアドバンテージを計算します。
エージェント型GRPOとの違い:
生成単位: 単一応答ではなく完全な軌跡(10〜100ステップ)。各軌跡がグループ内の1「サンプル」になる。
報酬: 終端(タスク成功/失敗)または軌跡レベル(ステップ報酬の和)。トークン単位ではない。
マスキング: 方策損失はエージェントの出力(推論+ツール呼び出し)だけで計算する。ツール出力(環境応答)は勾配計算からマスクする。
グループサイズ: 軌跡は高コスト(軌跡ごとに多数のフォワードパス)なので、通常は小さい(\(N=4\)〜8)。
KLペナルティ: 各判断点でSFT方策からドリフトしないようステップごとに適用する。
長さの正規化: 入念な推論を罰しないよう、トークン数ではなくエージェントのアクション数で正規化する。
復習: 第7章と第12章(GRPO、LLMエージェント学習)。
Note
Q: エージェントにおけるSTaR、Reflexion、ReActを比較してください。
解答:
STaR(Self-Taught Reasoner): 推論チェーンを生成 \(\to\) 正しさでフィルタ \(\to\) 正しいものをFTする。使う場面: 検証可能なタスク(数学、コード)があり、RLなしでベースモデルから推論をブートストラップしたい場合。
Reflexion: 失敗後に言語フィードバック(「何が悪かったか?」)を生成 \(\to\) コンテキスト内の内省とともに再試行する。重み更新はない。使う場面: 推論時改善、学習計算が限られる場合、自己診断可能なタスク。
ReAct: 推論(考える)と行動(ツール利用)を構造化ループで交互に行う。使う場面: マルチステップのツール利用、透明性が必要(推論トレースを解釈できる)、エージェントが思考と行動を選ぶ必要がある場合。
主な違い:
復習: 第12章と第18章(LLMエージェント学習、エージェント設計パターン)。
Note
Q: なぜ研究エージェントではPPOよりGRPOが好まれるのですか?
解答: 20〜100ステップの軌跡を持つ研究エージェントでは:
PPOには価値モデルが必要: \(V(s_t)\) が現在状態からの期待総報酬を予測する必要がある。研究では状態が論文、コード、結果を含む128Kトークンのコンテキストであり、「3本の論文を読み、コードを一部書いた」価値の正確な予測は極めて難しい。
GRPOは価値推定を完全に避ける: 研究質問ごとに \(N\) 個の完全な軌跡を生成し、グループ内順位をアドバンテージとして使う。中間価値を予測せず、結果を比較するだけです。
その他の理由:
研究品質は二値に近い(良いレポートか悪いレポートか)ため、順位付けが自然。
軌跡は長く高コスト。GRPOの \(N=4\) は管理可能だが、PPOは安定した価値推定のため多数のロールアウトが必要。
終端報酬は疎であり、疎な報酬のGAEはどうせステップごとのアドバンテージがノイジーになる。
復習: 第7章と第12章(GRPO、LLMエージェント学習)。
Note
Q: コーディングエージェントの報酬関数を設計してください。どのような報酬ハッキングのリスクがありますか?
解答: 報酬設計: \[ R = 0.5 \cdot R_{\text{tests}} + 0.2 \cdot R_{\text{quality}} + 0.2 \cdot R_{\text{efficiency}} + 0.1 \cdot R_{\text{safety}} \]
\(R_{\text{tests}}\): 合格した単体テストの割合(0〜1)。正解を検証可能。
\(R_{\text{quality}}\): コードスタイル、ドキュメント、保守性をLLMジャッジで評価。
\(R_{\text{efficiency}}\): \(\max(0, 1 - \text{steps}/30)\)。早く終えるほどボーナス。
\(R_{\text{safety}}\): 危険な操作をしない(rm -rf、サンドボックス外のネットワークアクセス)。
報酬ハッキングのリスク:
ハードコード出力: 計算せず期待されるテスト出力を直接表示するよう学習する。修正: テスト入力をランダム化し、取り置きケースでテストする。
テスト削除: 失敗したテストを変更/削除する。修正: サンドボックス内のテストを読み取り専用にする。
自明な解法: テストは通るが一般化しない最小コードを書く。修正: 大規模で多様なテストスイートとプロパティベーステスト。
効率性の悪用: 効率ボーナスを最大化するため推論ステップを省く。修正: 効率ボーナスの前に最低品質しきい値を要求する。
復習: 第9、12、19章(報酬モデルの学習、LLMエージェント学習、エージェント型環境)。
リストワイズ報酬と高度なRMの問題
Note
Q: Plackett-Luceモデルを説明してください。Bradley-Terryをどう一般化しますか?
解答: Bradley-Terryはペアワイズ選好 \(P(y_1 \succ y_2) = \sigma(r(y_1) - r(y_2))\) をモデル化します。
Plackett-Luceは \(K\) 個のアイテムの完全な順位を逐次選択としてモデル化します。 \[ P(\pi) = \prod_{i=1}^K \frac{e^{r(y_{\pi(i)})}}{\sum_{j=i}^K e^{r(y_{\pi(j)})}} \] 解釈: 残りの中から最良のアイテムを順に選ぶ。1位は全 \(K\) のsoftmax、2位は残り \(K-1\) のsoftmax、以下同様です。
一般化: \(K=2\) ではPLはBTに正確に還元されます。\(P(y_1 \succ y_2) = \frac{e^{r(y_1)}}{e^{r(y_1)} + e^{r(y_2)}} = \sigma(r(y_1) - r(y_2))\)。
利点: \(K=8\) アイテムの順位は \(\binom{8}{2} = 28\) 個の暗黙のペア比較と相対マージン情報を与え、単一ペアよりはるかに豊かな学習信号になる。
復習: 第9章(報酬モデルの学習)。
Note
Q: プロセス報酬モデル(PRM)とは何で、結果報酬モデル(ORM)より優れるのはいつですか?
解答: ORM: 最終出力だけを採点する。\(r(x, y_{\text{final}})\) は完全な応答に対する1つのスカラー。
PRM: 推論の各ステップを採点する。\(r(x, y_{\text{step } t})\) は中間ステップごとのスカラー。
PRMが優れる場合:
長い推論チェーン: 10ステップ以上の数学問題。ORMはどのステップが間違ったか分からないが、PRMはステップごとのクレジットを与える。
探索/検証: PRMにより木探索が可能(推論ステップのビーム探索、ステップ報酬の低い枝の剪定)。
学習信号密度: PRMは軌跡ごとに \(T\) 個の報酬(ステップごと)を与え、ORMの単一報酬 \(\to\) アドバンテージ推定の分散が低い。
ORMが優れる場合: タスクが短い(単一ターン)、ステップ境界が不明確、ステップごとのラベル付けコストが高すぎる場合。
PRMのアノテーション: 「Math-Shepherd」方式で自動化できる。各ステップから解答を複数回完成させる。ステップ \(t\) からは成功するが \(t+1\) からは失敗するなら、\(t+1\) は誤りの可能性が高い。
復習: 第9章と第13章(報酬モデルの学習、大規模推論モデルのためのRL)。
大規模推論モデルのためのRLの問題
Note
Q: DeepSeek-R1は長い推論チェーンで学習するのに、なぜプロセス報酬モデルを使わないのですか?
解答: DeepSeek-R1が 結果ベースの報酬 (正確性+形式)だけを使う理由は次のとおりです。
検証可能なタスク: 数学とコードには決定論的な正解がある。二値の正確性報酬は長いチェーンでも十分な信号を与える。
PRMの失敗モード: ステップレベル報酬モデルは独自の報酬ハッキングを生む。モデルは実際には正しくなくても、PRMに「正しく見える」ステップを生成するよう学習できる。
GRPOのグループ正規化: プロンプトごとに \(G\) 個の完了をサンプリングし、グループ内でアドバンテージを正規化することで、ステップ報酬なしでもどの推論戦略が有効かという相対信号を自然に与える。
創発的な自己修正: 結果のみの報酬で、モデルはチェーン内で自己修正すること(「アハ体験」)を学ぶ。ステップ報酬が推論過程を細かく管理すると、これは創発しない。
重要な洞察: PRMを不要にするのはタスク領域の検証可能性です。主観的なタスク(創作)では結果のみの報酬では足りない可能性があります。
復習: 第13章(大規模推論モデルのためのRL)。
Note
Q: テスト時計算量スケーリング則と、モデルデプロイへの意味を説明してください。
解答: テスト時計算量スケーリング則は次のように表されます。 \[ \text{Accuracy}(C_{\text{train}}, C_{\text{test}}) \approx f(\alpha \log C_{\text{train}} + \beta \log C_{\text{test}}) \]
意味:
計算量の同等性: 推論トークンを64\(\times\)多く使う7Bモデルが、推論タスクでトークン1\(\times\)の70Bモデルに匹敵できる。
適応的割り当て: 易しい質問には短いチェーン(安価)、難しい質問には長いチェーン(高価)を割り当てる。常に大モデルを使うより平均コストが低い。
デプロイの柔軟性: 大モデル1つではなく小さな推論モデルをデプロイし、難易度に応じてクエリごとに推論計算量を増減する。
収穫逓減: 対数関係なので、テスト時計算量を2倍にしても精度の改善は逓減する。学習と推論の計算量には最適な配分がある。
「考えすぎ」の失敗: 非常に長いチェーンは、誤差の累積とアテンションの希薄化により精度を下げることがある。最適なチェーン長は問題の難易度に依存する。
復習: 第13章(大規模推論モデルのためのRL)。
Note
Q: MCTS(モンテカルロ木探索)はLLM推論にどう適用されますか?
解答: 推論のMCTSでは、部分解をそれぞれ木のノードとして扱います。
反復ごとの4段階:
選択: UCBを使って根から移動する: \(\text{UCB}(s) = Q(s) + c\sqrt{\frac{\ln N(\text{parent})}{N(s)}}\)
展開: LLMから新しい推論ステップ(子ノード)を生成する
シミュレーション: 新しいノードから解答を完成させる(ロールアウト)
逆伝播: 最終的な正しさに基づいて経路上のQ値を更新する
ゲームMCTSとの主な違い:
分岐係数: 推論の分岐係数は巨大(次の文は何でも可能)。実装ではLLMのtop-k出力を使って枝を制限する。
価値関数: 学習済みPRMが部分解の品質を推定し、ランダムロールアウトを置き換える。
ステップ粒度: 各「ステップ」は1文、1式、1つの論理推論など。粒度の選択が重要。
利用例: AlphaProof(数学オリンピック)。OpenAI o1/o3の隠れた推論にも使われると仮定されています。
復習: 第13章(大規模推論モデルのためのRL)。
Note
Q: 小さな推論モデルの作成における蒸留と直接RLを比較してください。
解答:
蒸留 (DeepSeek-R1-Distill方式):
大モデル(R1-671B)から推論チェーンを生成する
そのチェーンで小モデルをSFTする
結果: 小モデルが大モデルの推論形式を模倣する
利点: 安価(SFTだけ)。欠点: 真の推論ではなく表面的なパターンを学ぶ可能性。
小モデルへの直接RL:
検証可能な報酬に対してGRPO/PPOで小モデルを学習する
モデルが独自の推論戦略を発見する
利点: 本物の能力。欠点: 計算量がはるかに多く、非常に小さいモデルでは収束しない可能性。
経験的知見: R1-Distill-7B(蒸留)はほとんどのベンチマークでdirect-RL-7Bを上回る。大モデルの推論チェーンが強い教師信号なので、SFTだけでも競争力がある。ただし蒸留モデルは本当に新しい問題種別への一般化が弱い。
ベストプラクティス: まず蒸留して安価なベースラインを作り、その後必要なら蒸留モデルにRLを行ってさらに改善する(Qwenが使う「蒸留+RL」)。
復習: 第13章(大規模推論モデルのためのRL)。
LLM評価の問題
Note
Q: ELOレーティング更新則を導出し、Chatbot Arenaが使う理由を説明してください。
解答: ELOの導出:
AがBに対して得る期待スコアは \(E_A = \frac{1}{1 + 10^{(R_B - R_A)/400}}\)(ロジスティックモデル)。
実スコア \(S_A \in \{0, 0.5, 1\}\) の試合後は、\(R_A' = R_A + K(S_A - E_A)\)。
\(K\)係数が更新量を制御する(\(K\) が高いほど最近の結果に敏感)。
Chatbot ArenaがELOを使う理由:
推移性: AがBに勝ち、BがCに勝てば、ELOはAがCに勝つと予測する。ペア比較から全順序を得られる。
オンライン更新: すべてのペアを再評価せず新モデルを追加できる。新しい比較ごとにレーティングを逐次更新する。
信頼度: \(N\) 回の比較後、レーティングの不確実性は \(O(1/\sqrt{N})\) で縮む。標準誤差は \(\text{SE} \approx \frac{400}{\sqrt{N}}\)。
人間の選好を捉える: 実ユーザーは基準を言語化せず正直な選好を示し、集約結果が真のモデル品質を明らかにする。
Chatbot Arenaの詳細: ブートストラップ信頼区間付きBradley-Terry MLE(収束時にELOと同等)を使う。スタイル制御ELOで長さ/形式バイアスを除く。
復習: 第14章(LLM評価)。
Note
Q: コード生成のpass@k指標とは何で、なぜ不偏推定量が重要ですか?
解答: pass@k=生成した \(k\) サンプルの少なくとも1つが全テストケースに合格する確率。
素朴な(偏った)推定量: \(k\) サンプルを生成し、合格があるか確認する。分散が高く、問題ごとに多くの試行が必要で高コスト。
不偏推定量 (Chenら、2021): \(n \geq k\) サンプルを生成し、合格数 \(c\) を数える。 \[ \text{pass@}k = 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}} \]
不偏性が重要な理由:
\(n\) サンプル(例: \(n=200\))を一度生成し、同じサンプルからpass@1、pass@10、pass@100を計算できる。
評価全体を \(k\) 回繰り返す必要がない。
統計的に厳密(正しいサンプルを含まない \(k\) 部分集合の割合という組合せ論的根拠)。
log空間で数値的に安定して計算できる: \(\text{pass@}k = 1 - \exp\left(\sum_{i=0}^{k-1} \log(n-c-i) - \log(n-i)\right)\)
直感: 200サンプル中50個が合格(\(c=50\)、\(n=200\))なら、pass@1 \(\approx 0.25\)、pass@10 \(\approx 0.94\)。推定量は、\(k\) 個の抽出に少なくとも1つ成功が含まれる割合を数える。
復習: 第14章と第19章(LLM評価、エージェント型環境)。
Note
Q: ベンチマーク汚染をどう検出・緩和しますか?
解答: 汚染: 学習データにベンチマークのテスト例(または近い言い換え)が含まれ、スコアが水増しされる。
検出方法:
N-gram重複: 学習データにテスト項目との完全一致または近似一致があるか確認する。8-gramカバレッジ \(>\)80%は疑わしい。
カナリア文字列: テストセットに一意な識別子を入れ、モデルが再現できるか確認する。
言い換えベンチマーク: 意味は同じで文面が異なる版を作る。精度が大きく下がれば記憶が疑われる。
時間分析: 事前学習カットオフ前後のテスト項目で性能を比較する。古い項目だけ異常に高性能なら汚染を示唆する。
メンバーシップ推論: 特定例が学習データにあったかを統計的に検定する。
緩和策:
動的ベンチマーク: 新しいテスト項目を定期生成する(LiveCodeBench、Chatbot Arena)。
非公開テストセット: テスト項目を秘密にする(LMSYS)。
学習中の除染: 検出した重複を学習データから削除する。
汚染分析を報告: ベンチマークスコアとともに重複指標を開示する。
復習: 第14章(LLM評価)。
Note
Q: LLM-as-Judgeの位置バイアスと、その緩和方法を説明してください。
解答: 位置バイアス: LLMで2つの応答(A対B)を判定すると、品質にかかわらず特定位置(通常は最初または最後)の応答を系統的に好む。
経験的な大きさ: GPT-4は10〜15%、Claudeは5〜10%の位置バイアスを示す。小さいモデルほど大きい。
緩和戦略:
位置交換: 各ペアをA-BとB-Aの2回判定する。最終決定は多数決で、食い違えば「引き分け」とする。系統バイアスを除けるがコストは2倍。
複数ジャッジ: 3つ以上の異なるモデルを判定者にする。多数決で個別モデルのバイアスを減らす。
参照誘導: 評価基準または参照解答を与える。各応答を基準に対して独立採点し、スコアを比較する(ペア比較自体をなくす)。
較正済みプロンプト: 「提示順はランダムで、判定に影響させてはいけない」と明示する。
その他のバイアス: 冗長性バイアス(長い応答を好む)、自己高揚バイアス(モデル自身の出力を好む)、権威バイアス(出典を引用する応答に従う)。
復習: 第14章(LLM評価)。
エージェント型メモリの問題
Note
Q: エージェント型メモリの4種類を比較し、それぞれが重要になる場面を説明してください。
解答:
種類 保存するもの アクセスパターン 重要な場面 ワーキング 現在のコンテキスト/スクラッチパッド 常にコンテキスト内 複雑なマルチステップ推論 エピソード 過去の経験 類似性で検索 過去の失敗から学ぶ セマンティック 事実と知識 概念で検索 ドメイン固有タスク 手続き スキルとパターン タスク種別で起動 ツール利用の反復 重要な洞察: これらは独立ではなく相互作用する。エピソード記憶がセマンティック記憶へ情報を供給し(エピソードから事実へ一般化)、手続き記憶はエピソードのフィードバックで洗練される(有効なツール系列を学ぶ)。ワーキングメモリが他の種類からの検索を調整する。
MemGPTの類推: ワーキング=ホット(コンテキスト内)、エピソード/セマンティック=ウォーム(ベクトルストア)、手続き=コールド(アーカイブ済み方策)。エージェント自身が情報をページイン/アウトするタイミングを決める。
復習: 第16章(エージェント型メモリシステム)。
Note
Q: メモリ検索における時間減衰はどう機能し、なぜ重要ですか?
解答: 時間減衰 は検索時に古いメモリの重みを下げます。 \[ \text{score}(m) = \alpha \cdot \text{similarity}(q, m) + (1 - \alpha) \cdot \text{recency}(m) \] ここで \(\text{recency}(m) = e^{-\lambda \cdot \Delta t}\)、\(\Delta t\) は最後のアクセスからの時間です。
重要な理由:
関連性の減衰: ユーザーの選好は変化する。6か月前の選好は古い可能性がある。
矛盾解消: 古い情報と新しい情報が衝突すると、最近性バイアスが現在の真実を自然に優先する。
検索効率: 減衰がないとメモリは無制限に増え、検索が無関係な古い項目を返すようになる。
認知的妥当性: 人間も忘れるため、最近の出来事ほどアクセスしやすい。間隔効果を反映する。
アクセスに基づく更新: メモリを検索して使うとタイムスタンプを更新する(LRUキャッシュに似る)。頻繁にアクセスされるメモリは作成日に関係なく「新鮮」に保たれる。
減衰率の調整: \(\lambda\) は領域に依存する。カスタマーサービスは高減衰、法律/医療は低減衰。RLで学習できる。
復習: 第16章(エージェント型メモリシステム)。
Note
Q: RLを使ってメモリ操作をどう学習できますか?
解答: メモリ操作(write/read/update/delete)はエージェントのMDPにおけるアクションにできます。
定式化:
状態: 現在のコンテキスト+メモリ状態
アクション: 標準アクション+
memory_write(key/value)、memory_read(query)、memory_delete(key)報酬: タスク成功(メモリが役立ったか)+メモリ効率ペナルティ(読み取りが少ないほど良い)
RLが学ぶもの:
何を保存するか: 重要情報(APIキー/ユーザー選好)と一時的な詳細の区別
いつ検索するか: ドメイン質問への回答前か、一般チャット中か
圧縮方針: 古いメモリを要約するか、そのまま保持するか
忘却: 古い情報が陳腐化し、削除すべきタイミング
学習信号: 反実仮想。「このメモリを保存/検索しなかったとしてもエージェントは成功したか?」軌跡比較で実装し、メモリをうまく使った軌跡に高い報酬を与える。
課題: 遅延報酬。今保存した情報が100ステップ後にしか役立たないことがある。長期ホライズンの信用割当(高い \(\lambda\) のGAE)が必要です。
復習: 第12章と第16章(LLMエージェント学習、エージェント型メモリシステム)。
エージェントオーケストレーションの問題
Note
Q: コンテキスト予算問題と、動的割り当てによる解決方法を説明してください。
解答: 問題: エージェントのコンテキストウィンドウは \(L\) トークンだが、次のための領域が必要です。 \[ C = S + M + T + H + R \leq L \] ここで \(S\) はシステムプロンプト、\(M\) はメモリ/検索コンテキスト、\(T\) はツール説明、\(H\) は会話履歴、\(R\) は応答用の予約領域です。
会話が増えると \(H\) が増え、他の要素を押し出します。
動的割り当て戦略:
固定最小値: \(S_{\min}\)、\(R_{\min}\) は譲れない。
適応的履歴: \(H > H_{\max}\) になったら古いターンを要約する。最後の \(k\) ターンはそのまま、残りを要約する。
オンデマンドツール: 現在のクエリに関係するツール説明だけを含める(50個すべてではない)。分類器または埋め込み類似度で上位 \(k\) ツールを選ぶ。
遅延メモリ: 事前ロードせず、クエリを分析して必要なときだけメモリを検索する。
オーバーフロー処理: 圧縮しても合計が \(L\) を超える場合:
重要度の低いツール説明を捨てる
履歴を1ターン1文まで積極的に要約する
メモリスロットを減らす
それでも超えるなら、ユーザーへの警告付きで切り詰める
事前チェック: LLMを呼び出す前に必ずトークン数を数える。推論時にオーバーフローを発見してはいけない。
復習: 第17章(エージェントハーネス — コンテキスト管理とオーケストレーション)。
Note
Q: ReActとPlan-and-Executeのオーケストレーションパターンを比較してください。
解答:
ReAct (Reason + Act):
ループ: 思考 \(\to\) アクション \(\to\) 観察 \(\to\) 思考 \(\to\) …
各ステップで、これまでのすべての観察に基づいて次のアクションを決める
利点: 適応的で、ツール出力に基づいて方向を変えられる
欠点: 近視眼的で事前計画がない。ループに陥る可能性があり、各LLM呼び出しが全履歴を見るため高コスト。
Plan-and-Execute:
フェーズ1: 完全な計画(ステップ一覧)を生成
フェーズ2: ステップを逐次実行(より単純な実行器、場合によっては安価なモデル)
フェーズ3: 実行に失敗したら再計画
利点: 効率的(毎ステップ推論するより一度の計画が安い)。独立ステップを並列化できる。
欠点: 計画が脆い。初期ステップが失敗すると計画が無効になり得る。再計画はレイテンシを加える。
使い分け:
ReAct: 探索的タスク、未知の環境、各ステップの結果が次を決めるタスク
Plan-and-Execute: 明確なタスク、既知のツールセット、並列化可能なサブタスク、コスト重視のデプロイ
ハイブリッド: 高レベルで計画し、各計画ステップ内ではReActを使う(LangGraph推奨パターン)。
復習: 第17章と第18章(エージェントハーネス、エージェント設計パターン)。
Note
Q: エージェント実行の無限ループをどう検出・防止しますか?
解答: エージェントは、異なる結果を期待して同じアクションを繰り返すと無限ループに入ります。
検出方法:
最大反復ガード: 固い上限(例: 25ステップ)。単純だが、本当に長いタスクの作業を失う。
アクションハッシュ窓: 直近 \(k\) 個の(アクション/観察)ペアをハッシュ化する。現在のハッシュが直近 \(w\) ステップのものと一致すればループと検出する。
意味的類似度: 最近のアクションを埋め込む。連続アクションのコサイン類似度がしきい値(\(>\)0.95)を超えれば、行き詰まりの可能性が高い。
進捗監視: タスク固有の進捗指標を定義し、\(N\) ステップ進捗がなければ介入する。
回復戦略:
ヒント注入: 「アクションを繰り返しているようです。別の方法を試してください」というシステムメッセージを加える。
別アクションを強制: 次のステップでは繰り返したアクションをアクション空間からマスクする。
エスカレーション: 部分結果とともにユーザーへ返し、指示を求める。
バックトラック: ループ開始前のチェックポイントへ戻り、別経路を試す。
ベストプラクティス: 最大反復(安全網)+ハッシュ検出(早期介入)+適切なエスカレーション(ユーザーの信頼を維持)を組み合わせる。
復習: 第17章と第18章(エージェントハーネス、エージェント設計パターン)。
MCPプロトコルの問題
Note
Q: MCPのN+Mアーキテクチャと、エージェントエコシステムで重要な理由を説明してください。
解答: N\(\times\)M問題: MCPがなければ、\(N\) 個のエージェントフレームワークがそれぞれ \(M\) 個のツールと統合する必要があり、統合は合計 \(N \times M\) 個。新しいツール1つに \(N\) 実装が必要です。
MCPのN+M解決策: インターフェースを標準化する。各エージェントはMCPクライアントを1つ(合計 \(N\))、各ツールはMCPサーバーを1つ(合計 \(M\))実装する。統合は合計 \(N + M\) です。
具体例: MCPなしでは5フレームワーク(LangChain/AutoGen/CrewAI/Claude/独自)\(\times\)20ツール(GitHub/Slack/DB/ファイルシステムなど)=100統合。MCPなら5クライアント+20サーバー=25実装です。
重要な理由:
ツール再利用: ツールサーバーを一度作れば、MCP対応エージェントから利用できる。
エージェント可搬性: ツール統合を書き直さずClaudeから独自エージェントへ移行できる。
エコシステム成長: 新しいツール追加の障壁が下がり、コミュニティの開発を促す。
構成可能性: 実行時に複数サーバーを1つのエージェントへ動的接続できる。
類推: USBは周辺機器接続を標準化した。USB以前は機器ごとに独自コネクタが必要だったが、USB後は1ポートですべてに対応する。MCPはエージェントとツールの接続で同じことをする。
復習: 第20章(Model Context Protocol)。
Note
Q: MCPの4つの中核プリミティブとは何で、それぞれをいつ使いますか?
解答:
プリミティブ 方向 目的 例 ツール クライアント \(\to\) サーバー アクション実行 create_issue;query_dbリソース クライアント \(\to\) サーバー データ読み取り ファイル内容、DBレコード プロンプト クライアント \(\to\) サーバー テンプレート取得 「このPRを要約」テンプレート サンプリング サーバー \(\to\) クライアント LLM生成を要求 サーバーがLLMに分類を依頼 主な違い:
ツールとリソース: ツールには副作用(作成/変更/削除)があり、リソースは読み取り専用。安全性上、エージェントはリソースを自由に読めるが、ツールには承認が必要です。
サンプリング は方向を逆にする。通常はクライアント(エージェント)がサーバー(ツール)を呼ぶが、サンプリングではサーバーがクライアントのLLMに助けを求める。コード分析サーバーがコード片の解釈をLLMに依頼する場合など。
プロンプト は実行ではなくメタデータ(再利用可能なテンプレート)。エージェントがより良いツール呼び出しを作るのに役立つ。
復習: 第20章(Model Context Protocol)。
エージェント間通信(A2A)の問題
Note
Q: GoogleのA2AプロトコルはMCPとどう異なり、両方が必要なのはいつですか?
解答: 中核的な違い:
MCP: エージェント \(\leftrightarrow\) ツール(定義済みスキーマによる構造化関数呼び出し)
A2A: エージェント \(\leftrightarrow\) エージェント(不透明なタスク委譲。相手の動作方法は知らない)
A2Aの主要概念:
エージェントカード: エージェントの能力を記述するJSON(履歴書のようなもの)。発見機構。
不透明な実行: 依頼側は委譲先の内部推論を見ず、タスクを送り結果を受け取るだけ。
タスクライフサイクル: 送信 \(\to\) 実行中 \(\to\) 完了/失敗(SSEによるストリーミング更新付き)
両方が必要な場合:
オーケストレーターエージェントが A2A で「このトピックを調査して」と研究エージェントへ委譲する。
研究エージェントが MCP でウェブ検索、ファイル読み取り、データベースツールを呼ぶ。
結果がA2Aでオーケストレーターへ戻る。
アーキテクチャ: A2Aはエージェント間層、MCPはエージェントとツール層に位置する。完全なシステムは、エージェント間の調整にA2A、各エージェントのツールアクセスにMCPを使う。
復習: 第20章と第22章(MCP、エージェント間通信)。
Note
Q: Contract Net Protocolとは何で、LLMエージェントにどう適用しますか?
解答: Contract Net Protocol (CNP)は分散AIのタスク割り当て機構です。
手順:
告知: マネージャーが利用可能な全エージェントへタスク説明をブロードキャストする。
入札: エージェントが能力を評価し、入札(確信度、推定コスト、推定時間)を提出する。
落札: マネージャーが能力/コスト/可用性の基準で最良の入札を選ぶ。
実行: 落札エージェントがタスクを実行する。
報告: エージェントが結果をマネージャーへ返す。
LLMエージェントでは:
入札=自己評価: 各エージェントLLMが「このタスクをうまくできるか」を評価し、確信度を出す。較正された自己知識が必要。
専門化が創発: コードエージェントはコードタスク、研究エージェントは研究タスクに高く入札する。中央ルーティングロジックは不要。
負荷分散: あるエージェントが多忙(推定時間が長い)なら他が契約を得る。
障害処理: 落札エージェントが失敗したら残りへ再告知する(自動フェイルオーバー)。
LLMの限界: LLMは能力を過大評価しがち(確信度をハルシネーションする)。入札には自己申告の確信度だけでなく、類似タスクの過去成功率を含めるべきです。
復習: 第22章と第23章(A2A、マルチエージェントシステム)。
マルチエージェントシステムの問題
Note
Q: LLMの集中型と分散型マルチエージェントアーキテクチャを比較してください。
解答:
集中型(スーパーバイザー):
1つのオーケストレーターLLMが専門ワーカーへタスクをルーティングする。
制御フローが明確で、スーパーバイザーの判断を調べられるためデバッグしやすい。
単一障害点があり、スーパーバイザーがトークンのボトルネックになる。
適する場面: 明確なワークフロー、小規模なエージェントチーム(3〜5)。
分散型(ピアツーピア):
エージェントが直接通信し、中央コーディネーターがない。
耐障害性があり、水平スケールする。
創発的振る舞いのためデバッグが難しく、競合やデッドロックの可能性がある。
構造がなければ通信は \(O(n^2)\) で増える。
適する場面: 耐障害システム、大規模なエージェント集団、創発的振る舞いを望む創造的タスク。
ハイブリッド(階層型): サブマネージャーを持つ木構造。グループ内の局所自律性と上位の全体調整を両立し、通信は \(O(n \log n)\) で増える。
判断の枠組み: 予測可能性と監査可能性が必要なら集中型、耐障害性と創造性が必要なら分散型、大規模(\(>\)10エージェント)なら階層型を使う。
復習: 第23章(マルチエージェントシステム)。
Note
Q: CTDEとは何で、マルチエージェントLLMシステムの学習でなぜ重要ですか?
解答: CTDE=集中学習、分散実行です。
問題: マルチエージェントRLでは各エージェントの環境が非定常(他エージェントが同時に方策を変える)で、独立学習が不安定になる。
CTDEの解決策:
学習中: 集中クリティックが全エージェントの観察とアクションにアクセスする: \(V(s_1, s_2, \ldots, s_n, a_1, a_2, \ldots, a_n)\)。価値関数から非定常性を除き、学習を安定させる。
実行中: 各エージェントは自分の観察だけに基づき行動する: \(a_i = \pi_i(o_i)\)。推論時の通信オーバーヘッドがない。
LLMエージェントでは: 集中クリティックは全エージェントの合同出力を評価する報酬モデルにできる(例: チームが正しいソフトウェアシステムを作ったか)。各エージェントは反実仮想信用割当でチーム報酬への貢献を最大化するよう学習する。
実務上の課題: 完全なCTDEでは共有状態で全エージェントを同時学習するため、LLMでは高コスト。近似として、ラウンドごとにエージェントを学習(他を凍結)するか、定期同期付きの集団ベース学習を使う。
復習: 第23章(マルチエージェントシステム)。
エージェント開発フレームワークの問題
Note
Q: マルチエージェントシステム構築におけるLangGraph、AutoGen、CrewAIを比較してください。
解答:
観点 LangGraph AutoGen / CrewAI オーケストレーション 明示的状態グラフ(ノード+エッジ) 暗黙的(会話/役割ベース) 状態管理 TypedDictスキーマ、チェックポイント 会話履歴を状態にする マルチエージェント 条件ルーティング付きグラフ GroupChat/Crew デバッグ グラフ可視化、ステップ再生 チャットログ HITL 第一級(割り込みノード) 承認ツール経由 本番 LangGraph Cloud、永続化 限定的(AutoGen)、拡大中(CrewAI) 学習曲線 高い(グラフ概念) 低い(AutoGen)、非常に低い(CrewAI) LangGraphを選ぶ場面: 細粒度の制御、複雑な条件フロー、永続化とHuman-in-the-Loopを備えた本番デプロイが必要な場合。
AutoGenを選ぶ場面: マルチエージェント会話の高速プロトタイピング、コード実行エージェント、研究実験。
CrewAIを選ぶ場面: 単純な役割ベースチーム、逐次タスク実行、短時間のデモ、最小コード。
どれも選ばず独自実装する場面: 最大性能/制御が必要、フレームワークへのロックインを避けたい、または標準外のオーケストレーションパターンを使う場合。
復習: 第24章(エージェント開発フレームワーク)。
Note
Q: 本番のエージェントシステムをどうテスト・評価しますか?
解答: エージェントのテストは テストピラミッド に従います。
レベル1 — 単体テスト (高速、多数):
個別ツールを分離してテストする(LLMをモックし、ツールロジックを検証)
プロンプトテンプレートをテストする(コンテキストを与え、正しい構築を検証)
パーサーをテストする(LLM出力を与え、正しい抽出を検証)
レベル2 — 統合テスト (中速):
決定論的入力で完全なエージェントループをテストする
「ゴールデン軌跡」テスト: 再現できなければならない既知の正常実行トレース
ツールチェーンテスト: 複数ツールの系列が端から端まで機能することを検証
レベル3 — 振る舞いテスト (低速、少数):
エージェントは安全制約に従うか(敵対的入力)
適切な場合に明確化を求めるか
トークン/コスト予算内に収まるか
本番評価:
A/Bテスト: トラフィックの5%を新しいエージェント版へ送る
シャドーモード: 新旧エージェントを並行実行し、提供せず出力を比較する
LLM-as-judge: エージェント応答を自動品質採点する
ユーザー満足度: 高評価/低評価、タスク完了率、解決までの時間
主要指標: タスク成功率 (TSR)—人間の介入なしにエージェントが正しく完了したタスクの割合。
復習: 第14章と第24章(LLM評価、エージェント開発フレームワーク)。
エージェント型環境の問題
Note
Q: ウェブ閲覧エージェント環境の報酬関数を設計してください。
解答: WebArena型タスク(例: 「12月15日にNYCからSFへの最安航空便を探す」)では:
疎な報酬 (単純だが学習しにくい): \[ r = \begin{cases} 1 & \text{if final page/state matches ground truth} \\ 0 & \text{otherwise} \end{cases} \]
密な報酬 (学習には良いが設計が難しい):
進捗報酬: 目標に近づく各ページに \(+0.1\)(目標状態とのテキスト類似度で測定)
効率ペナルティ: アクションごとに \(-0.01\)(短い軌跡を促す)
マイルストーン報酬: 中間目標到達で \(+0.3\)(例: 航空便検索ページへ移動)
無効アクションペナルティ: エラーを起こすアクションに \(-0.05\)(404、フォーム検証失敗)
ポテンシャルベースのシェーピング (最適方策を維持): \[ r_{\text{shaped}}(s, a, s') = r(s, a, s') + \gamma \Phi(s') - \Phi(s) \] ここで \(\Phi(s) = -\text{min_steps_to_goal}(s)\)(ヒューリスティックまたは学習済み価値関数で推定)。
課題: 部分観測性(目標に近づいたか常に分かるわけではない)、確率的環境(ページ内容が変わる)、報酬ハッキング(報酬は満たすがユーザー意図を満たさない近道を見つける)。
復習: 第12章と第19章(LLMエージェント学習、エージェント型環境)。
Note
Q: SWE-benchを特に難しいエージェントベンチマークにしているものは何ですか?
解答: SWE-benchは人気のPythonリポジトリにある実際のGitHub Issueでエージェントをテストします。
難しい理由:
リポジトリ規模のコンテキスト: 10万行以上のコードベースを理解する必要がある。コンテキストウィンドウに収まらず、探索・検索・移動が必要。
不足仕様のタスク: Issueは暗黙のコンテキストを持つ人間の文章。エージェントは本当に必要なことを推測する必要がある。
複数ファイル編集: 解決策が連鎖依存を持つ複数ファイルにまたがることが多い。
テスト検証: 既存テストだけでなく、修正を検証する新しいテストにも合格する必要がある。
手助けがない: 単一関数のHumanEvalと異なり、Issueを読む \(\to\) コードを探索 \(\to\) バグを局所化 \(\to\) 修正を実装 \(\to\) 検証という完全なソフトウェア工学ワークフローが必要。
最先端の状況 (2024〜2025): 最良のエージェントはSWE-bench Verified(精選サブセット)の \(\sim\)50%、完全版SWE-benchの \(\sim\)30%を解く。
学習への重要な洞察: SWE-benchは「コーディング能力」(正しい関数を書く)と「ソフトウェア工学能力」(システムを理解し、コードベースを移動し、最小変更を行う)の差を明らかにする。SWE-bench型環境でのRLは、コード生成だけでなく探索・計画戦略を教える。
復習: 第19章(エージェント型環境とベンチマーク)。
エージェント型UIフレームワークの問題
Note
Q: エージェント向けのチャット型UIとキャンバス型UIのパラダイムを比較してください。
解答:
チャット型 (ChatGPT、Claudeのデフォルト):
線形メッセージストリーム: ユーザー \(\to\) アシスタント \(\to\) ユーザー \(\to\) …
利点: 馴染みのあるUX、探索とQ&Aに自然、実装が容易。
欠点: 生成アーティファクト(コード/文書)が会話に埋もれる。特定アーティファクトを反復しにくく、長い会話でコンテキストが失われる。
キャンバス/アーティファクト型 (Claude Artifacts、ChatGPT Canvas、Cursor):
サイドパネルに生成コンテンツ、チャットパネルに指示を表示。
エージェントが永続アーティファクトを作成、編集、反復できる。
利点: アーティファクトがチャットから独立して保持される。ユーザーが直接編集でき、バージョン履歴もある。
欠点: UIが複雑で、アーティファクト種別検出が必要。両パネルへのストリーミング実装も難しい。
使い分け:
チャット: ブレインストーミング、Q&A、短いタスク、モバイルUI。
キャンバス: コード生成、文書作成、データ分析など、反復が必要な永続出力を伴うタスク。
ハイブリッド (現代UIの多く): デフォルトはチャットで、コード/文書/可視化出力を検出したら自動的にキャンバスへ引き上げる。
エージェント学習では: UIパラダイムが報酬信号に影響する。キャンバスUIは明示的な編集フィードバック(ユーザーがアーティファクトを変更)を与え、オンライン学習に使える。
復習: 第25章(エージェント型UIフレームワーク)。
Note
Q: Human-in-the-Loopエージェントシステムの承認ゲートをどう設計しますか?
解答: 承認ゲートは重要な地点でエージェント実行を一時停止し、人間のレビューを受けます。
3段階モデル:
自動承認 (ゲートなし): 安全で取り消し可能なアクション。読み取り、検索、計算。
通知 (ソフトゲート): 影響はあり得るが回復可能。メール送信、下書き作成、ファイル変更。エージェントは進むが、ユーザーに通知し取り消せる。
ブロック (ハードゲート): 不可逆または高リスク。データ削除、送金、公開、副作用のあるコード実行。エージェントは明示承認を必ず待つ。
設計原則:
中断を最小化: ゲートが多すぎるとユーザーはエージェントを見放す。3段階モデルなら多くのアクションを流しつつ危険なものを捕捉できる。
コンテキストを表示: 承認ゲートで、何をするか、なぜか(エージェントの推論)、何が変わるか、どう取り消すかを表示する。
承認をまとめる: 5つのファイル書き込みが必要なら、1つずつでなくまとめて提示する。
タイムアウト処理: ユーザーが \(T\) 分以内に応答しなければ通知を再試行するか、安全なデフォルトで進むか、適切に中止する。
承認から学ぶ: 承認/拒否パターンを追跡する。常に承認されるアクション種別は自動承認へ昇格させることを検討する。
実装: ツールアノテーション(MCPの
destructiveHintとreadOnlyHint)でゲート割り当てを自動化する。コンテキストに基づく独自ルールで上書きできる。復習: 第17章と第25章(エージェントハーネス、エージェント型UIフレームワーク)。
RAGとエージェント型RAGの問題
Note
Q: Reciprocal Rank Fusion(RRF)と、ハイブリッド検索で機能する理由を説明してください。
解答: RRFはスコアの較正なしに複数検索システムの順位を組み合わせます。 \[ \text{RRF}(d) = \sum_{r \in R} \frac{1}{k + r(d)} \] ここで \(r(d)\) は文書 \(d\) の検索器 \(r\) における順位、\(k=60\) は上位文書の支配を防ぐ定数です。
機能する理由:
スコア正規化不要: BM25スコアは非有界、密ベクトル検索の類似度は \([-1, 1]\)。RRFは順位だけを使うため直接比較できる。
外れ値に頑健: 1位でも \(1/(k+1) \approx 0.016\) なので、1検索器の異常に高いスコアが支配しない。
相補的信号: BM25は正確なキーワード一致、密ベクトル検索は意味的類似を捉える。両方で上位の文書が強化される。
例: 文書 \(d\) がBM25で3位、密ベクトル検索で7位ならRRFスコアは \(= 1/(60+3) + 1/(60+7) = 0.0159 + 0.0149 = 0.0308\)。一方で片方が1位、他方が100位の文書は \(1/61 + 1/160 = 0.0226\) で、1位があっても低い。
実際には: ハイブリッド(BM25+密ベクトル検索+RRF)は、85%以上のベンチマークで単独方式を上回る。
復習: 第15章(検索拡張生成)。
Note
Q: エージェント型RAGとは何で、標準RAGとどう異なりますか?
解答: 標準RAG は固定パイプライン、クエリ \(\to\) 検索 \(\to\) 生成に従い、次の能力がない。
検索が必要かどうかを判断する
検索文書が十分か評価する
検索失敗時にクエリを再定式化する
複数検索ステップの情報を組み合わせる
エージェント型RAG は検索をエージェントのMDPにおけるアクションとして扱う。
検索するかの判断: エージェントが答えをすでに知っているか評価する(学習データ内の事実質問なら検索を省く)。
クエリ計画: 複雑な質問をサブクエリへ分解する(「Xは何年に起きたか」+「そのときの大統領は誰か」)。
自己評価: 検索後に関連性を採点し、不十分ならクエリを再定式化するか別ソースを試す。
マルチホップ推論: 検索 \(\to\) 推論 \(\to\) 知識ギャップ特定 \(\to\) 再検索
ソースルーティング: クエリを適切な知識ベースへ送る(時事はウェブ、社内情報は内部文書、プログラミングはコード検索)。
アーキテクチャ上の主な違い: 標準RAGは決定論的パイプライン、エージェント型RAGは条件遷移を持つ状態機械(retrieve/grade/rewrite/generateノードのLangGraphパターン)。
トレードオフ: エージェント型RAGは複雑なクエリで正確だが、ルーティング/採点の複数LLM呼び出しでレイテンシが増える。単純な事実検索には標準RAG、マルチホップや曖昧なクエリにはエージェント型RAGを使う。
復習: 第15章と第17章(RAG、エージェントハーネス)。
Note
Q: 検索品質改善におけるSelf-RAGとCRAGのアプローチを比較してください。
解答:
Self-RAG (Asaiら、2023):
特別なリフレクショントークンをLLM語彙に学習させる。
推論時、モデルは[Retrieve]、[IsRel]、[IsSup]、[IsUse]などのトークンを出力する。
モデルがいつ検索するかを決める(すべてのクエリに必要ではない)。
検索後、モデルが自己採点する。検索箇所は関連するか、解答はそこから導けるか。
学習: GPT-4のリフレクションラベルで拡張したデータをSFTする。
利点: 1つのモデルですべて処理。 欠点: 独自学習が必要。
CRAG (Corrective RAG、Yanら、2024):
軽量な検索評価器(別モデル)で検索文書を採点する。
確信度に応じて3アクション:
Correct(そのまま使う)、Ambiguous(ウェブ検索で補完)、Incorrect(破棄してウェブへフォールバック)。知識精錬ステップを加え、検索文書から関連文だけを抽出する。
利点: 任意の凍結LLMで動く。 欠点: 評価用の追加モデルとレイテンシが必要。
主な違い: Self-RAGは検索判断をLLM自身に埋め込む(学習が必要)。CRAGは任意のLLMを包むパイプライン方式(学習不要)。Self-RAGは洗練され、CRAGは既存モデルを使う本番に実用的です。
復習: 第15章(検索拡張生成)。
Note
Q: lost-in-the-middle問題とは何で、どう緩和しますか?
解答: 問題: 検索コンテキストが長い(多くの箇所がある)と、LLMはコンテキストの先頭と末尾に偏って注意を向け、中間の情報を無視する。10箇所中5番目に解答があると見落とす可能性がある。
経験的根拠: Liuら(2023)は、20文書検索で関連文書が1〜3位ではなく5〜15位にあると精度が15〜20%低下することを示した。
緩和戦略:
再ランキングと切り詰め: クロスエンコーダーで再順位付けし、最も関連する上位3箇所だけ含める(少ないほど問題が減る)。
戦略的順序付け: 関連度の高い箇所をコンテキストの先頭と末尾、低いものを中間に置く。
コンテキスト圧縮: 挿入前に各箇所を1〜2文へ要約する。テキストが少ないほど位置バイアスが小さい。
Map-reduce: 各箇所を独立処理(map)し、解答を結合(reduce)する。位置効果を完全に除く。
引用プロンプト: どの箇所を使ったか引用するよう求める。全箇所への注意を促す。
チャンクサイズ削減: チャンクを小さくすると、解答を覆うのに必要な総チャンク数が減る。
ベストプラクティス: 多く(20以上)検索し、上位3〜5に再ランキングし、関連度順(最良を先頭)にする。多くのユースケースで問題を回避できる。
復習: 第15章(検索拡張生成)。
クイックリファレンス
この章では、開発やデバッグ中にすぐ参照できるよう、主要な式、アーキテクチャ仕様、APIリファレンス、失敗モードの診断をまとめます。
RLとアラインメントの主要式
\[ \begin{aligned} \text{PPO Clip:}&\quad L = \mathbb{E}[\min(r_t\hat{A}_t, \text{clip}(r_t,1{\pm}\epsilon)\hat{A}_t)], \quad r_t = \pi_\theta(a_t|s_t)/\pi_{\text{old}}(a_t|s_t) \\ \text{DPO:}&\quad L = -\mathbb{E}[\log\sigma(\beta\log\tfrac{\pi_\theta(y_w|x)}{\pi_\text{ref}(y_w|x)} - \beta\log\tfrac{\pi_\theta(y_l|x)}{\pi_\text{ref}(y_l|x)})] \\ \text{GRPO:}&\quad \hat{A}_i = (r_i - \mu_G)/\sigma_G, \quad \text{then PPO clip update (no critic)} \\ \text{KTO:}&\quad L = \lambda_w(1 - v(y_w)) + \lambda_l \cdot v(y_l), \quad v = \sigma(\beta\log(\pi_\theta/\pi_\text{ref}) - z) \\ \text{IPO:}&\quad L = \mathbb{E}[(\log(\pi_\theta(y_w)/\pi_\text{ref}(y_w)) - \log(\pi_\theta(y_l)/\pi_\text{ref}(y_l)) - 1/(2\beta))^2] \\ \text{ORPO:}&\quad L = L_\text{SFT}(y_w) - \lambda\log\sigma(\log(\text{odds}(y_w)/\text{odds}(y_l))) \\ \text{GAE:}&\quad \hat{A}_t = \textstyle\sum_{l=0}^{T-t}(\gamma\lambda)^l\delta_{t+l}, \quad \delta_t = r_t + \gamma V(s_{t+1}) - V(s_t) \\ \text{KL Penalty:}&\quad R_\text{total} = r_\phi(x,y) - \beta D_\text{KL}[\pi_\theta(y|x)|\pi_\text{ref}(y|x)] \\ \text{RM (Bradley-Terry):}&\quad L = -\mathbb{E}[\log\sigma(r_\phi(x,y_w)-r_\phi(x,y_l))] \\ \text{Best-of-N:}&\quad y^* = \arg\max_{y_i \sim \pi_\theta(\cdot|x),, i=1..N} r_\phi(x, y_i) \end{aligned} \]
Transformerとアーキテクチャの式
\[ \begin{aligned} \text{Self-Attention:}&\quad \text{Attn}(Q,K,V) = \text{softmax}(QK^\top / \sqrt{d_k}) \cdot V \\ \text{Multi-Head:}&\quad \text{MHA}(X) = \text{Concat}(\text{head}_1, \ldots, \text{head}_h)W^O,\quad \text{head}_i = \text{Attn}(XW_i^Q, XW_i^K, XW_i^V) \\ \text{RoPE:}&\quad f(x_m, m) = x_m e^{im\theta_j}, \quad \theta_j = 10000^{-2j/d} \\ \text{LoRA:}&\quad W' = W_0 + (\alpha/r) \cdot BA, \quad B \in \mathbb{R}^{d \times r},; A \in \mathbb{R}^{r \times k} \\ \text{KD (soft targets):}&\quad L_\text{KD} = (1{-}\alpha)L_\text{CE}(y, \hat{y}) + \alpha, T^2 \cdot \text{KL}(p_T^\text{teacher} | p_T^\text{student}) \\ \text{FFN (SwiGLU):}&\quad \text{FFN}(x) = (\text{Swish}(xW_1) \odot xW_3) W_2 \end{aligned} \]
デコーディング手法
| 手法 | 式/ルール | 主要パラメータ |
|---|---|---|
| 貪欲法 | \(y_t = \arg\max_v P(v\vert y_{<t})\) | — |
| ビームサーチ | 共同確率で上位 \(B\) 個の部分系列を保持する | \(B=4\)–\(8\) |
| 温度 | \(P'(v) = \text{softmax}(\text{logit}_v / T)\) | \(T \in [0.1, 1.5]\) |
| Top-\(k\) | 上位 \(k\) 以外のロジットをゼロ化して再正規化する | \(k=40\)–\(100\) |
| Top-\(p\)(nucleus) | 最小の集合 \(V'\) を保持する(\(\sum_{v \in V'} P(v) \geq p\) を満たすもの) | \(p=0.9\)–\(0.95\) |
| Min-\(p\) | \(P(v) \geq p_\text{min} \cdot P(v_\text{max})\) のトークンを保持する | \(p_\text{min}=0.05\)–\(0.1\) |
| 反復ペナルティ | \(\text{logit}_v \leftarrow \text{logit}_v / \theta\) を、\(v\) が以前に出現していれば適用する | \(\theta=1.1\)–\(1.3\) |
システムと並列化
| 式 | 値(70B、BF16) | 説明 |
|---|---|---|
| モデルメモリ | \(2P\) bytes | \(140\) GB(重みのみ) |
| Adamオプティマイザー | \(2P \times 4\) bytes(m + v) | \(280\) GB |
| 完全学習フットプリント | \(\sim 8P\) bytes | \(560\) GB(重み+オプティマイザー+勾配) |
| FSDPのGPUあたりメモリ | \(8P / N_\text{GPUs}\) | GPU 8基で \(70\) GB |
| 生成の演算強度 | \(2P / 2P = 1\) FLOP/byte | 大幅なメモリ帯域幅律速 |
| 生成のトークンレート | HBM_BW \(/ (2P)\) | \(\sim\)14 tok/s(A100、batch=1) |
| 層あたりのTP AllReduce | \(2 \times 2 \cdot \frac{T-1}{T} \cdot bsd\) bytes | \(\sim\)188 MB(70B、TP=8) |
| PPバブル率 | \((P-1)/(P+M-1)\) | \(P\)=ステージ数、\(M\)=マイクロバッチ数 |
| MFU | observed_toks \(\times\) 6\(P\) / peak_FLOPS | 目標: \(>40%\) |
GPUハードウェア仕様
| GPU | メモリ | 帯域幅(HBM) | BF16 TFLOPS | NVLink | 注記 |
|---|---|---|---|---|---|
| A100-80GB | 80 GB HBM2e | 2.0 TB/s | 312 | 600 GB/s | 主力機、広く利用可能 |
| H100-80GB | 80 GB HBM3 | 3.35 TB/s | 989 | 900 GB/s | 現行世代、FP8対応 |
| H200-141GB | 141 GB HBM3e | 4.8 TB/s | 989 | 900 GB/s | 大きなコンテキスト/少ないGPU数 |
| B200 | 192 GB HBM3e | 8.0 TB/s | 2250 | 1800 GB/s | 次世代(2025年) |
ハイパーパラメータの範囲
| パラメータ | 通常の範囲 | デフォルト | 注記 |
|---|---|---|---|
| \(\beta\)(DPO/KTO) | 0.05–0.5 | 0.1 | 大きいほど保守的 |
| \(\epsilon\)(PPOクリップ) | 0.1–0.3 | 0.2 | 大きいほど更新が積極的 |
| \(\gamma\)(GAE割引) | 0.99–1.0 | 1.0 | エピソード型タスクでは1.0を使う |
| \(\lambda\)(GAE) | 0.9–0.99 | 0.95 | 小さいほどバイアスが大きく、分散が小さい |
| KL係数(\(\beta_\text{KL}\)) | 0.01–0.2 | 0.05 | 目標KL \(\approx\) 5–8へ自動適応 |
| LR(RLHF) | 1e-7 – 5e-6 | 5e-7 | 事前学習より大幅に小さい |
| LR(SFT) | 1e-5 – 5e-5 | 2e-5 | 標準的なファインチューニング範囲 |
| LoRAランク \(r\) | 8–128 | 16–64 | \(r\) が大きいほど容量とメモリが増える |
| LoRAのalpha \(\alpha\) | \(r\) – \(2r\) | \(2r\) | スケーリング係数。有効スケールは \(\alpha/r\) |
| 温度(生成) | 0.6–1.0 | 0.7 | 小さいほど候補の多様性が低い |
| 生成数 \(K\) | 4–64 | 4–16 | GRPO/Online DPO/Best-of-N用 |
| 勾配クリップノルム | 0.5–2.0 | 1.0 | 勾配爆発を防ぐ |
TRL APIクイックリファレンス
| トレーナー | 手法 | 主要設定 | データ形式 |
|---|---|---|---|
SFTTrainer | 教師ありFT | packing, max_seq_length | prompt + completion |
RewardTrainer | 報酬モデル | center_rewards_coefficient | prompt + chosen + rejected |
PPOTrainer | PPO | init_kl_coef, target_kl, cliprange | プロンプト(オンライン生成) |
DPOTrainer | DPO/IPO | beta, loss_type="sigmoid"/"ipo" | prompt + chosen + rejected |
GRPOTrainer | GRPO | num_generations, beta, use_vllm | プロンプト+reward_fn |
OnlineDPOTrainer | Online DPO | num_generations, reward_model_path | プロンプト(オンライン生成) |
KTOTrainer | KTO | desirable_weight, undesirable_weight | prompt + completion + label |
ORPOTrainer | ORPO | beta | prompt + chosen + rejected |
Best-of-N (manual) | Best-of-N | n_samples | プロンプト(推論) |
RAGパイプラインの式
\[ \begin{aligned} \text{Cosine similarity:}&\quad \text{sim}(q, d) = \frac{q \cdot d}{|q| \cdot |d|} \\ \text{Retrieval:}&\quad \mathcal{D}_k = \text{top-}k_{d \in \mathcal{C}} ; \text{sim}(\text{embed}(q),; \text{embed}(d)) \\ \text{RAG generation:}&\quad P(y|q) = P_\text{LLM}(y ;|; q, \mathcal{D}_k) \\ \text{Chunking overlap:}&\quad \text{stride} = \text{chunk_size} - \text{overlap} \\ \text{Reranker (cross-enc):}&\quad \text{score}(q, d) = \text{MLP}(\text{BERT}([q; d])) \end{aligned} \]
エージェント設計パターン
| パターン | 構造 | 適する用途 |
|---|---|---|
| ReAct | Think \(\to\) Act \(\to\) Observe \(\to\) loop | 一般的なツール利用エージェント |
| Plan-and-Execute | Plan \(\to\) Execute steps \(\to\) Revise | 長期計画、構造化タスク |
| Supervisor | ルーター \(\to\) 専門エージェント | 複数ドメイン、明確なサブタスク境界 |
| Swarm(ハンドオフ) | エージェントが制御とコンテキストを移譲 | カスタマーサービス、エスカレーションフロー |
| 階層型 | 委譲するエージェントの木構造 | 複雑な分解 |
| Human-in-the-loop | エージェント \(\to\) 承認ゲート \(\to\) 継続 | 高リスク、不可逆なアクション |
エージェント通信プロトコル
| プロトコル | 範囲 | トランスポート | 主要概念 |
|---|---|---|---|
| MCP | ツール統合 | stdio / HTTP+SSE | サーバーがツールを公開し、クライアントが発見・呼び出す |
| A2A | エージェント間 | HTTP + JSON-RPC | ライフサイクルを持つタスク(submitted\(\to\)working\(\to\)done) |
| OpenAI Function Calling | ツール利用 | APIペイロード | JSONスキーマをtools[]配列に置く |
コンテキストウィンドウ予算
\[ C \geq \underbrace{S}_{\text{system}} + \underbrace{M}_{\text{memory/RAG}} + \underbrace{T}_{\text{tool defs}} + \underbrace{H}_{\text{history}} + \underbrace{R}_{\text{reserved output}} \]
128Kコンテキストの 目安:
-
システムプロンプト: 1–4Kトークン(固定)
-
ツール定義: 2–8K(ツール数に応じて増加)
-
RAGコンテキスト: 4–16K(上位 \(k\) チャンク)
-
履歴: 無制限に増加 \(\rightarrow\) 要約/切り詰め
-
予約出力: 2–8K
よくある失敗モードと修正
手法選択の決定木
-
選好ペア(chosen+rejected)があるか?
-
ラベルにノイズがある \(\rightarrow\) IPO
-
メモリ制約があり、まだSFTをしていない \(\rightarrow\) ORPO
-
クリーンなデータで計算資源が限られる \(\rightarrow\) DPO
-
DPOが頭打ちで探索したい \(\rightarrow\) Online DPO
-
-
二値フィードバック(賛成/反対)しかないか? \(\rightarrow\) KTO
-
検証可能な報酬(数学/コード)があるか? \(\rightarrow\) GRPO
-
コストを問わず最高品質が必要か? \(\rightarrow\) PPO
-
学習なしで改善したいか? \(\rightarrow\) Best-of-N
評価指標
| 指標 | 範囲 | 測定対象 |
|---|---|---|
| パープレキシティ | \([1, \infty)\) | モデルの意外度。低いほど言語モデリングが良い |
| 勝率(ベースライン比) | \([0, 1]\) | 評価者または人間が好む出力の割合 |
| BLEU | \([0, 1]\) | 参照との \(n\)-gram重複(適合率重視) |
| ROUGE-L | \([0, 1]\) | 参照との最長共通部分列 |
| Pass@\(k\) | \([0, 1]\) | テストに合格するものが \(\geq\)1 個の、\(k\) 個のコードサンプルにおける確率 |
| MMLU / GPQA | \([0, 1]\) | 知識/推論ベンチマークの多肢選択正解率 |
| HumanEval | \([0, 1]\) | 生成コードの機能的正しさ |
| Faithfulness(RAG) | \([0, 1]\) | 検索コンテキストに裏付けられた主張の割合 |
| コンテキスト関連性 | \([0, 1]\) | クエリに関連する検索内容の割合 |
| 回答関連性 | \([0, 1]\) | 回答が質問に対応している度合い |
推論とテスト時計算量のスケーリング
| 手法 | 計算コスト | 仕組み |
|---|---|---|
| Chain-of-Thought(CoT) | 1.5–3\(\times\) トークン | プロンプトで「段階的に考える」 |
| Self-Consistency | \(N \times\) 生成 | \(N\) 個のCoT経路をサンプリングし、最終回答を多数決する |
| Tree-of-Thought(ToT) | \(B \times D \times\) 生成 | 推論木をBFS/DFSで探索し、分岐を評価する |
| Best-of-\(N\) | \(N \times\) 生成 | \(N\) 個をサンプリングし、RMで採点して最高のものを選ぶ |
| ビームサーチ(推論上) | \(B \times\) 生成 | 上位 \(B\) 個の部分推論系列を保持する |
| 予算強制 | 可変 | 難しい問題に動的に多くのトークンを割り当てる |
| 検証(ORM/PRM) | \(N \times\) 生成+採点 | \(N\) 個の解を生成し、結果/プロセスRMで順位付けする |
メモリシステムの種類
| 種類 | 保存先 | 用途 |
|---|---|---|
| 作業メモリ | コンテキストウィンドウ | 現在の会話、直近のツール結果 |
| エピソード記憶 | ベクトルストア | 過去の対話、ユーザー設定、セッション履歴 |
| 意味記憶 | 知識グラフ/埋め込み | 事実、概念、ドメイン知識 |
| 手続き記憶 | スキルライブラリ/コード | 手順、学習したワークフロー |
MCPクイックリファレンス
| プリミティブ | 方向 | 副作用? | 目的 |
|---|---|---|---|
| ツール | クライアント \(\to\) サーバー | あり | アクション(作成、変更、削除)を実行する |
| リソース | クライアント \(\to\) サーバー | なし(読み取り専用) | データ(ファイル、DBレコード、設定)を読む |
| プロンプト | クライアント \(\to\) サーバー | なし | 一般的なタスク用の再利用可能なテンプレート |
| サンプリング | サーバー \(\to\) クライアント | なし | サーバーがクライアントへLLM生成を要求する |
トランスポート: stdio(ローカルサブプロセス)またはHTTP+SSE(リモート、ストリーミング可能)。
発見: 接続初期化時にクライアントがtools/list、resources/list、prompts/listを呼び出す。
ツールアノテーション: readOnlyHint、destructiveHint、idempotentHint、openWorldHint。
A2Aプロトコルクイックリファレンス
| 概念 | 説明 |
|---|---|
| エージェントカード | /.well-known/agent.json にあるJSON。名前、スキル、対応コンテンツ型を含む |
| タスク | 作業単位: id、status、artifacts。ライフサイクル: submitted \(\to\) working \(\to\) completed/failed |
| メッセージ | タスク内の通信単位(role: user/agent、parts: text/file/data) |
| アーティファクト | エージェントが生成する出力(構造化データ、ファイル、生成コンテンツ) |
| プッシュ通知 | 長時間タスク向けのWebhook更新(tasks/pushNotification/set経由) |
主要エンドポイント: tasks/send(作成/更新)、tasks/get(ステータスのポーリング)、tasks/sendSubscribe(SSEストリーム)。
エージェントフレームワークの比較
| フレームワーク | オーケストレーション | マルチエージェント | 適する用途 |
|---|---|---|---|
| LangGraph | 明示的な状態グラフ | 条件付きルーティング | 本番: 永続化、HITL、細かな制御 |
| OpenAI Agents SDK | 宣言的なハンドオフ | ハンドオフベース | シンプルさ: ガードレール、トレーシング、迅速な開始 |
| AutoGen(AG2) | 会話駆動 | GroupChat | プロトタイピング: コード実行、調査 |
| CrewAI | 役割ベースのチーム | 逐次/並列 | ローコード: 迅速なデモ、単純なパイプライン |
| Google ADK | セッション+イベント | A2Aネイティブ | エンタープライズ: アーティファクト管理、マルチモーダル |
エージェント型RLの式
\[ \begin{aligned} \text{Trajectory GRPO:}&\quad \hat{A}_i = (R(\tau_i) - \mu_G)/\sigma_G, \quad R(\tau_i) = \sum_{t} r_t^{(\tau_i)} \\ \text{Agent reward:}&\quad R = w_1 R_\text{task} + w_2 R_\text{efficiency} + w_3 R_\text{safety}, \quad R_\text{eff} = \max(0, 1 - \text{steps}/N_\text{max}) \\ \text{Masking:}&\quad \mathcal{L} = \sum_{t \in \text{agent tokens}} \min(r_t \hat{A}_t,; \text{clip}(r_t) \hat{A}_t) \quad \text{(mask env outputs)} \\ \text{Pass@}k:&\quad 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}}, \quad n = \text{total samples},; c = \text{correct} \end{aligned} \]
エージェントセキュリティチェックリスト
| 脅威 | 層 | 緩和策 |
|---|---|---|
| プロンプトインジェクション(直接) | 入力 | 入力検証、指示階層、区切り文字 |
| プロンプトインジェクション(間接) | ツール出力 | ツール出力を信頼できないものとして扱い、検索文書内の指示に従わない |
| ツールの誤用 | 実行 | 最小権限、destructiveHintゲート、サンドボックス化 |
| データ漏えい | 出力 | 出力フィルタリング、許可ドメインへのツールアクセス制限 |
| 過度な自律性 | アーキテクチャ | 最大反復回数、コスト予算、人間の承認ゲート |
| 混乱した代理人 | マルチエージェント | タスクの出所を確認し、能力ベースのアクセス制御を行う |
エージェント評価指標
| 指標 | 式/定義 | 目標 |
|---|---|---|
| タスク成功率(TSR) | 正常完了数/タスク総数 | \(>85%\)(本番) |
| 完了までのステップ数 | 成功タスクあたりのエージェントアクション平均 | 低いほど効率的 |
| タスクあたりコスト | 総トークン数 \(\times\) トークン単価 | 予算に依存 |
| レイテンシ(TTFC) | リクエストから最初の有用な出力までの時間 | 対話用途では \(<5\)s |
| ツール呼び出し精度 | 正しいツール選択数/呼び出し総数 | \(>90%\) |
| 回復率 | 成功した再試行数/初期失敗数 | \(>60%\) |
| 人間へのエスカレーション率 | 人間が必要なタスク数/タスク総数 | \(<15%\) |
主要なエージェントベンチマーク
| ベンチマーク | ドメイン | 指標 | 最先端(2025年) |
|---|---|---|---|
| SWE-bench Verified | ソフトウェア工学 | 解決済み課題の割合 | \(\sim\)70% |
| WebArena | ウェブ閲覧 | タスク成功率 | \(\sim\)40% |
| OSWorld | デスクトップコンピューター利用 | タスク成功率 | \(\sim\)25% |
| GAIA | 汎用AIアシスタント | 完全一致正解率 | \(\sim\)75%(L1) |
| Tau-bench | ツール利用の信頼性 | 合格率(5試行) | \(\sim\)65% |
| HumanEval / MBPP | コード生成 | Pass@1 | \(>95%\) |
結論と今後の方向性
まとめ
このガイドでは、Transformerの基礎から、アラインメントのための強化学習、そして自律的なエージェント型システムの構築までを一貫してたどりました。全章を通じて浮かび上がる主要なテーマは次のとおりです。
-
アラインメントはシステムの問題である。 良い損失関数があるだけでは十分ではありません。本番のRLHFでは、4つ以上のモデルの管理、数百GPUへの計算の分散、耐障害性の処理、報酬ハッキングの監視を同時に行う必要があります。
-
唯一の最善手法はない。 PPOは最高品質のゴールドスタンダードであり続けていますが、莫大なエンジニアリング投資を必要とします。DPOとその派生手法は、インフラが限られたチームに魅力的なトレードオフを提供します。GRPOは検証可能な報酬の領域でその隔たりを埋めます。正しい選択は、データ、計算予算、品質基準によって決まります。
-
推論は報酬から創発する。 DeepSeek-R1は、推論の明示的なデモンストレーションなしに、単純な二値報酬信号とグループ相対最適化からチェーン・オブ・ソート、自己検証、バックトラッキングが創発し得ることを示しました。テスト時計算量のスケーリングにより、より多く考える小型モデルが大型モデルに匹敵できます。
-
標準がエコシステムを開く。 MCPはツール統合の問題を \(N \times M\) から \(N + M\) に削減します。A2Aにより、異なるチームが構築したエージェントが内部実装を共有せずに協働できます。これらのプロトコルは、ウェブにおけるHTTPに相当するエージェント型AIの基盤、つまりオープンなエコシステムを可能にするインフラです。
-
エージェントは自然な次の段階である。 モデルのアラインメントが済むと、最前線は「単一の応答はどれほど良いか?」から「モデルは複数ステップの問題を自律的に解けるか?」へ移ります。そのためには、新しい学習パラダイム(環境報酬を使うエージェント型RL)、新しいインフラ(ハーネス、ツールプロトコル、メモリシステム)、新しい評価手法(軌跡レベルのベンチマーク)が必要です。
-
評価がすべてを動かす。 報酬モデルの検証からエージェントのタスク成功率まで、汚染の検出からLLM-as-Judgeの較正まで、厳密な評価がなければ進歩を測定できず、退行も見えません。選択するベンチマークが、構築するシステムの形を決めます。
-
単純さはスケールする。 最も信頼できる本番エージェントは、要件を満たす最も単純なアーキテクチャを使います。自律ループの前にプロンプトチェーンとルーティングを、マルチエージェントのスウォームの前に単一エージェントを選びます。複雑さは、必要性を実証してから導入すべきです。
これからの道:未解決の課題
相互作用からの学習
現在のRLHFパイプライン (Ouyang et al. 2022)は、アラインメントを一度限りの学習段階として扱います。将来は、 デプロイ後の継続学習 へ向かうでしょう。つまり、壊滅的忘却 (Kirkpatrick et al. 2017)や報酬ドリフトを起こさず、すべてのユーザーインタラクション、ツール失敗、環境観測から改善するエージェントです。主な未解決問題は次のとおりです。
-
非定常な報酬分布でのオンライン学習。
-
本番環境での安全な探索 (Garcı́a and Fernández 2015)(学習中に有害なアクションを避けること)。
-
長いエージェント軌跡(数百回のツール呼び出し)に対する効率的な信用割当。
スケーラブルな監督
エージェントの能力が高まるほど、人間による監督がボトルネックになります。現在の手法(RLHF (Ouyang et al. 2022)、Constitutional AI (Bai et al. 2022))は人間がモデル出力を評価することに依存しています。しかし、モデル出力が人間の理解を超えたらどうなるでしょうか。
-
再帰的報酬モデリング (Christiano et al. 2017): AIを使って人間によるAIの評価を支援する。
-
ディベートと増幅 (Irving et al. 2018): 2つのモデルが議論し、人間がどちらの主張に説得力があるかを判断する。
-
プロセスベースの監督 (Lightman et al. 2023): 最終回答だけでなく、正しい推論ステップに報酬を与える。
-
メカニスティック解釈可能性 (Olsson et al. 2022): 出力だけでなく、モデルが内部で何をしているかを理解する。
世界モデルと計画
現在のエージェントは反応的で、一度に1ステップずつ観測して応答します。将来のエージェントには、先読み計画を可能にする 内部世界モデル (Hafner et al. 2020)が必要になります。
-
アクションを実行する前に、その結果を予測すること。
-
AlphaGo (Silver et al. 2016)やMuZero (Schrittwieser et al. 2020)のように、可能なアクション系列を木探索すること(ただしオープンエンドのタスク向け)。
-
相互作用の軌跡から環境ダイナミクスを学習すること。
マルチエージェントのエコシステム
A2Aプロトコル (Google 2025)とマルチエージェントフレームワークは、数百の専門エージェントが協働、交渉、委譲し、「エージェントの経済」 (Nisan et al. 2007)を形成する未来を示唆しています。未解決の課題は次のとおりです。
-
異なる主体に属するエージェント間の信頼と検証。
-
競争的な環境における創発的協力と創発的欺瞞の対比 (Hubinger et al. 2024)。
-
資源配分(計算資源、ツールアクセス、優先度)の市場メカニズム。
-
ガバナンス: 10個のエージェントの連鎖が有害な結果を生んだとき、誰が責任を負うのか? (Amodei et al. 2016)
エージェントのセキュリティと信頼
自律エージェントは、基盤となるLLMのあらゆるセキュリティ脆弱性を引き継ぎます。さらに、ツールアクセス、マルチエージェント委譲、永続メモリによって新たな攻撃対象領域も生まれます(第19〜21章)。重大な未解決問題は次のとおりです。
-
大規模なプロンプトインジェクション (Greshake et al. 2023): エージェントが信頼できないコンテンツ(ウェブページ、メール、API応答)を取り込むと、間接的なプロンプトインジェクションがシステム全体の問題になります。現在、堅牢な防御策は存在しません。
-
混乱した代理人攻撃: 正当な認証情報を持つエージェントが、データストリームに埋め込まれた攻撃者のためにそれを誤用するよう仕向けられます (Anthropic 2024b)。
-
機能を損なわないサンドボックス化: 最小権限の実行はエージェントにできることを制約しますが、制限が厳しすぎるサンドボックスはエージェント型システムの価値を失わせます。適切な境界を見つけることは未解決の設計問題です。
-
監査と帰属: エージェントの連鎖がA2A (Google 2025)を介して複数組織にまたがるとき、誰が どのアクションを承認したかを追跡することは、アーキテクチャ上未解決のままです。
-
信頼の較正: エージェントは、信頼してはいけない場面を学ぶ必要があります。たとえば、ツールの応答が本物か、別のエージェントの主張が検証済みかを判断することです。
ベンチマークを超える評価
第14章で示したように、ベンチマークは構築するシステムの形を決めます。しかし、現在の評価には重大な空白があります。
-
実世界のデプロイ指標: SWE-bench (Jimenez et al. 2024)やGAIA (Mialon et al. 2024)のようなベンチマークは孤立したタスクを測定しますが、本番エージェントは曖昧な目標、変化する要件、複数ターンの回復に直面します。
-
報酬モデルの妥当性: RLHFは報酬モデルが人間の選好を捉えると仮定しますが、報酬ハッキング (Skalse et al. 2022)と分布シフトが、この仮定を大規模に揺るがします。
-
コストと品質のフロンティア: 2つのエージェントが同じ正解率を達成しても、一方は10\(\times\)多くのトークンを要するかもしれません。評価はコストを考慮する必要があります。
-
分布シフト下の安全性: テストでは安全なエージェントが、新しい入力に対して安全でない振る舞いをする可能性があります。敵対的評価 (Perez et al. 2022)とエージェント型システム規模でのレッドチームは、まだ未成熟です。
効率とアクセシビリティ
70BモデルをRLHFで学習するには \(10K--\)100K の費用がかかります。自律エージェントの実行には、複雑なタスク1件あたり \(1--\)50 の費用がかかります。エージェント型AIが広い影響を実現するには、次が必要です。
-
大型モデルから小型モデルへのエージェント能力の蒸留 (Hinton et al. 2015; Kim et al. 2023)。
-
より効率的なRLアルゴリズム(少ないサンプル、低い分散) (Schulman et al. 2017)。
-
クラウドとの往復なしで動作するオンデバイスエージェント。
-
エージェント型タスクでプロプライエタリモデルの品質に匹敵するオープンウェイトモデル (DeepSeek-AI et al. 2025)。
さらに読む
基礎となる論文
-
Attention Is All You Need (Vaswani et al. 2017) — Transformerアーキテクチャ。
-
RLHF / InstructGPT (Ouyang et al. 2022) — 最初の大規模RLHFデプロイ。
-
PPO (Schulman et al. 2017) — 近接方策最適化。
-
DPO (Rafailov et al. 2023) — 直接選好最適化。
-
GRPO / DeepSeek-R1 (Shao et al. 2024; DeepSeek-AI et al. 2025) — グループ相対方策最適化と創発的推論。
-
ReAct (S. Yao, Zhao, et al. 2023) — LLMエージェントの推論+行動フレームワーク。
-
Toolformer (Schick et al. 2023) — LLMにツールの使い方を教える手法。
-
RAG (P. Lewis et al. 2020) — 検索拡張生成。
システムとスケーリング
-
Megatron-LM (Shoeybi et al. 2019) — テンソル並列とパイプライン並列。
-
DeepSpeed ZeRO (Rajbhandari et al. 2020) — メモリ効率の高い分散学習。
-
vLLM (Kwon et al. 2023) — 効率的なLLMサービングのためのPagedAttention。
-
Flash Attention (Dao et al. 2022) — I/Oを考慮した厳密なアテンション。
エージェント型AI
-
Building Effective Agents (Anthropic 2024a) — 設計パターンと原則。
-
Voyager (G. Wang et al. 2023) — Minecraft内のスキルライブラリを持つオープンエンドエージェント。
-
SWE-bench (Jimenez et al. 2024) — 自律的なソフトウェア工学のベンチマーク。
-
OSWorld (Xie et al. 2024) — 完全なコンピューター利用ベンチマーク。
-
GAIA (Mialon et al. 2024) — 実世界タスク向け汎用AIアシスタントのベンチマーク。
-
MemGPT (Packer et al. 2023) — 無制限コンテキストのためのOS着想のメモリ管理。
-
Model Context Protocol (Anthropic 2024b) — ツール統合のオープン標準。
-
Agent-to-Agent Protocol (Google 2025) — エージェント間通信の標準。
アラインメントと安全性
-
Constitutional AI (Bai et al. 2022) — 自己教師ありアラインメント。
-
Sleeper Agents (Hubinger et al. 2024) — 欺瞞的アラインメントへの懸念。
-
Reflexion (Shinn et al. 2023) — 言語的な自己内省からの学習。
-
Indirect Prompt Injection (Greshake et al. 2023) — LLM統合アプリケーションのセキュリティリスク。
オンラインリソース
-
HuggingFace TRL: https://github.com/huggingface/trl — 本番向けRLライブラリ。
-
LangGraph: https://github.com/langchain-ai/langgraph — エージェントワークフローのグラフ。
-
OpenAI Agents SDK: https://github.com/openai/openai-agents-python — 公式エージェントフレームワーク。
-
DeepSpeed-Chat: https://github.com/microsoft/DeepSpeedExamples — エンドツーエンドRLHFパイプライン。
-
DSPy: https://github.com/stanfordnlp/dspy — 宣言的なプロンプト最適化。
-
AutoGen: https://github.com/microsoft/autogen — マルチエージェント会話フレームワーク。
「未来を予測する最善の方法は、それを自らつくることだ。」
— Alan Kay
References
Abid, Abubakar, Ali Abdalla, Ali Abid, Dawood Khan, Abdulrahman Alfozan, and James Zou. 2019. Gradio: Hassle-Free Sharing and Testing of ML Models in the Wild. https://arxiv.org/abs/1906.02569.
Abnar, Samira, and Willem Zuidema. 2020. “Quantifying Attention Flow in Transformers.” Proceedings of the 58th Annual Meeting of the Association for Computational Linguistics (ACL). https://arxiv.org/abs/2005.00928.
Aghajanyan, Armen, Sonal Gupta, and Luke Zettlemoyer. 2021. “Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning.” Proceedings of the 59th Annual Meeting of the Association for Computational Linguistics (ACL). https://arxiv.org/abs/2012.13255.
AI, Meta. 2025. “The Llama 4 Herd: The Beginning of a New Era of Natively Multimodal AI.” Meta AI Blog. https://ai.meta.com/blog/llama-4-multimodal-intelligence/.
AI, Mistral. 2024. “Mistral Large 2.” Mistral AI Blog. https://mistral.ai/news/mistral-large-2407/.
Amodei, Dario, Chris Olah, Jacob Steinhardt, Paul Christiano, John Schulman, and Dan Mané. 2016. “Concrete Problems in AI Safety.” arXiv Preprint arXiv:1606.06565. https://arxiv.org/abs/1606.06565.
An, Yanxu, Li Shen, Yifan Xu, and Xinmei Liu. 2025. “DPPO: Direct Divergence-Based Policy Optimization for Language Model Alignment.” arXiv Preprint arXiv:2503.14532. https://arxiv.org/abs/2503.14532.
Andrychowicz, Marcin, Filip Wolski, Alex Ray, et al. 2017. “Hindsight Experience Replay.” Advances in Neural Information Processing Systems (NeurIPS).
Anthropic. 2024a. Building Effective Agents. https://www.anthropic.com/research/building-effective-agents.
Anthropic. 2024b. Model Context Protocol. https://modelcontextprotocol.io.
Anthropic. 2024c. “The Claude 3 Model Family: Opus, Sonnet, Haiku.” Anthropic Technical Report. https://www.anthropic.com/news/claude-3-family.
Anthropic. 2026. “Natural Language Autoencoders: Interpreting Neural Networks with Natural Language Descriptions.” Anthropic Research Blog. https://www.anthropic.com/research/natural-language-autoencoders.
Asai, Akari, Zeqiu Wu, Yizhong Wang, Avirup Sil, and Hannaneh Hajishirzi. 2023. “Self-RAG: Learning to Retrieve, Generate, and Critique Through Self-Reflection.” arXiv Preprint arXiv:2310.11511. https://arxiv.org/abs/2310.11511.
Azar, Mohammad Gheshlaghi, Mark Rowland, Bilal Piot, et al. 2024. “A General Theoretical Paradigm to Understand Learning from Human Feedback.” arXiv Preprint arXiv:2310.12036. https://arxiv.org/abs/2310.12036.
Ba, Jimmy Lei, Jamie Ryan Kiros, and Geoffrey E. Hinton. 2016. “Layer Normalization.” arXiv Preprint arXiv:1607.06450. https://arxiv.org/abs/1607.06450.
Bai, Yuntao, Andy Jones, Kamal Ndousse, et al. 2022. “Constitutional AI: Harmlessness from AI Feedback.” arXiv Preprint arXiv:2212.08073. https://arxiv.org/abs/2212.08073.
Banerjee, Satanjeev, and Alon Lavie. 2005. “METEOR: An Automatic Metric for MT Evaluation with Improved Correlation with Human Judgments.” Proceedings of the ACL Workshop on Intrinsic and Extrinsic Evaluation Measures for Machine Translation and/or Summarization. https://aclanthology.org/W05-0909/.
Barkan, Oren, Edan Hauon, Avi Caciularu, Ido Dagan, and Noam Koenigstein. 2021. “Grad-SAM: Explaining Transformers via Gradient Self-Attention Maps.” Proceedings of the 30th ACM International Conference on Information and Knowledge Management (CIKM). https://arxiv.org/abs/2104.13299.
Bellifemine, Fabio Luigi, Giovanni Caire, and Dominic Greenwood. 2007. JADE: Developing Multi-Agent Systems with JADE.
Beltagy, Iz, Matthew E. Peters, and Arman Cohan. 2020. “Longformer: The Long-Document Transformer.” arXiv Preprint arXiv:2004.05150. https://arxiv.org/abs/2004.05150.
Berners-Lee, Tim, James Hendler, and Ora Lassila. 2001. “The Semantic Web.” Scientific American.
Besta, Maciej, Nils Blach, Ales Kubicek, et al. 2024. “Graph of Thoughts: Solving Elaborate Problems with Large Language Models.” Proceedings of the AAAI Conference on Artificial Intelligence.
Beurer-Kellner, Luca, Marc Fischer, and Martin Vechev. 2023. “Prompting Is Programming: A Query Language for Large Language Models.” Proceedings of PLDI.
Bonatti, Rogerio, Dan Zhao, Francesco Bonacci, et al. 2024. “Windows Agent Arena: Evaluating Multi-Modal OS Agents at Scale.” arXiv Preprint arXiv:2409.08264. https://arxiv.org/abs/2409.08264.
Bono, Edward de. 1985. Six Thinking Hats.
Bradley, Ralph Allan, and Milton E. Terry. 1952. “Rank Analysis of Incomplete Block Designs: I. The Method of Paired Comparisons.” Biometrika. https://www.jstor.org/stable/2334029.
Bricken, Trenton, Adly Templeton, Joshua Batson, et al. 2023. “Towards Monosemanticity: Decomposing Language Models with Dictionary Learning.” Transformer Circuits Thread. https://transformer-circuits.pub/2023/monosemantic-features/index.html.
Brockman, Greg, Vicki Cheung, Ludwig Pettersson, et al. 2016. OpenAI Gym.
Brown, Tom, Benjamin Mann, Nick Ryder, et al. 2020. “Language Models Are Few-Shot Learners.” NeurIPS.
Burges, Chris, Tal Shaked, Erin Renshaw, et al. 2005. “Learning to Rank Using Gradient Descent.” Proceedings of the 22nd International Conference on Machine Learning (ICML).
Burges, Christopher J. C., Robert Ragno, and Quoc V. Le. 2006. “Learning to Rank with Nonsmooth Cost Functions.” Advances in Neural Information Processing Systems (NeurIPS).
Cai, Tianle, Yuhong Li, Zhengyang Geng, et al. 2024. “Medusa: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads.” Proceedings of the 41st International Conference on Machine Learning (ICML). https://arxiv.org/abs/2401.10774.
Campbell, Brian, John Bradley, Nat Sakimura, and Torsten Lodderstedt. 2020. OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens. https://datatracker.ietf.org/doc/html/rfc8705.
Campbell, Murray, A. Joseph Hoane Jr., and Feng-hsiung Hsu. 2002. “Deep Blue.” Artificial Intelligence.
Cao, Zhe, Tao Qin, Tie-Yan Liu, Ming-Feng Tsai, and Hang Li. 2007. “Learning to Rank: From Pairwise Approach to Listwise Approach.” Proceedings of the 24th International Conference on Machine Learning (ICML).
Carlini, Nicholas, Florian Tramer, Eric Wallace, et al. 2021. “Extracting Training Data from Large Language Models.” USENIX Security Symposium. https://arxiv.org/abs/2012.07805.
Chainlit. 2024. Chainlit: Build Production-Ready Conversational AI Applications. https://chainlit.io.
Chase, Harrison. 2022. LangChain. https://github.com/langchain-ai/langchain.
Chawla, Kushal, Jaysa Ramirez, Rene Clever, Gale Lucas, Jonathan May, and Jonathan Gratch. 2021. “CaSiNo: A Corpus of Campsite Negotiation Dialogues for Automatic Negotiation Systems.” Proceedings of NAACL.
Chen, Huayu, Guande Zheng, Yimeng Kim, and Yiwen Chen. 2024. “Noise Contrastive Alignment of Language Models with Explicit Rewards.” arXiv Preprint arXiv:2402.05369. https://arxiv.org/abs/2402.05369.
Chen, Jianlv, Shitao Luo, Mingzheng Zhang, Zheng Liu, Yingxia Xiao, and Defu Han. 2022. “Hybrid Retrieval for Open-Domain Question Answering.” arXiv Preprint arXiv:2210.06029. https://arxiv.org/abs/2210.06029.
Chen, Jianlv, Shitao Xiao, Peitian Zhang, Kun Luo, Defu Lian, and Zheng Liu. 2024. “BGE M3-Embedding: Multi-Lingual, Multi-Functionality, Multi-Granularity Text Embeddings Through Self-Knowledge Distillation.” arXiv Preprint arXiv:2402.03216.
Chen, Lingjiao, Matei Zaharia, and James Zou. 2023. “FrugalGPT: How to Use Large Language Models While Reducing Cost and Improving Performance.” arXiv Preprint arXiv:2305.05176. https://arxiv.org/abs/2305.05176.
Chen, Mark, Jerry Tworek, Heewoo Jun, et al. 2021. “Evaluating Large Language Models Trained on Code.” arXiv Preprint arXiv:2107.03374. https://arxiv.org/abs/2107.03374.
Chen, Shouyuan, Sherman Wong, Liangjian Chen, and Yuandong Tian. 2023. “Extending Context Window of Large Language Models via Positional Interpolation.” arXiv Preprint arXiv:2306.15595. https://arxiv.org/abs/2306.15595.
Chen, Tianqi, Bing Xu, Chiyuan Zhang, and Carlos Guestrin. 2016. “Training Deep Nets with Sublinear Memory Cost.” arXiv Preprint arXiv:1604.06174.
Chen, Zhiyu, Yiwei Deng, Ruiqi Zhang, Hao Sun, and Weizhu Chen. 2025. “GSPO: Sequence-Level Policy Optimization for Language Model Alignment.” arXiv Preprint arXiv:2502.12459. https://arxiv.org/abs/2502.12459.
Chhikara, Prateek, Dev Khant, Saket Aryan, Taranjeet Singh, and Deshraj Yadav. 2025. “Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory.” arXiv Preprint arXiv:2504.19413. https://arxiv.org/abs/2504.19413.
Choi, Seokhyun, Hyunji Park, Doyoung Moon, Kyuyoung Kim, and Edward Choi. 2025. “GOPO: Group Ordinal Policy Optimization for LLM Alignment with Non-Verifiable Rewards.” arXiv Preprint arXiv:2505.12948. https://arxiv.org/abs/2505.12948.
Chowdhery, Aakanksha, Sharan Narang, Jacob Devlin, et al. 2022. “PaLM: Scaling Language Modeling with Pathways.” arXiv Preprint arXiv:2204.02311.
Chowdhury, Sayak Ray, Anush Chakraborty, Srinadh Natarajan, Alekh Agarwal, and David Sontag. 2024. “Provably Robust DPO: Aligning Language Models with Noisy Feedback.” arXiv Preprint arXiv:2403.00409. https://arxiv.org/abs/2403.00409.
Christiano, Paul F., Jan Leike, Tom B. Brown, Miljan Martic, Shane Legg, and Dario Amodei. 2017. “Deep Reinforcement Learning from Human Preferences.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/1706.03741.
Chuang, Yung-Sung, Yujia Xie, Hongyin Luo, Yoon Kim, James Glass, and Pengcheng He. 2024. “DoLA: Decoding by Contrasting Layers Improves Factuality in Large Language Models.” Proceedings of the 12th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2309.03883.
Chung, Hyung Won, Le Hou, Shayne Longpre, et al. 2024. “Scaling Instruction-Finetuned Language Models.” Journal of Machine Learning Research. https://arxiv.org/abs/2210.11416.
Cohen, Jacob. 1960. “A Coefficient of Agreement for Nominal Scales.” Educational and Psychological Measurement.
Consortium, World Wide Web. 2022. Decentralized Identifiers (DIDs) v1.0. https://www.w3.org/TR/did-core/.
Cormack, Gordon V., Charles L. A. Clarke, and Stefan Buettcher. 2009. “Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods.” Proceedings of the 32nd International ACM SIGIR Conference.
Cunningham, Hoagy, Aidan Ewart, Logan Riggs, Robert Huben, and Lee Sharkey. 2024. “Sparse Autoencoders Find Highly Interpretable Features in Language Models.” Proceedings of the 12th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2309.08600.
Dao, Tri. 2024. “FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2307.08691.
Dao, Tri, Daniel Y. Fu, Stefano Ermon, Atri Rudra, and Christopher Ré. 2022. “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2205.14135.
Das, Abhishek, Théophile Gerber, Georgia Gkioxari, Stefan Lee, Devi Parikh, and Dhruv Batra. 2019. “TarMAC: Targeted Multi-Agent Communication.” Proceedings of the 36th International Conference on Machine Learning (ICML). https://arxiv.org/abs/1810.11187.
Databricks. 2024. “DBRX: A New State-of-the-Art Open LLM.” Databricks Blog. https://www.databricks.com/blog/introducing-dbrx-new-state-art-open-llm.
DeepMind, Google. 2024. AlphaProof and AlphaGeometry 2: Solving Olympiad Geometry Without Human Demonstrations. https://deepmind.google/discover/blog/ai-solves-imo-problems-at-silver-medal-level/.
DeepSeek-AI. 2024a. “DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model.” arXiv Preprint arXiv:2405.04434. https://arxiv.org/abs/2405.04434.
DeepSeek-AI. 2024b. “DeepSeek-V3 Technical Report.” arXiv Preprint arXiv:2412.19437. https://arxiv.org/abs/2412.19437.
DeepSeek-AI, Daya Guo, Dejian Yang, et al. 2025. “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning.” arXiv Preprint arXiv:2501.12948. https://arxiv.org/abs/2501.12948.
Deng, Xiang, Yu Gu, Boyuan Zheng, et al. 2023. “Mind2Web: Towards a Generalist Agent for the Web.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2306.06070.
Dennis, Michael, Natasha Jaques, Eugene Vinitsky, et al. 2020. “Emergent Complexity and Zero-Shot Transfer via Unsupervised Environment Design.” Advances in Neural Information Processing Systems (NeurIPS).
Dettmers, Tim, Artidoro Pagnoni, Ari Holtzman, and Luke Zettlemoyer. 2023. “QLoRA: Efficient Finetuning of Quantized Language Models.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2305.14314.
Devlin, Jacob, Ming-Wei Chang, Kenton Lee, and Kristina Toutanova. 2019. “BERT: Pre-Training of Deep Bidirectional Transformers for Language Understanding.” Proceedings of NAACL-HLT. https://arxiv.org/abs/1810.04805.
Dong, Yixin, Charlie Moon, Yuekai Wang, et al. 2024. “XGrammar: Flexible and Efficient Structured Generation Engine for Large Language Models.” arXiv Preprint arXiv:2411.15100.
Du, Yilun, Shuang Li, Antonio Torralba, Joshua B. Tenenbaum, and Igor Mordatch. 2023. “Improving Factuality and Reasoning in Language Models Through Multiagent Debate.” Proceedings of the 41st International Conference on Machine Learning (ICML). https://arxiv.org/abs/2305.14325.
Duan, Yan, John Schulman, Xi Chen, Peter L. Bartlett, Ilya Sutskever, and Pieter Abbeel. 2016. “RL2: Fast Reinforcement Learning via Slow Reinforcement Learning.” arXiv Preprint arXiv:1611.02779.
Durfee, Edmund H., Victor R. Lesser, and Daniel D. Corkill. 1989. “Trends in Cooperative Distributed Problem Solving.” IEEE Transactions on Knowledge and Data Engineering. https://doi.org/10.1109/69.43404.
Ebbinghaus, Hermann. 1885. Über Das Gedächtnis: Untersuchungen Zur Experimentellen Psychologie.
Edge, Darren, Ha Trinh, Newman Cheng, et al. 2024. “From Local to Global: A Graph RAG Approach to Query-Focused Summarization.” arXiv Preprint arXiv:2404.16130. https://arxiv.org/abs/2404.16130.
Egiazarian, Vage, Andrei Panferov, Denis Kuznedelev, Elias Frantar, Artem Babber, and Dan Alistarh. 2024. “Extreme Compression of Large Language Models via Additive Quantization.” arXiv Preprint arXiv:2401.06118. https://arxiv.org/abs/2401.06118.
Elo, Arpad E. 1978. The Rating of Chess Players, Past and Present.
Es, Shahul, Jithin James, Luis Espinosa-Anke, and Steven Schockaert. 2023. “RAGAs: Automated Evaluation of Retrieval Augmented Generation.” arXiv Preprint arXiv:2309.15217. https://arxiv.org/abs/2309.15217.
Ethayarajh, Kawin. 2019. “How Contextual Are Contextualized Word Representations? Comparing the Geometry of BERT, ELMo, and GPT-2 Embeddings.” Proceedings of the 2019 Conference on Empirical Methods in Natural Language Processing (EMNLP). https://arxiv.org/abs/1909.00512.
Ethayarajh, Kawin, Winnie Xu, Niklas Muennighoff, Dan Jurafsky, and Douwe Kiela. 2024. “KTO: Model Alignment as Prospect Theoretic Optimization.” arXiv Preprint arXiv:2402.01306. https://arxiv.org/abs/2402.01306.
Face, Hugging. 2025. OpenEnv: An Interface Library for RL Post-Training with Environments. https://github.com/huggingface/OpenEnv.
Fedus, William, Barret Zoph, and Noam Shazeer. 2022. “Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity.” Journal of Machine Learning Research.
Ferrucci, David, Eric Brown, Jennifer Chu-Carroll, et al. 2010. “Building Watson: An Overview of the DeepQA Project.” AI Magazine.
Firth, John Rupert. 1957. “A Synopsis of Linguistic Theory, 1930–1955.” Studies in Linguistic Analysis.
Fleiss, Joseph L. 1971. “Measuring Nominal Scale Agreement Among Many Raters.” Psychological Bulletin.
Formal, Thibault, Carlos Lassance, Benjamin Piwowarski, and Stéphane Clinchant. 2021. “SPLADE v2: Sparse Lexical and Expansion Model for Information Retrieval.” arXiv Preprint arXiv:2109.10086.
Formal, Thibault, Benjamin Piwowarski, and Stéphane Clinchant. 2021. “SPLADE: Sparse Lexical and Expansion Model for First Stage Ranking.” Proceedings of the 44th International ACM SIGIR Conference on Research and Development in Information Retrieval.
Forte, Tiago. 2022. Building a Second Brain: A Proven Method to Organize Your Digital Life and Unlock Your Creative Potential.
Francis, Nadime, Alastair Green, Paolo Guagliardo, et al. 2018. “Cypher: An Evolving Query Language for Property Graphs.” Proceedings of the 2018 International Conference on Management of Data (SIGMOD).
Frantar, Elias, and Dan Alistarh. 2023. “SparseGPT: Massive Language Models Can Be Accurately Pruned in One-Shot.” Proceedings of the 40th International Conference on Machine Learning (ICML). https://arxiv.org/abs/2301.00774.
Frantar, Elias, Saleh Ashkboos, Torsten Hoefler, and Dan Alistarh. 2023. “GPTQ: Accurate Post-Training Quantization for Generative Pre-Trained Transformers.” arXiv Preprint arXiv:2210.17323. https://arxiv.org/abs/2210.17323.
Fu, Yao, Rameswar Panda, Xinyao Niu, et al. 2024. “Data Engineering for Scaling Language Models to 128K Context.” arXiv Preprint arXiv:2402.10171. https://arxiv.org/abs/2402.10171.
Fu, Yichao, Peter Bailis, Ion Stoica, and Hao Zhang. 2024. “Break the Sequential Dependency of LLM Inference Using Lookahead Decoding.” arXiv Preprint arXiv:2402.02057. https://arxiv.org/abs/2402.02057.
Gal, Avigdor, Ateret Anaby-Tavor, Alberto Trombetta, and Danilo Montesi. 2005. “A Framework for Modeling and Evaluating Automatic Semantic Reconciliation.” Proceedings of the 31st International Conference on Very Large Data Bases (VLDB). https://link.springer.com/chapter/10.1007/11896548_42.
Gallegos, Isabel O., Ryan A. Rossi, Joe Barber, Shanshan Tong, et al. 2024. “Bias and Fairness in Large Language Models: A Survey.” Computational Linguistics. https://arxiv.org/abs/2309.00770.
Gao, Leo, John Schulman, and Jacob Hilton. 2023. “Scaling Laws for Reward Model Overoptimization.” Proceedings of the 40th International Conference on Machine Learning (ICML).
Gao, Luyu, Xueguang Ma, Jimmy Lin, and Jamie Callan. 2023. “Precise Zero-Shot Dense Retrieval Without Relevance Labels.” Proceedings of the 61st Annual Meeting of the Association for Computational Linguistics (ACL). https://arxiv.org/abs/2212.10496.
Garcı́a, Javier, and Fernando Fernández. 2015. “A Comprehensive Survey on Safe Reinforcement Learning.” Journal of Machine Learning Research.
Gemini Team, Google. 2024. “Gemini 1.5: Unlocking Multimodal Understanding Across Millions of Tokens of Context.” arXiv Preprint arXiv:2403.05530. https://arxiv.org/abs/2403.05530.
Gerganov, Georgi. 2023. GGUF: GPT-Generated Unified Format (llama.cpp). https://github.com/ggerganov/llama.cpp.
Geva, Mor, Roei Schuster, Jonathan Berant, and Omer Levy. 2021. “Transformer Feed-Forward Layers Are Key-Value Memories.” Proceedings of the 2021 Conference on Empirical Methods in Natural Language Processing (EMNLP).
Gloeckle, Fabian, Badr Youbi Idrissi, Baptiste Rozière, David Lopez-Paz, and Gabriel Synnaeve. 2024. “Better & Faster Large Language Models via Multi-Token Prediction.” Proceedings of the 41st International Conference on Machine Learning (ICML). https://arxiv.org/abs/2404.19737.
Goodhart, Charles A. E. 1984. “Problems of Monetary Management: The U.K. Experience.” Monetary Theory and Practice.
Google. 2025. Agent2Agent (A2A) Protocol. https://developers.google.com/agent2agent.
Gorbatenko, Alexander. 2024. “Online DPO with Synchronised Reference Model Updates.” TRL Documentation.
Grassé, Pierre-Paul. 1959. “La Reconstruction Du Nid Et Les Coordinations Interindividuelles Chez Bellicositermes Natalensis Et Cubitermes Sp. La Théorie de La Stigmergie: Essai d’interprétation Du Comportement Des Termites Constructeurs.” Insectes Sociaux.
Grattafiori, Aaron, Abhimanyu Dubey, Abhinav Jauhri, et al. 2024. “The Llama 3 Herd of Models.” arXiv Preprint arXiv:2407.21783. https://arxiv.org/abs/2407.21783.
Graves, Alex, Greg Wayne, Malcolm Reynolds, et al. 2016. “Hybrid Computing Using a Neural Network with Dynamic External Memory.” Nature.
Greshake, Kai, Sahar Abdelnabi, Shailesh Mishra, Christoph Endres, Thorsten Holz, and Mario Fritz. 2023. “Not What You’ve Signed up for: Compromising Real-World LLM-Integrated Applications with Indirect Prompt Injection.” Proceedings of the 16th ACM Workshop on Artificial Intelligence and Security (AISec).
Group, JSON-RPC Working. 2010. JSON-RPC 2.0 Specification. https://www.jsonrpc.org/specification.
Gu, Albert, and Tri Dao. 2023. “Mamba: Linear-Time Sequence Modeling with Selective State Spaces.” arXiv Preprint arXiv:2312.00752. https://arxiv.org/abs/2312.00752.
Gu, Albert, and Tri Dao. 2024. “Mamba: Linear-Time Sequence Modeling with Selective State Spaces.” Proceedings of the 41st International Conference on Machine Learning (ICML). https://arxiv.org/abs/2312.00752.
Guo, Chuan, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger. 2017. “On Calibration of Modern Neural Networks.” International Conference on Machine Learning (ICML).
Guo, Mandy, Joshua Ainslie, David Uthus, et al. 2022. “LongT5: Efficient Text-to-Text Transformer for Long Sequences.” Findings of the Association for Computational Linguistics: NAACL 2022. https://arxiv.org/abs/2112.07916.
Guo, Ruiqi, Philip Sun, Erik Lindgren, et al. 2020. “Accelerating Large-Scale Inference with Anisotropic Vector Quantization.” International Conference on Machine Learning (ICML).
Guo, Shangmin, Biao Zhang, Tianlin Liu, et al. 2024. “Direct Language Model Alignment from Online AI Feedback.” arXiv Preprint arXiv:2402.04792. https://arxiv.org/abs/2402.04792.
Guu, Kelvin, Kenton Lee, Zora Tung, Panupong Pasupat, and Ming-Wei Chang. 2020. “REALM: Retrieval-Augmented Language Model Pre-Training.” Proceedings of the 37th International Conference on Machine Learning (ICML). https://arxiv.org/abs/2002.08909.
Haarnoja, Tuomas, Aurick Zhou, Pieter Abbeel, and Sergey Levine. 2018. “Soft Actor-Critic: Off-Policy Maximum Entropy Deep Reinforcement Learning with a Stochastic Actor.” Proceedings of the 35th International Conference on Machine Learning (ICML). https://arxiv.org/abs/1801.01290.
Hafner, Danijar, Timothy Lillicrap, Jimmy Ba, and Mohammad Norouzi. 2020. “Dream to Control: Learning Behaviors by Latent Imagination.” Proceedings of the 8th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/1912.01603.
Han, Chi, Minlie Li, and Wenting Chen. 2025. “SAPO: Soft Adaptive Policy Optimization for Efficient LLM Alignment.” arXiv Preprint arXiv:2503.01739. https://arxiv.org/abs/2503.01739.
Han, Daniel, and Michael Han. 2024. Unsloth: Efficient LLM Fine-Tuning. https://github.com/unslothai/unsloth.
Hardt, Dick. 2012. The OAuth 2.0 Authorization Framework. https://datatracker.ietf.org/doc/html/rfc6749.
Harris, Steve, and Andy Seaborne. 2013. SPARQL 1.1 Query Language. https://www.w3.org/TR/sparql11-query/.
Hayes-Roth, Barbara. 1985. “A Blackboard Architecture for Control.” Artificial Intelligence. https://doi.org/10.1016/0004-3702(85)90063-3.
Hayou, Soufiane, Nikhil Ghosh, and Bin Yu. 2024. “LoRA+: Efficient Low Rank Adaptation of Large Models.” arXiv Preprint arXiv:2402.12354. https://arxiv.org/abs/2402.12354.
Hendrycks, Dan, Collin Burns, Steven Basart, et al. 2021. “Measuring Massive Multitask Language Understanding.” International Conference on Learning Representations (ICLR).
Herbrich, Ralf, Tom Minka, and Thore Graepel. 2006. “TrueSkill: A Bayesian Skill Rating System.” Advances in Neural Information Processing Systems (NeurIPS). https://proceedings.neurips.cc/paper/2006/hash/f44ee263952e65b3610b8ba51229d1f9-Abstract.html.
Hinton, Geoffrey, Oriol Vinyals, and Jeff Dean. 2015. “Distilling the Knowledge in a Neural Network.” arXiv Preprint arXiv:1503.02531.
Hoffmann, Jordan, Sebastian Borgeaud, Arthur Mensch, et al. 2022. “Training Compute-Optimal Large Language Models.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2203.15556.
Hong, Jiwoo, Noah Lee, and James Thorne. 2024. “ORPO: Monolithic Preference Optimization Without Reference Model.” arXiv Preprint arXiv:2403.07691. https://arxiv.org/abs/2403.07691.
Hong, Sirui, Mingchen Zhuge, Jonathan Chen, et al. 2024. MetaGPT: Meta Programming for a Multi-Agent Collaborative Framework.
Hosseini, Arian, Xingdi Yuan, Pascal Poupart, Adam Trischler, and Yoshua Bengio. 2024. “V-STaR: Training Verifiers for Self-Taught Reasoners.” arXiv Preprint arXiv:2402.06457.
Houlsby, Neil, Andrei Giber, Stanislaw Jastrzebski, et al. 2019. “Parameter-Efficient Transfer Learning for NLP.” International Conference on Machine Learning (ICML). https://arxiv.org/abs/1902.00751.
Hsu, Pin-Lun, Yun Dai, Vignesh Kothapalli, et al. 2024. “Liger-Kernel: Efficient Triton Kernels for LLM Training.” arXiv Preprint arXiv:2410.10989. https://arxiv.org/abs/2410.10989.
Hu, Edward J., Yelong Shen, Phillip Wallis, et al. 2022a. “LoRA: Low-Rank Adaptation of Large Language Models.” arXiv Preprint arXiv:2106.09685.
Hu, Edward J., Yelong Shen, Phillip Wallis, et al. 2022b. “LoRA: Low-Rank Adaptation of Large Language Models.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2106.09685.
Hu, Jian, Xibin Tao, Weixun Zhu, Sicheng Yang, Jingwen Liu, and Zilin Li. 2024. “OpenRLHF: An Easy-to-Use, Scalable and High-Performance RLHF Framework.” arXiv Preprint arXiv:2405.11143.
Hu, Qinghao, Shang Yang, et al. 2025. “Taming the Long-Tail: Efficient Reasoning RL Training with Adaptive Drafter.” arXiv Preprint arXiv:2511.16665. https://arxiv.org/abs/2511.16665.
Hu, Shengding, Yuge Tu, Xu Han, et al. 2024. “MiniCPM: Unveiling the Potential of Small Language Models with Scalable Training Strategies.” arXiv Preprint arXiv:2404.06395. https://arxiv.org/abs/2404.06395.
Huang, Qian, Jian Vora, Percy Liang, and Jure Leskovec. 2024. “MLAgentBench: Evaluating Language Agents on Machine Learning Experimentation.” Proceedings of the 41st International Conference on Machine Learning (ICML). https://arxiv.org/abs/2310.03302.
Huang, Yanping, Youlong Cheng, Ankur Bapna, et al. 2019. “GPipe: Efficient Training of Giant Neural Networks Using Pipeline Parallelism.” Advances in Neural Information Processing Systems (NeurIPS).
Hubinger, Evan, Carson Denison, Jesse Mu, et al. 2024. “Sleeper Agents: Training Deceptive LLMs That Persist Through Safety Training.” arXiv Preprint arXiv:2401.05566.
Inc, LangChain. 2024a. LangGraph Studio: The First Agent IDE. https://github.com/langchain-ai/langgraph-studio.
Inc, LangChain. 2024b. LangGraph: Build Stateful Multi-Actor Applications with LLMs. https://github.com/langchain-ai/langgraph.
Inc, Streamlit. 2024. Streamlit: The Fastest Way to Build and Share Data Apps. https://streamlit.io.
Intelligent Physical Agents, Foundation for. 2002. FIPA ACL Message Structure Specification. http://www.fipa.org/specs/fipa00061/.
Irving, Geoffrey, Paul Christiano, and Dario Amodei. 2018. “AI Safety via Debate.” arXiv Preprint arXiv:1805.00899. https://arxiv.org/abs/1805.00899.
Jaderberg, Max, Wojciech M. Czarnecki, Iain Dunning, et al. 2019. “Human-Level Performance in 3D Multiplayer Games with Population-Based Reinforcement Learning.” Science. https://www.science.org/doi/10.1126/science.aau6249.
Jain, Neel, Ping-yeh Chiang, Yuxin Wen, et al. 2024. “NEFTune: Noisy Embeddings Improve Instruction Finetuning.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2310.05914.
Jain, Sarthak, and Byron C. Wallace. 2019. “Attention Is Not Explanation.” Proceedings of the 2019 Conference of the North American Chapter of the Association for Computational Linguistics (NAACL). https://arxiv.org/abs/1902.10186.
Jang, Eric, Shixiang Gu, and Ben Poole. 2017. “Categorical Reparameterization with Gumbel-Softmax.” International Conference on Learning Representations (ICLR).
Jeong, Soyeong, Jinheon Baek, Sukmin Cho, Sung Ju Hwang, and Jong C. Park. 2024. “Adaptive-RAG: Learning to Adapt Retrieval-Augmented Large Language Models Through Question Complexity.” arXiv Preprint arXiv:2403.14403. https://arxiv.org/abs/2403.14403.
Ji, Jiatao, Adam Fisch, Jason Weston, and Sainbayar Sukhbaatar. 2024. “Towards Exact Optimization of Language Model Alignment.” arXiv Preprint arXiv:2402.05369. https://arxiv.org/abs/2402.05369.
Ji, Ziwei, Nayeon Lee, Rita Frieske, et al. 2023. “Survey of Hallucination in Natural Language Generation.” ACM Computing Surveys. https://arxiv.org/abs/2202.03629.
Jiang, Albert Q., Alexandre Sablayrolles, Arthur Mensch, et al. 2023. “Mistral 7B.” arXiv Preprint arXiv:2310.06825. https://arxiv.org/abs/2310.06825.
Jiang, Albert Q., Alexandre Sablayrolles, Antoine Roux, et al. 2024. “Mixtral of Experts.” arXiv Preprint arXiv:2401.04088. https://arxiv.org/abs/2401.04088.
Jiang, Minqi, Edward Grefenstette, and Tim Rocktäschel. 2021. “Prioritized Level Replay.” Proceedings of the 38th International Conference on Machine Learning (ICML). https://arxiv.org/abs/2010.03934.
Jimenez, Carlos E., John Yang, Alexander Wettig, et al. 2024. “SWE-bench: Can Language Models Resolve Real-World GitHub Issues?” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2310.06770.
Jin, Bowen, Hansi Zeng, et al. 2025. “Search-R1: Training LLMs to Reason and Leverage Search Engines with Reinforcement Learning.” arXiv Preprint arXiv:2503.09516.
Johnson, Jeff, Matthijs Douze, and Hervé Jégou. 2021. “Billion-Scale Similarity Search with GPUs.” IEEE Transactions on Big Data.
Jones, Michael, John Bradley, and Nat Sakimura. 2015. JSON Web Token (JWT). https://datatracker.ietf.org/doc/html/rfc7519.
Joshi, Mandar, Eunsol Choi, Daniel Weld, and Luke Zettlemoyer. 2017. “TriviaQA: A Large Scale Distantly Supervised Challenge Dataset for Reading Comprehension.” Proceedings of ACL.
Jumper, John, Richard Evans, Alexander Pritzel, et al. 2021. “Highly Accurate Protein Structure Prediction with AlphaFold.” Nature.
Kadavath, Saurav, Tom Conerly, Amanda Askell, et al. 2022. “Language Models (Mostly) Know What They Know.” arXiv Preprint arXiv:2207.05221. https://arxiv.org/abs/2207.05221.
Kalajdzievski, Damjan. 2023. “A Rank Stabilization Scaling Factor for Fine-Tuning with LoRA.” arXiv Preprint arXiv:2312.03732. https://arxiv.org/abs/2312.03732.
Kaplan, Jared, Sam McCandlish, Tom Henighan, et al. 2020. “Scaling Laws for Neural Language Models.” arXiv Preprint arXiv:2001.08361. https://arxiv.org/abs/2001.08361.
Karpukhin, Vladimir, Barlas Oğuz, Sewon Min, et al. 2020. “Dense Passage Retrieval for Open-Domain Question Answering.” Proceedings of the 2020 Conference on Empirical Methods in Natural Language Processing (EMNLP). https://arxiv.org/abs/2004.04906.
Khattab, Omar, Arnav Singhvi, Paridhi Maheshwari, et al. 2024. “DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines.” Proceedings of the 12th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2310.03714.
Khattab, Omar, and Matei Zaharia. 2020. “ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT.” Proceedings of the 43rd International ACM SIGIR Conference. https://arxiv.org/abs/2004.12832.
Kim, Seungone, Se June Jang, et al. 2023. “Distilling Step-by-Step! Outperforming Larger Language Models with Less Training Data and Smaller Model Sizes.” Findings of the ACL. https://arxiv.org/abs/2305.02301.
Kingma, Diederik P., and Jimmy Ba. 2015. “Adam: A Method for Stochastic Optimization.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/1412.6980.
Kirkpatrick, James, Razvan Pascanu, Neil Rabinowitz, et al. 2017. “Overcoming Catastrophic Forgetting in Neural Networks.” Proceedings of the National Academy of Sciences.
Kocsis, Levente, and Csaba Szepesvári. 2006. “Bandit Based Monte-Carlo Planning.” European Conference on Machine Learning (ECML).
Koh, Jing Yu, Robert Lo, Lawrence Jang, et al. 2024. “VisualWebArena: Evaluating Multimodal Agents on Realistic Visual Web Tasks.” Proceedings of the 62nd Annual Meeting of the Association for Computational Linguistics (ACL). https://arxiv.org/abs/2401.13649.
Kojima, Takeshi, Shixiang Shane Gu, Machel Reid, Yutaka Matsuo, and Yusuke Iwasawa. 2022. “Large Language Models Are Zero-Shot Reasoners.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2205.11916.
Kopiczko, Dawid Jan, Tijmen Blankevoort, and Markus Nagel. 2024. “VeRA: Vector-Based Random Matrix Adaptation.” arXiv Preprint arXiv:2310.11454. https://arxiv.org/abs/2310.11454.
Korthikanti, Vijay Anand, Jared Casper, Sangkug Lym, et al. 2023. “Reducing Activation Recomputation in Large Transformer Models.” Proceedings of Machine Learning and Systems (MLSys).
Krizhevsky, Alex, Ilya Sutskever, and Geoffrey E. Hinton. 2012. “ImageNet Classification with Deep Convolutional Neural Networks.” NeurIPS.
Kuhn, Lorenz, Yarin Gal, and Sebastian Farquhar. 2023. “Semantic Uncertainty: Linguistic Invariances for Uncertainty Estimation in Natural Language Generation.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2302.09664.
Küttler, Heinrich, Nantas Nardelli, Alexander H. Miller, et al. 2020. “The NetHack Learning Environment.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2006.13760.
Kwiatkowski, Tom, Jennimaria Palomaki, Olivia Redfield, et al. 2019. “Natural Questions: A Benchmark for Question Answering Research.” Transactions of the Association for Computational Linguistics.
Kwon, Woosuk, Zhuohan Li, Siyuan Zhuang, et al. 2023. “Efficient Memory Management for Large Language Model Serving with PagedAttention.” Proceedings of the ACM SIGOPS 29th Symposium on Operating Systems Principles (SOSP). https://arxiv.org/abs/2309.06180.
Lacroix, Timothée, Guillaume Obozinski, and Nicolas Usunier. 2020. “Tensor Decompositions for Temporal Knowledge Base Completion.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2004.04926.
Lála, Jakub, Odhran O’Donoghue, Aleksandar Shtedritski, Sam Cox, Samuel G. Rodriques, and Andrew D. White. 2023. “PaperQA: Retrieval-Augmented Generative Agent for Scientific Research.” arXiv Preprint arXiv:2312.07559. https://arxiv.org/abs/2312.07559.
Lambert, Nathan, Jacob Morrison, Valentina Pyatkin, et al. 2024. “Tülu 3: Pushing Frontiers in Open Language Model Post-Training.” arXiv Preprint arXiv:2411.15124. https://arxiv.org/abs/2411.15124.
Lazaridou, Angeliki, and Marco Baroni. 2020. “Emergent Multi-Agent Communication in the Deep Learning Era.” arXiv Preprint arXiv:2006.02419. https://arxiv.org/abs/2006.02419.
Le, Hung, Yue Wang, Akhilesh Deepak Gotmare, Silvio Savarese, and Steven Hoi. 2023. “CodeRL: Mastering Code Generation Through Pretrained Models and Deep Reinforcement Learning.” Advances in Neural Information Processing Systems (NeurIPS).
Le Sellier De Chezelles, Thibault, Maxime Gasse, Alexandre Drouin, Massimo Caccia, et al. 2024. “The BrowserGym Ecosystem for Web Agent Research.” arXiv Preprint arXiv:2412.05467. https://arxiv.org/abs/2412.05467.
Lee, Chankyu, Rajarshi Roy, Menber Xu, et al. 2024. “NV-Embed: Improved Techniques for Training LLMs as Generalist Embedding Models.” arXiv Preprint arXiv:2405.17428.
Lee, Katherine, Daphne Ippolito, Andrew Nystrom, et al. 2022. “Deduplicating Training Data Makes Language Models Better.” Proceedings of the 60th Annual Meeting of the ACL. https://arxiv.org/abs/2107.06499.
Lester, Brian, Rami Al-Rfou, and Noah Constant. 2021. “The Power of Scale for Parameter-Efficient Prompt Tuning.” Proceedings of the 2021 Conference on EMNLP. https://arxiv.org/abs/2104.08691.
Leviathan, Yaniv, Matan Kalman, and Yossi Matias. 2023. “Fast Inference from Transformers via Speculative Decoding.” Proceedings of the 40th International Conference on Machine Learning (ICML). https://arxiv.org/abs/2211.17192.
Lewis, Mike, Yinhan Liu, Naman Goyal, et al. 2020. “BART: Denoising Sequence-to-Sequence Pre-Training for Natural Language Generation, Translation, and Comprehension.” Proceedings of the 58th Annual Meeting of the Association for Computational Linguistics (ACL). https://arxiv.org/abs/1910.13461.
Lewis, Mike, Denis Yarats, Yann N. Dauphin, Devi Parikh, and Dhruv Batra. 2017. “Deal or No Deal? End-to-End Learning for Negotiation Dialogues.” Proceedings of EMNLP.
Lewis, Patrick, Ethan Perez, Aleksandra Piktus, et al. 2020. “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2005.11401.
Li, Shen, Yanli Zhao, Rohan Varma, et al. 2020. “PyTorch Distributed: Experiences on Accelerating Data Parallel Training.” Proceedings of the VLDB Endowment.
Li, Xiang Lisa, and Percy Liang. 2021. “Prefix-Tuning: Optimizing Continuous Prompts for Generation.” Proceedings of the 59th Annual Meeting of the ACL. https://arxiv.org/abs/2101.00190.
Li, Xian, Ari Holtzman, Daniel Fried, et al. 2023. “Contrastive Decoding: Open-Ended Text Generation as Optimization.” ACL.
Li, Yuhui, Fangyun Wei, Chao Zhang, and Hongyang Zhang. 2024. “EAGLE: Speculative Sampling Requires Rethinking Feature Uncertainty.” Proceedings of the 41st International Conference on Machine Learning (ICML). https://arxiv.org/abs/2401.15077.
Li, Zehan, Xin Zhang, Yanzhao Zhang, Dingkun Long, Pengjun Xie, and Meishan Zhang. 2023. “Towards General Text Embeddings with Multi-Stage Contrastive Learning.” arXiv Preprint arXiv:2308.03281.
Lightman, Hunter, Vineet Kosaraju, Yura Burda, et al. 2023. “Let’s Verify Step by Step.” arXiv Preprint arXiv:2305.20050.
Lin, Chin-Yew. 2004. “ROUGE: A Package for Automatic Evaluation of Summaries.” Text Summarization Branches Out: Proceedings of the ACL-04 Workshop. https://aclanthology.org/W04-1013/.
Lin, Ji, Jiaming Tang, Haotian Tang, et al. 2024. “AWQ: Activation-Aware Weight Quantization for LLM Compression and Acceleration.” Proceedings of Machine Learning and Systems (MLSys). https://arxiv.org/abs/2306.00978.
Lin, Kevin, Charlie Snell, Yu Wang, et al. 2025. “Sleep-Time Compute: Beyond Inference Scaling at Test-Time.” arXiv Preprint arXiv:2504.13171. https://arxiv.org/abs/2504.13171.
Lin, Long-Ji. 1992. “Self-Improving Reactive Agents Based on Reinforcement Learning, Planning and Teaching.” Machine Learning.
Lin, Xiaoqiang, Aritra Ghosh, Bryan Kian Hsiang Low, Anshumali Shrivastava, and Vijai Mohan. 2025. “REFRAG: Rethinking RAG Based Decoding.” arXiv Preprint arXiv:2509.01092.
Liu, Haokun, Derek Tam, Mohammed Muqeeth, et al. 2022. “Few-Shot Parameter-Efficient Fine-Tuning Is Better and Cheaper Than in-Context Learning.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2205.05638.
Liu, Hao, Matei Zaharia, and Pieter Abbeel. 2023. “Ring Attention with Blockwise Transformers for Near-Infinite Context.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2310.01889.
Liu, Jiachang, Dinghan Shen, Yizhe Zhang, Bill Dolan, Lawrence Carin, and Weizhu Chen. 2022. “What Makes Good in-Context Examples for GPT-3?” Proceedings of Deep Learning Inside Out (DeeLIO), ACL Workshop. https://arxiv.org/abs/2101.06804.
Liu, Nelson F., Kevin Lin, John Hewitt, et al. 2024b. “Lost in the Middle: How Language Models Use Long Contexts.” Transactions of the Association for Computational Linguistics. https://arxiv.org/abs/2307.03172.
Liu, Nelson F., Kevin Lin, John Hewitt, et al. 2024a. “Lost in the Middle: How Language Models Use Long Contexts.” Transactions of the Association for Computational Linguistics. https://arxiv.org/abs/2307.03172.
Liu, Qiguang et al. 2025. “Answer Convergence as a Signal for Early Stopping in Reasoning.” Proceedings of EMNLP. https://aclanthology.org/2025.emnlp-main.904/.
Liu, Shih-Yang, Chien-Yi Wang, Hongxu Yin, et al. 2024. “DoRA: Weight-Decomposed Low-Rank Adaptation.” arXiv Preprint arXiv:2402.09353. https://arxiv.org/abs/2402.09353.
Liu, Xiao, Hao Yu, Hanchen Zhang, et al. 2023. “AgentBench: Evaluating LLMs as Agents.” arXiv Preprint arXiv:2308.03688.
Liu, Xingyu et al. 2025. “Learning Like Humans: Advancing LLM Reasoning Capabilities via Adaptive Difficulty Curriculum Learning.” arXiv Preprint arXiv:2505.08364. https://arxiv.org/abs/2505.08364.
Liu, Yang, Dan Iter, Yichong Xu, Shuohang Wang, Ruochen Xu, and Chenguang Zhu. 2023. “G-Eval: NLG Evaluation Using GPT-4 with Better Human Alignment.” Proceedings of the 2023 Conference on Empirical Methods in Natural Language Processing (EMNLP). https://arxiv.org/abs/2303.16634.
Liu, Yihao, Lefan Han, Yifan Tan, et al. 2025. “Understanding and Mitigating the Pretraining Distribution Bias in GRPO.” arXiv Preprint arXiv:2505.07888. https://arxiv.org/abs/2505.07888.
Liu, Yinhan, Myle Ott, Naman Goyal, et al. 2019. “RoBERTa: A Robustly Optimized BERT Pretraining Approach.” arXiv Preprint arXiv:1907.11692. https://arxiv.org/abs/1907.11692.
Liu, Zechun, Barlas Oguz, Changsheng Zhao, et al. 2023. “LLM-QAT: Data-Free Quantization Aware Training for Large Language Models.” arXiv Preprint arXiv:2305.17888. https://arxiv.org/abs/2305.17888.
Liu, Zhuokai, Hao Dong, et al. 2025. “DLER: Doing Length Penalty Right—Incentivizing More Intelligence Per Token.” arXiv Preprint arXiv:2510.15110. https://arxiv.org/abs/2510.15110.
Liu, Zirui, Jiayi Yuan, Hongye Jin, et al. 2024. “KIVI: A Tuning-Free Asymmetric 2bit Quantization for KV Cache.” International Conference on Machine Learning (ICML). https://arxiv.org/abs/2402.02750.
Loshchilov, Ilya, and Frank Hutter. 2019. “Decoupled Weight Decay Regularization.” arXiv Preprint arXiv:1711.05101. https://arxiv.org/abs/1711.05101.
Lowe, Ryan, Yi Wu, Aviv Tamar, Jean Harb, Pieter Abbeel, and Igor Mordatch. 2017. “Multi-Agent Actor-Critic for Mixed Cooperative-Competitive Environments.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/1706.02275.
Lu, Chris, Cong Lu, Robert Tjarko Lange, Jakob Foerster, Jeff Clune, and David Ha. 2024. “The AI Scientist: Towards Fully Automated Open-Ended Scientific Discovery.” arXiv Preprint arXiv:2408.06292. https://arxiv.org/abs/2408.06292.
Lu, Yao, Max Bartolo, Alastair Moore, Sebastian Riedel, and Pontus Stenetorp. 2022. “Fantastically Ordered Prompts and Where to Find Them: Overcoming Few-Shot Prompt Order Sensitivity.” Proceedings of the 60th Annual Meeting of the Association for Computational Linguistics (ACL). https://arxiv.org/abs/2104.08786.
Luo, Zhenyu, Ziyan Chen, Yulun Jiang, et al. 2025. “ScaleRL: Scaling Reinforcement Learning for LLM Reasoning.” arXiv Preprint arXiv:2505.16356. https://arxiv.org/abs/2505.16356.
Luo, Zhixun, Jiaqi Shi, Tao Yu, and Minlie Chen. 2025. “VESPO: Variational Sequence-Level Soft Policy Optimization for LLM Alignment.” arXiv Preprint arXiv:2505.07508. https://arxiv.org/abs/2505.07508.
Madaan, Aman, Niket Tandon, Prakhar Gupta, et al. 2023. “Self-Refine: Iterative Refinement with Self-Feedback.” Advances in Neural Information Processing Systems (NeurIPS).
Maharana, Adyasha, Dong-Ho Lee, Sergey Tuber, Mohit Ruber, Francesco Barbieri, and Mohit Bansal. 2024. “LoCoMo: Long-Context Conversation with Memory Operations.” Proceedings of the 2024 Conference on Empirical Methods in Natural Language Processing (EMNLP).
Malkov, Yu. A., and D. A. Yashunin. 2020. “Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs.” IEEE Transactions on Pattern Analysis and Machine Intelligence.
Manakul, Potsawee, Adian Liusie, and Mark J. F. Gales. 2023. “SelfCheckGPT: Zero-Resource Black-Box Hallucination Detection for Generative Large Language Models.” Proceedings of EMNLP. https://arxiv.org/abs/2303.08896.
McCandlish, Sam, Jared Kaplan, Dario Amodei, and OpenAI Dota Team. 2018. “An Empirical Model of Large-Batch Training.” arXiv Preprint arXiv:1812.06162.
Mei, Jiangfei et al. 2025. “APRIL: Active Partial Rollouts in Reinforcement Learning to Tame Long-Tail Generation.” arXiv Preprint arXiv:2509.18521. https://arxiv.org/abs/2509.18521.
Meng, Yu, Mengzhou Xia, and Danqi Chen. 2024. “SimPO: Simple Preference Optimization with a Reference-Free Reward.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2405.14734.
Mialon, Grégoire, Clémentine Fourrier, Craig Swift, Thomas Wolf, Yann LeCun, and Thomas Scialom. 2024. “GAIA: A Benchmark for General AI Assistants.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2311.12983.
Micikevicius, Paulius, Sharan Narang, Jonah Alben, et al. 2018. “Mixed Precision Training.” International Conference on Learning Representations (ICLR).
Microsoft. 2023. Semantic Kernel: SDK for Integrating AI Models into Applications. https://github.com/microsoft/semantic-kernel.
Min, Sewon, Xinxi Lyu, Ari Holtzman, et al. 2022. “Rethinking the Role of Demonstrations: What Makes in-Context Learning Work?” Proceedings of the 2022 Conference on Empirical Methods in Natural Language Processing (EMNLP). https://arxiv.org/abs/2202.12837.
Mnih, Volodymyr, Adrià Puigdomènech Badia, Mehdi Mirza, et al. 2016. “Asynchronous Methods for Deep Reinforcement Learning.” Proceedings of the 33rd International Conference on Machine Learning (ICML). https://arxiv.org/abs/1602.01783.
Mnih, Volodymyr, Koray Kavukcuoglu, David Silver, et al. 2015. “Human-Level Control Through Deep Reinforcement Learning.” Nature. https://www.nature.com/articles/nature14236.
MosaicML. 2023. “MPT-7B: A New Standard for Open-Source, Commercially Usable LLMs.” MosaicML Blog. https://www.mosaicml.com/blog/mpt-7b.
Moura, João. 2023. CrewAI: Framework for Orchestrating Role-Playing Autonomous AI Agents. https://github.com/crewAIInc/crewAI.
Nakano, Reiichiro, Jacob Hilton, Suchir Balaji, et al. 2021. “WebGPT: Browser-Assisted Question-Answering with Human Feedback.” arXiv Preprint arXiv:2112.09332. https://arxiv.org/abs/2112.09332.
Narayanan, Deepak, Aaron Harlap, Amar Phanishayee, et al. 2019. “PipeDream: Generalized Pipeline Parallelism for DNN Training.” Proceedings of the 27th ACM Symposium on Operating Systems Principles (SOSP).
Narayanan, Deepak, Mohammad Shoeybi, Jared Casper, et al. 2021. “Efficient Large-Scale Language Model Training on GPU Clusters Using Megatron-LM.” arXiv Preprint arXiv:2104.04473.
Ng, Andrew Y., Daishi Harada, and Stuart J. Russell. 1999. “Policy Invariance Under Reward Transformations: Theory and Application to Reward Shaping.” Proceedings of the 16th International Conference on Machine Learning (ICML).
Nguyen, Minh. 2024. “Min-p Sampling: A Simple Baseline for Better LLM Decoding.” arXiv Preprint arXiv:2310.06022.
Nisan, Noam, Tim Roughgarden, Éva Tardos, and Vijay V. Vazirani. 2007. Algorithmic Game Theory. https://www.cs.cmu.edu/~sandholm/cs15-892F13/algorithmic-game-theory.pdf.
Nogueira, Rodrigo, and Kyunghyun Cho. 2019. “Passage Re-Ranking with BERT.” arXiv Preprint arXiv:1901.04085.
Nogueira, Rodrigo, Zhiying Jiang, Ronak Pradeep, and Jimmy Lin. 2020. “Document Ranking with a Pretrained Sequence-to-Sequence Model.” Findings of EMNLP.
Nye, Maxwell, Anders Johan Andreassen, Guy Gur-Ari, et al. 2021. “Show Your Work: Scratchpads for Intermediate Computation with Language Models.” arXiv Preprint arXiv:2112.00114. https://arxiv.org/abs/2112.00114.
Olsson, Catherine, Nelson Elhage, Neel Nanda, et al. 2022. “In-Context Learning and Induction Heads.” Transformer Circuits Thread. https://transformer-circuits.pub/2022/in-context-learning-and-induction-heads/index.html.
OpenAI. 2023. “GPT-4 Technical Report.” arXiv Preprint arXiv:2303.08774.
OpenAI. 2024a. Learning to Reason with LLMs. https://openai.com/index/learning-to-reason-with-llms/.
OpenAI. 2024b. Swarm: An Educational Framework for Lightweight Multi-Agent Orchestration. https://github.com/openai/swarm.
OpenAI. 2025a. OpenAI Agents SDK. https://github.com/openai/openai-agents-python.
OpenAI. 2025b. OpenAI o3 and o4-mini System Card. https://openai.com/index/o3-and-o4-mini-system-card/.
Ouyang, Long, Jeffrey Wu, Xu Jiang, et al. 2022. “Training Language Models to Follow Instructions with Human Feedback.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2203.02155.
Packer, Charles, Vivian Fang, Shishir G. Patil, Kevin Lin, Sarah Wooders, and Joseph E. Gonzalez. 2023. “MemGPT: Towards LLMs as Operating Systems.” arXiv Preprint arXiv:2310.08560. https://arxiv.org/abs/2310.08560.
Papineni, Kishore, Salim Roukos, Todd Ward, and Wei-Jing Zhu. 2002. “BLEU: A Method for Automatic Evaluation of Machine Translation.” Proceedings of the 40th Annual Meeting of the Association for Computational Linguistics (ACL). https://aclanthology.org/P02-1040/.
Parasuraman, Raja, and Victor Riley. 1997. “Humans and Automation: Use, Misuse, Disuse, Abuse.” Human Factors. https://doi.org/10.1518/001872097778543886.
Park, Joon Sung, Joseph C. O’Brien, Carrie J. Cai, Meredith Ringel Morris, Percy Liang, and Michael S. Bernstein. 2023. “Generative Agents: Interactive Simulacra of Human Behavior.” Proceedings of the 36th Annual ACM Symposium on User Interface Software and Technology (UIST). https://arxiv.org/abs/2304.03442.
Pathak, Deepak, Pulkit Agrawal, Alexei A. Efros, and Trevor Darrell. 2017. “Curiosity-Driven Exploration by Self-Supervised Prediction.” Proceedings of the 34th International Conference on Machine Learning (ICML).
Patil, Shishir G., Tianjun Zhang, Xin Wang, and Joseph E. Gonzalez. 2024. “Gorilla: Large Language Model Connected with Massive APIs.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2305.15334.
Peng, Bo, Eric Alcaide, Quentin Anthony, et al. 2023. “RWKV: Reinventing RNNs for the Transformer Era.” Findings of the Association for Computational Linguistics: EMNLP 2023. https://arxiv.org/abs/2305.13048.
Peng, Bowen, Jeffrey Quesnelle, Honglu Fan, and Enrico Shao. 2023. “YaRN: Efficient Context Window Extension of Large Language Models.” arXiv Preprint arXiv:2309.00071.
Perez, Ethan, Saffron Huang, Francis Song, et al. 2022. “Red Teaming Language Models with Language Models.” Proceedings of EMNLP. https://arxiv.org/abs/2202.03286.
Plackett, R. L. 1975. “The Analysis of Permutations.” Journal of the Royal Statistical Society: Series C (Applied Statistics).
Press, Ofir, Noah A. Smith, and Mike Lewis. 2022. “Train Short, Test Long: Attention with Linear Biases Enables Input Length Generalization.” ICLR.
Preston-Werner, Tom. 2024. Semantic Versioning 2.0.0. https://semver.org/.
Putta, Pranav, Edmund Mills, Naman Garg, et al. 2024. “Agent Q: Advanced Reasoning and Learning for Autonomous AI Agents.” arXiv Preprint arXiv:2408.07199.
Qi, Penghui, Xinyi Wan, Guangxing Huang, and Min Lin. 2023. “Zero Bubble Pipeline Parallelism.” arXiv Preprint arXiv:2401.10241.
Qi, Zhenting, Mingyuan Wan, Jialin Cao, and Min Lin. 2024. “Mutual Reasoning Makes Smaller LLMs Stronger Problem-Solvers.” arXiv Preprint arXiv:2408.06195.
Qin, Yiwei, Xuefeng Li, Haoyang Zou, et al. 2024. “O1 Replication Journey: A Strategic Progress Report.” arXiv Preprint arXiv:2410.18982. https://arxiv.org/abs/2410.18982.
Qin, Yujia, Shihao Liang, Yining Ye, et al. 2024. “ToolLLM: Facilitating Large Language Models to Master 16000+ Real-World APIs.” Proceedings of the 12th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2307.16789.
Rackauckas, Adrian H. 2024. RAG-Fusion: A New Take on Retrieval-Augmented Generation.
Radford, Alec, Jeffrey Wu, Rewon Child, David Luen, Dario Amodei, and Ilya Sutskever. 2019. “Language Models Are Unsupervised Multitask Learners.” OpenAI Blog. https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf.
Rafailov, Rafael, Archit Sharma, Eric Mitchell, Christopher D. Manning, Stefano Ermon, and Chelsea Finn. 2023. “Direct Preference Optimization: Your Language Model Is Secretly a Reward Model.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2305.18290.
Raffel, Colin, Noam Shazeer, Adam Roberts, et al. 2020. “Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer.” Journal of Machine Learning Research. https://arxiv.org/abs/1910.10683.
Rajbhandari, Samyam, Jeff Rasley, Olatunji Rber, and Yuxiong He. 2020. “ZeRO: Memory Optimizations Toward Training Trillion Parameter Models.” arXiv Preprint arXiv:1910.02054.
Rajbhandari, Samyam, Olatunji Ruwase, Jeff Rasley, Shaden Smith, and Yuxiong He. 2021. “ZeRO-Infinity: Breaking the GPU Memory Wall for Extreme Scale Deep Learning.” arXiv Preprint arXiv:2104.07857.
Rashid, Tabish, Mikayel Samvelyan, Christian Schroeder de Witt, Gregory Farquhar, Jakob Foerster, and Shimon Whiteson. 2018. “QMIX: Monotonic Value Function Factorisation for Deep Multi-Agent Reinforcement Learning.” Proceedings of the 35th International Conference on Machine Learning (ICML). https://arxiv.org/abs/1803.11605.
Rescorla, Eric. 2018. The Transport Layer Security (TLS) Protocol Version 1.3. https://datatracker.ietf.org/doc/html/rfc8446.
Robbins, Herbert, and Sutton Monro. 1951. “A Stochastic Approximation Method.” The Annals of Mathematical Statistics.
Robertson, Stephen, and Hugo Zaragoza. 2009. “The Probabilistic Relevance Framework: BM25 and Beyond.” Foundations and Trends in Information Retrieval.
Rumelhart, David E., Geoffrey E. Hinton, and Ronald J. Williams. 1986. “Learning Representations by Back-Propagating Errors.” Nature. https://doi.org/10.1038/323533a0.
Rummery, Gavin A., and Mahesan Niranjan. 1994. On-Line q-Learning Using Connectionist Systems.
Sanh, Victor, Lysandre Debut, Julien Chaumond, and Thomas Wolf. 2019. “DistilBERT, a Distilled Version of BERT: Smaller, Faster, Cheaper and Lighter.” arXiv Preprint arXiv:1910.01108.
Santhanam, Keshav, Omar Khattab, Jon Saad-Falcon, Christopher Potts, and Matei Zaharia. 2022. “ColBERTv2: Effective and Efficient Retrieval via Lightweight Late Interaction.” Proceedings of the 2022 Conference of the North American Chapter of the Association for Computational Linguistics (NAACL).
Schaul, Tom, John Quan, Ioannis Antonoglou, and David Silver. 2016. “Prioritized Experience Replay.” Proceedings of the 4th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/1511.05952.
Schick, Timo, Jane Dwivedi-Yu, Roberto Dessì, et al. 2023. “Toolformer: Language Models Can Teach Themselves to Use Tools.” Advances in Neural Information Processing Systems.
Schrittwieser, Julian, Ioannis Antonoglou, Thomas Hubert, et al. 2020. “Mastering Atari, Go, Chess and Shogi by Planning with a Learned Model.” Nature. https://arxiv.org/abs/1911.08265.
Schulman, John, Sergey Levine, Pieter Abbeel, Michael Jordan, and Philipp Moritz. 2015. “Trust Region Policy Optimization.” Proceedings of the 32nd International Conference on Machine Learning (ICML). https://arxiv.org/abs/1502.05477.
Schulman, John, Philipp Moritz, Sergey Levine, Michael Jordan, and Pieter Abbeel. 2016. “High-Dimensional Continuous Control Using Generalized Advantage Estimation.” Proceedings of the 4th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/1506.02438.
Schulman, John, Filip Wolski, Prafulla Dhariwal, Alec Radford, and Oleg Klimov. 2017. “Proximal Policy Optimization Algorithms.” arXiv Preprint arXiv:1707.06347. https://arxiv.org/abs/1707.06347.
Sennrich, Rico, Barry Haddow, and Alexandra Birch. 2016. “Neural Machine Translation of Rare Words with Subword Units.” Proceedings of the 54th Annual Meeting of the ACL. https://arxiv.org/abs/1508.07909.
Sergeev, Alexander, and Mike Del Balso. 2018. “Horovod: Fast and Easy Distributed Deep Learning in TensorFlow.” arXiv Preprint arXiv:1802.05799.
Shah, Jay, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, and Tri Dao. 2024. “FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-Precision.” arXiv Preprint arXiv:2407.08691. https://arxiv.org/abs/2407.08691.
Shao, Zhihong, Peiyi Wang, Qihao Zhu, et al. 2024. “DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models.” arXiv Preprint arXiv:2402.03300. https://arxiv.org/abs/2402.03300.
Shapley, Lloyd S. 1953. “Stochastic Games.” Proceedings of the National Academy of Sciences. https://www.pnas.org/doi/10.1073/pnas.39.10.1095.
Shazeer, Noam, Azalia Mirhoseini, Krzysztof Maziarz, et al. 2017. “Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/1701.06538.
Shinn, Noah, Federico Cassano, Ashwin Gopinath, Karthik Narasimhan, and Shunyu Yao. 2023. “Reflexion: Language Agents with Verbal Reinforcement Learning.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2303.11366.
Shoeybi, Mohammad, Mostofa Patwary, Raul Puri, Patrick LeGresley, Jared Casper, and Bryan Catanzaro. 2019. “Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism.” arXiv Preprint arXiv:1909.08053.
Shoham, Yoav, and Kevin Leyton-Brown. 2008. Multiagent Systems: Algorithmic, Game-Theoretic, and Logical Foundations. http://www.masfoundations.org/.
Shridhar, Mohit, Xingdi Yuan, Marc-Alexandre Côté, Yonatan Bisk, Adam Trischler, and Matthew Hausknecht. 2021. “ALFWorld: Aligning Text and Embodied Environments for Interactive Learning.” International Conference on Learning Representations (ICLR).
Silver, David, Aja Huang, Chris J. Maddison, et al. 2016. “Mastering the Game of Go with Deep Neural Networks and Tree Search.” Nature. https://www.nature.com/articles/nature16961.
Silver, David, Julian Schrittwieser, Karen Simonyan, et al. 2017. “Mastering the Game of Go Without Human Knowledge.” Nature. https://www.nature.com/articles/nature24270.
Skalse, Joar, Nikolaus Howe, Dmitrii Krasheninnikov, and David Krueger. 2022. “Defining and Characterizing Reward Hacking.” Advances in Neural Information Processing Systems. https://arxiv.org/abs/2209.13085.
Smith, Reid G. 1980. “The Contract Net Protocol: High-Level Communication and Control in a Distributed Problem Solver.” IEEE Transactions on Computers. https://doi.org/10.1109/TC.1980.1675516.
Snell, Charlie, Jaehoon Lee, Kelvin Xu, and Aviral Kumar. 2024. “Scaling LLM Test-Time Compute Optimally Can Be More Effective Than Scaling Model Parameters.” arXiv Preprint arXiv:2408.03314.
Sparck Jones, Karen. 1972. “A Statistical Interpretation of Term Specificity and Its Application in Retrieval.” Journal of Documentation.
Squire, Larry R. 1992. “Declarative and Nondeclarative Memory: Multiple Brain Systems Supporting Learning and Memory.” Journal of Cognitive Neuroscience. https://doi.org/10.1162/jocn.1992.4.3.232.
Stiennon, Nisan, Long Ouyang, Jeffrey Wu, et al. 2020. “Learning to Summarize from Human Feedback.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2009.01325.
Su, Jianlin, Murtadha Ahmed, Yu Lu, Shengfeng Pan, Wen Bo, and Yunfeng Liu. 2024. “RoFormer: Enhanced Transformer with Rotary Position Embedding.” Neurocomputing.
Su, Jianlin, Jiarun Cao, Weijie Liu, and Yangyiwen Ou. 2021. “Whitening Sentence Representations for Better Semantics and Faster Retrieval.” arXiv Preprint arXiv:2103.15316. https://arxiv.org/abs/2103.15316.
Sukhbaatar, Sainbayar, Arthur Szlam, and Rob Fergus. 2016. “Learning Multiagent Communication with Backpropagation.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/1605.07736.
Sukhbaatar, Sainbayar, Arthur Szlam, Jason Weston, and Rob Fergus. 2015. “End-to-End Memory Networks.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/1503.08895.
Sumers, Theodore R., Shunyu Yao, Karthik Narasimhan, and Thomas L. Griffiths. 2024. “Cognitive Architectures for Language Agents.” Transactions on Machine Learning Research (TMLR). https://arxiv.org/abs/2309.02427.
Sun, Mingjie, Zhuang Liu, Anna Bair, and J. Zico Kolter. 2024. “A Simple and Effective Pruning Approach for Large Language Models.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2306.11695.
Sutton, Richard S. 1988. “Learning to Predict by the Methods of Temporal Differences.” Machine Learning.
Sutton, Richard S., and Andrew G. Barto. 2018. Reinforcement Learning: An Introduction. http://incompleteideas.net/book/the-book-2nd.html.
Team, Meta PyTorch. 2025. TorchForge: PyTorch-Native Post-Training at Scale. https://github.com/meta-pytorch/torchforge.
Team, PyTorch. 2024. Torchtune: PyTorch Native Post-Training Library. https://github.com/pytorch/torchtune.
Team, Qwen. 2024a. “Qwen2.5: A Party of Foundation Models.” arXiv Preprint arXiv:2412.15115. https://arxiv.org/abs/2412.15115.
Team, Qwen. 2024b. “QwQ: Reflect Deeply on the Boundaries of the Unknown.” Qwen Blog. https://qwenlm.github.io/blog/qwq-32b-preview/.
Team, Qwen. 2025. “Qwen3 Technical Report.” arXiv Preprint arXiv:2505.09388.
Templeton, Adly, Tom Conerly, Jonathan Marcus, et al. 2024. “Scaling Monosemanticity: Extracting Interpretable Features from Claude 3 Sonnet.” Transformer Circuits Thread. https://transformer-circuits.pub/2024/scaling-monosemanticity/index.html.
Todd, Eric, Millicent L. Li, Arnab Sen Sharma, Aaron Mueller, Byron C. Wallace, and David Bau. 2024. “Function Vectors in Large Language Models.” Proceedings of the 12th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2310.15213.
Towers, Mark, Ariel Kwiatkowski, Jordan Terry, et al. 2024. “Gymnasium: A Standard Interface for Reinforcement Learning Environments.” NeurIPS Datasets and Benchmarks. https://arxiv.org/abs/2407.17032.
Tulving, Endel. 1985. “Memory and Consciousness.” Canadian Psychology / Psychologie Canadienne.
Vaswani, Ashish, Noam Shazeer, Niki Parmar, et al. 2017. “Attention Is All You Need.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/1706.03762.
Vercel. 2024. Vercel AI SDK. https://sdk.vercel.ai.
Vig, Jesse. 2019. “A Multiscale Visualization of Attention in the Transformer Model.” Proceedings of the 57th ACL: System Demonstrations. https://arxiv.org/abs/1906.05714.
Vijayakumar, Ashwin K., Michael Cogswell, Ramprasaath R. Selvaraju, et al. 2018. “Diverse Beam Search: Decoding Diverse Solutions from Neural Sequence Models.” AAAI.
Voita, Elena, David Talbot, Fedor Moiseev, Rico Sennrich, and Ivan Titov. 2019. “Analyzing Multi-Head Self-Attention: Specialized Heads Do the Heavy Lifting, the Rest Can Be Pruned.” Proceedings of the 57th Annual Meeting of the Association for Computational Linguistics (ACL). https://arxiv.org/abs/1905.09418.
Wang, Guanzhi, Yuqi Xie, Yunfan Jiang, et al. 2023. “Voyager: An Open-Ended Embodied Agent with Large Language Models.” arXiv Preprint arXiv:2305.16291. https://arxiv.org/abs/2305.16291.
Wang, Junkang, Jianfei Duan, Yue Liu, Zhangchen Yue, Hanghang Tong, and James Wang. 2024. “Beyond Reverse KL: Generalizing Direct Preference Optimization with Diverse Divergence Constraints.” Proceedings of the 12th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2309.16240.
Wang, Lei, Wanyu Xu, Yiber Lan, et al. 2023. “Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models.” Proceedings of the 61st Annual Meeting of the Association for Computational Linguistics (ACL). https://arxiv.org/abs/2305.04091.
Wang, Peiyi, Lei Li, Zhihong Shao, et al. 2024. “Math-Shepherd: Verify and Reinforce LLMs Step-by-Step Without Human Annotations.” arXiv Preprint arXiv:2312.08935. https://arxiv.org/abs/2312.08935.
Wang, Xingyao, Boxuan Ding, Ziniu Hoang, et al. 2024. “OpenHands: An Open Platform for AI Software Developers as Generalist Agents.” arXiv Preprint arXiv:2407.16741.
Wang, Xuezhi, Jason Wei, Dale Schuurmans, Quoc Le, et al. 2023. “Self-Consistency Improves Chain of Thought Reasoning in Language Models.” Proceedings of the 11th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2203.11171.
Wang, Xuezhi, Jason Wei, Dale Schuurmans, Quoc V. Le, et al. 2023. “Self-Consistency Improves Chain of Thought Reasoning in Language Models.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2203.11171.
Wang, Yifan et al. 2025. “Improving Data Efficiency for LLM Reinforcement Fine-Tuning via Difficulty-Targeted Online Data Selection.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2506.05316.
Wang, Yizhong, Yeganeh Kordi, Swaroop Mishra, et al. 2022. “Self-Instruct: Aligning Language Models with Self-Generated Instructions.” arXiv Preprint arXiv:2212.10560. https://arxiv.org/abs/2212.10560.
Watkins, Christopher J. C. H. 1989. Learning from Delayed Rewards.
Wei, Jason, Xuezhi Wang, Dale Schuurmans, et al. 2022. “Chain-of-Thought Prompting Elicits Reasoning in Large Language Models.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2201.11903.
Weiss, Gerhard. 1999. Multiagent Systems: A Modern Approach to Distributed Artificial Intelligence.
Werra, Leandro von, Younes Belkada, Lewis Tunstall, et al. 2022. TRL: Transformer Reinforcement Learning. https://github.com/huggingface/trl.
Weston, Jason, Sumit Chopra, and Antoine Bordes. 2015. “Memory Networks.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/1410.3916.
Willard, Brandon T., and Rémi Louf. 2023. “Efficient Guided Generation for Large Language Models.” arXiv Preprint arXiv:2307.09702. https://arxiv.org/abs/2307.09702.
Williams, Ronald J. 1992. “Simple Statistical Gradient-Following Algorithms for Connectionist Reinforcement Learning.” Machine Learning. https://link.springer.com/article/10.1007/BF00992696.
Wilson, Edwin B. 1927. “Probable Inference, the Law of Succession, and Statistical Inference.” Journal of the American Statistical Association. https://www.jstor.org/stable/2276774.
Wooldridge, Michael. 2009. An Introduction to MultiAgent Systems.
Wooldridge, Michael, Nicholas R. Jennings, and David Kinny. 2000. “The Gaia Methodology for Agent-Oriented Analysis and Design.” Autonomous Agents and Multi-Agent Systems.
Workshop, BigScience. 2023. “BLOOM: A 176B-Parameter Open-Access Multilingual Language Model.” arXiv Preprint arXiv:2211.05100. https://arxiv.org/abs/2211.05100.
Wu, Di, Hongwei Wang, Wenhao Yu, Yuwei Zhang, Kai-Wei Chang, and Dong Yu. 2025. “LongMemEval: Benchmarking Chat Assistants on Long-Term Interactive Memory.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2410.10813.
Wu, Qingyun, Gagan Bansal, Jieyu Zhang, et al. 2023. “AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation.” arXiv Preprint arXiv:2308.08155.
Wu, Zhengbao, Aman Arora, Zhiqiang Wang, Byung-Gon Kim, and Tian Huang. 2024. “Retrieval Head Mechanistically Explains Long-Context Factuality.” arXiv Preprint arXiv:2404.15574. https://arxiv.org/abs/2404.15574.
Wu, Zhenyu, Qinghua Hu, Yin Zhang, Yiming Gao, and Jiangtao Chen. 2024. “An Empirical Analysis of Compute-Optimal Inference for Problem-Solving with Language Models.” arXiv Preprint arXiv:2408.00724.
Xi, Zhiheng, Yiwen Ding, Wenxiang Chen, et al. 2024. “AgentGym: Evolving Large Language Model-Based Agents Across Diverse Environments.” arXiv Preprint arXiv:2406.04151. https://arxiv.org/abs/2406.04151.
Xia, Fen, Tie-Yan Liu, Jue Wang, Wensheng Zhang, and Hang Li. 2008. “Listwise Approach to Learning to Rank: Theory and Algorithm.” Proceedings of the 25th International Conference on Machine Learning (ICML).
Xiao, Guangxuan, Ji Lin, Mickael Seznec, Hao Wu, Julien Demouth, and Song Han. 2023. “SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models.” Proceedings of the 40th International Conference on Machine Learning (ICML). https://arxiv.org/abs/2211.10438.
Xiao, Guangxuan, Yuandong Tian, Beidi Chen, Song Han, and Mike Lewis. 2024a. “Efficient Streaming Language Models with Attention Sinks.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2309.17453.
Xiao, Guangxuan, Yuandong Tian, Beidi Chen, Song Han, and Mike Lewis. 2024b. “Efficient Streaming Language Models with Attention Sinks.” Proceedings of the 12th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2309.17453.
Xiao, Shitao, Zheng Liu, Peitian Zhang, and Niklas Muennighoff. 2023. “C-Pack: Packaged Resources to Advance General Chinese Embedding.” arXiv Preprint arXiv:2309.07597.
Xie, Sang Michael, Aditi Raghunathan, Percy Liang, and Tengyu Ma. 2022. “An Explanation of in-Context Learning as Implicit Bayesian Inference.” Proceedings of the 10th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2111.02080.
Xie, Tianbao, Danyang Zhang, Jixuan Chen, et al. 2024. “OSWorld: Benchmarking Multimodal Agents for Open-Ended Tasks in Real Computer Environments.” Advances in Neural Information Processing Systems.
Xu, Can, Qingfeng Sun, Kai Zheng, et al. 2023. “WizardLM: Empowering Large Language Models to Follow Complex Instructions.” arXiv Preprint arXiv:2304.12244. https://arxiv.org/abs/2304.12244.
Xu, Haoxiang, Hongyuan Zhao, Ying Liu, and Dong Wei. 2025. “It Takes Two: Pairwise Preference Optimization with Two Rollouts.” arXiv Preprint arXiv:2505.07856. https://arxiv.org/abs/2505.07856.
Xu, Wujiang, Zujie Liang, Kai Mei, Hang Gao, Juntao Tan, and Yongfeng Zhang. 2025. “A-Mem: Agentic Memory for LLM Agents.” Advances in Neural Information Processing Systems (NeurIPS).
Yan, Shi-Qi, Jia-Chen Gu, Yun Zhu, and Zhen-Hua Ling. 2024. “Corrective Retrieval Augmented Generation.” arXiv Preprint arXiv:2401.15884. https://arxiv.org/abs/2401.15884.
Yang, Chengrun, Xuezhi Wang, Yifeng Lu, et al. 2024. “Large Language Models as Optimizers.” Proceedings of the 12th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2309.03409.
Yang, Jingwen, Yuxuan Zhu, Binyuan Wang, et al. 2025. “ARQ: Attentive Reasoning Queries for Multi-Hop Question Answering over Long Contexts.” arXiv Preprint arXiv:2501.08290. https://arxiv.org/abs/2501.08290.
Yang, John, Carlos E. Jimenez, Alexander Wettig, et al. 2024. “SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2405.15793.
Yang, Zhilin, Zihang Dai, Yiming Yang, Jaime Carbonell, Ruslan Salakhutdinov, and Quoc V. Le. 2019. “XLNet: Generalized Autoregressive Pretraining for Language Understanding.” Advances in Neural Information Processing Systems (NeurIPS).
Yang, Zhilin, Peng Qi, Saizheng Zhang, et al. 2018. “HotpotQA: A Dataset for Diverse, Explainable Multi-Hop Question Answering.” Proceedings of EMNLP.
Yao, Shunyu, Dian Yu, Jeffrey Zhao, et al. 2023. “Tree of Thoughts: Deliberate Problem Solving with Large Language Models.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2305.10601.
Yao, Shunyu, Dian Yu, Jeffrey Zhao, et al. 2024. “Tree of Thoughts: Deliberate Problem Solving with Large Language Models.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2305.10601.
Yao, Shunyu, Jeffrey Zhao, Dian Yu, et al. 2023. “ReAct: Synergizing Reasoning and Acting in Language Models.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2210.03629.
Yao, Zhewei, Samyam Rajbhandari, Reza Yazdani Aminabadi, et al. 2023. “DeepSpeed-Chat: Easy, Fast and Affordable RLHF Training of ChatGPT-Like Models at All Scales.” arXiv Preprint arXiv:2308.01320.
Yoo, Jaeseok, and Dongmin Shin. 2025. “Adaptive Episode Length Adjustment for Multi-Agent Reinforcement Learning.” Proceedings of the 24th International Conference on Autonomous Agents and Multiagent Systems (AAMAS).
Yu, Gyeong-In, Joo Seong Jeong, Geon-Woo Kim, Soojeong Kim, and Byung-Gon Chun. 2022. “Orca: A Distributed Serving System for Transformer-Based Generative Models.” Proceedings of the 16th USENIX Symposium on Operating Systems Design and Implementation (OSDI).
Yu, Qiying, Zheng Sun, Shang Wen, et al. 2025. “DAPO: An Open-Source LLM Reinforcement Learning System.” arXiv Preprint arXiv:2503.14476. https://arxiv.org/abs/2503.14476.
Yu, Xiao, Baolin Peng, Ruize Xu, et al. 2026. “Reinforcement World Model Learning for LLM-Based Agents.” arXiv Preprint arXiv:2602.05842.
Zadouri, Ted, Jay Shah, Ganesh Bikshandi, and Tri Dao. 2026. “FlashAttention-4: Hardware-Efficient Attention on Blackwell GPUs with Minimal Software Design.” arXiv Preprint arXiv:2603.05451. https://arxiv.org/abs/2603.05451.
Zaheer, Manzil, Guru Guruganesh, Kumar Avinava Dubey, et al. 2020. “Big Bird: Transformers for Longer Sequences.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2007.14062.
Zaken, Elad Ben, Shauli Ravfogel, and Yoav Goldberg. 2022. “BitFit: Simple Parameter-Efficient Fine-Tuning for Transformer-Based Masked Language-Models.” Proceedings of the 60th Annual Meeting of the ACL. https://arxiv.org/abs/2106.10199.
Zelikman, Eric, Georges Harik, Yijia Shao, Varuna Jayasiri, Nick Haber, and Noah D. Goodman. 2024. “Quiet-STaR: Language Models Can Teach Themselves to Think Before Speaking.” arXiv Preprint arXiv:2403.09629. https://arxiv.org/abs/2403.09629.
Zelikman, Eric, Yuhuai Wu, Jesse Mu, and Noah D. Goodman. 2022. “STaR: Bootstrapping Reasoning with Reasoning.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2203.14465.
Zhang, Alex L., Seyyed Hasan Mahdavi, Percy Liang, and Tatsunori Hashimoto. 2025. “Recursive Language Models.” arXiv Preprint arXiv:2512.24601. https://arxiv.org/abs/2512.24601.
Zhang, Biao, and Rico Sennrich. 2019. “Root Mean Square Layer Normalization.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/1910.07467.
Zhang, Jiayi, Simon Yu, Derek Chong, et al. 2025. “Verbalized Sampling: How to Mitigate Mode Collapse and Unlock LLM Diversity.” arXiv Preprint arXiv:2510.01171. https://arxiv.org/abs/2510.01171.
Zhang, Kaiqing, Zhuoran Yang, and Tamer Başar. 2021. “Multi-Agent Reinforcement Learning: A Selective Overview of Theories and Algorithms.” Handbook of Reinforcement Learning and Control. https://arxiv.org/abs/1911.10635.
Zhang, Qingru, Minshuo Chen, Alexander Bukharin, et al. 2023. “AdaLoRA: Adaptive Budget Allocation for Parameter-Efficient Fine-Tuning.” arXiv Preprint arXiv:2303.10512. https://arxiv.org/abs/2303.10512.
Zhang, Tianjun, Shishir G. Patil, Naman Jain, et al. 2024. “RAFT: Adapting Language Model to Domain Specific RAG.” arXiv Preprint arXiv:2403.10131. https://arxiv.org/abs/2403.10131.
Zhang, Tianyi, Varsha Kishore, Felix Wu, Kilian Q. Weinberger, and Yoav Artzi. 2020. “BERTScore: Evaluating Text Generation with BERT.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/1904.09675.
Zhang, Xinrong, Yingfa Chen, Shengding Hu, et al. 2024. “InfiniteBench: Extending Long Context Evaluation Beyond 100K Tokens.” Proceedings of the 62nd Annual Meeting of the Association for Computational Linguistics (ACL). https://arxiv.org/abs/2402.13718.
Zhang, Zhenyu, Ying Sheng, Tianyi Zhou, et al. 2023. “H2O: Heavy-Hitter Oracle for Efficient Generative Inference of Large Language Models.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2306.14048.
Zhao, Yanli, Andrew Gu, Rohan Varma, et al. 2023. “PyTorch FSDP: Experiences on Scaling Fully Sharded Data Parallel.” Proceedings of the VLDB Endowment.
Zhao, Yao, Rishabh Joshi, Tianqi Liu, Misha Khalman, Mohammad Saleh, and Peter J. Liu. 2023. “SLiC-HF: Sequence Likelihood Calibration with Human Feedback.” arXiv Preprint arXiv:2305.10425. https://arxiv.org/abs/2305.10425.
Zheng, Lianmin, Wei-Lin Chiang, Ying Sheng, et al. 2023. “Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2306.05685.
Zhong, Yanqi, Yifu Chen, Zijun Li, and Minlie Chen. 2025. “Importance Sampling Corrections for Large Language Model Alignment with Asynchronous Generation.” arXiv Preprint arXiv:2503.09057. https://arxiv.org/abs/2503.09057.
Zhong, Yanqi, Jiaqi Shi, Yifu Chen, and Minlie Chen. 2025. “GDPO: Learning to Directly Align Language Models with Group-Decoupled Reward.” arXiv Preprint arXiv:2501.17888. https://arxiv.org/abs/2501.17888.
Zhou, Andy, Kai Yan, Michal Shlapentokh-Rothman, Haohan Wang, and Yu-Xiong Wang. 2024. “Language Agent Tree Search Unifies Reasoning Acting and Planning in Language Models.” arXiv Preprint arXiv:2310.04406.
Zhou, Chunting, Pengfei Liu, Puxin Xu, et al. 2023. “LIMA: Less Is More for Alignment.” Advances in Neural Information Processing Systems (NeurIPS). https://arxiv.org/abs/2305.11206.
Zhou, Shuyan, Frank F. Xu, Hao Zhu, et al. 2024. “WebArena: A Realistic Web Environment for Building Autonomous Agents.” International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2307.13854.
Zhou, Yongchao, Andrei Ioan Muresanu, Ziwen Han, et al. 2023. “Large Language Models Are Human-Level Prompt Engineers.” Proceedings of the 11th International Conference on Learning Representations (ICLR). https://arxiv.org/abs/2211.01910.
Ziegler, Daniel M., Nisan Stiennon, Jeffrey Wu, et al. 2019. “Fine-Tuning Language Models from Human Preferences.” arXiv Preprint arXiv:1909.08593. https://arxiv.org/abs/1909.08593.
Zou, Andy, Zifan Wang, J. Zico Kolter, and Matt Fredrikson. 2023. “Universal and Transferable Adversarial Attacks on Aligned Language Models.” arXiv Preprint arXiv:2307.15043. https://arxiv.org/abs/2307.15043.