
6,509 文字

みなさん、こんにちは。私はAllyと申します。Cosineの共同創業者兼CEOを務めています。Cosineでは、Genieという製品を開発しました。Genieは完全自律型のAIエンジニアです。開発方法としては、実際の開発者がタスクを完了する過程を示した合成的に拡張された実世界のデータを使って、GPT-4oをファインチューニングしました。この背景にある考え方は、事前学習コーパスには、ソフトウェアエンジニアがタスクを実行する例が含まれていないということです。通常、PRやコードファイルなど、タスクが完了した最終成果物しか見ることができません。
これまでの開発を通じて、ファインチューニングシステムを構築する際の有用なテクニックをいくつか見出しました。今日はそれらを皆さんと共有したいと思います。
まず、ファインチューニングについてですが、私はこれがLLMツールボックスの中で非常に過小評価されているツールだと感じています。しかし、製品の品質が真に重要なミッションである場合、現時点では不可欠だと考えています。基本的に、私たちはベースとなるLLMからは望むパフォーマンスを得られないことがわかったため、モデルを1つの分野に特化させるためにファインチューニングに踏み切りました。
一般的に比較的少ない例で済みますが、データの詳細な取り扱いが極めて重要です。プロンプトエンジニアリングと比較すると、ファインチューニングには大きな時間のトレードオフがあります。プロンプトを使用した製品なら1日で相当な成果を上げることができますが、最後の20%のパフォーマンスを得るには、ファインチューニングが必要でした。しかし、人間と同じようにソフトウェアエンジニアとしての振る舞いをモデルに示すには、現実世界には存在しないデータを合成するために、膨大なデータのクリーニングとキュレーションが必要でした。
この面で学んだ最後のことは、検証駆動型でなければならないということです。多くの人と同様に、私たちも最初は重要だと思われる事柄について感覚的にチェックするところから始めましたが、すぐに私たちが重要だと考えていたことが、製品を広く使用する人々にとって代表的なものではないことに気付きました。
次に話したいのは推論についてです。推論は、LLMツール分野における重要な柱の1つとして長く存在してきました。これは最近のo1とo1 miniのリリースで顕著に示されています。Genieの開発において、私たちはカスタマイズされた推論トレースを活用して、特にソフトウェアエンジニアリングに関して、モデルがより人間らしく考えるようにしました。私たちは一般的な推論全般には興味がなく、特定の問題を解決する際に私や私たちのように考えられることだけを目指しました。
複雑なタスク、例えばソフトウェアエンジニアリングなどでは、回答を出す前にモデルの考え方を変更することが意味を持つことがあります。Chain of Thoughtのような手法は長く存在し、基礎モデルの短所を確実に緩和することができます。そして、o1のような専用の推論モデルのリリースで特に気付いたのは、合成プロセス中にそれらのモデルを使用してカスタム推論トレースを生成し、それをより小さな、あるいはより基本的なモデルに蒸留できるということです。
これは、望ましい出力があるものの、モデルが自力でそこに到達しない傾向がある場合に理想的な方法です。例えば、私たちの場合、モデルに書いて欲しいパッチがありますが、ベースモデルが一貫してそれを書けない場合、カスタム推論トレースが、望むものと入力を結びつける接着剤として機能します。
例を挙げましょう。コードレビューの性能を向上させるためにモデルをファインチューニングしたい場合を考えてみましょう。PRがあり、そのPRにレビューコメントを残したとします。これをファインチューニングする最も単純な方法は、PRそのものを入力とし、アシスタントメッセージとしてPRに残されたレビューコメントを使用することです。しかし、そのパフォーマンスはさらに向上させることができます。
PRとレビューコメントをo1に入力として与えた場合を想像してください。ここでo1に、なぜこのコメントが残されたのかを推測させることができます。PRとコメントを与え、o1が利用可能な全ての情報に基づいて、このコメントを残したユーザーの理由を理解しようとします。これは非常に重要です。なぜなら、そうしなければベースモデルは単に無関係なことや、その瞬間に人間が考えないようなことを指摘し始めるからです。この推論が、人間が与えるような出力に収束させるための接着剤となります。
その出力を得たら、ファインチューニングの状況でのトレーニングサンプルは次のようになります:入力としてPRがあり、次にo1が与えた思考プロセスがモデルによって生成され、最後に開発者が実際に残したコメントが来ます。この手法は、私たちがGenieの中で頻繁に使用しており、ベースモデル以上に望ましい回答セットに収束するのに大きく役立ちました。
推論の次に話したいテクニックは、セルフプレイです。セルフプレイは、先ほど述べたような、現実世界では入手が困難か不可能なトレーニング例を生成するための優れたテクニックです。例えば、ブラウザベースのタスクに優れたパーソナルアシスタントを構築したい場合、実世界には存在しないこれらのトレースを生成するためにセルフプレイを使用すると非常に有益です。
私たちの課題は、Genieの構築において、ソフトウェアエンジニアが段階的にコードを取得し、取得したものが関連しているかどうかを判断し、解決策を書き始めるという例が存在しないことでした。通常は最終成果物しか見ることができません。セルフプレイを使用することで、モデルが複製できるようにこれらのトレースを合成することができます。
私たちは主にコードベースの探索と取得に関してこれを活用しました。500万から1000万行のコードベースという非常に大規模なコードベースでも、必要なものを見つけ出すことに非常に長けています。この分野での最新の取り組みの1つは、セルフプレイパイプラインにあるモデルをファインチューニングして、さらに性能を向上させることです。
セルフプレイの主な注意点の1つは、最終的なデータセットに汚れたデータが混入する大きな経路となることです。そのため、クリーニングが非常に重要です。これにはコストがかかる可能性がありますが、o1 miniなどのモデルは、セルフプレイに非常に優れており、実行コストも比較的低くなっています。
実例として、私たちのセルフプレイパイプラインはこのような形になっています。プレイヤーモデルとスーパーバイザーモデルがあります。これらがどのように機能するかについては後ほど説明しますが、基本的には、モデルに理解してほしい元のプロンプトがあり、プレイヤーモデルにはそのプロンプトだけが与えられます。
スーパーバイザーモデルは、このループで教師として機能することを想定しており、プロンプトと望ましい結果の両方が与えられます。ユースケースに応じて、これはコードベースで取得すべきファイルや、パーソナルアシスタントに訪問してほしいウェブサイトなどになります。プレイヤーモデルには、相互作用できるツールのセットがあります。私たちの場合、ファイルの読み取りやperplexityでの検索などができます。
ループは非常にシンプルです。プロンプトがプレイヤーに与えられ、リクエストが行きます。プレイヤーは「ツールにリクエストを送る必要がある」と考え、レスポンスを得ます。その時点で、その全てのコンテキストウィンドウがスーパーバイザーに送られ、スーパーバイザーはその決定を判断し、プレイヤーモデルに対して次に何をすべきかについての、答えを明かさないアドバイスを返します。
具体例を見てみましょう。コードベースに対してこのタスクを実行したいとします:「サーバーのフィンガープリントが全てのHTTPレスポンスのヘッダーとして返されるように変更を加える」とし、これを実現するために編集が必要な3つのファイルがわかっているとします。
この場合、元のプロンプトがプレイヤーに渡されます。プレイヤーは「まずコードベースにあるものを見て、このファイルを読みたいので検索を行おう」と考えます。ツールがそれをプレイヤーに返し、プレイヤーはこのコンテキストウィンドウをスーパーバイザーに送ります。
スーパーバイザーは全体のコンテキストウィンドウを見て、プレイヤーがここまでうまくやっていると判断し、「良いスタートです。次はヘッダーがどのように設定されているかを見てみてはどうでしょうか」というようなアドバイスを返します。
この場面で重要なのは、スーパーバイザーがプレイヤーに答えを明かさないようにチューニングすることです。論理的な飛躍は避け、プレイヤーが解決策に収束していく過程で学習していくようにしたいのです。解決策に直接ジャンプすることは避けたいのです。
このケースでは、プレイヤーは続けて「ではheaders.phpを見てみよう」と考え、そのレスポンスを受け取り、このループは探していた全てのファイル、つまりこの場合の終了条件に達するまで続きます。スーパーバイザーが返答し、モデルは最後の取得を行い、必要なものを全て見つけたところで停止できます。
先ほど述べたように、これは素晴らしいアプローチですが、ファインチューニングによってさらなる改善が可能です。Cosineで行った取り組みの1つは、プレイヤーモデルとスーパーバイザーモデルの両方をファインチューニングすることです。これにより、これらのモデルからより人間らしい推論を引き出すことができ、収束がより早く達成され、推論が望ましい形により近くなります。
これらのモデルを上手く機能させるために必要なのは、数千のアノテーションではなく、おそらく最大で数百程度です。その労力を惜しまなければ、ファインチューニングによって、すでに機能しているシステムの性能を大きく向上させることができます。
この作業を終えた結果として得られるのは、以下のようなトレーニングサンプルとコンテキストウィンドウです。プレイヤーモデルがリクエストを行い、ツールからレスポンスを受け取っているのが分かります。このサンプルをトレーニング可能にするには、ヒントを取り除くだけでよく、その時点でモデルに答えを明かすことをやめます。これは、モデルがコードベース内でこれらのファイルを見つけるべき方法と、これまでの全ての行動の結果として次に何をすべきかの理由を示すトレースとなります。これは私たちにとって非常に強力なアプローチとなっています。
まとめると、カスタム推論トレースは、難しいタスクにおけるモデルのパフォーマンスを向上させるのに非常に効果的です。先ほど述べたように、開始点から目標地点への接着剤として機能することは非常に強力です。現世代のフロンティアモデルからでも、プロンプトの繰り返しではなく、データセットの品質を向上させることで、さらなるパフォーマンスを引き出すことができます。プロンプトを85-90%まで最適化した後でさらに改善するのは非常に困難ですが、データセットの品質向上はより容易です。
セルフプレイは、エージェントベースのワークフローに非常に効果的です。エージェントベースのものを構築する場合は、垂直特化型エージェントをトレーニングするためのデータを合成するために使用することをお勧めします。
最後に述べたように、全てはデータにかかっています。モデルは常に与えられたものを学習します。全ては検証駆動型でなければならず、遭遇する問題がモデルのアーキテクチャやベースモデルに起因する可能性は0.数%しかありません。より可能性が高いのは、トレーニングデータの中で気付いていない、あるいはまだ気付いていない特性によるものです。
最後に、Genieの実際の動作をクイックデモでお見せします。うまく設定できていることを願います。完璧ですね。こちらがCosineのウェブプラットフォームです。フルスクリーンにしてみましょう。
Genieの使用開始は非常に簡単です。基本的に、プラットフォームにオンボードして、個人アカウントを使用するか、同僚とチームを作成します。最初に必要なのは、GitHubに接続することです。GitHubに接続したら、CosineにGitHubアプリとしてGitHub組織を接続します。数回クリックするだけです。それが完了すると、そのGitHub組織内の任意のリポジトリを選択して、ここで見ているようにCosineにインポートすることができます。
インポートが完了すると、リポジトリのインデックス作成を開始します。Cosineのコンテキストでリポジトリのインデックス作成とは、コードベース全体を完全に意味的に埋め込み、そのコードベースのキーワードインデックスを生成し、全ての言語サーバーを実行することを意味します。これら全ての情報がGenieで利用可能となり、検索に使用できます。
先ほどの例で示したように、Genieはこのようなループ的な方法で検索を行い、それを実現するためにこれらのツールを内部で使用します。初期インデックスを作成すると、そのインデックスはメインブランチと同期が保たれ、作業中の他のブランチも選択できます。そのため、変更を加えるたびにGenieはそれを認識します。
実際にGenieにタスクを実行させてみましょう。ここに入力するのは、数日前に発生した本番環境のエラーの生のSentry出力で、全く編集していないものです。これをそのままGenieに与えて、修正を依頼してみましょう。「Sentryでこのエラーが発生しています」とウェブサイトから直接コピーしたものを入力し、すぐに開始します。思っていたより時間がかかりませんでした。
開始すると、Genieは検索ツールを使用して何をしたいのか判断し始めます。ここでプランを立て、「エラーはこのファイルを指しているので、このファイルを取得しよう」と判断しました。そして実行に移り、Genieがタスクを実行するために必要な全てを読み終え、書き終えたと判断すると、タスクを実行するためのパッチを作成します。
ファインチューニングのもう1つの利点は、プロンプトベースでは出力できないような方法でモデルをトレーニングできることです。ここでは、LLMが好む独自のパッチフォーマットを書くようGenieをトレーニングしました。このことに詳しい方なら、それが困難な課題であることをご存知でしょう。
Genieがコードを書いたら、そのコードが実際に動作し、パスするかどうかを確認するために、自動的にGitHubのCIを実行します。これにより、レスポンスを得て、コード実行の結果を確認し、パスしなかった場合はそこから続けることができます。
最後に、CosineとGenieが行うことは全て自動的にGitHubにミラーリングされます。Genieが作成したブランチに移動したり、自分が開始したブランチにGenieを参加させたりすることができます。ここで見たように、完了するとすぐにPRがオープンされ、ファイルは全く同じで、PRの説明も書かれています。実際、この例では問題を解決するのに十分な品質でしたので、すぐにマージしてしまいます。
これは、Sentryから問題が報告され、2分以内に修正をマージできた例です。Genieが全ての重要な作業を行ってくれました。これはGenieの将来的な大きなユースケースの1つですが、Genieは汎用モデルであり、これは将来的な使用方法の1つに過ぎません。
お時間をいただき、ご清聴ありがとうございました。
コメント