這是一篇來自 Antirez(Redis 之父Salvatore Sanfilippo)的博文,分享給大家
人類程序員依然技高一籌:為什么說 AI 目前還差點火候
這篇短文,是想聊聊為什么我覺得咱們人類程序員,比起現在大火的 LLM(大語言模型)還是要強太多。先聲明,我可不是什么 AI 反對者,了解我或者關注我動態的朋友應該都清楚。LLM 我經常用,就像今天,我會用它來碰撞靈感、做代碼評審、看看有沒有比我最初構想更好的方案、探索那些快要超出我知識邊界的領域,諸如此類吧。(大約兩年前,LLM 還沒那么流行的時候,我就寫過一篇關于用 LLM 輔助編程的博客:那時候我就已經在用 LLM 寫代碼了,并且一直沒停過。這事兒我得抽空再寫篇更新,但今天的主題不是這個。)
但是,話說回來:眼下 AI 的水平,確實有用,也相當不錯,可要跟人類智能相比,那還差得遠呢。我特別想強調這一點,因為近來想就這個問題進行平衡的討論都很難,輿論往往一邊倒。
話說今天,我正在為 Redis 的向量集(Vector Sets)功能修復一個特別復雜的缺陷:在我離開 Redis 開發的那段時間里,我的同事們為 RDB 和 RESTORE 的數據引入了一種防損壞機制,即便數據的校驗和通過了,這個機制也會生效。這個功能默認是關閉的,但為有需求的用戶提供了一個增強的安全層。
但……這里有個巨大的隱患:為了讓 HNSW(分層可導航小世界圖)能夠快速地存入 Redis RDB 并加載回來,我序列化的是整個圖結構本身,而不是單個的“元素-向量”對。否則的話,我得把數據重新插入到 HNSW 中,那樣速度會慢上大概 100 倍(沒錯,就是這么多!)。所以我把節點之間所有的連接關系都以整數形式存儲,然后在加載時再將它們解析成指針。這個技巧很巧妙,效果也非常好。可如果你把這套機制,跟數據表示層面可能出現的隨機損壞,再加上我自己對 HNSW 的一點改進——強制節點間的連接必須是雙向的(我自己實現了一套 HNSW,加入了不少實用特性,而雙向連接是啟用其中許多特性的基礎)——這些因素結合起來,就可能出現以下情況:
1. 我們加載了一份已損壞的數據,數據顯示 A 鏈接到 B,但 B 已經不再鏈接回 A(因為節點 ID 損壞了)。
2. 我們刪除了節點 B:由于雙向性被破壞,從 A 到 B 的鏈接并沒有被清除。
3. 然后當我們掃描圖結構,訪問到 B 時,試圖去訪問 A:好了,use-after-free(懸垂指針訪問)發生了!:-D :-) :-|
所以,數據加載完成后,我需要檢查每一個鏈接是否都是雙向的。如果用最直接的方法,復雜度會是 O(N^2)——對于每個節點,都需要遍歷其所有層級,在每個層級遍歷其所有鄰居節點,并且還要反過來檢查該鄰居節點在這一層級是否也鏈接回當前節點。這效率太低了,行不通。
# 人類 vs LLM
起初,我按照常規方法實現了這個檢查,想看看模糊測試工具(fuzzer)是否還會發現那個缺陷。結果確實沒再出現,但是,一個包含 2000 萬個向量的大型向量集,加載時間從 45 秒一下子增加到了 90 秒左右。太坑了!于是我趕緊打開一個 Gemini 2.5 PRO 的聊天窗口,問它:“老兄,這情況有什么好辦法嗎?有沒有什么超快的解決方案?”
Gemini 能給出的最佳方案是:對鄰居鏈接的指針進行排序,這樣就可以使用二分查找了。嗯,好吧,這個方法我懂,但我不太確定在一個只有 16 或 32 個指針的數組里,這樣做到底是更快還是更慢。我又追問:“還有其他方案嗎?” 回答:“沒有更好的了。”
于是我跟它說:你看這樣如何?當我們發現 A 在 X 層鏈接到 B 時,我們把 A:B:X 存入一個哈希表(但我們總是對 A 和 B 進行排序,使得 A>B,這樣無論鏈接方向如何,其表示都是一致的)。當我們再次遇到這個鏈接時,就把它從哈希表中移除。這一次,我們就像之前解析 ID 到指針的鏈接時那樣,完整地掃描整個圖結構。如果最后哈希表不為空,那就說明肯定存在非雙向的鏈接,對吧?
Gemini 表示這是個不錯的主意,但指出使用snprintf()
生成鍵以及哈希運算本身會耗費時間,不過,它也承認這確實比我最初的方法(即使是排序指針的版本)要好。我提醒它,snprintf()
并非必需。我們可以直接用memcpy()
將指針復制到一個固定大小的鍵中。它認可了這種做法的可行性,然后,我突然有了個新想法……
我跟 Gemini 說,我們能不能用一個固定大小的累加器來處理 A:B:X 呢?完全不需要哈希表。每次我們遇到一個鏈接(A:B:X,也就是 8+8+4 字節),就把它與當前一個 12 字節的累加器進行異或(XOR)運算。如果一個鏈接出現兩次,兩次異或操作就會相互抵消。所以,最后如果累加器的值不為零,我們就知道數據有問題了!不過,我預先跟 Gemini 指出,這個系統可能存在沖突(collision)的風險,需要進行評估。即使這個功能在 Redis 中通常是關閉的,但當用戶啟用這種額外的檢查時,他們通常也期望能獲得更多保護,以防止攻擊者故意構造惡意的負載。
Gemini 對這個想法印象相當深刻,但仍然指出,指針這種東西……你知道,它們的結構都很相似,只在少數幾個比特位上有差異。所以,如果恰好出現了三個偽造的鏈接 L1、L2、L3,有可能 L1 和 L2 異或的結果正好與 L3 的比特位相同,這樣我們就可能得到一個假陰性(即累加器為零,但實際上存在問題)。我還注意到,內存分配器的行為往往非常具有可預測性,并且容易被外部猜測到。
我讓 Gemini 幫忙想辦法改進這個方案:它沒能提出什么特別好的主意。然后我想,等等,我們實際上可以用一個足夠好并且速度仍然很快的哈希函數來處理它,比如 murmur-128 或類似的算法(這個任務并不要求它具備密碼學級別的安全性),然后我向 Gemini 提出了以下方案:
1. 取鏈接 A:B:X,但使用一個通過 /dev/urandom 獲取的種子 S 作為所有鍵的前綴,所以我們實際處理的是 S:A:B:X。
2. 我們將 murmur-128(S:A:B:X) 的輸出與一個 128 位的寄存器進行異或操作。
3. 最后,檢查該寄存器是否為 0(如果為 0,則表示所有鏈接都是雙向的)。
我讓 Gemini 對這個方案進行分析,它最終表示滿意,說這樣做會使得無論是偶然發現幾個恰好異或結果為 0 的孤立鏈接,還是外部攻擊者想利用這一點進行有效攻擊,都變得困難得多。因為“S”是未知的,攻擊者還需要同時控制指針,所有這些因素組合起來非常難以實現。此外,這個功能本身就是一種盡力而為的額外保護措施,需要用戶手動啟用,它通常是關閉狀態,并且為了保證實用性,它不應該帶來過大的性能開銷。
代價
嗯,說了這么多,其實就是想表達:我剛剛完成了分析,就停下來寫這篇博客了。我不確定最終是否會采用這個系統(但可能性很大),但是,人類的創造力依然占據著優勢。我們能夠真正地跳出固有思維模式,構想出那些看似奇特和不那么精確,但實際上卻能比其他方案更好地解決問題的辦法。這種能力,對于 LLM 來說是極其困難的。當然,在驗證我所有想法的過程中,Gemini 還是非常有用的,或許我之所以能從這些角度思考問題,也是因為我有一個“聰明的橡皮鴨”(指可以與之對話并梳理思路的對象)可以交流吧。
source:
https://www.antirez.com/news/153
?星標AI寒武紀,好內容不錯過?
用你的贊和在看告訴我~
求贊
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.