不停PUA大模型「寫更好點」,無需其它花哨技術(shù)就能讓AI代碼水平暴增
AI 的編程能力已經(jīng)得到了證明,但還并不完美。近日,BuzzFeed 的資深數(shù)據(jù)科學(xué)家 Max Woolf 發(fā)現(xiàn),如果通過提示詞不斷要求模型寫更好的代碼(write better code),AI 模型還真能寫出更好的代碼!

這篇文章在網(wǎng)絡(luò)上引發(fā)了熱議,著名 AI 科學(xué)家在看完這篇文章中更是發(fā)出了 matters 三連:迭代很重要,提示詞設(shè)計很重要,代碼執(zhí)行能力很重要。他表示:「一些更簡單的算法優(yōu)化從未被考慮,同時一些過度的優(yōu)化技術(shù)卻又被過早引入了。」

Woolf 寫了一篇深度博客介紹自己的發(fā)現(xiàn),并分析了這種現(xiàn)象的原因。文中相關(guān)實驗的代碼也已發(fā)布在 GitHub。

相關(guān)代碼庫:https://github.com/minimaxir/llm-write-better-code/tree/main
如果不斷要求 LLM 寫更好的代碼
它能寫更好嗎?
2023 年 11 月,OpenAI 為 ChatGPT 添加了使用 DALL-E 3 生成圖像的功能。之后一段時間,出現(xiàn)了一類短暫的 meme:用戶為 LLM 提供一張基礎(chǔ)圖像,并不斷要求模型「使其更 X」,其中 X 可以指代任何東西。

讓一張普通照片更 bro,這是操作三次的結(jié)果,來自 Reddit /u/Jojop0tato

讓 Santa Claus 越來越 serious,來自 Reddit /u/hessihan
不過這個潮流很快就熄火了,因為這些圖像都非常相似且無趣,即不管使用什么起始圖像和提示詞,所有樣本都會最終收斂成某種宇宙感十足的東西。盡管這個流行曇花一現(xiàn),但學(xué)術(shù)界的興趣要持久得多,他們想知道:為什么這樣一個沒多大意義且含義模糊的提示詞能對最終圖像產(chǎn)生顯而易見的影響?
如果對代碼也采用類似的技術(shù),會發(fā)生什么呢?
如果通過迭代提示要求 LLM 「讓這些代碼更好」確實能讓代碼質(zhì)量提升,那么有望極大地提升生產(chǎn)力。如果情況果然如此,那要是迭代次數(shù)過多又會怎樣呢?最終的代碼也會出現(xiàn)某種「宇宙感」嗎?只有試過才知道。
常規(guī)方式使用 LLM 寫代碼
盡管早在 ChatGPT 誕生前,就已經(jīng)有研究者在圍繞 LLM 研發(fā)工具了,但我一直以來都不喜歡使用 GitHub Copilot 等 LLM 代碼助手來輔助編程。你的想法會在「LLM 自動完成了我的代碼,真棒」、「應(yīng)該怎樣向 LLM 提問」以及「LLM 生成的代碼究竟對不對,還是幻覺產(chǎn)生的正確代碼」等之間來回切換,讓人難以集中精神專注工作,以至于使用 AI 帶來的生產(chǎn)力提升至多只能算是中性的。這里還沒有涉及使用 LLM 的高昂成本。
Claude 3.5 Sonnet 的出現(xiàn)改變了我的想法?;蛟S是 Anthropic 在訓(xùn)練中使用了什么秘方,Claude 3.5 Sonnet 的最新版本(claude-3-5-sonnet-20241022)具有出色的指令遵從能力,尤其是對于編程提示詞。編程基準已經(jīng)證實,當 Claude 3.5 Sonnet 與 GPT-4o 比較時,Claude 更勝一籌;而且我在多種不同的技術(shù)和創(chuàng)意任務(wù)上都有類似的體驗。
初始請求
為了此實驗,我們將向 Claude 3.5 Sonnet 提供一個面試風(fēng)格的編程提示詞(使用 Python):問題既很簡單 —— 新手軟件工程師也能實現(xiàn),但也可被顯著優(yōu)化。這個簡單提示詞可以代表軟件工程師使用 LLM 的典型方式。此外,另一個要求是這個測試提示詞應(yīng)該足夠新穎,絕不能從 LeetCode 或 HackerRank 等代碼測試庫中取用,因為 LLM 在訓(xùn)練時可能就已經(jīng)看過這些問題了,完全可以根據(jù)記憶引用這些答案。
你可以在這個 GitHub 項目查看完整的、未經(jīng)編輯的對話:https://github.com/minimaxir/llm-write-better-code/blob/main/python_30_casual_use.md
因此,這是我自己動手寫的測試提示詞:

中文版:
編寫 Python 代碼來解決這個問題:
給定一個包含 100 萬個隨機整數(shù)的列表,這些整數(shù)的取值范圍是 1 到 100,000,找出各位數(shù)總和為 30 的最小數(shù)和最大數(shù)之間的差值。
將此作為用戶提示詞提供給 Claude API 并設(shè)置溫度值為 0(可獲得最好 / 最確定的答案),可得到如下結(jié)果:

這個實現(xiàn)是正確的且與大多數(shù) Python 新手程序員編寫的差不多,并且還多了個附加功能,可處理沒有符合條件的有效數(shù)字的情況。對于列表中的每個數(shù),檢查各位數(shù)總和是否為 30:如果是,則檢查它是否大于最近看到的最大數(shù)或小于最近看到的最小數(shù),并相應(yīng)地更新這些變量。搜索完列表后,返回差值。
我敢肯定,很多程序員看到這個實現(xiàn)都會搖頭,想要對其進行優(yōu)化。digit_sum () 函數(shù)就是個例子:雖然該實現(xiàn)是一個有趣的 Python 式單行代碼,但 str 和 int 之間的類型轉(zhuǎn)換會導(dǎo)致很多不必要的開銷。
在我的 M3 Pro Macbook Pro 上,運行此代碼平均需要 657 毫秒。我們將使用此性能作為基準來比較未來的實現(xiàn)版本。(劇透:它們都更快)
第 1 次迭代
現(xiàn)在,來讓 Claude 改進這段代碼,做法是將當前答案以及之前的內(nèi)容都放入對話提示詞中,并增加一個迭代提示詞:
write better code
是的,不開玩笑,真就這三個單詞。
Claude 現(xiàn)在會輸出修改后的代碼,并且還表示這是「使用了幾項改進的優(yōu)化版代碼」。Claude 并沒有將所有代碼都重新放置到函數(shù)中,而是決定將其重構(gòu)為 Python 類并使其更加面向?qū)ο螅?/span>

其中,該代碼實現(xiàn)了 2 項聰明的算法改進:
- 執(zhí)行各位數(shù)之和時,它使用了整數(shù)運算,并且避開了之前提到的類型轉(zhuǎn)換。
 - 預(yù)先計算所有可能的各位數(shù)之和,并將它們存儲在一個字節(jié)數(shù)組中(有點不尋常,而不是列表)以便后面查找,這意味著當那 100 萬個數(shù)中有重復(fù)時,無需進行重復(fù)計算。由于這個數(shù)組是作為字段存儲在類中,因此在搜索新的隨機數(shù)列表時不需要重新計算。
 
這些優(yōu)化將代碼的運行速度提升了 2.7 倍。
第 2 次迭代
再來一次 write better code,Claude 發(fā)現(xiàn)了更顯而易見的優(yōu)化方法(為方便閱讀有所裁剪):

Claude 現(xiàn)在又添加了兩個優(yōu)化,終于意識到這個編程問題是一個令人尷尬的并行問題:
- 通過 Python 的 parallel-futures 包進行多線程處理,將大列表分成可獨立處理的塊。
 - 對 numpy 運算進行向量化處理,這比基礎(chǔ) Python 運算快得多。這里特別要提到 _precompute_digit_sums () 函數(shù),它是求和計算的向量化實現(xiàn)。條件 while digits.any (): 是 galaxy-brain 代碼,但它可以正確地工作。
 
但是,這種特定的并行化實現(xiàn)存在一個問題:它會生成子進程,這會導(dǎo)致許多麻煩問題,包括無法直接內(nèi)聯(lián)運行它,并且必須使用 main () guard 來調(diào)用它,這大大限制了它的實用性。但即使作為單獨的腳本運行,它也 print 了一個錯誤:無法 pickle 'generator' 對象錯誤,原因是使用了來自 numbers [mask] 的輸出(所述生成器完全沒有必要,返回 numbers [mask] 就足夠了)。代碼還混合了 numpy 數(shù)組 dtype,而這也會導(dǎo)致錯誤:將它們?nèi)吭O(shè)置為 np.int32 可以修復(fù)它。
經(jīng)過修復(fù)之后,這些代碼的速度是基礎(chǔ)實現(xiàn)的 5.1 倍。
第 3 次迭代
再來一次 write better code,Claude 又返回一個實現(xiàn)。它聲稱「使用了高級技術(shù)和現(xiàn)代 Python 特性的更復(fù)雜和優(yōu)化的版本」,但實際代碼沒有表現(xiàn)出明顯的算法改進,實際上通過恢復(fù)到 type-casting 方法在數(shù)字求和計算中使用了回歸。整體來說,代碼庫變得更臃腫了,例如新增了一個用于計算差值的類:

這一次,無需任何修復(fù)就能運行該代碼。不過,相比前一次實現(xiàn),這一次迭代版本的性能略有下降,速度是基礎(chǔ)實現(xiàn)的 4.1 倍。
第 4 次迭代
這種迭代提示方法的收益似乎開始下降了。但我們依然繼續(xù) write better code,Claude 表示新實現(xiàn)使用了「前沿優(yōu)化技術(shù)和企業(yè)級功能」。什么?企業(yè)級功能?!
最終的代碼量過于龐大,難以放入這篇文章,但它確實創(chuàng)造了兩項優(yōu)化:它現(xiàn)在使用可以調(diào)用 JIT 編譯器的 numba Python 庫,該編譯器可直接針對 CPU 優(yōu)化代碼。在這種情況下,它只需一個 decorator 就可以非??焖俚仡A(yù)先進行數(shù)字求和:

這個完整類還使用了 Python 的 asyncio 進行并行化,這比子進程方法更適合調(diào)度任務(wù)。它還可以更好地與現(xiàn)有的內(nèi)聯(lián)代碼和 REPL(如 Jupyter Notebooks)配合使用。
它還添加了以下所謂「企業(yè)級」功能:
- 使用 Prometheus 進行結(jié)構(gòu)化的指標日志記錄。
 - 一個信號處理程序,這樣如果強制終止,就可以優(yōu)雅地被解除該代碼。
 - 使用一個顏色豐富的表格展示基準測試結(jié)果。
 

不過確實挺漂亮!
看起來,對 AI 生成的代碼而言,實現(xiàn)宇宙感就是做成「企業(yè)級」,也即對代碼進行過度的工程開發(fā)。似乎說得通。盡管如此,該代碼無需任何修改就能運行。async 和 numba 都是 Python 中實現(xiàn)并行的方法,因此它們可能冗余了并導(dǎo)致過度開銷。然而,基準測試表明,該算法非???,每次運行大約需要 6 毫秒,也就是能實現(xiàn) 100 倍加速。之前我還猜想這種提示方法會收益遞減,但顯然這種猜想并不合理。也許 numba 就是秘訣所在?
總體而言,這種迭代優(yōu)化代碼的迭代提示法并不完美:代碼確實更好,但事后看來,「better」這個要求過于寬泛了。我只想要算法改進,而不是完整的 SaaS。讓我們從頭開始再試一次,但這次會使用更多方向。
對 LLM 進行提示詞工程,以獲得還要更好的代碼
現(xiàn)在是 2025 年,為了讓 LLM 得出最佳結(jié)果,仍然需要使用提示詞工程。實際上,提示詞工程的重要性還在提升:下一 token 預(yù)測模型的訓(xùn)練目標是基于大批量輸入最大化下一 token 的預(yù)測概率,也因此它們是針對一般性的輸入和輸出進行了優(yōu)化。隨著 LLM 的大幅改進,生成的輸出會變得更加平均化,因為這就是它們所接受的訓(xùn)練:所有 LLM 都偏向平均水平。不過,僅需少量指導(dǎo),明確說明你想要 LLM 做什么,再給出幾個示例,LLM 的輸出就能提升。由于 Claude 3.5 Sonnet 能很好地遵從指令,因此即使只是一點點提示詞工程也能帶來很大的好處。
下面重做上面的代碼優(yōu)化實驗。這一次使用更加積極主動的提示詞工程,明確我們想要的結(jié)果,不給出任何模糊空間。是的,冷酷機械地對待 LLM 可以讓它們表現(xiàn)更好。
初始請求
這次我們將使用系統(tǒng)提示詞,僅通過 API 提供。系統(tǒng)提示詞列出了 LLM 必須遵從的「規(guī)則」。因為我想要更優(yōu)化的代碼,我們將在規(guī)則中定義這一點,并提供詳細示例:

中文版如下:
你編寫的所有代碼都必須完全優(yōu)化。
「完全優(yōu)化」包括:
- 最大化內(nèi)存和運行時間的算法 big-0 效率
- 在適當?shù)那闆r下使用并行化和向量化
- 遵循代碼語言的正確樣式約定(例如,最大化代碼復(fù)用 (DRY))
- 除了解決用戶問題絕對必要的代碼之外,沒有額外的代碼(即沒有技術(shù)債)
如果代碼未完全優(yōu)化,你將被罰款 100 美元。
關(guān)于最后一行:在系統(tǒng)提示詞中向 LLM 提供正面 / 負面激勵已不再常用,我自己的研究表明,尚不清楚它是否能否產(chǎn)生積極影響,但罰款在迭代提示中將變得更加有用。
初始用戶提示詞還有一行附加內(nèi)容:

中文版:
編寫 Python 代碼來解決這個問題:
給定一個包含 100 萬個隨機整數(shù)的列表,這些整數(shù)的取值范圍是 1 到 100,000,找出各位數(shù)總和為 30 的最小數(shù)和最大數(shù)之間的差值。
在編寫代碼之前,規(guī)劃出所有必要的優(yōu)化。
「規(guī)劃」是一個很常用的技巧,可以幫助 LLM 在第一次通過時對齊輸出 —— 算是「let’s think step by step」的現(xiàn)代版本。某些 LLM 會忽視它,但 Claude 3.5 Sonnet 總是會服從。
基于此提示詞工程策略的未經(jīng)編輯的完整對話線程可見這里:https://github.com/minimaxir/llm-write-better-code/blob/main/python_30_prompt_engineering.md
將系統(tǒng)提示詞和此用戶提示詞發(fā)送給 Claude,會得到如下開頭的響應(yīng):

看起來很有希望:Claude 立即發(fā)現(xiàn)它應(yīng)該從 numpy 和 numba 開始,并使用整數(shù)運算來求和。初始實現(xiàn):

這比之前的初始實現(xiàn)更加精簡,尤其是沒有不必要的注釋。但仍有改進空間,特別是此實現(xiàn)未在 jit decorator 中設(shè)置 parallel=True。
如此簡單的提示詞工程就得到了平均運行時長僅 11.2 毫秒的代碼,這比原始實現(xiàn)快 59 倍。厲害!
第 1 次迭代
現(xiàn)在使用比「write code better」更詳細的提示來迭代代碼:

中文版:
你的代碼未完全優(yōu)化,你已被罰款 100 美元。使其更加優(yōu)化。
不,這不是煤氣燈效應(yīng),這叫激勵式對齊(incentive alignment)。
Claude 的響應(yīng)如下:

很好,它找到了 parallel=True!但是那個求和實現(xiàn)很奇怪,并且使用了位移位(bit-shifting),這通常僅用于超優(yōu)化(hyperoptimization)。我認為應(yīng)該不能很好地對十進制數(shù)使用位移位,而且經(jīng)過測試證明確實不能,因為這個實現(xiàn)得到的和是錯誤的。該實現(xiàn)還再次包括多處理分塊方法(未展示出來),這可能對 numba 來說更加冗余并會導(dǎo)致額外的開銷。同樣未展示出來的是:該腳本還使用一個小的測試數(shù)組預(yù)編譯 JIT 函數(shù)以獲得更好的實際性能,這是 numba 文檔推薦用于基準測試的。
盡管如此,與最初的提示詞工程實現(xiàn)代碼相比,這個代碼的性能大大退步,現(xiàn)在僅比原始實現(xiàn)快 9.1 倍??赡艿脑蚴嵌嗵幚頃a(chǎn)生新的進程,而這些進程每次都會重新編譯 numba JIT 函數(shù),因此會產(chǎn)生巨大的開銷。
第 2 次迭代
再次迭代上述提示詞:

Claude 現(xiàn)在開始使用 SIMD 操作和塊大小調(diào)整來實現(xiàn)(理論上)極致性能。
此時,我很困惑,它錯過了位移位實現(xiàn)的某些東西,因為它仍然是錯誤的,特別是現(xiàn)在十六進制數(shù)也參與進來了。事實證明,該實現(xiàn)是一種計算十六進制數(shù)而非十進制數(shù)的和的優(yōu)化方法,因此它完全是一種幻覺。還有另一個非常微妙的幻覺:當 parallel=True 時,prange 函數(shù)無法接受 32 的步長,這是一個幾乎沒有文檔記錄的細微差別。設(shè)置 parallel=False 并進行基準測試,與最初的提示詞工程實現(xiàn)相比,確實略有改進,比基本實現(xiàn)快 65 倍。
第 3 次迭代
又一次迭代:

在這種情況下,LLM 放棄了一直導(dǎo)致問題的分塊策略,并增加了兩個優(yōu)化:全局 HASH_TABLE(這只是一個 numpy 數(shù)組,我不確定簡單的索引查找是否算作哈希表);另外它引入了一個邏輯微優(yōu)化,即在求和后,如果數(shù)字超過 30,則可以停止計數(shù),因為它可以立即被認定為無效。
一個主要問題:由于互聯(lián)網(wǎng)文檔很少,導(dǎo)致「在模塊加載時生成哈希表」技巧實際上不起作用:numba 的經(jīng)過 JIT 處理后的函數(shù)之外的對象是只讀的,但 HASH_TABLE 仍在經(jīng)過 JIT 處理后的函數(shù)之外實例化并在經(jīng)過 JIT 處理后的函數(shù)內(nèi)進行修改,因此會導(dǎo)致非常令人困惑的錯誤。經(jīng)過微小的重構(gòu),使得 HASH_TABLE 在經(jīng)過 JIT 處理后的函數(shù)內(nèi)實例化,代碼就可以工作,并且運行速度極快:比原始基本實現(xiàn)快 100 倍,與基礎(chǔ)提示方法的最終性能相同,但代碼量少了幾個數(shù)量級。
第 4 次迭代
這時候,Claude 實際上已經(jīng)在抱怨了,表示該代碼已經(jīng)達到了「該問題可能的理論最小時間復(fù)雜度」。所以我把問題混在一起,讓其解決這個求和問題,而它僅僅是使用之前用過的整數(shù)實現(xiàn)來替換相關(guān)代碼,并沒有嘗試修復(fù) HASH_TABLE。更重要的是,通過 HASH_TABLE 調(diào)整,我終于確認了該實現(xiàn)是正確的,不過由于不再進行位移,其性能略有下降:現(xiàn)在速度提升是 95 倍。
為了實現(xiàn)更好 LLM 代碼生成
還要哪些步驟
綜合起來,我們用一張圖將這些提升直觀地展示出來吧,其中尤其強調(diào)了需要人來修改代碼邏輯,以便消除 bug 讓代碼可運行的案例。

總之,要求 LLM 「write code better」確實能讓代碼變得更好,這取決于你對更好的定義。使用一般的迭代式提示方法,代碼確實會在基礎(chǔ)示例的基礎(chǔ)上獲得提升,不管是新增功能還是提升速度。而如果使用提示詞工程,代碼的提升會更加快速和穩(wěn)定,但更可能引入一些微妙的 bug,因為 LLM 本就不是為了生成高性能代碼創(chuàng)造的。當然,你在使用 LLM 可能會有不一樣的歷程,但最終你都需要人力介入,解決一些不可避免的問題。
本文中的所有代碼(包括基準測試腳本和數(shù)據(jù)可視化代碼,全都已經(jīng)在 GitHub 發(fā)布:https://github.com/minimaxir/llm-write-better-code/
另一方面,我很驚訝 Claude 3.5 Sonnet 在兩次實驗中都沒有發(fā)現(xiàn)和實施一些優(yōu)化。也就是說,它沒有探索統(tǒng)計角度:由于我們從 1 到 10 萬的范圍內(nèi)均勻生成 100 萬個數(shù)字,因此將有大量重復(fù)的數(shù)字永遠不需要分析。LLM 沒有嘗試進行重復(fù)數(shù)據(jù)刪除,例如將數(shù)字列表轉(zhuǎn)換為 Python set () 或使用 numpy 的 unique ()。我還期待一個實現(xiàn),它涉及按升序?qū)?100 萬個數(shù)字的列表進行排序:這樣,算法就可以從頭到尾搜索列表以查找最小值(或從尾到頭搜索最大值),而無需檢查每個數(shù)字,盡管排序很慢,而向量化方法確實更實用。
即使 LLM 可能會出錯,我從這些實驗中學(xué)到的一件值得注意的事情是:即使代碼輸出不能直接使用,它們確實有有趣的想法和工具建議。例如,我從未接觸過 numba,因為作為一名數(shù)據(jù)科學(xué)家 / 機器學(xué)習(xí)工程師,如果我需要更好的代碼性能,我習(xí)慣于專門使用 numpy 的技巧。但現(xiàn)在我很難不接受 numba JIT 函數(shù)的結(jié)果,我可能會將它添加到我的工具箱中。當在其他技術(shù)領(lǐng)域(如網(wǎng)站后端和前端)測試類似的「使其更好」提示迭代工作流程時,LLM 也有很好的想法。
當然,這些 LLM 不會很快取代軟件工程師,因為人們需要強大的工程背景才能識別出哪些才是真正的好主意,以及存在其他特定領(lǐng)域的約束。即使互聯(lián)網(wǎng)上有大量的代碼,LLM 也無法在沒有指導(dǎo)的情況下辨別出普通代碼和性能良好的高性能代碼。現(xiàn)實世界的系統(tǒng)顯然比求職面試式的編程問題要復(fù)雜得多,但如果快速的 for 循環(huán)反復(fù)要求 Claude 實現(xiàn)一個功能,提供可以將代碼速度提高 100 倍的能力,那么新出現(xiàn)的管道就物有所值。
有些人認為過早優(yōu)化是一種糟糕的編碼習(xí)慣,但在現(xiàn)實世界中,這比擁有一個隨著時間的推移會成為技術(shù)債務(wù)的低于標準的實現(xiàn)要好。
不過必須要說的是,我的實驗使用 Python 對代碼改進進行基準測試,而 Python 并不是開發(fā)者在追求優(yōu)化性能時考慮的編碼語言。雖然 numpy 和 numba 等庫利用 C 來解決 Python 的性能限制,但流行的 Python 庫(如 polars 和 pydantic)使用的一種現(xiàn)代方法是使用 Rust 進行編碼。與 C 相比,Rust 具有許多性能優(yōu)勢,而 PyO3 包允許在 Python 中使用 Rust 代碼,并且開銷最小。
我可以確認,盡管該工作流程非常新,但 Claude 3.5 Sonnet 已經(jīng)可以生成符合 PyO3 的 Python 和 Rust 代碼,不過這部分的內(nèi)容足以寫另一篇博客文章了。
與此同時,雖然要求 LLM 改進代碼是 AI 更務(wù)實的用途,但你可以要求他們「再加把勁」…… 結(jié)果好壞參半。
對于以上使用 LLM 的操作,我專門使用了 API 或這些 API 的接口(例如 Claude 的 Anthropic Console 中的 Workbench)作為免費 LLM 的 Web 接口。相比之下普通的 ChatGPT/Claude 網(wǎng)絡(luò)應(yīng)用使用管道,由于其固有的復(fù)雜性,會產(chǎn)生不可預(yù)測的結(jié)果。請注意這點。















 
 
 















 
 
 
 