僅從程式碼工作的編碼代理會產生淺層的假設。加入研究階段——arXiv 論文、競爭性分支、其他後端——產生了 5 個核心融合,使 llama.cpp 的 CPU 推理速度提升了 15%。
重點摘要:編碼代理在接觸程式碼之前,透過閱讀論文和研究競爭專案,能產生更好的優化。我們為 autoresearch / pi-autoresearch 迴圈添加了一個文獻搜尋階段,將其指向具有 4 個雲端 VM 的 llama.cpp,大約 3 小時後,它產生了 5 個優化,使 flash attention 的文字生成在 x86 上快了 15%,在 ARM 上快了 5%(TinyLlama 1.1B)。整個設定適用於任何具有基準測試和測試套件的專案。
Karpathy 的 autoresearch 顯示,編碼代理可以自主改進神經網路訓練腳本。在我們上一篇文章中,我們將其擴展到 16 個 GPU,並觀察到代理在 8 小時內運行了約 910 個實驗,將 val_bpb 降低了 2.87%。代理僅從程式碼上下文進行腦力激盪,所有實驗都是對同一個 train.py 的變體。
此後,pi-autoresearch 將迴圈泛化為任何可基準測試目標的可重複使用擴展。Shopify CEO Tobi Lütke 在 Liquid 上運行了它,Liquid 是處理每年 2920 億美元商品交易量的 Ruby 模板引擎。代理運行了約 120 個實驗,產生了 93 個提交,將解析+渲染時間減少了 53%,分配減少了 61%,且在 974 個單元測試中沒有回歸(Simon Willison 的報導,Tobi 的貼文)。
在這種情況下,優化表面在原始碼中可見。Liquid 代理可以讀取分詞器,發現 StringScanner 是瓶頸,並僅從程式碼庫中進行腦力激盪。
並非所有優化問題都如此。程式碼庫告訴你程式碼的作用,但沒有告訴你它為什麼慢或這個程式碼庫之外存在什麼替代方案。當答案存在於原始碼之外(在 arXiv 論文中、競爭專案中、例如資深工程師會帶來的領域知識中),僅從程式碼工作的代理會產生淺層的假設。
當我們將代理指向 llama.cpp 的 CPU 推理路徑時,我們看到了這一點。優化搜尋空間不是「嘗試不同的學習率」。而是「我應該融合這兩個記憶體通道嗎?」、「這個工作負載是計算密集型還是記憶體密集型?」、「ik_llama.cpp 已經嘗試過什麼?」
代理的第一波實驗顯示了問題。僅從程式碼上下文工作,它直接針對 GGML 的矩陣乘法熱路徑中的 SIMD 微優化。它嘗試了:
所有都在雜訊範圍內。代理的事後分析:
一個 606 MiB 的模型,約 49 個 token/秒,消耗約 30 GB/秒的記憶體頻寬,接近 c6i.2xlarge 的 DRAM 極限。當 CPU 因等待模型權重從 DRAM 到達而停滯時,任何 SIMD 技巧都無濟於事。但程式碼本身並不會告訴你這一點。你需要知道目標硬體的記憶體頻寬,理解 roofline 模型,並認識到 batch-size-1 推理是記憶體密集型的。這是代理沒有的領域知識。
如果瓶頸是假設品質,請為代理提供更好的輸入。在運行任何實驗之前,讓它閱讀論文、研究分支,並查看其他專案已經嘗試過什麼。這與資深工程師在接觸不熟悉的程式碼之前所做的準備相同。
原始的 autoresearch 迴圈是:編輯程式碼 -> 運行實驗 -> 檢查指標 -> 保留或捨棄。pi-autoresearch 將其泛化為任何具有可基準測試指標的專案。我們的版本基於此,並增加了研究步驟和並行雲執行:
代理編寫自己的基準測試腳本 (autoresearch.sh) 和正確性檢查 (autoresearch.checks.sh),然後使用 SkyPilot 將實驗分散到雲端 VM 上。每個實驗都在自己的 VM 上運行:建置專案、運行基準測試、運行正確性檢查、報告指標。代理透過 sky logs 檢查結果,提交獲勝者,並排隊下一波。
CPU 密集型程式碼優化不需要 GPU。如果您的目標需要 GPU 基準測試,請使用 --gpus 覆蓋。
我們將 Claude Code 指向 llama.cpp,透過 SkyPilot 為其提供了 4 個 AWS VM,並指示它加快 CPU 推理速度。
目標:TinyLlama 1.1B (Q4_0 量化) 的 CPU 推理吞吐量,在兩種架構上進行基準測試:
指標:提示處理 (pp) 和文字生成 (tg) 的 tokens/秒,使用 llama-bench -p 512 -n 128 -t 8 -r 5 測量。
它從 4 個 x86 VM 開始以建立基準並運行實驗。之後它配置了 ARM VM 以檢查可移植性;每個核心融合都包含 AVX2/FMA 和 NEON 路徑,以及標量備份。
在實驗波之間,代理運行了兩個並行研究線程:
分支分析比 arXiv 搜尋更有用。從研究 ik_llama.cpp 和 llamafile 已經發布的內容中獲得了幾個可行的想法。研究 CUDA 和 Metal 後端也直接導致了下面的優化 #4——代理注意到 RMS_NORM + MUL 融合存在於除 CPU 之外的所有後端。
在第一波實驗失敗後,代理改變了方向:
Matmul 佔推理時間的約 95%,因此剩餘的操作(softmax、RMS norm、量化)僅留下約 5% 的餘裕。但這些操作足夠小,可以成為計算密集型而不是記憶體密集型,因此減少它們內部的記憶體通道可以有所幫助。
30 多個實驗中有五個進入了最終程式碼。每個實驗都針對非 matmul 開銷的不同部分:
現有程式碼在三個獨立的數據通道中執行 copy -> scale -> add mask。代理將它們融合為一個:
相同的模式。原始程式碼執行 memcpy(y, x) 然後 ggml_vec_scale_f32(y, scale) 作為兩個通道。融合為一個通道中的 y[i] = x[i] * scale。
from_float 量化迴圈(將激活轉換為點積輸入格式)使用了一種一刀切的並行策略。現在,當有很多行時(提示處理),它按行分區;當行數很少時(文字生成),它按元素分區。
透過在同一 VM 上進行乾淨的 A/B 比較(不使用 flash attention,以隔離這三個變化的影響)進行驗證:
文字生成幾乎沒有變化,正如預期的那樣:TG 是記憶體頻寬密集型(如上面第一波中所述),這些更改不觸及 matmul 路徑。提示處理增加了 +2.5%,因為 PP 是計算密集型,並且受益於更少的記憶體通道。
這個來自研究階段。在研究其他後端如何處理相同操作時,代理發現了一個差距:
CUDA 和 Metal 後端已經融合了這些,但 CPU 後端沒有。如果代理不在研究階段研究其他後端,它就不會尋找這個。僅從 CPU 程式碼來看,兩步法看起來不錯。
它在 CPU 圖形執行迴圈中實現了模式檢測。當它看到 RMS_NORM 後面跟著 MUL,且 MUL 的輸入是 RMS_NORM 的輸出時,它會調用一個融合核心,該核心在單個通道中使用顯式的 AVX2 和 NEON intrinsics 計算 y = x * (1/sqrt(mean_sq + eps)) * weights:
第一個版本沒有幫助,代理弄清楚了原因:
因此,它使用顯式的 AVX2 和 NEON intrinsics 重寫了核心。單獨來看,測量到的影響在雜訊範圍內,但它與 flash attention 融合疊加,並減少了 TG 變異性,可能是因為更可預測的記憶體訪問模式。
平鋪的 flash attention 路徑將 scale -> pad -> add mask -> find max 作為 QK 瓦片上的獨立通道。代理將它們融合為一個 AVX2 FMA 通道:
需要說明的是:代理的核心融合專門針對 flash attention 平鋪路徑。Flash attention (-fa 1) 是 llama.cpp 的現有功能,並非代理發明的。但代理的融合存在於該程式碼路徑中,因此基準測試需要啟用 -fa 1 才能運行它們。代理在過程中意識到這一點,並相應地更改了基準測試。
最終比較是蘋果對蘋果:啟用 FA 的基準測試 vs. 啟用 FA 的優化版本。兩者都使用相同的標誌;區別在於融合的核心。透過 5 次重複的乾淨 A/B 建置進行驗證:
x86,Intel Xeon (c6i.2xlarge, AVX-512)
TG 的改進比 PP 大,因為融合的注意路徑在文字生成期間更重要,此時注意是總運行時間的更大一部分。變異性也值得注意:baseline+FA TG 有 ±19 t/s 的雜訊,而 optimized+FA 在 x86 上有 ±0.59 t/s。融合消除了污染快取的中間寫入,使熱路徑更具可預測性。
一個警告:我們在具有 5 次重複的共享租賃 EC2 實例上運行。嘈雜的鄰居可能會影響共享硬體上的結果(請參閱下面的 Cloud VMs are noisy)。我們相信跨兩種架構和多個 VM 的方向是真實的,但請相應地對確切的百分比進行處理。
我們尚未提交 PR。完整的 diff 在這裡。
30 多個實驗中有 25 個沒有成功。一些代表性的失敗:
一個Recurring的主題:編譯器和硬體已經在做許多你認為可以手動嘗試的事情。沒有編譯器行為的經驗,代理無法預測哪些「優化」編譯器已經處理。
我們的 autoresearch.sh 有一個 JSON 解析錯誤,將文字生成的 14 t/s 報告為 52 t/s。在我們發現之前,多個實驗都針對錯誤的基準進行了運行。錯誤:llama-bench 輸出的 JSON 包含 n_prompt 和 n_gen 欄位,解析腳本過濾了一個不存在的欄位名稱。
人類也會犯這個錯誤,但可能會更早地注意到不合理的低數字。代理信任了自己的腳本。
EC2 實例在共享硬體上,由於嘈雜的鄰居,運行之間的差異高達 30%。我們是艱難地學到的:exp-08 顯示「+2.1% 的改進」,當重新測量基準時,發現它在雜訊範圍內。
緩解措施:用新的 VM 替換嘈雜的 VM(新 VM 經常落在較安靜的主機上),使用標準差作為品質信號,並且只信任 stddev < 平均值 2% 的結果。
在產生優化之後,代理根據 llama.cpp 的程式碼約定和過去維護者的反饋審查了自己的更改。它發現了自己的圖形融合程式碼中的一個正確性錯誤:手寫的模式檢測沒有檢查中間 RMS norm 輸出是否在圖形中有其他消費者。如果另一個節點從該輸出讀取,融合核心(僅寫入 MUL 輸出)將使其未初始化。
修復:使用現有的 ggml_can_fuse() 基礎設施,該基礎設施驗證使用計數、計算標誌、輸出標誌和視圖源鏈。所有其他後端(CUDA、Metal、Vulkan、OpenCL)都已在使用此功能。
標準的 autoresearch 迴圈(從程式碼腦力激盪、運行實驗、檢查指標)在優化表面在原始碼中可見時有效。Liquid 的結果證明了這一點。但對於程式碼庫沒有足夠資訊來產生良好假設的問題,讓代理存取論文和競爭性實現會改變它的嘗試。
llama.cpp 上的第一波實驗都是「讓這個迴圈更快」的變體,這是當你唯一的上下文是程式碼時會產生的假設。在閱讀了關於運算子融合的論文並研究了 CUDA/Metal 後端如何處理相同操作之後,代理開始提出不同的問題:「我能否融合這兩個操作以消除記憶體通道?」以及「這個模式是否存在於其他後端但不在 CPU 中?」這些問題導致了優化 #4 和 #5。
以下是這次運行與我們之前 GPU autoresearch 工作 的比較。請注意,它們針對的是非常不同的問題(ML 訓練超參數 vs. 編譯的 C++ 核心),因此數字無法直接比較:
代理閱讀論文 + 分析瓶頸
實驗次數較少,因為每個 llama.cpp 實驗都涉及完整的 CMake 建置(約 2 分鐘)加上基準測試(約 3 分鐘),並且代理在波之間花費時間閱讀論文和分析。對於 GPU autoresearch,代理可以在每波中啟動 10-13 個實驗,並在 5 分鐘內獲得結果。在這裡,它每波運行 4 個實驗(每個 VM 一個),並在波之間花費時間進行研究。
該設定適用於任何具有基準測試和測試套件的專案。克隆你的目標,下載兩個文件,然後將你的編碼代理指向說明:
ML 推理框架是好的候選者,因為它們發展迅速,具有清晰的吞吐量指標,並且隨著每個主要功能的出現不斷出現新的優化機會。一些起點:
透過 benchmark_throughput.py 的 tokens/s
PagedAttention 調度、前綴快取、投機解碼
RadixAttention、受限解碼、分塊預填充
運算子融合、量化 matmul、快取效率高的注意
核心融合、KV 快取優化、佇列中批次處理
SIMD 核心、量化格式、圖形優化
投機解碼、批次束搜尋
我們也嘗試了更成熟的專案(Valkey/Redis、PostgreSQL、CPython、SQLite),發現更難發現改進。這些程式碼庫經過數十年數百名貢獻者的優化,代理發現的收益在雜訊範圍內。
設定基礎設施:在 YAML 中指定目標後端(infra: k8s 用於 Kubernetes,infra: aws 用於 AWS 等)。
完整的設定在 skypilot/examples/autonomous-code-optimization。
要接收最新更新,請為專案的 GitHub 儲存庫加星標並關注,關注 @skypilot_org,或加入 SkyPilot 社群 Slack。
Karpathy 的 autoresearch 一次運行一個實驗。我們讓它存取我們的 GPU 基礎設施,並讓它並行運行實驗。