自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

對Copilot進行逆向工程之后,我發(fā)現(xiàn)它可能只用了參數(shù)量12B的小模型

人工智能 新聞
為了弄清楚 Copilot 內(nèi)部有哪些秘密,來自伊利諾伊大學香檳分校的一位研究者對 Copilot 進行了粗略的逆向工程。

2021 年,微軟、OpenAI、Github 三家聯(lián)合打造了一個好用的代碼補全與建議工具 ——Copilot。

它會在開發(fā)者的代碼編輯器內(nèi)推薦代碼行,比如當開發(fā)者在 Visual Studio Code、Neovim 和 JetBrains IDE 等集成開發(fā)環(huán)境中輸入代碼時,它就能夠推薦下一行的代碼。此外,Copilot 甚至可以提供關(guān)于完整的方法和復(fù)雜的算法等建議,以及模板代碼和單元測試的協(xié)助。

圖片

一年多過去,這一工具已經(jīng)成為不少程序員離不開的「編程伙伴」。前特斯拉人工智能總監(jiān) Andrej Karpathy 表示,「Copilot 大大加快了我的編程速度,很難想象如何回到『手動編程』。目前,我仍在學習如何使用它,它已經(jīng)編寫了我將近 80% 的代碼,準確率也接近 80%。」

習慣之余,我們對于 Copilot 也有一些疑問,比如 Copilot 的 prompt 長什么樣?它是如何調(diào)用模型的?它的推薦成功率是怎么測出來的?它會收集用戶的代碼片段發(fā)送到自己的服務(wù)器嗎?Copilot 背后的模型是大模型還是小模型?

為了解答這些疑問,來自伊利諾伊大學香檳分校的一位研究者對 Copilot 進行了粗略的逆向工程,并將觀察結(jié)果寫成了博客。

圖片

Andrej Karpathy 在自己的推特中推薦了這篇博客。 

圖片

以下是博客原文。

對 Copilot 進行逆向工程

Github Copilot 對我來說非常有用。它經(jīng)常能神奇地讀懂我的心思,并提出有用的建議。最讓我驚訝的是它能夠從周圍的代碼(包括其他文件中的代碼)中正確地「猜測」函數(shù) / 變量。只有當 Copilot 擴展從周圍的代碼發(fā)送有價值的信息到 Codex 模型時,這一切才會發(fā)生。我很好奇它是如何工作的,所以我決定看一看源代碼。

在這篇文章中,我試圖回答有關(guān) Copilot 內(nèi)部結(jié)構(gòu)的具體問題,同時也描述了我在梳理代碼時所得到的一些有趣的觀察結(jié)果。

這個項目的代碼可以在這里找到:

圖片

代碼地址:https://github.com/thakkarparth007/copilot-explorer

整篇文章結(jié)構(gòu)如下:

圖片

逆向工程概述

幾個月前,我對 Copilot 擴展進行了非常淺顯的「逆向工程」,從那時起我就一直想要進行更深入的研究。在過去的近幾周時間終于得以抽空來做這件事。大體來講,通過使用 Copilot 中包含的 extension.js 文件,我進行了一些微小的手動更改以簡化模塊的自動提取,并編寫了一堆 AST 轉(zhuǎn)換來「美化」每個模塊,將模塊進行命名,同時分類并手動注釋出其中一些最為有趣的部分。

你可以通過我構(gòu)建的工具探索逆向工程的 copilot 代碼庫。它可能不夠全面和精致,但你仍可以使用它來探索 Copilot 的代碼。

圖片


工具鏈接:https://thakkarparth007.github.io/copilot-explorer/

Copilot:概述

Github Copilot 由如下兩個主要部分組成:


  • 客戶端:VSCode 擴展收集你輸入的任何內(nèi)容(稱為 prompt),并將其發(fā)送到類似 Codex 的模型。 無論模型返回什么,它都會顯示在你的編輯器中。
  • 模型:類似 Codex 的模型接受 prompt 并返回完成 prompt 的建議。

秘訣 1:prompt 工程

現(xiàn)在,Codex 已經(jīng)在大量公共 Github 代碼上得到了訓練,因此它能提出有用的建議是合理的。但是 Codex 不可能知道你當前項目中存在哪些功能,即便如此,它還是能提出涉及項目功能的建議,它是如何做到的?

讓我們分兩個部分來對此進行解答:首先讓我們來看一下由 copilot 生成的一個真實 prompt 例子,而后我們再來看它是如何生成的。

prompt 長啥樣

Copilot 擴展在 prompt 中編碼了大量與你項目相關(guān)的信息。Copilot 有一個相當復(fù)雜的 prompt 工程 pipeline。如下是一個 prompt 的示例:

{  "prefix": "# Path: codeviz\\app.py\n# Compare this snippet from codeviz\\predictions.py:\n# import json\n# import sys\n# import time\n# from manifest import Manifest\n# \n# sys.path.append(__file__ + \"/..\")\n# from common import module_codes, module_deps, module_categories, data_dir, cur_dir\n# \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#                 continue\n#             else:\n#                 raise e\n#     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 snippet\n#     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 json\nfrom flask import Flask, render_template, request, send_from_directory\nfrom common import *\nfrom predictions import predict_snippet_description, predict_module_name\n\napp = Flask(__name__)\n\n@app.route('/')\ndef home():\n    return render_template('code-viz.html')\n\n@app.route('/data/<path:filename>')\ndef get_data_files(filename):\n    return send_from_directory(data_dir, filename)\n\n@app.route('/api/describe_snippet', methods=['POST'])\ndef describe_snippet():\n    module_id = request.json['module_id']\n    module_name = request.json['module_name']\n    snippet = request.json['snippet']\n    description = predict_snippet_description(\n        module_id,\n        module_name,\n        snippet,\n    )\n    return json.dumps({'description': description})\n\n# predict name of a module given its id\n@app.route('/api/predict_module_name', methods=['POST'])\ndef suggest_module_name():\n    module_id = request.json['module_id']\n    module_name = predict_module_name(module_id)\n",  "suffix": "if __name__ == '__main__':\r\n    app.run(debug=True)",  "isFimEnabled": true,  "promptElementRanges": [    { "kind": "PathMarker", "start": 0, "end": 23 },    { "kind": "SimilarFile", "start": 23, "end": 2219 },    { "kind": "BeforeCursor", "start": 2219, "end": 3142 }  ]}

正如你所見,上述 prompt 包括一個前綴和一個后綴。Copilot 隨后會將此 prompt(在經(jīng)過一些格式化后)發(fā)送給模型。在這種情況下,因為后綴是非空的,Copilot 將以 “插入模式”,也就是 fill-in-middle (FIM) 模式來調(diào)用 Codex。

如果你查看前綴,將會看到它包含項目中另一個文件的一些代碼。參見 # Compare this snippet from codeviz\\predictions.py: 代碼行及其之后的數(shù)行

prompt 是如何準備的?

Roughly, the following sequence of steps are executed to generate the prompt:

一般來講,prompt 通過以下一系列步驟逐步生成:

1. 入口點:prompt 提取發(fā)生在給定的文檔和光標位置。其生成的主要入口點是 extractPrompt (ctx, doc, insertPos)

2. 從 VSCode 中查詢文檔的相對路徑和語言 ID。參見:getPromptForRegularDoc (ctx, doc, insertPos)

3. 相關(guān)文檔:而后,從 VSCode 中查詢最近訪問的 20 個相同語言的文件。請參閱 getPromptHelper (ctx, docText, insertOffset, docRelPath, docUri, docLangId) 。這些文件后續(xù)會用于提取將要包含在 prompt 中的類似片段。我個人認為用同一種語言作為過濾器很奇怪,因為多語言開發(fā)是相當常見的。不過我猜想這仍然能涵蓋大多數(shù)情況。

4. 配置:接下來,設(shè)定一些選項。具體包括:

  • suffixPercent(多少 prompt tokens 應(yīng)該專用于后綴?默認好像為 15%)
  • fimSuffixLengthThreshold(可實現(xiàn) Fill-in-middle 的后綴最小長度?默認為 -1,因此只要后綴非空,F(xiàn)IM 將始終啟用,不過這最終會受 AB 實驗框架控制)
  • includeSiblingFunctions 似乎已被硬編碼為 false,只要 suffixPercent 大于 0(默認情況下為 true)。

5. 前綴計算:現(xiàn)在,創(chuàng)建一個「Prompt Wishlist」用于計算 prompt 的前綴部分。這里,我們添加了不同的「元素」及其優(yōu)先級。例如,一個元素可以類似于「比較這個來自 < path> 中的片段」,或本地導(dǎo)入的上下文,或每個文件的語言 ID 及和 / 或路徑。這都發(fā)生在  getPrompt (fs, curFile, promptOpts = {}, relevantDocs = [])  中。

  • 這里有 6 種不同類型的「元素」 – BeforeCursor, AfterCursor, SimilarFile, ImportedFile ,LanguageMarker,PathMarker。
  • 由于 prompt 大小有限,wishlist 將按優(yōu)先級和插入順序排序,其后將由元素填充到該 prompt 中,直至達到大小限制。這種「填充」邏輯在 PromptWishlist.fulfill (tokenBudget) 中得以實現(xiàn)。
  • LanguageMarkerOption、NeighboringTabsPositionOption、SuffixStartMode 等一些選項控制這些元素的插入順序和優(yōu)先級。一些選項控制如何提取某些信息,例如,NeighboringTabsOption 控制從其他文件中提取片段的積極程度。某些選項僅為特定語言定義,例如,LocalImportContextOption 僅支持為 Typescript 定義。
  • 有趣的是,有很多代碼會參與處理這些元素的排序。但我不確定是否使用了所有這些代碼,有些于我而言看起來像是死代碼。例如,neighborTabsPosition 似乎從未被設(shè)置為 DirectlyAboveCursor…… 但我可能是錯的。同樣地,SiblingOption 似乎被硬編碼為 NoSiblings,這意味著沒有實際的同級(sibling)函數(shù)提取發(fā)生??傊?,也許它們是為未來設(shè)計的,或者可能只是死代碼。

6. 后綴計算:上一步是針對前綴的,但后綴的邏輯相對簡單 —— 只需用來自于光標的任意可用后綴填充 token budget 即可。這是默認設(shè)置,但后綴的起始位置會根據(jù) SuffixStartMode 選項略有不同, 這也是由 AB 實驗框架控制的。例如,如果 SuffixStartMode 是 SiblingBlock,則 Copilot 將首先找到與正在編輯的函數(shù)同級的功能最相近的函數(shù),并從那里開始編寫后綴。

  • 后綴緩存:有件事情十分奇怪,只要新后綴與緩存的后綴相差「不太遠」,Copilot 就會跨調(diào)用緩存后綴, 我不清楚它為何如此。這或許是由于我難以理解代碼混淆(obfuscated code)(盡管我找不到該代碼的替代解釋)。

仔細觀察一下片段提取

對我來說,prompt 生成最完整的部分似乎是從其他文件中提取片段。它在此處被調(diào)用并被 neighbor-snippet-selector.getNeighbourSnippets 所定義。根據(jù)選項,這將會使用「Fixed window Jaccard matcher」或「Indentation based Jaccard Matcher」。我難以百分百確定,但看起來實際上并沒有使用 Indentation based Jaccard Matcher。

默認情況下,我們使用 fixed window Jaccard Matcher。這種情況下,將給定文件(會從中提取片段的文件)分割成固定大小的滑動窗口。然后計算每個窗口和參考文件(你正在錄入的文件)之間的 Jaccard 相似度。每個「相關(guān)文件」僅返回最優(yōu)窗口(盡管存在需返回前 K 個片段的規(guī)定,但從未遵守過)。默認情況下,F(xiàn)ixedWindowJaccardMatcher 會被用于「Eager 模式」(即窗口大小為 60 行)。但是,該模式由 AB Experimentation framework 控制,因此我們可能會使用其他模式。

秘訣 2:模型調(diào)用

Copilot 通過兩個 UI 提供補全:Inline/GhostText 和 Copilot Panel。在這兩種情況下,模型的調(diào)用方式存在一些差異。

Inline/GhostText

主要模塊:https://thakkarparth007.github.io/copilot-explorer/codeviz/templates/code-viz.html#m9334&pos=301:14

在其中,Copilot 擴展要求模型提供非常少的建議 (1-3 條) 以提速。它還積極緩存模型的結(jié)果。此外,如果用戶繼續(xù)輸入,它會負責調(diào)整建議。如果用戶打字速度很快,它還會請求模型開啟函數(shù)防抖動功能(debouncing)。

這個 UI 也設(shè)定了一些邏輯來防止在某些情況下發(fā)送請求。例如,若用戶光標在一行的中間,那么僅當其右側(cè)的字符是空格、右大括號等時才會發(fā)送請求。

1、通過上下文過濾器(Contextual Filter)阻止不良請求

更有趣的是,在生成 prompt 后,該模塊會檢查 prompt 是否「足夠好」,以便調(diào)用模型, 這是通過計算「上下文過濾分數(shù)」來實現(xiàn)的。這個分數(shù)似乎是基于一個簡單的 logistic 回歸模型,它包含 11 個特征,例如語言、之前的建議是否被接受 / 拒絕、之前接受 / 拒絕之間的持續(xù)時間、prompt 中最后一行的長度、光標前的最后一個字符等。此模型權(quán)重包含在擴展代碼自身。

如果分數(shù)低于閾值(默認 15% ),則不會發(fā)出請求。探索這個模型會很有趣,我觀察到一些語言比其他語言具有更高的權(quán)重(例如 php > js > python > rust > dart…php)。另一個直觀的觀察是,如果 prompt 以 ) 或 ] 結(jié)尾,則分數(shù)低于以 ( 或 [ 結(jié)尾的情況 。這是有道理的,因為前者更可能表明早已「完成」,而后者清楚地表明用戶將從自動補全中受益。

Copilot Panel

主要模塊:https://thakkarparth007.github.io/copilot-explorer/codeviz/templates/code-viz.html#m2990&pos=12:1

Core logic 1:https://thakkarparth007.github.io/copilot-explorer/codeviz/templates/code-viz.html#m893&pos=9:1

Core logic 2:https://thakkarparth007.github.io/copilot-explorer/codeviz/templates/code-viz.html#m2388&pos=67:1

與 Inline UI 相比,此 UI 會從模型中請求更多樣本(默認情況下為 10 個)。這個 UI 似乎沒有上下文過濾邏輯(有道理,如果用戶明確調(diào)用它,你不會想不 prompt 該模型)。

這里主要有兩件有趣的事情:


  1. 根據(jù)調(diào)用它的模式(OPEN_COPILOT/TODO_QUICK_FIX/UNKNOWN_FUNCTION_QUICK_FIX),它會略微修改 prompt。不要問我這些模式是如何激活的。
  2. 它從模型中請求 logprobs,解決方案列表按 mean logprobs 分類排序。

不顯示無用的補全建議:

在(通過任一 UI)顯示建議之前,Copilot 執(zhí)行兩個檢查:

如果輸出是重復(fù)的(如:foo = foo = foo = foo...),這是語言模型的常見失敗模式,那么這個建議會被丟棄。這在 Copilot proxy server 或客戶端都有可能發(fā)生。

如果用戶已經(jīng)打出了該建議,該建議也會被丟棄。

秘訣 3:telemetry

Github 在之前的一篇博客中聲稱,程序員編寫的代碼中有 40% 是由 Copilot 編寫的(適用于 Python 等流行語言)。我很好奇他們是如何測出這個數(shù)字的,所以想在 telemetry 代碼中插入一些內(nèi)容。

我還想知道它收集了哪些 telemetry 數(shù)據(jù),尤其是是否收集了代碼片段。我想知道這一點,因為雖然我們可以輕松地將 Copilot 擴展指向開源 FauxPilot 后端而不是 Github 后端,該擴展可能仍然會通過 telemetry 發(fā)送代碼片段到 Github,讓一些對代碼隱私有疑慮的人放棄使用 Copilot。我想知道情況是不是這樣。

問題一:40% 的數(shù)字是如何測量的?

衡量 Copilot 的成功率不僅僅是簡單地計算接受數(shù) / 拒絕數(shù)的問題,因為人們通常都會接受推薦并進行一些修改。因此,Github 的工作人員會檢查被接受的建議是否仍然存在于代碼中。具體來說,他們會在建議代碼被插入之后的 15 秒、30 秒、2 分鐘、5 分鐘、10 分鐘進行檢查。

現(xiàn)在,對已接受的建議進行精確搜索過于嚴格,因此他們會測量建議的文本和插入點周圍的窗口之間的編輯距離(在字符級別和單詞級別)。如果插入和窗口之間的「單詞」級編輯距離小于 50%(歸一化為建議大?。?,則該建議被視為「仍在代碼中」。

當然,這一切只針對已接受代碼。

問題二:telemetry 數(shù)據(jù)包含代碼片段嗎?

是的,包含。

在接受或拒絕建議 30 秒后,copilot 會在插入點附近「捕獲」一份快照。具體來說,該擴展會調(diào)用 prompt extraction 機制來收集一份「假設(shè) prompt」,該 prompt 可以用于在該插入點提出建議。copilot 還通過捕獲插入點和所「猜測」的終結(jié)點之間的代碼來捕獲「假設(shè) completion」。我不太明白它是怎么猜測這個端點的。如前所述,這發(fā)生在接受或拒絕之后。

我懷疑這些快照可能會被用作進一步改進模型的訓練數(shù)據(jù)。然而,對于假設(shè)代碼是否「穩(wěn)定下來」,30 秒似乎太短了。但我猜,考慮到 telemetry 包含與用戶項目對應(yīng)的 github repo,即使 30 秒的時間內(nèi)會產(chǎn)生嘈雜的數(shù)據(jù)點,GitHub 的工作人員也可以離線清理這些相對嘈雜的數(shù)據(jù)。當然,所有這些都只是我的猜測。

注意,GitHub 會讓你選擇是否同意用你的代碼片段「改進產(chǎn)品」,如果你不同意,包含這些片段的 telemetry 就不會被發(fā)送到服務(wù)器上(至少在我檢查的 v1.57 中是這樣,但我也驗證了 v1.65)。在它們通過網(wǎng)絡(luò)發(fā)送之前,我通過查看代碼和記錄 telemetry 數(shù)據(jù)點來檢查這一點。

其他觀察結(jié)果

我稍微修改了擴展代碼以啟用 verbose logging(找不到可配置的參數(shù))。我發(fā)現(xiàn)這個模型叫做「cushman-ml」,這強烈地暗示了 Copilot 使用的可能是 12B 參數(shù)模型而不是 175B 參數(shù)模型。對于開源工作者來說,這是非常令人鼓舞的,這意味著一個中等大小的模型就可以提供如此優(yōu)秀的建議。當然,Github 所擁有的巨量數(shù)據(jù)對于開源工作者來說仍然難以獲得。

在本文中,我沒有介紹隨擴展一起發(fā)布的 worker.js 文件。乍一看,它似乎基本上只提供了 prompt-extraction logic 的并行版本,但它可能還有更多的功能。

文件地址:https://thakkarparth007.github.io/copilot-explorer/muse/github.copilot-1.57.7193/dist/worker_expanded.js

啟用 verbose logging

如果你想啟用 verbose logging,你可以通過修改擴展代碼來實現(xiàn):

  1. 搜索擴展文件。它通常在~/.vscode/extensions/github.copilot-<version>/dist/extension.js 下。
  2. 搜索字符串 shouldLog (e,t,n){ ,如果找不到,也可以嘗試 shouldLog ( 。在幾個搜索匹配中,其中一個將是非空函數(shù)定義。
  3. 在函數(shù)體的開頭,添加 return true。

如果你想要一個現(xiàn)成的 patch,只需復(fù)制擴展代碼:https://thakkarparth007.github.io/copilot-explorer/muse/github.copilot-1.57.7193/dist/extension.js

注意,這是針對 1.57.7193 版本的。

原文中有更多細節(jié)鏈接,感興趣的讀者可以查看原文。

責任編輯:張燕妮 來源: 機器之心
點贊
收藏

51CTO技術(shù)棧公眾號