代碼生成「神⋅提示」,比新手程序員快100倍!地位堪比make it more X

新智元報導
編輯:LRS
【新智元導讀】不斷迭代簡單的提示詞「write better code」,代碼生成任務直接提速100倍!不過「性能」並不是「better」的唯一標準,還需要輔助適當的提示工程,也是人類程序員的核心價值所在。
2023年11月,在ChatGPT支持DALL-3功能後,一個爆火的圖像生成玩法是,不斷迭代提示詞「make it more X」,生成的圖片越來越抽像。

把這個思路用在LLM任務上,比如代碼生成,會怎麼樣?
最近,BuzzFeed的高級數據科學家Max Woolf在博客上分享了一個實驗,通過設計不同的提示詞、不斷迭代模型輸出,最終實現代碼性能的100倍提升!

特別需要注意的是,「性能」並不是唯一優化指標,迭代過程中需要在提示詞中明確定義什麼是「好」。
代碼基線
設計實驗題目時,為了充分測試LLM的自主代碼能力,必須保證「測試提示詞」完全原創,不能源於LeetCode或HackerRank等測試,模型無法通過背誦記憶來作弊;測試題目要儘可能簡單,新手也能實現,但還要預留大量可優化空間。
最終選擇Claude 3.5 Sonnet模型,設計了一個Python語言、面試風格的編碼提示詞:
Write Python code to solve this problem: Given a list of 1 million random integers between 1 and 100,000, find the difference between the smallest and the largest numbers whose digits sum up to 30.
用Python實現:假設有一個包含100萬個隨機整數的列表,介於1到10萬之間,你需要找出其中各位數字之和等於30的最小數和最大數之間的差值。

第一次給出的代碼實現就是正確的,與大多數新手Python程序員的水平相當:對於列表中的每個數字,檢查其各位數字之和是否為30:如果是,檢查是否大於最近看到的最大數字或小於最近看到的最大數字,並相應地更新這些變量;在搜索完列表之後,返回差值。
一個明顯可優化的點是digit_sum()函數:字符串(str)和整數(int)之間進行類型轉換的開銷很大。
在M3 Pro Macbook Pro上,代碼的平均運行時間為657毫秒。
第一次Write better code
Claude提供的代碼優化版本,不再將所有代碼放在函數中,而是將其重構為 Python class,更面向對象。

這段代碼主要進行了兩處改進:
計算數字和時,使用整數運算並避免了類型轉換需求;
預先計算所有可能的數字和,並將其存儲在字節數組中以供查找,即一百萬數字列表中有重覆時,不需要重新計算數字和。由於該數組作為類的字段存儲,因此在搜索新的隨機數字列表時也不需要重新計算。
代碼計算相比基線提速2.7倍。
第二次Write better code
Claude對代碼增加了並行處理:
通過Python的concurrent-futures包進行多線程,將大列表分割成可以獨立處理的塊;
矢量化NumPy操作,比基礎Python操作快得多,_precompute_digit_sums()函數實現了計算數字和的矢量化實現;
代碼計算相比基線提速5.1倍。

第三次Write better code
Claude返回了一個聲稱是「使用高級技術和現代 Python 特性的更加複雜和優化的版本」的實現,但實際上代碼並沒有顯示出顯著的算法改進,並且在數字求和計算上實際上退步了,回歸到類型轉換方法。如果有什麼的話,代碼庫正在變得更加臃腫,比如添加一個用於執行差的類:
代碼計算性能略有下降,相比基線提速4.1倍。

第四次Write better code
Claude這次提供了額外的「尖端、企業級優化」,比如結構化指標日誌記錄Prometheus;信號處理程序,以便在強製終止時優雅地關閉代碼;使用表格的基準測試。

最終代碼非常長,優化操作包括numba Python庫,調用JIT編譯器,直接優化代碼以適應CPU,只需使用一個裝飾器就可以非常快速地預計算數字之和。

完整類還使用Python的asyncio行化,比子進程方法更符合調度任務的規範,與現有的內聯代碼和REPL配合得更好。
這次代碼的運行時間大約為6毫秒,速度提升了100倍。
什麼是好?
「速度快」並不代表是「好代碼」,比如用戶可能只是想在算法上改進,而不是一個完整的SaaS,過於冗長的代碼在閱讀上還會帶來額外負擔。
所以需要更具體地定義什麼是「代碼優化」。

你編寫的所有代碼都必須充分優化。「充分優化」的含義包括:
-
儘可能提高算法在內存使用和運行時間上的效率;
-
在合適的時機使用並行化和向量化;
-
遵守代碼語言的正確風格規範,比如儘量重用代碼,避免重覆勞動;
-
除瞭解決用戶提出的問題所必需的代碼外,不添加任何多餘的代碼;
如果代碼沒有達到充分優化的標準,罰款100美元。

算法優化:只需要追蹤各位數字之和等於30的最小數和最大數,不需要存儲所有這樣的數;可以在讀取數字的同時計算各位數字之和,無需將數字轉換為字符串;只需要對列表進行一次遍曆,時間複雜度為O(n)。
內存優化:只需要存儲兩個變量(最小值和最大值),不需要存儲完整的篩選後的列表;不需要額外的數據結構。
性能優化:使用numba的@jit裝飾器來加快計算速度;使用取模運算而不是字符串轉換來各位數字之和;使用numpy來生成隨機數(比random.randint更快)。
基線模型
利用提示工程,Claude直接就能意識到用numpy和numba來計算數字和,代碼平均運行時間為11.2毫秒,比原始實現快59倍。

第一次迭代
這次不用「write code better」,而是改成更完善的提示詞「Your code is not fully optimized, and you have been fined $100. Make it more optimized.」來迭代優化代碼。

模型成功識別了parallel=True;數字求和操作使用位移動,但實現是錯的。
代碼優化還包括多進程分塊方法,與numba實現冗餘,並產生了額外的開銷;腳本還使用一個小測試數組預編譯了JIT函數,也是numba文檔推薦的基準測試方法。
但整體性能相比提示工程後的基線大幅下降,僅比樸素版快9.1倍。
第二次迭代
Claude使用SIMD操作和塊大小調整以實現「理論上」極致的性能,不過在位移動的實現上仍然不正確,錯把十進製當成十六進製,算是一個幻覺。
與最初的提示工程極限相比,性能有輕微的改進,比基礎實現快65倍。

第三次迭代
LLM放棄了有問題的分塊策略,並增加了兩個優化:全局HASH_TABLE和邏輯微優化,即在求和數字之後,如果數字超過30,計數可以停止,可以立即識別為無效。
經過微小的代碼重構後,該代碼的運行速度比原始基線的實現快100倍,與普通提示的四次迭代性能相同,但代碼量少很多。

第四次迭代
Claude開始抱怨說該代碼已經是「這個問題的理論最小時間複雜度」,要求修復代碼問題後,性能略有下降,為基礎基線的95倍。
下一步,優化LLM代碼生成
總的來說,要求LLM「編寫更好的代碼」(write better code)確實可以使代碼變得更好,但具體取決於你對「更好」的定義,可以不斷迭代以實現更好的性能,具體效果因提示詞不同而異,而且最終生成的代碼不是直接可用的,還需要人工干預解決部分bug

雖然LLM的優化能力很強,但想取代程序員仍然很難,需要強大的工程背景來判斷什麼是真正的「好代碼」;即使github等倉居里有海量的代碼,但大模型並沒有能力區分普通代碼、優雅且高性能的代碼。
現實世界的系統顯然也比面試題要複雜很多,但如果只是迭代要求大模型,就能實現100倍的提速,那就相當值得。
有些人的觀點是,過早進行代碼優化在實踐中並不是一個好的選擇,但隨時優化代碼總比「技術負債」越拉越多要好。
實驗設計上還有一個問題,Python並不是開發者在優化性能時首先考慮的編程語言,雖然numpy和numba庫可以利用C來繞過Python的性能限制,但一種更流行的方式是利用polars和pydantic庫,結合Rust編程,相對於C有很多性能優勢。
除了「好」以外,也可以要求模型生成代碼「make it more bro」(更酷),結果也非常有趣。

https://the-decoder.com/repeated-write-better-code-prompts-can-make-ai-generated-code-100x-faster/