Cursor:如何構(gòu)建 AI Coding 最佳實(shí)踐?

0 評論 3039 瀏覽 2 收藏 93 分鐘

在AI編程的新時(shí)代,Cursor正以其創(chuàng)新的IDE平臺,重新定義代碼與開發(fā)者的互動方式。隨著LLM技術(shù)的進(jìn)步,Cursor不僅在底層模型上進(jìn)行了革命性的提升,更在UI/UX設(shè)計(jì)上不斷突破,為程序員提供了一個(gè)更為智能、高效的編程環(huán)境。本文深入探討了Cursor如何通過AI的力量,提升編程體驗(yàn),以及它對未來編程模式的深刻洞見。

AI coding 是模型推理能力增加之后的下一個(gè)競爭高地,除了模型廠商、AI Labs 之外,這個(gè)領(lǐng)域的參與者也有著 Cursor 這樣的初創(chuàng)團(tuán)隊(duì)。作為一個(gè) LLM-first IDE,Cursor 在今年迅速出圈,一方面在于底層模型 Claude Sonnet 3.5 模型 coding 能力提升帶來的體驗(yàn)升級,另一方面也在于團(tuán)隊(duì)在 AI Coding UI/UX 上的持續(xù)投入。

本篇內(nèi)容是 Lex 對 Cursor 團(tuán)隊(duì)訪談的詳細(xì)整理,團(tuán)隊(duì)創(chuàng)始成員 Aman Sanger (CEO)、Arvid Lunnemark(CTO)、Sualeh Asif(COO)和 Michael Truell(設(shè)計(jì)主管)詳細(xì)分享了 Cursor 產(chǎn)品體驗(yàn)、infra、模型訓(xùn)練、數(shù)據(jù)安全等細(xì)節(jié)以及他們對于 AI coding、AI Agent 的思考,通過這些分享也能了解 Cursor UI/UX 背后的理念。

? o1 不會干掉 Cursor,AI Coding 領(lǐng)域才剛剛開始;

? 圍繞代碼預(yù)測、補(bǔ)齊等各類任務(wù) Cursor 還訓(xùn)練了一系列專門的小模型;

? Cursor 正在試驗(yàn)一個(gè)叫做 Shadow Space 的產(chǎn)品概念,通過在后臺運(yùn)行一個(gè)隱藏窗口讓 AI 在不影響到開發(fā)者的操作的情況下進(jìn)行 coding 任務(wù);

? 團(tuán)隊(duì)在 code base  indexing 上投入了大量精力,這個(gè) indexing 系統(tǒng)會成為接下來其他代碼任務(wù)可以展開的基礎(chǔ);

? 未來編程會是自然語言和代碼將共存,根據(jù)具體任務(wù)選擇最有效的交互;

? AI 正在重塑編程體驗(yàn),提高效率的同時(shí)保持程序員的創(chuàng)造力和控制力;

? Cursor 認(rèn)為 Claude 3.5 Sonnet 的綜合實(shí)力更強(qiáng),Sonnet 最強(qiáng)的地方在于能夠很好地理解開發(fā)者表述并不清晰的目標(biāo),預(yù)測程序員接下來的操作、給出適當(dāng)建議;

? 即便是 SOTA 模型也不擅長找 bug,這會是 Cursor 的機(jī)會;

? 當(dāng)前的代碼任務(wù)基準(zhǔn)測試并不能準(zhǔn)確反映模型的真實(shí)能力,因?yàn)楝F(xiàn)實(shí)中的代碼任務(wù)更加復(fù)雜多樣,并且充滿了模糊性和上下文依賴。Cursor 團(tuán)隊(duì)更傾向于通過真實(shí)用戶的使用反饋來評估模型的性能;

? 目前還沒有人能很好地解決 models routing 問題,底座模型和自有模型之間可以初步實(shí)現(xiàn)模型切換,但如果是 GPT-4o、Claude sonnet 和 o1 之間的切換可能要更加復(fù)雜。

         ?? 目錄 ??        

   01 Cursor 的誕生

   02 Cursor 的產(chǎn)品

   03 Cursor 是如何提高產(chǎn)品響應(yīng)速度的

   04 Priompt :以網(wǎng)頁為靈感的提示詞工程  

   05 Shadow Workspace:

        Coding Agent  的工作流設(shè)計(jì)

   06 SOTA 模型也不擅長找 bug

   07 Code Indexing 和 Context

   08 Scaling law 和復(fù)現(xiàn) o1 模型

一、Cursor 的誕生

Github Copilot 是第一個(gè) LLM-driven 的消費(fèi)級應(yīng)用

Lex:代碼編輯器這個(gè)領(lǐng)域整體是什么樣的?

Michael:代碼編輯器(code editor)主要用來開發(fā)軟件。對外行來說,可以把代碼編輯器想象成是一個(gè)專門給程序員設(shè)計(jì)的高級版的文字處理器。之所以說它是高級版,是因?yàn)榇a有不同結(jié)構(gòu),而代碼編輯器能做的事情比普通的文字處理器要多得多。

比如,代碼編輯器能讓我們直觀地區(qū)分代碼中不同的標(biāo)記、快速瀏覽代碼、在代碼庫里快速瀏覽內(nèi)容,就像在網(wǎng)上點(diǎn)擊超鏈接一樣,快速跳轉(zhuǎn)到需要的定義;甚至還能檢查錯(cuò)誤,幫助發(fā)現(xiàn)一些簡單的 bug。代碼編輯器主要的功能一般就是這些。但我認(rèn)為,在接下來的 10 年里,由于軟件開發(fā)方式發(fā)生變化,代碼編輯器的概念也會發(fā)生很大的改變。

Lex:Cursor 是新一代代碼編輯器,flok 了VS Code 的一個(gè)分支。你們自己使用代碼編輯器的經(jīng)歷是什么樣的?

Aman:一開始我們都是用 Vim 做代碼編輯。當(dāng)時(shí)還沒有 Neovim,只有 Vim 和一個(gè)終端。2021 年 Copilot 發(fā)布的時(shí)候,我很想體驗(yàn)一下 Copilot,但由于 Copilot 只能在 VS Code 上使用,所以我就開始用 VS Code 了。Copilot 和 VS Code 這個(gè)組合的使用體驗(yàn)特別好,所以即便我很喜歡 Vim,我還是轉(zhuǎn)向了 VS Code。在我們開發(fā) Cursor 之前,VS Code 都是我的默認(rèn)編輯器。

Arvid:我覺得 GitHub Copilot 有一個(gè)被低估的方面是,即使它的代碼補(bǔ)齊出現(xiàn)了錯(cuò)誤,但最多只是讓人覺得有點(diǎn)麻煩,我們再多打一個(gè)字符,它可能就懂我們的需求了,所以也不會太差。

Sualeh:其實(shí)就是通過“犯錯(cuò)-糾正”的過程來迭代。Copilot 還有一個(gè)被低估的地方,它是第一個(gè)真正的 AI 產(chǎn)品,也是第一個(gè) LLM-driven 的消費(fèi)級產(chǎn)品。

Lex: Cursor 是怎么誕生的?

Michael:大概 2020 年的時(shí)候,OpenAI 提出了 Scaling Law,從那時(shí)候開始 LLM 領(lǐng)域的進(jìn)步就變得清晰可預(yù)測,只要算力和數(shù)據(jù)夠多,模型表現(xiàn)就能更好,也是在那時(shí)候,開始有很多人討論“這項(xiàng)技術(shù)的進(jìn)步能給我們帶來什么”,“它要怎么去改善各種不同的知識工作領(lǐng)域”等等。

有幾個(gè)時(shí)刻讓我感受到 scaling law 的預(yù)測變得越來越具體。第一個(gè)是 Copilot  beta 版本推出,第二時(shí)刻是 GPT-4 發(fā)布的時(shí)候。

GPT-4 的提升讓我們看到 scaling laws 對于“算力-模型能力”的預(yù)測已經(jīng)可以落到具體的地方了,有可能很快就能開發(fā)出來更多有價(jià)值的東西。而且,如果我們繼續(xù)往下做,不僅能點(diǎn)對點(diǎn)地解決問題,甚至整個(gè)編程領(lǐng)域都可以用到這些模型,只不過需要我們搭載一個(gè)不一樣的編程環(huán)境,提供一種不同的編程方式。這就是為什么我們開始圍繞這個(gè)更大的 vision 去進(jìn)行開發(fā)。

Sualeh:2022 年 6 月左右,Shengtong 和 Aman 打過一個(gè)賭,預(yù)測到 2024 年 6 月或 7 月,人們能不能靠模型能力拿到 IMO 金牌。雖然很確定模型能取得進(jìn)步,但我當(dāng)時(shí)的想法是模型贏得奧數(shù)金牌完全不可能,但 Aman 認(rèn)為可以。

Aman:不過我還要補(bǔ)充一點(diǎn),能取得多大進(jìn)步,也取決于我們在哪個(gè)領(lǐng)域。數(shù)學(xué)就是一個(gè)很好的領(lǐng)域,尤其是形式化定理證明(formal theorem proving),因?yàn)槲覀兡芡ㄟ^信號來驗(yàn)證對不對,所以 RL 就能很好地起作用。不過,即便有的模型可以在數(shù)學(xué)能力上超越人類,但是我們還是沒有達(dá)到 AGI。

Cursor 是什么?

Lex:VS Code 可以說在某種程度上統(tǒng)一了開發(fā)者社區(qū),既然已經(jīng)可以在  VS Code 里安裝很多 Copilot 這樣的 AI extension,你們?yōu)槭裁催€要重新做一個(gè)  VS Code?

Michael:開發(fā)一個(gè)編輯器的想法其實(shí)是自然而然形成的。因?yàn)樵陂_發(fā) Cursor 的時(shí)候,我們想的是,模型肯定還會變得更好,能力還會提高,不但能顯著提高生產(chǎn)力,也會從根本上改變現(xiàn)在我們開發(fā)軟件的方式。如果只是在現(xiàn)有的編輯器里加一個(gè)插件,對編輯器的控制提升是很有限,我們不像被這些限制束縛,希望能夠構(gòu)建最有用的東西。

Lex:VS Code 和 Copilot 的組合可以說是 Cursor 最直接的競爭對手,Cursor 要如何在競爭中獲勝?

Aman:如果我們回看之前的幾次科技浪潮,會發(fā)現(xiàn)它們的普遍特點(diǎn)是,有某一件大事發(fā)生,然后圍繞這個(gè)大事件一波新的公司開始出現(xiàn)。但現(xiàn)在不同的是,每年只要有一個(gè)模型的能力取得了飛躍,都會出來一波新的功能,有一波新的可能性。尤其是在代碼領(lǐng)域,甚至都不需要一年,只要領(lǐng)先幾個(gè)月,就可以讓我們的產(chǎn)品更有用。一年后我們再回過頭來看今天的 Cursor,會發(fā)現(xiàn)它也已經(jīng)過時(shí)了。Microsoft 確實(shí)做了很多非常出色的事情,但是從編程的角度上來說,我覺得他們不像別的初創(chuàng)公司一樣在一直創(chuàng)新。

Sualeh:我其實(shí)不會從功能的角度來考慮,我會從程序員的能力角度來看這個(gè)問題。o1 模型剛出來的時(shí)候,我就覺得肯定還會有很多不同的模型,比如更長的上下文、或者算力更快的模型。所有這些聽起來很瘋狂的想法都值得嘗試,最后大概能有 1/10 的想法可以真正做成比較有用的東西,我們希望它們推出的速度能更快。

還有一個(gè)被低估的事實(shí)是,我們其實(shí)是在給自己制作這個(gè)工具。我們之所以有 Cursor 這個(gè)想法也是因?yàn)槲覀兡苊鞔_感受到模型能力的提升,但 Copilot 給我們帶來的體驗(yàn)并沒有變得更好,也很疑惑為什么。當(dāng)然,Copilot 賣得很好,對于 Microsoft 來說也是一個(gè)很成功的業(yè)務(wù),但相當(dāng)長時(shí)間 Copilot 都沒有新東西出來,我個(gè)人來說會很想試試新的東西。

Arvid:我們是一整個(gè)團(tuán)隊(duì)在開發(fā) Cursor,不管是開發(fā)用戶體驗(yàn),還是探索模型的交互方式,我們都在同步去開發(fā)怎么才能讓模型給出更好的答案,比如怎么構(gòu)建 prompt,怎么找到上下文,怎么用 Cursor Tab 訓(xùn)模型等等。

二、Cursor 的產(chǎn)品

Cursor Tab

Lex:Cursor 的交互中, Tab 鍵是一個(gè)很獨(dú)特的存在,它可以看作是增強(qiáng)版的自動補(bǔ)全,幾乎什么都會??梢哉归_講講 Tab 鍵的邏輯的嗎?

Sualeh:我們很想要實(shí)現(xiàn)的一件事就是讓模型幫我們做代碼編輯。為此我們嘗試了很多種辦法,當(dāng)我們有了能力很強(qiáng)的模型之后,為了讓使用體驗(yàn)更加流暢,我們花了很大功夫提高了推理速度,優(yōu)化用戶體驗(yàn)。

Tab 鍵存在的目標(biāo)是,如果用戶接受了某一處編輯建議,模型應(yīng)該很自然地就知道下一個(gè)可能需要編輯的位置在哪里。比如說在我改動了一個(gè)地方之后,模型就應(yīng)該知道下一處要改動的地方需要跳轉(zhuǎn)到 18 行下面,而不是像 Vim 那樣需要用戶自己來按 18JJ 這樣的快捷鍵。

所以我們解決這個(gè)需求的方式就是 Tab 鍵。用戶只需要按下 Tab 鍵,模型就可以自動跳轉(zhuǎn)到第 18 行可能需要編輯的位置,顯示下一條編輯建議,再按一下 Tab 鍵,又會顯示下一條,可以這么一直按下去。

所以 Tab 背后的核心思想是:如何讓編輯過程的熵為 0。一旦用戶表達(dá)了他們的想法,模型應(yīng)該能夠推斷出接下來的編輯內(nèi)容,也就是說沒有新的信息需要用戶輸入。但現(xiàn)在用戶還是需要輸入一些字符,才能讓計(jì)算機(jī)理解他們的想法。理想情況下,模型應(yīng)該能夠“讀懂”用戶的想法,所有不需要新信息的編輯部分都應(yīng)該通過按 Tab 鍵來跳過。

Aman:如果我們觀察不同類型語言模型的 loss,會發(fā)現(xiàn)在代碼領(lǐng)域,比特每字節(jié)(bits per byte)的 loss 比語言領(lǐng)域要低。也就是說,在代碼領(lǐng)域,很多 token 和字符完全是可以預(yù)測的。所以除了自動補(bǔ)全以外,在預(yù)測用戶編輯代碼后的下一步操作時(shí),這種可預(yù)測的現(xiàn)象會更明顯。

Cursor Tab 就是要消除編輯器中所有低熵的操作。一旦確定了用戶的操作步驟,就應(yīng)該直接跳過這些低熵的操作,讓用戶直接跳到下一處編輯點(diǎn)。

Lex:Cursor 是怎么做預(yù)測的?背后的技術(shù)細(xì)節(jié)是什么樣的?

Aman:首先,Cursor 的延遲很低。我們訓(xùn)練了專門的小模型來解決這類任務(wù)。這些模型很依賴 pre-fill tokens,也就是說,這些模型面對的是非常長的 prompt,需要處理很多代碼行,但是實(shí)際生成的 token 并不多。這種情況下使用稀疏模型(Sparse Model)就很合適,這是一種 MoE 模型。這是我們做的一個(gè)突破,這個(gè)突破顯著提高了模型處理長上下文時(shí)的性能。

另一個(gè)關(guān)鍵點(diǎn)是我們基于推測解碼(Speculative Decoding)構(gòu)建了推測編輯(Speculative Edits)。

這兩個(gè)因素在我看來是 Cursor 生成質(zhì)量高、速度快的關(guān)鍵。

緩存在這里的作用很大,由于輸入的 token 非常多,如果每在一個(gè)給定行輸入,都要在 token 上重新運(yùn)行模型,那么延遲會很高,GPU 也會因?yàn)檫^載而崩掉,所以就需要設(shè)計(jì)模型的 prompt,這樣它們就可以進(jìn)行緩存。我們也會跨請求重用 KV Cache,這樣就減少了工作量和需要的算力。

Lex:除了代碼生產(chǎn)、代碼補(bǔ)齊、跨行編輯,以及在同一個(gè)文件內(nèi)的不同位置之間跳轉(zhuǎn)外,Cursor 的 Tab 鍵還能做哪些事情?

Sualeh:我們接下來想通過 Tab 鍵來實(shí)現(xiàn)在不同文件之間的跳轉(zhuǎn)。有可能用戶先編輯了某個(gè)文件,但是想要在另一個(gè)文件中繼續(xù)編輯,這樣的話 Tab 也應(yīng)該要能跳轉(zhuǎn)到另一個(gè)文件。

Arvid:這個(gè)過程可以概括成下一步動作預(yù)測(next action prediction)。比如有時(shí)候在終端運(yùn)行命令,需要 Cursor 根據(jù)已經(jīng)寫好的代碼來推薦命令,它雖然能給出一些建議,但如果沒有更多的信息,用戶很難判斷到底對不對。所以,Tab 就可以引導(dǎo)用戶先跳轉(zhuǎn)到定義,了解了定義知識以后再跳轉(zhuǎn)回來,這樣用戶就知道了這個(gè)概念,愿意接受下一處補(bǔ)全。

Michael:Coding 是一個(gè)很獨(dú)特的領(lǐng)域。很多時(shí)候,在編程中其實(shí)是可以根據(jù)用戶前面的工作來預(yù)測接下來 5 分鐘他們想要做什么的。

所以我們可能可以發(fā)展到這樣一個(gè)狀態(tài):在人類開發(fā)者已經(jīng)做了一些工作之后,接下來要么讓 Cursor 幫忙接管接下來 5 分鐘的任務(wù),對于人類員工來說,只需要稍微看一下 Cursor  的步驟,發(fā)現(xiàn)它完成得很不錯(cuò),再在屏幕上點(diǎn)擊 Cursor 做出的改動就可以了。

Code diff

Lex:Cursor diff 會通過紅綠對比來告訴我們怎么修改代碼,在聊天窗里也可以查看并且接受 diff。diff 的設(shè)計(jì)邏輯是什么樣的?

Sualeh:我們總共有四到五種不同的 diff 視圖,比如我們就為自動補(bǔ)全優(yōu)化了專門的 diff 視圖,這個(gè) diff 界面和審查大塊代碼時(shí)的 diff 界面是不一樣的。針對處理多個(gè)不同文件的情況,我們也在優(yōu)化響應(yīng)的 diff 視圖。

從 high-level 角度,自動補(bǔ)全會要求更快的閱讀速度,程序員的視線需要集中在一個(gè)特定區(qū)域,不能關(guān)注到太多地方,避免注意力分散,所以我們做這個(gè)任務(wù)的 diff 的時(shí)候會優(yōu)先考慮這個(gè)問題。

現(xiàn)在自動補(bǔ)齊的 diff 界面中,除了代碼寫作框外,如果我們想要?jiǎng)h掉某個(gè)位置的代碼,添加另一個(gè)代碼,它會在旁邊獨(dú)立顯示一個(gè)框。

我們圍繞代碼補(bǔ)齊做過三、四種不同的 diff 界面。最初是類似于 Google Doc 的刪除線的形態(tài),還嘗試過紅色高亮塊等等形態(tài),最終選定了這個(gè)方案。下一版迭代會更有趣。首先會有一個(gè)藍(lán)色高亮來突出 AI 給出建議的位置,但具體的建議內(nèi)容不會直接顯示出來,用戶可以通過按住 Option 鍵的方式來查看 AI 的建議到底是什么,如果松開 Option 就又會回到原始代碼界面。

Arvid:我個(gè)人很期待接下來可以圍繞 diff 做出很多改進(jìn)和優(yōu)化。diff 涉及的場景通常是代碼審查里面的驗(yàn)證難題,diffs 很適合小范圍的代碼改動,但如果代碼改動的范圍比較大,或者涉及到多個(gè)文件,要來回審查這些 diff 其實(shí)就有點(diǎn)難了。

關(guān)于這一點(diǎn),我們其實(shí)也有幾種解決思路:

其中一個(gè)想法是,在比較大規(guī)模的代碼改動中,有些 diff 的信息量很大,很重要,而有些 diff 只是一樣的信息來回重復(fù),也就是說它們的熵很低。所以我們就把重要的那些 diff 高亮,不重要的標(biāo)成灰色。

另一種思路是,我們可以有一個(gè)模型來對這些 diff 進(jìn)行審查對比,如果找到某個(gè)地方有 bug 的話,就用紅色波浪線劃出來,讓用戶重新審查這里的 diff。

這些想法都讓我覺得在產(chǎn)品體驗(yàn)上我們還有很多事情值得做。

Lex:這些產(chǎn)品思路最終想達(dá)到的目標(biāo)是讓人類工程師在代碼編輯和審查中只關(guān)注他們最需要閱讀的內(nèi)容,從而達(dá)到最佳效果?

Arvid:我們也考慮過設(shè)計(jì)一個(gè)模型來做這件事,現(xiàn)在 diff 環(huán)節(jié)的算法只是普通的算法,沒有特別智能。雖然我們在設(shè)計(jì)這套算法的時(shí)候?qū)⑷祟惖闹悄苋谌肓诉M(jìn)去,但在實(shí)際執(zhí)行中,算法并不會關(guān)心處理的具體任務(wù)是什么,而這正是我們希望模型能做到的事情。

Sualeh:問題就在于,這些模型會越來越聰明,如果模型越來越聰明,它們能提出的代碼改動的內(nèi)容和范圍也會越來越大。如果改動越來越大,程序員就要做越來越多的代碼審查工作,而 Cursor 就可以幫他們做這個(gè),這樣他們就不需要花大把的時(shí)間來做代碼審查。

Lex:涉及到多個(gè)文件的代碼審查時(shí),diff 又是怎么設(shè)計(jì)和實(shí)現(xiàn)的?

Aman:就像 Arvid 剛才說的,我們可以把人從代碼審查中解放出來?,F(xiàn)在,我們要花很長時(shí)間去理解陌生的代碼,而且它找出的 bug 往往非常多。但是使用 LLM  就可以很大程度上提高審查的體驗(yàn),比方說,Arvid 剛剛說的那些想法就可以引導(dǎo)用戶關(guān)注真正重要的地方。

代碼審查的用戶體驗(yàn)設(shè)計(jì)通常要考慮到 reviewer 和寫代碼的人兩類角色,但如果寫代碼的是 LLM 的話,我們就可以不用考慮代碼編寫的具體體驗(yàn)了,從而可以更多去圍繞 reviewer 來設(shè)計(jì)整個(gè)產(chǎn)品體驗(yàn),從而讓 reviewer 可以輕松高效地完成任務(wù)。

我覺得只是關(guān)注代碼審查環(huán)節(jié)本身的話并不能真正解決問題,我們在產(chǎn)品設(shè)計(jì)中可以更有創(chuàng)意,去探索可能性的邊界。

Arvid:我覺得需要指出來的一點(diǎn)是,順序也很關(guān)鍵(ordering matters)。一般來說,在審查時(shí)我們會先看到一串文件列表,然后習(xí)慣性地從上往下看。但實(shí)際上,有些內(nèi)容因?yàn)檫壿嬌鲜腔A(chǔ)的,應(yīng)該最先理解這部分內(nèi)容,之后才是其他部分信息。這種梳理順序的工作就可以交給模型,讓模型來引導(dǎo)我們?nèi)フ业叫畔⑦壿嬳樞颍賮硗瓿蓪彶椤?/p>

Lex:未來代碼的創(chuàng)作過程是不是會越來越多地使用自然語言?變得像寫書一樣。

Arvid:有時(shí)候會用到自然語言,但不是所有的編程任務(wù)都會用到自然語言。比如,如果我和 Sualeh 一起編程,Sualeh 可能坐在電腦前敲鍵盤,而我在開車,那我會告訴 Sualeh 要怎么去做。但也有些有時(shí)候,要給 Sualeh 解釋清楚要做什么很麻煩,那我就會直接接過鍵盤,給他演示一下要怎么做,我只需要寫一部分代碼,他就懂了,這種溝通方式很簡單直接。對 AI 來說也是這樣的。有時(shí)候最簡單的溝通方式就是給 AI 一個(gè)例子,這樣它就會繼續(xù)往下做完這個(gè)任務(wù)。

或者,如果我們要設(shè)計(jì)網(wǎng)站,最簡單的辦法并不是告訴 AI 我想要的是什么,而是直接拖動、繪制頁面,給它展示應(yīng)該怎么做。可能到最后我們也會通過腦機(jī)接口讓 AI 理解我們的想法,到那時(shí)自然語言一定會有一席之地,但是我覺得大多數(shù)時(shí)候,自然語言不會是大多數(shù)人寫代碼的方式。

底層模型

Lex: Cursor 背后的模型是什么?

Aman:除了調(diào)用最新一代模型能力之外,Cursor 自己也訓(xùn)練了一些自定義模型(custom models),這些模型會配合那些在復(fù)雜推理任務(wù)上表現(xiàn)更好的頭部模型,共同完成任務(wù)。

Cursor Tab 就是一個(gè)很好的例子,在 Cursor Tab 的需求下,我們訓(xùn)練了一個(gè)針對這個(gè)特定任務(wù)的模型,最新一代特定模型在特定任務(wù)上的表現(xiàn)甚至超過前沿模型,這一點(diǎn)從評估結(jié)果就能看出來。

另一個(gè)需要特定任務(wù)模型的是 Apply,在 Apply 任務(wù)中,自定義模型不僅很有必要,而且效果也很好。雖然頭部模型很擅長做代碼規(guī)劃、生成大致的修改框架,但是它們不能很好地顯示 diff。

無論是 Sonnet 還是 o1,這些模型都會在一些簡單的細(xì)節(jié)上出錯(cuò),比如數(shù)代碼行,尤其是在處理超大文件時(shí),這類問題會更普遍。為了解決這個(gè)問題,我們就讓模型先勾勒出一個(gè)大致的代碼塊,用來表示需要修改的內(nèi)容,然后再訓(xùn)一個(gè)模型把更改 Apply 到文件中。

我們也還在探索怎么把 o1 模型的能力嵌入到產(chǎn)品體驗(yàn)里,我覺得如何目前就 o1 的最佳實(shí)踐還沒有定論,但比較明確的方向是,它可以讓我們更容易地在后臺處理一些任務(wù)、agent 的屬性更強(qiáng)。

Aman:o1 也有一些明顯的限制,先拋開能力本身,它不支持流式輸出這一點(diǎn)就很影響體驗(yàn)。o1 目前的狀態(tài)更像是一個(gè) V0 版本,還有很多需要改進(jìn)的方面。

Lex:有消息說 GitHub Copilot 可能會以某種方式整合 o1 模型的能力,也因此會有人說“Cursor 完了”,你們怎么看這件事?

Michael:我認(rèn)為這個(gè)領(lǐng)域與過去 2010 年左右的軟件領(lǐng)域有些不同,因?yàn)檫@里的上限真的非常高。我認(rèn)為再等 3-4 年,到那個(gè)時(shí)候最好的 AI 編程產(chǎn)品可能比現(xiàn)在的要實(shí)用得多。就當(dāng)下說只要能打造出更好的產(chǎn)品,就有機(jī)會超越那些擁有大量用戶的競爭者。因此,我認(rèn)為接下來的幾年關(guān)鍵在于打造最好的產(chǎn)品和系統(tǒng),不僅包括模型引擎的改進(jìn),還包括優(yōu)化代碼編輯體驗(yàn)。

Aman:Cursor 相比其他產(chǎn)品對于用戶的附加值不僅僅在于能快速整合 o1 這樣的新模型,它的價(jià)值還來自于我們針對各種類型任務(wù)開發(fā)的專有模型,這些模型可能在用戶不知情的情況下默默發(fā)揮作用,并且每個(gè)功能都是圍繞用戶體驗(yàn)精心設(shè)計(jì)的。

Lex:在你們的實(shí)踐經(jīng)驗(yàn)中,GPT 和 Claude 對比,誰的代碼能力更強(qiáng)?不過由于 Cursor 各個(gè)環(huán)節(jié)涉及到的模型不同,答案可能不是絕對的?

Aman:沒有哪個(gè)模型能在所有方面的表現(xiàn)都比其他模型更好,包括速度、代碼編輯能力、處理大量代碼的能力、上下文長度和代碼能力等等。不過,就目前來看,我覺得整體上表現(xiàn)最好的模型是 Sonnet,這也是大家的共識。

o1 確實(shí)很有意思,它很擅長推理,如果給它一些非常難的編程面試題問題或者 LeetCode 題目,它可以處理得很好,但是它不像 Sonnet 那樣能很好地理解到用戶的模糊需求。

至于其他的頭部模型,我并不是想說它們是針對基準(zhǔn)測試訓(xùn)練出來的,但確實(shí)存在一個(gè)顧慮是,和中腰部水平模型相比,它們在基準(zhǔn)測試中表現(xiàn)很出色,但一旦涉及到超出這些范圍的任務(wù),這些模型的表現(xiàn)就會下降。

但當(dāng)我們的任務(wù)范圍超出基準(zhǔn)測試時(shí),Sonnet 是最能保持其原有能力的模型,它在基準(zhǔn)測試中展現(xiàn)的能力和在實(shí)際代碼任務(wù)中的表現(xiàn)是保持一致的。

Lex:前面提到的 Apply 這個(gè)功能可以查看代碼,提出很好的建議,告訴我們接下來怎么做。對我們來說,把這兩個(gè)模型結(jié)合起來好像也不難,為什么你們覺得并不簡單?

Sualeh:和大家的猜測相反,Apply 的實(shí)現(xiàn)并沒有采用確定性算法(deterministic algorithm)。

Aman:人們在其他代碼產(chǎn)品那里看到的 Apply 其實(shí)都是對 Cursor Apply 的簡單模仿,可能很多人覺得 Apply 是可以通過確定性匹配的方式來實(shí)現(xiàn),但這種方式下有 40% 的概率都會出錯(cuò),產(chǎn)品體驗(yàn)也會很差。

我們在設(shè)計(jì) Apply 的時(shí)候還能最智能的模型在生成代碼時(shí),使用較少的 token,從而降低延遲和成本。我們可以給出一個(gè)非常粗略的代碼,讓模型去跑,因?yàn)榕芡ㄟ@個(gè)框架的代碼對模型來說很簡單。

這樣的形式還可以繼續(xù)下去。可以用越來越智能的模型來做規(guī)劃,然后讓不那么智能的模型來跑。比如我們可以用 o1 或者更智能的模型來做更 high-level 的規(guī)劃,再先后讓 Sauna 和 Apply 來處理。圍繞“如何讓 Cursor 反應(yīng)更快”也有很多細(xì)節(jié)在里面。

三、Cursor 是如何提高產(chǎn)品響應(yīng)速度的

Lex:你們是怎么提高 Cursor 響應(yīng)速度的?

Aman:其中一個(gè)重要組成就是推測編輯(speculative edits),這是從推測解碼(speculative decoding)中衍生出來的。

可以先解釋一下推測解碼是什么。大部分情況下,尤其是在 LLM 生成受內(nèi)存限制的情況下,并行處理 tokens 比逐個(gè)生成 tokens 更快。如果查看每秒生成的 tokens 數(shù)量,會發(fā)現(xiàn)處理 prompt tokens 的速度比逐個(gè)生成 tokens 要快得多。推測解碼可以通過并行處理提高模型速度。

傳統(tǒng)的推測解碼是用一個(gè)很小的模型來預(yù)測 draft tokens,然后用更大的模型來做驗(yàn)證。但因?yàn)轭A(yù)測的 code 實(shí)際就是已有的 code 本身,而我們已經(jīng)很熟悉這些 code,所以可以直接把原始的 code 片段輸入到模型中,讓模型去驗(yàn)證代碼相不相符。絕大多數(shù)情況下,模型會認(rèn)為這些 code 片段是正確的,并且直接原樣輸出。

這樣,我們可以并行處理足夠多的代碼行,直到模型預(yù)測的文本與原始 code 出現(xiàn)不一致的地方。這時(shí),模型會生成新的 token,我們則根據(jù) code 的匹配程度判斷什么時(shí)候重新開始檢測代碼塊。

最終呈現(xiàn)出來的效果就像是加速版的代碼編輯,看起來就像是模型在以更快的速度重寫所有 code。所以我們就可以用和 diff 一樣的界面,只不過數(shù)據(jù)傳輸?shù)男矢摺?/p>

Sualeh:這樣設(shè)計(jì)的好處是,我們可以在數(shù)據(jù)傳輸?shù)臅r(shí)候就開始做代碼審查而不是等到全部加載完才開始整個(gè)過程。這個(gè)設(shè)計(jì)很有意思,但其實(shí) speculation 這個(gè)路徑現(xiàn)在已經(jīng)很常見了,不僅僅是 LLM,CPU 和數(shù)據(jù)庫實(shí)踐中都有用到 speculation。

Lex:具體到 chat、diff 這種功能上,是怎么讓模型響應(yīng)這么快的?

Aman:我們用了很多策略,其中一個(gè)比較有意思的就是緩存預(yù)熱(cache warming)。當(dāng)用戶在輸入時(shí),我們能預(yù)判他們可能用到的上下文信息。我們之前提到過,復(fù)用 KV catch 可以降低延遲和成本,還能跨請求共享。只要用戶開始輸入內(nèi)容,我們就可以立即使用當(dāng)前的文件內(nèi)容預(yù)熱緩存,這樣用戶按下回車后,需要預(yù)處理的 token 就少了很多,可以更快開始生成響應(yīng),大大降低了首次響應(yīng)時(shí)間(TTFT)。

Lex:還有哪些更 high-level 的緩存機(jī)制可以幫助提升響應(yīng)速度?

Aman:在 Cursor Tab 的設(shè)計(jì)里,系統(tǒng)可以預(yù)測用戶可能會接受哪個(gè)建議,提前處理可能會發(fā)生的下一個(gè)請求操作。這種方法結(jié)合了緩存和預(yù)測。通過預(yù)測用戶的選擇,系統(tǒng)可以提前緩存下一個(gè)建議,這樣一來,當(dāng)用戶按下 Tab 鍵的時(shí)候,系統(tǒng)就會立即給用戶呈現(xiàn)下一個(gè)建議。這是一種啟發(fā)式的策略,雖然模型本身沒有變化,但通過更 high level 的緩存機(jī)制,會讓用戶感覺響應(yīng)速度很快。

Sualeh:如果我們能讓 KV Cache 變得更小,就可以做更多預(yù)測。比如,系統(tǒng)可以預(yù)測并緩存接下來 10 個(gè)可能的建議,用戶在這 10 個(gè)選項(xiàng)中選中一個(gè)的概率會比單個(gè)預(yù)測要高得多。用戶可能輸入新字符后會選擇緩存中的其他選項(xiàng)。這個(gè)策略在 RL 上也很有用,單個(gè)預(yù)測樣本可能不夠準(zhǔn)確,但如果有多個(gè)預(yù)測選項(xiàng),其中至少有一個(gè)正確的概率就會大大提升。

模型內(nèi)部會對于哪個(gè)建議是正確的、用戶想要的是什么建議,都存在一定的不確定性。我們在用 RL 訓(xùn) Cursor Tab 的模型時(shí)的其中一個(gè)事情就是預(yù)測模型生成的 100 個(gè)建議中,哪一個(gè)對用戶來說更合適,更符合他們的偏好。

模型可能在某些方面能夠做出很準(zhǔn)確的預(yù)測,而在其他方面預(yù)測得就不是太準(zhǔn)確,基于用戶反饋,我們可以對模型進(jìn)行獎(jiǎng)勵(lì)或懲罰,如果用戶喜歡這個(gè)建議,就給它獎(jiǎng)勵(lì),如果模型喜歡而用戶不喜歡,就給它懲罰。通過這種方式,我們可以訓(xùn)練模型輸出更多用戶會喜歡的建議。

Aman:雖然這樣不能直接提高響應(yīng)速度,但是二者之間其實(shí)是有聯(lián)系的。因?yàn)榧词故切∧P?,只要能?RL 訓(xùn)模型,就有可能達(dá)到與大模型相當(dāng)?shù)男阅芩健3饲懊娴?KV catch 的角度,還有一些技術(shù)能幫忙提高速度。

比如,兩年前比較主流的是多頭注意力機(jī)制(multi-head attention),現(xiàn)在已經(jīng)逐漸轉(zhuǎn)向使用更加高效的注意力機(jī)制,比如 group query 或者 multi-query attention,這些機(jī)制能在更大的 batch size 下更快地生成 token。

但這些方法并不會影響首個(gè) token 的預(yù)填充速度,它們主要是提升的是后續(xù) token 的生成速度。這是因?yàn)橛脩羯?token 時(shí)面臨的瓶頸不再是在所有 token 上執(zhí)行高度可并行的矩陣乘法,而是在處理 long context 和更大規(guī)模的 batch size 時(shí),如何快速讀取那些緩存的 keys 和 values。這就又涉及到內(nèi)存帶寬的問題,我們可以通過壓縮 keys 和 values 來提升速度。group query 或者 multi-query attention 的目的都是減少 KV Cache 的大小。

DeepSeek 開發(fā)的 MLA(Multi-Latent Attention,多隱向量注意力機(jī)制)也是一種,但MLA 的思路更新:用一個(gè)大的共享向量來存儲所有keys 和 values,同時(shí)為每個(gè) token 配備較小的向量,這樣系統(tǒng)就只需存儲較小的向量,它的原理是把所有注意力頭的鍵值都轉(zhuǎn)換成一個(gè)隱向量,這個(gè)向量隨后會在時(shí)間維度上擴(kuò)展。

Lex:這些在處理內(nèi)存限制上的細(xì)節(jié)最終是如何影響用戶體驗(yàn)的?

Aman:主要有兩方面的影響。首先,我們可以大幅增加緩存容量,因?yàn)?KV Cache 占用空間變小了。這也意味著我們可以緩存更多內(nèi)容,提高緩存命中率(cache hits),從而減少首個(gè) token 的生成時(shí)間。其次,即使用戶的請求量增加、批次變大,模型推理時(shí)的速度也不會明顯下降,因?yàn)槟P湍芤云ヅ涞乃俣壬?token。

Sualeh:這種方法還能支持更長的 prompt,從而給到模型的提示也更明確。

Aman:KV Cache 是“所有 prompts 的總大小”和“并行處理的 prompt 數(shù)量”的乘積,所以這兩個(gè)維度中任何一個(gè)的增加也都不會降低 token 生成的延遲。

四、Priompt :以網(wǎng)頁設(shè)計(jì)為靈感的提示詞工程

Lex:我們剛剛提到,基準(zhǔn)測試中的 prompt 往往結(jié)構(gòu)清晰、表述規(guī)范。Arvid 也寫過一篇 Prompt Design 的文章,在你看來一個(gè)好的 prompt 能起到什么作用?

??

Arvid 在 Prompt Design 一文中參考現(xiàn)代網(wǎng)頁設(shè)計(jì)實(shí)踐提出了“提示詞設(shè)計(jì)”的概念,認(rèn)為二者的共性在于都要做到清晰有效的溝通、對動態(tài)內(nèi)容作出響應(yīng)、涉及到不同“屏幕尺寸”的適應(yīng)等。

Arvid:每個(gè)模型都不太一樣,對不同的 prompt 的反應(yīng)也不一樣,比如之前的一系列模型都對 prompt 的要求非常嚴(yán)格,上下文窗口也很小。

在代碼開發(fā)場景中,我們會遇到大量和 prompt 相關(guān)的信息,包括文檔、添加的文件和對話歷史等。這就帶來一個(gè)問題:在 context window 有限的情況下,該如何篩選和組織這些信息?拿現(xiàn)在的模型來說,即使上下文窗口更長了,但要是塞滿整個(gè)窗口,模型的響應(yīng)速度就會變慢,有些時(shí)候還會被 prompt 混淆,而且有的模型比其他模型更容易受到混淆。

為了解決這個(gè)問題,我們開發(fā)了一個(gè)叫 Priompt 的內(nèi)部系統(tǒng),它在一定程度上幫我們解決了這個(gè)問題。

Priompt 借鑒了現(xiàn)代網(wǎng)頁開發(fā)的最佳實(shí)踐。和固定版式的雜志排版不同,網(wǎng)站開發(fā)中會涉及到的一個(gè)情況是,不同設(shè)備的中信息的展示多少、格式等是動態(tài)變化的,而用戶到底在哪里查看網(wǎng)站開發(fā)者事前并不知道,但無論終端怎么變,都要保證網(wǎng)站信息在不同設(shè)備上正常顯示。AI 提示詞工程也是類似,我們要做到無論 input 內(nèi)容多大、怎么變,output 的格式都能正確展示。

我們比較喜歡用 React 庫的聲明式編程方法(declarative approach),也就是說,我們可以通過 JSX 直接描述期望的 UI 呈現(xiàn)方式,例如通過設(shè)置樣式屬性或CSS類名來聲明元素的展示層級(如z-index)或其他視覺特性。

就像瀏覽器有自己的渲染引擎一樣,Cursor 也有一個(gè) Priompt  渲染器,它的作用是把內(nèi)容合理地排布在頁面上,我們只需要告訴它你想要什么,它就會幫你實(shí)現(xiàn)。

我們發(fā)現(xiàn)這種方法非常有用,而且它的作用也在不斷演變,一開始設(shè)計(jì)它是為了應(yīng)對有限的上下文窗口,現(xiàn)在它幫我們把數(shù)據(jù)處理和實(shí)際展示分成了兩個(gè)部分,這樣做的好處是 debug 就會變得更容易:在不實(shí)際修改代碼的情況下,我們可以先通過輸入 prompt、Priompt 幫助預(yù)渲染的方式來看某個(gè)代碼改動是否真的有效。

Arvid:Priompt 和 React 類似,它還有一些組件,比如文件組件會獲取當(dāng)前光標(biāo)的位置,因?yàn)槔碚撋嫌脩粽诓榭吹哪切写a往往是最重要的。我們會給不同的代碼行設(shè)置優(yōu)先級,光標(biāo)所在行的優(yōu)先級最高,每遠(yuǎn)離一行優(yōu)先級就降低一級。最終在渲染的時(shí)候,系統(tǒng)會計(jì)算一共能顯示多少行代碼,并以光標(biāo)所在行為整個(gè)代碼的中心。

Aman:Priompt 和還有一些更復(fù)雜的功能,如果整個(gè)代碼庫中有大量代碼塊,我們還可以使用 retrieval 、embedding 和 re-ranking scores 等方式讓組件自動設(shè)定優(yōu)先級。

Lex:為了讓模型更好理解用戶提問,是不是也可以用到類似方法?

Arvid:我們的目標(biāo)是,“讓用戶做自己的事情就好”,我們的工作或者說整個(gè) Cursor 要做的就是如何去 retrieve 相關(guān)信息。

Lex:我和 Perplexity 的 CEO Aravind Srinivas 也聊過這個(gè)問題,他認(rèn)為要讓用戶想怎么問就怎么問。對于程序員來說,我們是不是還可以提出更高的要求?在完全放任用戶隨意提問和引導(dǎo)用戶給出更多 prompt 之外是不是也存在一種情況,即系統(tǒng)在鼓勵(lì)甚至要求用戶把想法表達(dá)得更清楚,這里說的不是語法或者句子要多規(guī)范,而是 prompt 中體現(xiàn)出的思考深度。

Aman:我認(rèn)為即便模型能力已經(jīng)接近完美,當(dāng)用戶向模型提出需求的時(shí)候,往往還是沒法完全表達(dá)自己的想法。要解決這個(gè)問題有幾個(gè)方法:

? 第一,讓模型直接告訴用戶,基于用戶的問題,它還是不太確定這部分內(nèi)容,讓用戶再明確一下;

? 第二,如果模型可以一次生成 5-6 種可能的結(jié)果,基于這些結(jié)果也可以告訴用戶,由于用戶的問題有一些模糊的地方,模型會把所有可能的結(jié)果都展示出來,讓用戶來選擇最合適的。

Lex:讓模型主動提問有多難?我是不是需要通過提問來獲得更多信息,從而減少一些可能發(fā)生歧義的情況?

Sualeh:最近我們在 Cursor 里新增了一個(gè)文件推薦功能。用戶在編輯問題的時(shí)候,模型就能猜到問題是什么,比如,用戶在編寫 API 代碼的時(shí)候,模型會根據(jù)這個(gè)文件之前的提交記錄,推測出客戶端和服務(wù)器端的相關(guān)代碼可能也很重要。這背后存在一個(gè)技術(shù)上的難題:怎么在歷史記錄中找到之前的信息,并根據(jù)用戶當(dāng)前給的 prompt 判斷出哪些文件最重要?目前我們的版本還比較簡單,這個(gè)功能還在試驗(yàn)階段,相信未來會越來越精確。有個(gè)這個(gè)功能,用戶就可以很方便地添加文件,讓模型來編輯。

比如說,當(dāng)用戶在編寫 API 時(shí),可能還需要修改使用這個(gè) API 的客戶端代碼和處理 API 請求的服務(wù)器端代碼。如果 Cursor 能在用戶輸入 prompt 并按下回車鍵之前,就幫助理解這些模糊的地方,就能提供很大幫助。

五、Shadow Workspace:Coding Agent 的工作流設(shè)計(jì)

Lex:你們怎么看 Agent?

Arvid:雖然目前 agent 在很多場景下還不是特別實(shí)用,不過我覺得它們離真正派上用場已經(jīng)不遠(yuǎn)了。有些任務(wù)非常適合使用 agent,比如我們遇到了一個(gè) 產(chǎn)品 bug,這個(gè) bug 可能是“聊天框里的內(nèi)容不能復(fù)制粘貼”,我只需要用兩句話來說明這個(gè)任務(wù):“這里出問題了,請修復(fù)一下”,agent 就會立刻去處理。第二天我回來看看結(jié)果就行了。

在這個(gè)任務(wù)里,Agent 首先能定位到正確的文件,嘗試重現(xiàn)并修復(fù) bug,然后驗(yàn)證修復(fù)后的代碼對不對。這個(gè)過程可能需要很長時(shí)間,所以我很希望能有這樣的 agent 來幫忙。

很多人覺得代碼 Agent 最終會完全取代程序員,但我不這么想,因?yàn)榫幊痰膬r(jià)值在于迭代,很多人在看到一個(gè)初始版本之前并不明確地知道自己想要什么。在大多數(shù)的編程任務(wù)里,用戶需要的是一個(gè)能立即給出初始版本的系統(tǒng),讓他們可以立即開始迭代優(yōu)化、補(bǔ)充更多信息。

Lex:在 Replica Agent 的設(shè)計(jì)中,Agent 不僅能設(shè)置開發(fā)環(huán)境、解決軟件包問題、配置數(shù)據(jù)庫,還能部署應(yīng)用,這種設(shè)計(jì)是不是符合你們對于代碼 Agent 的預(yù)期?Cursor 會開發(fā)類似的功能嗎?

Arvid:是的,Replica Agent 的確是在這個(gè)方向上。對于某些類型的編程來說,這個(gè)工具非常好用。

目前 Cursor 還沒有去積極開發(fā)類似的產(chǎn)品,不過我們的目標(biāo)是讓程序員的工作更輕松有趣,那些繁瑣的重復(fù)性任務(wù)完全可以交給 agent 處理。我們甚至可以讓 agent 在后臺運(yùn)行,理解程序員的操作意圖。

假設(shè)有一個(gè)同時(shí)涉及后端和前端的 pull request,程序員在前端工作時(shí),可以讓一個(gè) agent 在后臺運(yùn)行,理解程序員的做法。當(dāng)程序員從前端轉(zhuǎn)到后端部分時(shí),agent 就可以生成一些初始代碼,供程序員在這一基礎(chǔ)上進(jìn)行迭代。

Lex:Arvid 寫過一篇文章叫 Shadow Workspace: Iterating on Code in the Background??梢跃唧w講一下 Shadow Workspace 嗎?

Arvid:我們希望很多任務(wù)可以在后臺處理完成,我們也在這個(gè)方向上做嘗試。

除了緩存預(yù)熱(cache warming)或者確定進(jìn)入命令提示詞的正確上下文之外,我們還沒有太多后臺操作。整體想法是,如果我們能在后臺進(jìn)行計(jì)算,那么就可以在更長的時(shí)間維度上幫助用戶完成任務(wù),不僅僅是預(yù)測接下來幾行你要寫什么,而是預(yù)測在接下來的 10 分鐘里,用戶打算做什么,通過在后臺處理,可以投入更多的計(jì)算資源。

基于這個(gè)想法我們實(shí)驗(yàn)了 Shadow Workspace 。想要充分利用后臺處理的優(yōu)勢,就需要給模型提供反饋信號。如果沒有反饋,單純延長模型的思考時(shí)間也能提升性能,比如 o1 就是個(gè)很好的例子。

另一種提升性能的方式是讓模型迭代和獲取反饋。對于程序員來說,一個(gè)非常重要的反饋來源是 language server ,它們可以在開發(fā)者 coding 的時(shí)候就給出錯(cuò)誤提示,還支持跳轉(zhuǎn)到代碼的定義位置,理解代碼的結(jié)構(gòu)。我們的做法和 VS code 一樣,基于 LSP 給開發(fā)者作出對應(yīng)提示,但我們還想在不影響用戶的前提下,在后臺把同樣的信息提供給底層的 LLM 模型。

??

Language Server Protocol (語言服務(wù)器協(xié)議,簡稱 LSP)是微軟在 2016 年提出的一套統(tǒng)一的通訊協(xié)議方案。,定義了一套代碼編輯器或 IDE 與語言服務(wù)器之間使用的協(xié)議,語言服務(wù)器提供自動完成、轉(zhuǎn)到定義、查找所有引用等語言功能。每種編程語言都有其獨(dú)立的 language server,這些 language server 都會通過 LSP(language server protocol)與VS Code進(jìn)行交互,由此 VS Code 就不需要自己內(nèi)置一些東西來支持各類提示需求。

所以 Shadow Workspace 背后的思路是,也許我們可以在后臺運(yùn)行一個(gè)隱藏窗口,通過特定的標(biāo)志,把這個(gè)隱藏的窗口設(shè)置成不可見狀態(tài),開發(fā)者用戶視角下是無法看到這個(gè)窗口的。在這個(gè)窗口中,AI agent 可以隨意修改代碼,只要它們不保存就行,因?yàn)殡[藏窗口和主窗口共用一個(gè)文件夾,只要不保存修改,就不會影響到實(shí)際文件。然后 agent 可以從 linting 中獲得反饋,實(shí)現(xiàn) go to definition 功能,對代碼進(jìn)行迭代。

最終的預(yù)期是 Agent 可以在后臺執(zhí)行一切。

我們的很多 blog 也都在在探討怎么實(shí)現(xiàn)這件事,因?yàn)榇_實(shí)有點(diǎn)復(fù)雜。我們希望這個(gè)系統(tǒng)能在用戶的電腦上運(yùn)行,這樣它就能精確地鏡像用戶的環(huán)境。

在 Linux 系統(tǒng)上,我們可以鏡像文件系統(tǒng)。通過這種方式對文件進(jìn)行修改,會讓 AI 以為自己是在操作實(shí)際文件,但這些修改都是存儲在內(nèi)存中的。要實(shí)現(xiàn)這個(gè)功能,需要開發(fā)一個(gè)類似內(nèi)核的擴(kuò)展。在 Mac 和 Windows 系統(tǒng)上,實(shí)現(xiàn)起來可能會稍微困難一些,但這個(gè)技術(shù)挑戰(zhàn)也很有意思。

Aman:為了避免人類開發(fā)者和后臺 agent 同時(shí)工作時(shí)可能出現(xiàn)的文件寫入混亂,我們還設(shè)計(jì)了一個(gè)寫入鎖定的限制。具體來說,在個(gè)過程中,我們 LLM 會對文件寫入權(quán)限加鎖,Agent 只在 Shadow Workspace 中操作,所有改動都只存在于內(nèi)存中,而不直接修改磁盤上的實(shí)際文件。在這個(gè)過程中, LLM/Agent 仍然可以觸發(fā)代碼檢查和獲取語法提示。當(dāng) Agent  需要運(yùn)行代碼時(shí),系統(tǒng)會發(fā)出提示,如果用戶同意這個(gè)操作,就會從 language server 或 Shadow Workspace 那里解除鎖定。

六、SOTA 模型也不擅長找 bug

Lex:讓模型修改文檔這件事聽起來很有趣,以后我們就可以讓 agent 去執(zhí)行一系列任務(wù),第二天再來看這個(gè) Agent 同事都完成了哪些工作。

Aman:我們可能會設(shè)計(jì)不同的運(yùn)行版本。有些任務(wù)比較簡單,比如用戶在編程時(shí),有些操作模型幾分鐘就可以幫用戶完成,這種時(shí)候可以讓代碼在用戶的本地機(jī)器上運(yùn)行。但有些任務(wù)更復(fù)雜,需要更長時(shí)間來完成更大的更改,可能更適合在遠(yuǎn)程沙盒環(huán)境中執(zhí)行。這就帶來了一個(gè)問題:如何精確地復(fù)制或最大程度復(fù)制用戶的本地環(huán)境,確保遠(yuǎn)程沙盒與用戶本地環(huán)境中運(yùn)行代碼的效果一致。

Lex:我覺得 Agent 應(yīng)用范圍不應(yīng)該局限于 coding。比如說我們現(xiàn)在正在錄的這期播客就涉及視頻剪輯、上傳視頻、翻譯和配音等,我可能會希望 Agent 可以幫助我們把很多和視頻編輯不直接相關(guān)的任務(wù)自動化。在 coding 上,我比較期待 agent 能幫忙找 bug,特別是邏輯錯(cuò)誤,以及幫助確定大方向等等。

Aman:有一個(gè)很有意思的現(xiàn)象是,如果用戶給出的 prompt 是找 bug,那么模型的準(zhǔn)確率其實(shí)很低,在這類任務(wù)上即便是最聰明的模型表現(xiàn)也很差,甚至是 o1 這樣的模型。

Lex:怎么解釋這個(gè)現(xiàn)象?

Aman:模型是 pre-train 數(shù)據(jù)分布的一個(gè)強(qiáng)映射,隨著 loss 越來越低,模型的泛化能力也會不斷提升,但目前 loss 還沒有低到能讓模型在代碼領(lǐng)域完全實(shí)現(xiàn)泛化。我們目前使用到的最新一代的模型很擅長代碼生成和問答,這是因?yàn)樵?pre-train 階段這類數(shù)據(jù)特別多,GitHub 上有幾萬億個(gè) token,Stack Overflow 上也有海量的問答內(nèi)容。

但是如果我們想讓模型做一些網(wǎng)上很少見的任務(wù),比如 Cursor Tab 要做的根據(jù)已完成的編輯預(yù)測下一處編輯,這些模型在這類任務(wù)和場景上的弱點(diǎn)就會暴露出來。漏洞檢測(bug  detection)也是一個(gè)很好的例子能說明模型的這個(gè)短板?;ヂ?lián)網(wǎng)上真實(shí)的 bug 檢測和修復(fù)的完整案例其實(shí)很少,并不像代碼問答那樣數(shù)據(jù)豐富,所以模型在這類任務(wù)上就表現(xiàn)得很吃力。

不過我認(rèn)為這是模型能力遷移的問題。就像我們已經(jīng)看到的,我們可以把模型在 pre-tarin 階段獲得的通用的代碼理解能力遷移到 Cursor Tab 的任務(wù)上。如果一個(gè)通用模型在代碼方面很強(qiáng),也應(yīng)該能通過類似的方式把它的能力遷移到 bug 檢測任務(wù)上,只是需要一些適當(dāng)?shù)囊龑?dǎo)和調(diào)整。

Sualeh:需要說清楚的一點(diǎn)是,這些頭部模型其實(shí)對代碼的理解是很到位的。在  pre-train 階段,在構(gòu)建內(nèi)部 representation 時(shí) ,我們幾乎可以確定,在處理流程的某個(gè)環(huán)節(jié),模型能夠感知到代碼中可能存在一些問題,但是僅僅是感知到“不對勁”是不夠的,最關(guān)鍵的問題在于,人類對于哪些bug真正重要有著準(zhǔn)確的判斷力,比如有的 bug 無關(guān)緊要,有的卻會導(dǎo)致服務(wù)器宕機(jī)。

人類工程師之所以能做到這一點(diǎn)一部分也是行業(yè)經(jīng)驗(yàn)和文化的積累。為什么資深工程師很厲害,是因?yàn)樗麄冎?3 年前有人寫了一段有問題的代碼導(dǎo)致了服務(wù)器宕機(jī),相比之下,如果只是在做實(shí)驗(yàn)性質(zhì)的代碼,有一些 bug 也沒關(guān)系,因?yàn)橹饕康氖菄L試和體驗(yàn)。所以如果在寫實(shí)驗(yàn)代碼時(shí),模型過分較真,不停地給出提示,反而會很煩人。

但如果是在寫生產(chǎn)環(huán)境的代碼,比如寫一個(gè)數(shù)據(jù)庫,在 Postgres 或 Linux 這樣的系統(tǒng)寫代碼,或者假如我們是 Linus Torvalds 這樣的人(注:Linux 的創(chuàng)始人),那就必須做到萬無一失,任何 edge case 的 bug 都不能有,這就要求模型能準(zhǔn)確理解開發(fā)者對代碼質(zhì)量的要求程度。

Aman:但即使作為開發(fā)者我們把對模型代碼任務(wù)的質(zhì)量要求調(diào)到最高,模型還是不能完全理解到這些微妙的差異。

Lex:但其實(shí)人類工程師要真正做到準(zhǔn)確判斷不同 bug 的重要性也很難。比如你們也有一個(gè)原則是,如果某一段代碼可能會帶來不可控的影響,應(yīng)該給它特別注釋去說明“This line of code is dangerous.”,甚至還會用到大寫標(biāo)記來突出。

Arvid:是的,我覺得這個(gè)原則對今天的 AI 模型同樣適用。如果每一行代碼上都有專門的標(biāo)注,那么模型就會更關(guān)注這一部分,也更有可能發(fā)現(xiàn)這一部分的bug。但這個(gè)做法本身也也存在爭議。一些人會覺得這種做法不太美觀,比如 Sualeh 就不喜歡。

除非我們可以做到所有代碼都能通過嚴(yán)格的數(shù)學(xué)方法來證明其正確性,到那個(gè)時(shí)候就可以隨意寫代碼了,因?yàn)橹灰?yàn)證通過,我們就能確定這里沒有引入到任何 bug。

Sualeh:雖然從代碼審美角度來看我并不喜歡這種做法,但我認(rèn)為它確實(shí)對模型和人類都有用,因?yàn)槿祟惤?jīng)常會忘記很多事情,而且很容易犯一些小錯(cuò)誤,從而導(dǎo)致服務(wù)器崩潰。就算我們進(jìn)行了大量的測試,但在這方面我們還是要格外謹(jǐn)慎。

Aman:是的,就拿普通的文檔注釋來說,人們在修改代碼時(shí)往往只是匆匆掃一眼,想著“哦,我知道怎么做”,但實(shí)際上我們還需要更加明確地提醒他們注意某些細(xì)節(jié),否則很容易就疏忽了。

Arvid:我認(rèn)為未來人們不會再手寫測試代碼了。當(dāng) coding 的同時(shí),AI 模型會自動為這段代碼生成一個(gè)規(guī)范說明(spec),開發(fā)者只需要審核這個(gè)規(guī)范是否符合預(yù)期。同時(shí),推理模型會通過計(jì)算來驗(yàn)證代碼實(shí)現(xiàn)是否完全符合這個(gè)規(guī)范。我覺得這種方式將適用于大多數(shù)代碼功能的測試。

Michael:這件事是不是又會涉及到我們在前面提到的,人們在開發(fā)軟件時(shí)可能很難很清楚地描述自己的目標(biāo)和意圖,而有時(shí)候之所以難以證明代碼實(shí)現(xiàn)符合預(yù)期,也可能正是因?yàn)槲覀兒茈y準(zhǔn)確地描述出我們想要什么。

即便模型給出了一個(gè) spec ,我們還是要面對一個(gè)問題:我們真的能做到  formal verification 嗎?這在技術(shù)上是否可行?我覺得這里面還有很多需要深入探討的地方。又比如 spec 是用自然語言編寫的還是用公式來表達(dá)?即便是 spec 也還有很多細(xì)節(jié)很難被清晰界定。所以只是考慮  formal verification  還不夠。

Lex:你在前面提到模型在處理 bug findings 這類任務(wù)時(shí)通常表現(xiàn)不是很好,長遠(yuǎn)來看你的預(yù)期是什么樣的?

Sualeh:我覺得模型首先應(yīng)該能幫助我們解決那些簡單的小錯(cuò)誤,它應(yīng)該能快速檢查并及時(shí)發(fā)現(xiàn)“差一錯(cuò)誤(off by one error)”這種簡單的 bug,因?yàn)檫@類問題非常常見。在這個(gè)時(shí)候模型就應(yīng)該給出對應(yīng)提示,但長遠(yuǎn)來看,它肯定還需要能夠捕捉到更難發(fā)現(xiàn)的bug。

Michael:我覺得很重要的一點(diǎn)是,找 bug 能力是讓 AI 完成更多 coding 任務(wù)的必要條件。當(dāng)我們想要讓 AI 幫我們構(gòu)建越來越多的系統(tǒng)時(shí),就不僅需要它幫助做代碼生成,還需要能驗(yàn)證代碼的正確性,如果沒有這種驗(yàn)證能力,我們前面聊到的這些模型在代碼任務(wù)上的問題就很難解決,讓 AI 擁有找 bug 的能力不僅僅是為了找出人類寫代碼時(shí)產(chǎn)生的 bug,更關(guān)鍵的是能夠驗(yàn)證和檢查 AI 生成的代碼。

Arvid:關(guān)于如何實(shí)現(xiàn)讓 AI 更好地找到 bug 我們也有過很多次討論,比如如何訓(xùn)練一個(gè) bug 檢測模型,還有一個(gè)更受歡迎的想法是:“制造 bug 比找 bug 更簡單”,所以對應(yīng)的思路是,我們可以先訓(xùn)練一個(gè)模型,讓它學(xué)會在現(xiàn)有的正確代碼中注入各種類型的 bug,基于這種方式我們會得到大量帶有已知 bug 的訓(xùn)練數(shù)據(jù),然后,就可以用這些合成數(shù)據(jù)來訓(xùn)練另一個(gè)模型,專門用于發(fā)現(xiàn)和檢測代碼中的 bug。這只是眾多可能方法中的一個(gè)例子,還有很多其他的訓(xùn)練思路可以嘗試。

Michael:還可以有其他思路來完成。我們可以先想象有一個(gè) access 范圍足夠?qū)挼哪P停@個(gè)模型可以接觸到包括代碼在內(nèi)的各種信息,因?yàn)橹欢⒅粋€(gè)文件來找 bug 其實(shí)是很難的,對人類來說也很難,所以通常情況下我們的做法是運(yùn)行代碼、查看traces、通過調(diào)試器逐步檢查。這是另一個(gè)完全不同的方向。

所以這里有兩種不同的產(chǎn)品形態(tài)。一種是有一個(gè)非常專業(yè)且運(yùn)行速度很快的模型在后臺運(yùn)行,一直去尋找 bug,另一種情況就是開發(fā)者已經(jīng)很明確有一個(gè) bug 需要解決,在這種情況下,人們可能會投入大量的計(jì)算資源來做這件事。

Lex:你們考慮過代碼生成、編輯和 debugging 環(huán)節(jié)里引入付費(fèi)的可能性嗎?如果能幫助發(fā)現(xiàn) bug,或者生成讓用戶非常滿意的代碼,用戶愿意為之付費(fèi)或者打賞,而除了獲得收入之外,付費(fèi)也是比“接受代碼”更強(qiáng)的信號來幫助優(yōu)化產(chǎn)品和模型。

Arvid:這個(gè)思路很有意思但也很有爭議,并且還涉及到我們對于用戶心理的把握,也涉及到算力成本和定價(jià)之間的平衡。但整體上我們會覺得引入這個(gè)機(jī)制可能會讓產(chǎn)品變得不那么有趣,可能通過訂閱的方式,每月支付一定費(fèi)用后享受所有這些功能更符合開發(fā)者的需求,而不是將 debugging 等功能點(diǎn)單獨(dú)付費(fèi),即便是打賞機(jī)制也會有影響。

Lex:在具體的代碼任務(wù)和代碼終端結(jié)果之間有更好的信息交互?比如是否存在一個(gè)類似于 code -> terminal -> output -> analysis -> code suggestions 這樣的循環(huán)和反饋機(jī)制?

Aman:用戶在使用 command+K 功能的時(shí)候會利用到終端上下文信息,但我們目前還沒有實(shí)現(xiàn)循環(huán)反饋的部分,但我們覺得這樣的功能應(yīng)該很有價(jià)值,問題在于這個(gè)功能是在前臺執(zhí)行,還是像我們之前討論的那樣在后臺執(zhí)行。

Lex:前面提到基準(zhǔn)測試并不能真實(shí)反應(yīng)模型代碼任務(wù)的能力?這二者之間有什么區(qū)別?如果要評估模型,我們看到的基礎(chǔ)測試有什么不足?

Sualeh:基準(zhǔn)測試和現(xiàn)實(shí)中的代碼任務(wù)中間有一個(gè)非常重要且關(guān)鍵的細(xì)節(jié)差異,那就是現(xiàn)實(shí)中的代碼任務(wù)并不是“編程面試題”。在現(xiàn)實(shí)的工作中,人們有時(shí)會用不標(biāo)準(zhǔn)的英語來表達(dá)任務(wù)需求,或者會給出“照我之前那樣做”、“添加這個(gè),然后幫我做那個(gè),再做一個(gè) UI 元素” 之類的指令。很多任務(wù)都是依賴于具體場景的。

所以對于模型和產(chǎn)品來說,真正需要的是理解用戶意圖并完成他們想要的事情,真實(shí)的人類需求往往是模糊的、不那么明確的,而不是“面試題”那樣具有非常明確、詳細(xì)規(guī)范說明的任務(wù)。

Michael:除了 Sualeh 和  Aman  提到的問題外,基準(zhǔn)測試還存在一個(gè)問題是,實(shí)際可以建模的內(nèi)容與真實(shí)的編程之間存在一定的偏差,這種偏差很難概括,因?yàn)檎鎸?shí)的編程是很混雜的,有些時(shí)候很難去明確解釋什么是對的,什么是不對的。

而且,由于測試集是公開數(shù)據(jù)集的原因,這件事會變得更難。有兩方面的原因,首先,公開的基準(zhǔn)測試有時(shí)會被進(jìn)行針對性地“爬坡優(yōu)化”(hill climbing),另一方面,要從模型中剔除掉這些公開基準(zhǔn)測試的數(shù)據(jù)也非常難。

舉個(gè)例子,SWE-Bench 是目前最受歡迎的 Agent 基準(zhǔn)測試之一,但它的測試數(shù)據(jù)也是 Foundation Model 的訓(xùn)練數(shù)據(jù)的一部分。所以如果我們給到 Foundation Model 一個(gè) SWE-Bench 問題,即使沒有提供代碼庫的上下文,模型還是可以“編造”出一個(gè)正確的文件路徑和函數(shù)名。

??

SWE-Bench 是一個(gè)用于評估 LLM 解決從 GitHub 獲取的現(xiàn)實(shí)世界軟件問題的能力的基準(zhǔn)測試。該基準(zhǔn)測試包括向 Agent 提供代碼存儲庫和問題描述,并要求它們生成可解決問題描述的補(bǔ)丁。SWE-bench 中的每個(gè)測試樣本都來自 GitHub 上 12 個(gè)開源 Python 存儲庫之一中已解決的 GitHub issue。每個(gè)樣本都有一個(gè)關(guān)聯(lián)的拉取請求 (PR),其中包括解決方案代碼和用于驗(yàn)證代碼正確性的單元測試。

為了更準(zhǔn)確地評估 AI 模型的軟件工程能力,OpenAI  在對 SWE-bench 的改進(jìn)基礎(chǔ)上推出了 SWE-bench Verified。

Aman:在這種情況下,模型可以直接在 issues 和 pull requests 上訓(xùn)練,也許各個(gè)實(shí)驗(yàn)室會開始做得更好,或者他們已經(jīng)在數(shù)據(jù)清理方面做得不錯(cuò)了,但他們不太可能完全剔除掉本身的訓(xùn)練數(shù)據(jù)。這些都是一些最受歡迎的 Python 庫,比如 SimPy。我不認(rèn)為他們會為了在基準(zhǔn)測試中獲得真實(shí)的評估分?jǐn)?shù),而犧牲模型在 SimPy 這些主流 Python 庫上的性能。

Michael:由于基準(zhǔn)測試的這些 bug,很多大模型團(tuán)隊(duì)開始用其它測評方式來獲得模型反饋,比如讓真人實(shí)打?qū)嵉厝ナ褂眠@些系統(tǒng)并提供定性反饋。我還知道有一些 foundation model 公司中,有專門的員工就是專門做這件事。在我們內(nèi)部,除了用我們自己的私有測試集外,我們也非常依賴這種定性評估。

七、Code Indexing 和 Context

Code base Indexing

Lex:如何防止Cursor對數(shù)據(jù)庫進(jìn)行未經(jīng)授權(quán)的或錯(cuò)誤的修改?

Sualeh:目前確實(shí)有一些還不錯(cuò)的解決方案,我們也在基于 PlantScale 開發(fā)一個(gè) API ,這個(gè) API 的核心功能是為數(shù)據(jù)庫提供分支(branches)。如果開發(fā)人員在開發(fā)某個(gè)功能并想要對整個(gè)數(shù)據(jù)庫進(jìn)行測試,但又不想直接對整個(gè)數(shù)據(jù)庫進(jìn)行測試時(shí),就可以通過給數(shù)據(jù)庫添加 branches 來做這件事。他們實(shí)現(xiàn)這一功能的方式是在 write-ahead log 中添加一個(gè)分支,而要做到完全正確涉及很多技術(shù)復(fù)雜性。今天數(shù)據(jù)庫公司也在不斷迭代來應(yīng)對新的需求。

我們使用的一個(gè)數(shù)據(jù)庫 Turbopuffer 未來可能會實(shí)現(xiàn) write-ahead log 的分支功能,AI agents可以利用這些分支進(jìn)行測試,這可能會成為數(shù)據(jù)庫必須支持的一個(gè)功能。

并且我覺得到未來所有東西都需要 branches,隨著 branches 越來越多就要設(shè)計(jì)出更好的算法來做內(nèi)存、CPU 以及其它資源的優(yōu)化。

Lex:隨著用戶規(guī)模的擴(kuò)大,Cursor 遇到哪些挑戰(zhàn)嗎?

Michael:用戶增長的過程其實(shí)也是每秒請求數(shù)不斷增加一個(gè)數(shù)量級的過程。一開始我們是用通用組件進(jìn)行緩存和數(shù)據(jù)庫操作,隨著系統(tǒng)規(guī)模越來越大,就會遇到各種問題,今天我們的規(guī)模就會出現(xiàn)表溢出(table overflows)這樣的問題。所以到后來,我們也構(gòu)建了一些自定義系統(tǒng),比如我們的 indexing 系統(tǒng),用于計(jì)算代碼庫的語義索引并回答關(guān)于代碼庫的問題。我覺得系統(tǒng)一直最難 scale 的事情之一。

Sualeh:我有一些朋友是很資深的工程師,他們經(jīng)常提到,在系統(tǒng) scale 的過程中,很難準(zhǔn)確預(yù)判判斷系統(tǒng)會在哪個(gè)環(huán)節(jié)出問題,即使事先做出了預(yù)測,但還是會有遺漏,比如每次增加新功能時(shí)總會發(fā)生一些意外。

不過對于剛剛提到的代碼 indexing 系統(tǒng),我們目前在做的事情是,在當(dāng)上傳代碼時(shí),我們將所有代碼分塊讓然后發(fā)送出去。我們對待客戶問題非常謹(jǐn)慎,將代碼 embedding 存儲在數(shù)據(jù)庫中實(shí)際上儲存的并不是代碼本身的內(nèi)容,這樣做的好處是可以確保我們不會引入客戶端的bug。此外,我們還在服務(wù)器上存儲了很多關(guān)于代碼塊的詳細(xì)信息,并且所有內(nèi)容都已加密處理。

我們的方法是先把所有代碼分塊,然后將這些代碼做 embedding,在這之后,我們把這些 emebdings 存儲到數(shù)據(jù)庫中,而不是去存儲任何源代碼。這么做首先客戶確保不會引入用戶代碼庫中的 bug,另外這些存到服務(wù)器上的所有內(nèi)容也都是加密的。

在這個(gè)過程中我們遇到的一個(gè)技術(shù)難點(diǎn)就是,如何確保本地 index 和代碼庫的狀態(tài)與服務(wù)器上的保持一致?

我們最終采取的技術(shù)方案是,給每一個(gè)文件和文件夾都設(shè)置一個(gè)哈希值,其中文件夾的哈希值是其所有子文件和子文件夾哈希值的組合。這個(gè)過程可以遞歸進(jìn)行,直到根目錄。這其實(shí)是相對復(fù)雜的一種方式,還有另一種簡單的方法是,為每一個(gè)文件保存一個(gè)哈希值,然后再以每分鐘為單位嘗試從服務(wù)器上下載這些哈希值,找出哪些文件在服務(wù)器上不存在,從而保證客戶端和服務(wù)器之間的狀態(tài)一致。

但這種操作會給客戶端帶來嚴(yán)重的網(wǎng)絡(luò)負(fù)擔(dān),沒有人希望使用 Cursor 時(shí),我們一直占用它們的網(wǎng)絡(luò)帶寬,而且這種操作也會給數(shù)據(jù)庫帶來龐大負(fù)擔(dān),差不多每秒鐘都要讀取數(shù) 10TB,甚至接近 20TB 的數(shù)據(jù)庫,所以肯定不能這樣做。

所以我們采取的方法是只嘗試對項(xiàng)目根目錄的單個(gè)哈希值進(jìn)行比對,如果發(fā)現(xiàn)有不匹配,才進(jìn)一步去找出具體哪里不一致,再通過子節(jié)點(diǎn)一層一層向下找到具體哪里發(fā)生了變化。這種逐層檢查和遞歸的方式,能夠在確保同步準(zhǔn)確性的同時(shí)最大程度地減少網(wǎng)絡(luò)和數(shù)據(jù)庫成本,因?yàn)閷τ诖蠖鄶?shù)用戶、大部分任務(wù)來說,哈希值都是一致的。這個(gè)結(jié)構(gòu)叫做 Merkle Tree。

Sualeh:代碼檢索系統(tǒng)的 scale 之所以很難是因?yàn)椋粌H用戶量增加,其中一些客戶的代碼庫相當(dāng)龐大的,我們一開始設(shè)計(jì)的時(shí)候參考的是 Electron 這樣大型的代碼庫,但它的規(guī)模顯然和那些存在了20 年、擁有海量文件的公司的代碼庫還差很多,與此同時(shí)還需要考慮到怎么支持一大群程序員來使用。

這里涉及很多細(xì)節(jié),構(gòu)建一個(gè)簡單的方案很容易,但要考慮到擴(kuò)展到支持大量用戶、大量公司客戶顯然是個(gè)很困難的問題。如果去拓展代碼檢索系統(tǒng)是 Cursor 規(guī)模擴(kuò)展過程中遇到的挑戰(zhàn)之一。過去幾周幾個(gè)月里一直在處理這些擴(kuò)展性問題。

Aman:我們在設(shè)計(jì)這個(gè) index 系統(tǒng)的時(shí)候也加入了很多巧妙的細(xì)節(jié)。比如,成本的瓶頸并不在于向量數(shù)據(jù)庫或數(shù)據(jù)庫中存儲的數(shù)據(jù)量激增,而在于 embedding ,假如一個(gè)公司中有好幾個(gè)人使用了相同的一段代碼,就沒必要為每個(gè)人的每一次操作單獨(dú)做 embedding 的計(jì)算,即便這些代碼在不同 branches 上,或者只是做了一些本地修改。

在這個(gè)時(shí)候,就可以使用到一個(gè)技巧:對給定代碼塊的哈希值所計(jì)算出的實(shí)際向量進(jìn)行緩存。這意味著,當(dāng)公司里第 n 個(gè)人需要嵌入他們的代碼庫時(shí),系統(tǒng)就可以快速響應(yīng)。而且,這個(gè)過程中我們其實(shí)不需要在服務(wù)器上存儲源代碼,因?yàn)橹徽{(diào)用到了向量數(shù)據(jù)庫和向量緩存中存儲向量。

Lex:對代碼庫 indexing 的投入帶來的好處是什么?

Arvid:我認(rèn)為最明顯的好處是,當(dāng)用戶在龐大的代碼庫中想要查找某個(gè)功能的具體實(shí)現(xiàn)位置時(shí),可能只記得個(gè)大概,比如“我想找到我們實(shí)現(xiàn) X 功能的地方”,但在普通的文本搜索中,用高糊并不清楚應(yīng)該搜索什么關(guān)鍵詞,如果有了 code base 的 indexing,就可以向一個(gè) code base chatbot 提問,很多時(shí)候,code base chatbot  都能準(zhǔn)確找到對應(yīng)的代碼位置。

Aman:我認(rèn)為在未來,這個(gè)功能只會變得越來越強(qiáng),我們也在努力提高檢索的質(zhì)量。我認(rèn)為這代碼搜索這部分的想象力比人們想象中的要大。

Lex:在這個(gè)過程中為什么沒有考慮過端側(cè)方案?

Arvid:在本地做 embedding 聽起來很厲害,但實(shí)際操作起來很難。有一點(diǎn)我想指出的是,用戶們所使用的設(shè)備性能參差不齊,比如有些用戶用的是最新的MacBook Pro,但實(shí)際中超過80%的用戶用的是 Windows系統(tǒng)的電腦,而且其中很多電腦的性能都不是很好。因此,本地模型實(shí)際上實(shí)際上只適用于最新的高性能電腦而且會帶來很大的負(fù)擔(dān),即使我們想實(shí)現(xiàn)本地嵌入,目前也沒辦法集中精力去實(shí)現(xiàn)這一點(diǎn)。

Sualeh:除了計(jì)算機(jī)性能的限制之外,還有個(gè)現(xiàn)實(shí)問題是這種方案會犧牲輕松和高效的體驗(yàn)。大公司的代碼庫規(guī)模極大,即便一個(gè)公司給自己的程序員都配上了最高性能的電腦,處理這么大的代碼庫也會非常吃力,即便是頭部公司的頂尖程序員,如果一切都在本地運(yùn)行,體驗(yàn)也會非常糟糕。

Aman:是的,在處理近似最近鄰算法(nearest neighbors)和大型代碼庫時(shí),本地做 embedding 會大量占用內(nèi)存和CPU 資源?,F(xiàn)在端側(cè)模型的發(fā)展的一個(gè)趨勢是向 MoE方向發(fā)展,好處是它更多地依賴于內(nèi)存帶寬,而不是 GPU,但缺點(diǎn)是這些模型通常都很大,甚至可能需要多個(gè)節(jié)點(diǎn)來支持,即使是性能非常好的 MacBook 也無法支持這些模型。特別是在 coding 場景,這不僅是一個(gè)模型夠不夠好的問題,而是模型能否滿足開發(fā)者日益增長的期望。也許對于一些問題,本地模型已經(jīng)模型夠用,但人們總想用最好、最智能、最強(qiáng)大的模型。但這些模型對于大多數(shù)人來說,在本地運(yùn)行都非常困難。

Lex:相對于中心化的頭部 AI Labs,還是有不少人在提升端側(cè)模型能力的,尤其是在開源社區(qū)。你們怎么看這一趨勢?

Arvid:除了端側(cè)模型,還有一個(gè)我很喜歡的替代案,但目前還處于研究階段,就是用同態(tài)加密(homomorphic encryption)來做 LLM 的推理,也就是說,我們在本地設(shè)備上的輸入會被加密后發(fā)送到云端來做計(jì)算推理,這部分可以調(diào)用頭部模型能力來完成,但服務(wù)器看不到原始數(shù)據(jù),當(dāng)服務(wù)器完成推理計(jì)算后返回答案,我們可以在本地進(jìn)行解密,整個(gè)過程中只有用戶自己才能看到最終的結(jié)果。

這個(gè)技術(shù)還很早期,并且這里面的關(guān)鍵在于如何降低計(jì)算成本。同態(tài)加密可以很好解決目前的用戶數(shù)據(jù)隱私會暴露給模型的問題,所以如果這件事能實(shí)現(xiàn)就會影響很大。因?yàn)殡S著模型能力越來越強(qiáng)、可以帶來的經(jīng)濟(jì)價(jià)值是越來越高的。

??

同態(tài)加密(homomorphic encryption):密碼學(xué)中的一種特殊加密技術(shù),它允許在不解密數(shù)據(jù)的情況下對密態(tài)數(shù)據(jù)執(zhí)行特定的計(jì)算操作,并且計(jì)算結(jié)果仍然是密文狀態(tài),對密態(tài)結(jié)果解密后可以得到與直接用明文數(shù)據(jù)計(jì)算相同的結(jié)果。這一技術(shù)被用于隱私計(jì)算與安全多方協(xié)同計(jì)算領(lǐng)域。

如何解決 Context 問題

Lex:Cursor 是怎么解決 Context 問題的?模型自動找出上下文這件事有多難?

Michael:Context 是一個(gè)很復(fù)雜的問題,我認(rèn)為未來我們的確可以在自動找上下文這件事上做得更好,但也需要指出來的是,自動識別 context 這件事是有成本的。

首先,當(dāng)我們給模型提供的上下文越多,它們的響應(yīng)速度就越慢、請求的成本就越高,這就會犧牲我們對模型能力的調(diào)用以及可以在后臺處理的任務(wù)就會減少,此外,對于許多模型來說,如果 prompt 中包含大量信息,它們也會感到困惑。因此,我們在給模型提供上下文時(shí)需要確保信息的準(zhǔn)確性和相關(guān)性都很高。

我們其實(shí)已經(jīng)在產(chǎn)品中實(shí)現(xiàn)了一些自動上下文的功能,這是我們希望做得更好的地方,比如更好的 indexing 系統(tǒng)、embedding 和 retrieval 等。

這里面還有很多很有意思的學(xué)術(shù)問題,既有我們內(nèi)部在嘗試的,也有一些共識性的討論。

比如,如何讓語言模型真正理解一個(gè)新的信息語料庫?很多人都會提到的一個(gè)方向是,能不能讓上下文窗口無限大?如果可以實(shí)現(xiàn),那么能不能讓模型在具體的任務(wù)中考慮到這個(gè)無限大上下文?如果這個(gè)問題成立,那么接下來就又會考慮能不能對無限大的上下文進(jìn)行緩存?

與此同時(shí)我們也在嘗試其他思路,比如類似于模型微調(diào)的思路,將這些信息融入到 weights 中進(jìn)行學(xué)習(xí)。如果模型更多地是在 weights 級別上學(xué)習(xí)到這些信息,而不是在上下文的話,那么結(jié)果又會不一樣。

但最終要怎么實(shí)現(xiàn)目前還沒一個(gè)定論。

Aman:讓模型直接在 weights 層面學(xué)習(xí)這個(gè)路徑可以先用 VS Code 來做概念驗(yàn)證。因?yàn)?VS Code 本質(zhì)是開源的,也就是說,模型在 pre-train 階段就學(xué)習(xí)過這些代碼,甚至也學(xué)習(xí)過相關(guān)的代碼問答內(nèi)容,然后經(jīng)過微調(diào)和 RLHF 后,這些模型就被訓(xùn)練到能夠回答一些代碼相關(guān)的通用問題。當(dāng)我們問這個(gè)模型和 VS Code 相關(guān)的問題時(shí),雖然有時(shí)候它會產(chǎn)生幻覺,但有時(shí)候它確實(shí)能很好給出答案,我認(rèn)為這只是它碰巧表現(xiàn)得還不錯(cuò),但如果我們專門地對模型進(jìn)行訓(xùn)練或者 post-training,從而讓它更好地理解整個(gè)代碼庫,可能結(jié)果又會不一樣?

還有一個(gè)值得探索的問題是,我們到底是希望模型能夠端到端地完成所有工作,比如自己做完 retrieval、回答問題、生成代碼,還是說我們更希望將 retrieval 和底層模型獨(dú)立開?如果是后者,可能在未來幾個(gè)月內(nèi)就會出現(xiàn)一批比今天開源 SOTA 模型還要強(qiáng)的模型,在這種情況下,我們可能可以把一個(gè)高質(zhì)量的開源模型單獨(dú)訓(xùn)練成 retrieval 模型,由這個(gè)獨(dú)立的 retrieval 模型給更強(qiáng)的頭部底層模型提供 context。

八、Scaling law 、復(fù)現(xiàn) o1 模型

Scaling laws

Lex:你們怎么看接下來 scaling law 的發(fā)展?

Aman:我覺得 Chinchilla 之后,大家某些程度上在 scaling law 上有些走偏,現(xiàn)在大家更關(guān)心的是,怎么在給定的inference budget下,讓模型的表現(xiàn)盡可能好。

Scaling law 實(shí)際上考慮的維度比我們之前提到的算力規(guī)模、參數(shù)數(shù)量和數(shù)據(jù)量要多得多。例如,inference computer 就是一個(gè)顯而易見的維度,我認(rèn)為 context 長度也是一個(gè)。如果有人跟關(guān)注這兩者的優(yōu)化,那么可能就會訓(xùn)練出一個(gè)處理超長上下文時(shí)成本更低、速度更快的模型,即便訓(xùn)這個(gè)模型需要投入多 10 倍的算力,但這個(gè)過程中優(yōu)先級目標(biāo)是 long context window 和 inference compute。所以怎么理解人們對不同緯度之間的關(guān)系,以及圍繞這些緯度做權(quán)衡和探索其實(shí)很有意思。

Lex:也有人認(rèn)為 Scaling law 即將遇到上限。

Aman:單純就智能能力講的話,一定是模型越大、能力越好。

我很看好看好蒸餾。蒸餾也許是解決 data wall 的另一種方法。當(dāng)數(shù)據(jù)不夠用的時(shí)候,我們可以先在已有這些 tokens 上訓(xùn)練一個(gè)非常大的模型,然后將其蒸餾成一個(gè)較小的模型。這樣一來,與直接訓(xùn)練這個(gè)小模型相比,我們可能能夠從這個(gè)更小的模型中獲得每個(gè)token的更多信息。

如果投入大量資金進(jìn)行訓(xùn)練,能夠得到最具性價(jià)比的模型,那么要調(diào)整多少個(gè)參數(shù)來實(shí)現(xiàn)。應(yīng)該重視模型推理時(shí)間的計(jì)算。然而,有些人可能已經(jīng)采取了一種過于簡單的方法來關(guān)注推理時(shí)間的計(jì)算,即他們只是用遠(yuǎn)超必要數(shù)量的數(shù)據(jù)來過度訓(xùn)練那些70億參數(shù)的模型,就像Llama。

如果你真的很在意這個(gè)問題,也許可以采用 Google Gamma-27B 的做法:不要僅僅訓(xùn)練 token,而是直接訓(xùn)練模型來最小化與Gemma 27B分布之間的KL散度(KL divergence)。這就是知識蒸餾的思路。我們實(shí)際上是在用這個(gè) 270 億參數(shù)的模型處理所有這些token,最終目的是得到一個(gè)更小的模型。

如何復(fù)現(xiàn) OpenAI o1

Lex:你們怎么看 OpenAI o1?Test time compute system 未來會將在代碼任務(wù)中扮演什么角色?

Aman:Test time compute 提供了 scaling up 之外的一個(gè)不同的思路,即,我們也可以通過增加 inference 使用的 flops 來提升模型性能,通過更長時(shí)間的 inference 來實(shí)現(xiàn)和更大規(guī)模模型的同等質(zhì)量的輸出。

有一個(gè)很有意思的現(xiàn)實(shí)是,有些任務(wù)可能需要一個(gè)擁有 100 萬億參數(shù)、基于100 萬億 tokens 訓(xùn)練的超大模型才能解決,但這樣的問題可能只占到 queries 的 1% 甚至 0.1%。那么,這種情況下愿意投入如此巨大資源、時(shí)間和算力去訓(xùn)練一個(gè)成本高昂但使用頻率極低的模型是否還有意義呢?這樣做感覺很浪費(fèi)。

相比之下,一個(gè)更合理的策略是,訓(xùn)練一個(gè)規(guī)模較小的模型,這個(gè)模型能夠高效地處理 99.9% 的查詢,然后對于那些對智能水平極高的 query,可以選擇通過更長的 inference time 來實(shí)現(xiàn)解決。

Lex:如何判斷某個(gè)問題需要哪種程度的智能?能不能根據(jù)實(shí)際情況動態(tài)決定,比如什么時(shí)候使用GPT-4,什么時(shí)候使用小模型、什么時(shí)候又需要用到 o1?

Aman:這個(gè)問題還沒有答案,我覺得目前還沒有人能很好地解決這種 model routing 問題。我們在 Cursor Tab 上做過類似的初步嘗試,但如果是 GPT-4o、Claude sonnet 和 o1 之間的切換可能要更加復(fù)雜。

這里還會涉及到個(gè)一個(gè)問題是,我們需要什么水平的模型來幫助判斷某個(gè)任務(wù)是否超出了 GPT-4 水平模型的能力范圍?可能需要一個(gè) o1 水平的模型來作判斷。這個(gè)問題目前也沒有答案。

Lex:OpenAI 的做法是不直接給用戶展示思維鏈,而是讓模型對思維鏈進(jìn)行總結(jié)。同時(shí)他們還在后臺監(jiān)控這些思維鏈,以確保模型不會嘗試操控用戶,你怎么看這種做法?

Michael:對于OpenAI來說的一個(gè)顧慮可能是他們不想讓人們從他們的模型中提煉出這種能力。如果能夠訪問到那些被隱藏的思維鏈數(shù)據(jù),復(fù)刻 o1 可能會變得更加容易,因?yàn)槲覀兛梢灾苯涌吹侥P偷玫阶罱K結(jié)果所采取的步驟,這些數(shù)據(jù)是非常重要的。

Lex:CoT 數(shù)據(jù)可以被用來直接訓(xùn)模型嗎?

Michael:有一些類似情況可以參考,不過也只是推測。之前有些模型的 API 會提供所有生成 token 的對數(shù)概率(log probabilities)的訪問權(quán)限,包括對 prompt tokens 的概率,不過后來一些模型 API 移除了這些功能。其中一個(gè)猜測就是:如果外界可以拿到對數(shù)概率數(shù)據(jù),就像今天我們可以拿到被 OpenAI 隱藏的 CoT 數(shù)據(jù)一樣,這些數(shù)據(jù)都能幫助提供更多信息,讓人們能夠把 SOTA 模型的能力提取出來,再蒸餾到自有模型中。

編譯:海外獨(dú)角獸
本文由人人都是產(chǎn)品經(jīng)理作者【海外獨(dú)角獸】,微信公眾號:【海外獨(dú)角獸】,原創(chuàng)/授權(quán) 發(fā)布于人人都是產(chǎn)品經(jīng)理,未經(jīng)許可,禁止轉(zhuǎn)載。

題圖來自Unsplash,基于 CC0 協(xié)議。

更多精彩內(nèi)容,請關(guān)注人人都是產(chǎn)品經(jīng)理微信公眾號或下載App
評論
評論請登錄
  1. 目前還沒評論,等你發(fā)揮!