2021 年、Microsoft、OpenAI、Github は共同で、便利なコード補完および提案ツール Copilot を作成しました。
開発者が Visual Studio Code、Neovim、JetBrains IDE などの統合開発環境にコードを入力する場合など、開発者のコード エディター内のコード行を推奨します。コード行。さらに、Copilot は、完全なメソッドと複雑なアルゴリズムに関するアドバイスを提供するだけでなく、テンプレート コードや単体テストの支援も提供します。
1 年以上が経ち、このツールは多くのプログラマーにとって切っても切れない「プログラミング パートナー」になりました。テスラ社の元人工知能担当ディレクター、アンドレイ・カルパシー氏は、「Copilot のおかげで私のプログラミング速度が大幅に向上しました。「手動プログラミング」に戻る方法を想像するのは難しいです。現在、私はまだ使い方を学んでいます。私のコードの 80% 近くがプログラムされており、精度は 80% 近くです。」
Copilot に慣れることに加えて、Copilot について次のような質問もあります。 Copilot のプロンプトはどのようなものですか?モデルはどのように呼ばれるのでしょうか?推奨の成功率はどのように測定されますか?ユーザーのコード スニペットを収集し、独自のサーバーに送信しますか? Copilot の背後にあるモデルは大型モデルですか、それとも小型モデルですか?
これらの質問に答えるために、イリノイ大学アーバナ シャンペーン校の研究者が Copilot を大まかにリバース エンジニアリングし、その観察結果をブログに投稿しました。
Andrej Karpathy さんがツイートでこのブログを推奨しました。
以下はブログの原文です。
Github Copilot は私にとって非常に役に立ちます。それはしばしば魔法のように私の心を読んで、役立つ提案をしてくれます。私が最も驚いたのは、周囲のコード (他のファイルのコードを含む) から関数/変数を正しく「推測」できる機能です。これは、Copilot 拡張機能が周囲のコードから Codex モデルに貴重な情報を送信する場合にのみ発生します。どのように動作するのか興味があったので、ソースコードを見てみることにしました。
この投稿では、Copilot の内部に関する具体的な質問に答えるとともに、コードを調べているときに得たいくつかの興味深い観察についても説明します。
このプロジェクトのコードはここにあります:
コード アドレス: https : //github.com/thakkarparth007/copilot-explorer
記事全体は次のように構成されています:
数か月前、私は Copilot 拡張機能の非常に表面的な「リバース エンジニアリング」を行いました。それ以来、さらに深く掘り下げたいと考えていました。ここ数週間で、ようやくこれを実行できるようになりました。基本的に、Copilot に含まれる extension.js ファイルを使用して、モジュールの自動抽出を簡素化するために手動でいくつかの小さな変更を加え、各モジュールを「美しく」するための一連の AST 変換を作成し、モジュールに名前を付けました。また、分類して手動で注釈を付けました。最も興味深い部分のいくつか。
私が構築したツールを使用して、リバース エンジニアリングされたコパイロットのコード ベースを探索できます。包括的で洗練されたものではないかもしれませんが、それでも Copilot のコードを調べるために使用できます。
ツールリンク: https://thakkarparth007.github.io/copilot-explorer/
Github Copilot は、次の 2 つの主要な部分で構成されます:
現在、Codex は大量の公開 Github コードでトレーニングされているため、それが役立つことは当然です。提案。ただし、Codex には、現在のプロジェクトにどのような機能が存在するかを知る方法がありません。それでも、プロジェクトの機能に関連する提案を行うことはできます。
これに 2 つの部分に分けて答えましょう。まず、コパイロットによって生成される実際のプロンプトの例を見てみましょう。次に、それがどのように生成されるかを見てみましょう。
プロンプトの外観
Copilot 拡張機能は、プロンプト内のプロジェクトに関連する多くの情報をエンコードします。 Copilot には、かなり複雑なプロンプト エンジニアリング パイプラインがあります。プロンプトの例を次に示します。
{"prefix": "# Path: codeviz\app.pyn# Compare this snippet from codeviz\predictions.py:n# import jsonn# import sysn# import timen# from manifest import Manifestn# n# sys.path.append(__file__ + "/..")n# from common import module_codes, module_deps, module_categories, data_dir, cur_dirn# n# gold_annots = json.loads(open(data_dir / "gold_annotations.js").read().replace("let gold_annotations = ", ""))n# n# M = Manifest(n# client_name = "openai",n# client_connection = open(cur_dir / ".openai-api-key").read().strip(),n# cache_name = "sqlite",n# cache_connection = "codeviz_openai_cache.db",n# engine = "code-davinci-002",n# )n# n# def predict_with_retries(*args, **kwargs):n# for _ in range(5):n# try:n# return M.run(*args, **kwargs)n# except Exception as e:n# if "too many requests" in str(e).lower():n# print("Too many requests, waiting 30 seconds...")n# time.sleep(30)n# continuen# else:n# raise en# raise Exception("Too many retries")n# n# def collect_module_prediction_context(module_id):n# module_exports = module_deps[module_id]["exports"]n# module_exports = [m for m in module_exports if m != "default" and "complex-export" not in m]n# if len(module_exports) == 0:n# module_exports = ""n# else:n# module_exports = "It exports the following symbols: " + ", ".join(module_exports)n# n# # get module snippetn# module_code_snippet = module_codes[module_id]n# # snip to first 50 lines:n# module_code_snippet = module_code_snippet.split("\n")n# if len(module_code_snippet) > 50:n# module_code_snippet = "\n".join(module_code_snippet[:50]) + "\n..."n# else:n# module_code_snippet = "\n".join(module_code_snippet)n# n# return {"exports": module_exports, "snippet": module_code_snippet}n# n# #### Name prediction ####n# n# def _get_prompt_for_module_name_prediction(module_id):n# context = collect_module_prediction_context(module_id)n# module_exports = context["exports"]n# module_code_snippet = context["snippet"]n# n# prompt = f"""\n# Consider the code snippet of an unmodule named.n# nimport jsonnfrom flask import Flask, render_template, request, send_from_directorynfrom common import *nfrom predictions import predict_snippet_description, predict_module_namennapp = Flask(__name__)nn@app.route('/')ndef home():nreturn render_template('code-viz.html')nn@app.route('/data/<filename>')ndef get_data_files(filename):nreturn send_from_directory(data_dir, filename)nn@app.route('/api/describe_snippet', methods=['POST'])ndef describe_snippet():nmodule_id = request.json['module_id']nmodule_name = request.json['module_name']nsnippet = request.json['snippet']ndescription = predict_snippet_description(nmodule_id,nmodule_name,nsnippet,n)nreturn json.dumps({'description': description})nn# predict name of a module given its idn@app.route('/api/predict_module_name', methods=['POST'])ndef suggest_module_name():nmodule_id = request.json['module_id']nmodule_name = predict_module_name(module_id)n","suffix": "if __name__ == '__main__':rnapp.run(debug=True)","isFimEnabled": true,"promptElementRanges": [{ "kind": "PathMarker", "start": 0, "end": 23 },{ "kind": "SimilarFile", "start": 23, "end": 2219 },{ "kind": "BeforeCursor", "start": 2219, "end": 3142 }]}</filename>
ご覧のとおり、上記のプロンプトにはプレフィックスとサフィックスが含まれています。次に、Copilot はこのプロンプトを (何らかのフォーマットを行った後) モデルに送信します。この場合、サフィックスは空ではないため、Copilot は「挿入モード」、つまり、中間フィル (FIM) モードで Codex を呼び出します。
プレフィックスを見ると、プロジェクト内の別のファイルのコードが含まれていることがわかります。 # codeviz\predictions.py のスニペットを比較してください: コードの行とその後の行を参照してください。
#プロンプトはどのように準備されていますか?
プロンプトを生成するには、大まかに次の一連のステップが実行されます。
一般的に、プロンプトは次の一連のステップを通過します。 - ステップごとの生成:
#1. エントリ ポイント: プロンプト 抽出は、指定されたドキュメントとカーソルの位置で行われます。生成されるメイン エントリ ポイントは extractPrompt (ctx, doc, insertPos)
2 です。VSCode からドキュメントの相対パスと言語 ID をクエリします。参照: getPromptFor RegularDoc (ctx, doc, insertPos)
3. 関連ドキュメント: 次に、VSCode から同じ言語で最近アクセスされた 20 個のファイルをクエリします。 getPromptHelper (ctx、docText、insertOffset、docRelPath、docUri、docLangId) を参照してください。これらのファイルは、後でプロンプトに含める同様のスニペットを抽出するために使用されます。個人的には、複数言語の開発が非常に一般的であるため、フィルターとして同じ言語を使用するのは奇妙だと思います。しかし、それでもほとんどのケースはカバーできると思います。
4. 設定: 次に、いくつかのオプションを設定します。具体的には次のものが含まれます:
#5. プレフィックスの計算: 次に、プロンプトのプレフィックス部分を計算するための「プロンプト ウィッシュリスト」を作成します。ここでは、さまざまな「要素」とその優先順位を追加します。たとえば、要素は、「 のこのフラグメントを比較」のようなもの、ローカル インポートのコンテキスト、または各ファイルの言語 ID やパスなどです。これはすべて getPrompt (fs, curFile, promptOpts = {},relevantDocs = []) で行われます。
6. サフィックスの計算: 前のステップはプレフィックスに関するものですが、サフィックスのロジックは比較的単純で、カーソルから取得できる利用可能なサフィックスをトークン バジェットに入力するだけです。これはデフォルト設定ですが、サフィックスの開始位置は、AB 実験フレームワークによって制御される SuffixStartMode オプションに応じてわずかに異なります。たとえば、SuffixStartMode が SiblingBlock の場合、Copilot はまず編集中の関数に最も近い関数の兄弟を見つけ、そこからサフィックスを書き込みます。
フラグメント抽出を詳しく見る
プロンプト生成の最も完全な部分は、他の部分から抽出されているように見えます。ファイルの断片。これはここで呼び出され、neighbor-snippet-selector.getNeighbourSnippets によって定義されます。オプションに応じて、「固定ウィンドウ Jaccard マッチャー」または「インデント ベースの Jaccard マッチャー」が使用されます。 100% 確信はありませんが、インデント ベースの Jaccard Matcher が実際に使用されているようには見えません。
デフォルトでは、固定ウィンドウの Jaccard Matcher を使用します。この場合、(フラグメントが抽出される) 指定されたファイルは、固定サイズのスライディング ウィンドウに分割されます。次に、各ウィンドウと参照ファイル (入力しているファイル) の間の Jaccard の類似性を計算します。 「関連ファイル」ごとに最適なウィンドウのみが返されます (上位 K 個のフラグメントを返すという要件がありますが、これは決して守られません)。デフォルトでは、FixedWindowJaccardMatcher は「Eager モード」で使用されます (つまり、ウィンドウ サイズは 60 行です)。ただし、このモードは AB Experimentation フレームワークによって制御されるため、他のモードを使用する場合があります。
Copilot は、Inline/GhostText と Copilot パネルという 2 つの UI を通じて補完を提供します。どちらの場合も、モデルの呼び出し方法にいくつかの違いがあります。
Inline/GhostText
メインモジュール: https://thakkarparth007.github.io/copilot-explorer/ codeviz/templates/code-viz.html#m9334&pos=301:14
Copilot 拡張機能では、速度を上げるためにモデルが非常に少ない提案 (1 ~ 3 項目) を提供する必要があります。 。また、モデルの結果を積極的にキャッシュします。さらに、ユーザーが入力を続けた場合に候補を調整します。ユーザーが非常に速く入力している場合は、関数のデバウンスをオンにするようモデルに要求します。
この UI は、特定の状況下でリクエストが送信されないようにするためのロジックも設定します。たとえば、ユーザー カーソルが行の途中にある場合、その右側の文字がスペースや右中括弧などである場合にのみリクエストが送信されます。
1. コンテキスト フィルターを通じて不正なリクエストをブロックします。
さらに興味深いのは、プロンプトを生成した後、モジュールがそのプロンプトがモデルを呼び出すのに「十分」であるかどうかをチェックすることです。これは、「コンテキスト フィルタリング スコア」を計算することで実現されます。このスコアは、単純なロジスティック回帰モデルに基づいているようです。このモデルには、言語、前の提案が受け入れられたか拒否されたか、前回の受け入れ/拒否の間の期間、プロンプトの最後の行の長さ、最後の行の長さなど、11 個の特徴が含まれています。キャラクターなどこのモデルの重みは拡張コード自体に含まれています。
スコアがしきい値 (デフォルトは 15%) を下回る場合、リクエストは行われません。このモデルを調査してみると興味深いでしょう。一部の言語は他の言語よりも重みが高いことがわかりました (例: php > js > python >rust > dart...php)。もう 1 つの直感的な観察は、プロンプトが ) または ] で終わる場合、スコアは ( または [ で終わる場合よりも低いということです。前者はすでに「完了」していることを示す可能性が高いため、これは理にかなっています。後者は、ユーザーがオートコンプリートの恩恵を受けることを明確に示しています。 /thakkarparth007.github.io/copilot-explorer/codedeviz/templates/code-viz.html#m2990&pos=12:1
コア ロジック 1: https://thakkarparth007.github. io/copilot-explorer /codeviz/templates/code-viz.html#m893&pos=9:1
コア ロジック 2: https://thakkarparth007.github.io/copilot-explorer /codeviz/templates/ code-viz.html#m2388&pos=67:1この UI は、インライン UI よりも多くのサンプル (デフォルトでは 10) をモデルから要求します。コンテキスト フィルタリング ロジックを使用しないようにします (当然のことですが、ユーザーがモデルを明示的に呼び出した場合にモデルへのプロンプトを表示しないことは望ましくありません)。
ここには主に 2 つの興味深い点があります:
## 呼び出されるモード (OPEN_COPILOT/TODO_QUICK_FIX/UNKNOWN_FUNCTION_QUICK_FIX) に応じて、プロンプトが若干変更されます。これらのモードがどのようにアクティブ化されるか。
モデルから logprobs を要求し、ソリューション リストは平均 logprobs カテゴリによって並べ替えられます。
出力が重複している場合 (例: foo = foo = foo = foo...)、これは言語モデルの一般的な失敗モードですが、この提案は破棄されます。これは Copilot プロキシ サーバーまたはクライアントで発生する可能性があります。 .ユーザーがすでに提案を入力している場合、その提案も破棄されます。
秘密 3: テレメトリ
Github は、以前のブログで、プログラマーによって書かれたコードの 40% が Copilot (Python およびその他の一般的な言語用) によって書かれていると主張しました。この数値をどのように測定したのか興味があったので、テレメトリ コードに何かを挿入したいと思いました。
また、特にコード スニペットを収集する場合、どのようなテレメトリ データが収集されるのかも知りたいです。私がこれについて疑問に思うのは、Copilot 拡張機能を Github バックエンドではなくオープン ソースの FauxPilot バックエンドに簡単に指定できる一方で、拡張機能は依然としてテレメトリ経由でコード スニペットを Github に送信する可能性があり、コードのプライバシーを懸念する一部の人々がその拡張機能を使用することを思いとどまらせる可能性があるためです。副操縦士。そうなのかな。
Copilot の成功を測定することは、単に承認/拒否を数えるだけの問題ではありません。人々は、修正を加えた推奨事項を受け入れることがよくあるからです。したがって、Github スタッフは、受け入れられた提案がコード内にまだ存在するかどうかを確認します。具体的には、提案されたコードが挿入されてから 15 秒、30 秒、2 分、5 分、10 分後にチェックします。
受け入れられた提案の完全検索は制限が厳しすぎるため、提案されたテキストと挿入ポイントの周囲のウィンドウの間の編集距離 (文字レベルおよび単語レベルで) が測定されます。挿入とウィンドウの間の単語レベルの編集距離が 50% 未満 (提案サイズに正規化) の場合、提案は「コード内にある」とみなされます。
もちろん、これはすべて、承認されたコードにのみ適用されます。 #質問 2: テレメトリ データにはコード スニペットが含まれていますか? 提案を受け入れるか拒否してから 30 秒後、副操縦士は挿入ポイント付近のスナップショットを「キャプチャ」します。具体的には、拡張機能はプロンプト抽出メカニズムを呼び出して、その挿入ポイントで提案を行うために使用できる「仮説プロンプト」を収集します。また、copilot は、挿入ポイントと「推測された」エンドポイントの間のコードをキャプチャすることにより、「仮説的な完了」もキャプチャします。このエンドポイントをどのように推測するのかよくわかりません。前述したように、これは承認または拒否の後に発生します。 これらのスナップショットは、モデルをさらに改善するためのトレーニング データとして使用される可能性があると思われます。ただし、コードが「安定した」とみなすには 30 秒は短すぎるように思えます。しかし、テレメトリにユーザーのプロジェクトに対応する Github リポジトリが含まれていることを考えると、30 秒の期間でノイズの多いデータ ポイントが生成されたとしても、GitHub スタッフはこの比較的ノイズの多いデータをオフラインでクリーンアップできると思います。もちろん、これらはすべて私の推測にすぎません。 GitHub では、「製品を改善する」ためにコード スニペットを使用することに同意するかどうかを選択できることに注意してください。同意しない場合、これらのスニペットを含むテレメトリは送信されません。サーバー (少なくとも私が確認した v1.57 ではそうでしたが、v1.65 も確認しました)。これを確認するには、コードを調べてテレメトリ データ ポイントをネットワーク経由で送信する前に記録します。 追加の所見 この記事では、拡張子付きで配布されている worker.js ファイルについては説明しませんでした。一見すると、基本的にはプロンプト抽出ロジックの並列バージョンのみを提供しているように見えますが、さらに多くの機能がある可能性があります。 ファイルアドレス: https://thakkarparth007.github.io/copilot-explorer/muse/github.copilot-1.57.7193/dist/worker_expanded.js 詳細ログを有効にしたい場合は、拡張コード #拡張ファイルを検索します。通常、これは ~/.vscode/extensions/github.copilot- 注、これは 1.57 .7193 バージョン用です。 原文にはさらに詳細なリンクがあります。興味のある読者は原文を確認してください。
以上がCopilot をリバース エンジニアリングした結果、パラメータが 12B の小規模なモデルのみを使用している可能性があることがわかりました。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。