Human coders are still better than LLMs

Salvatore Sanfilippo

人类程序员依然比LLM更强

这是一个关于人类能力依然远超LLM的小故事。先说明一下,我并非反对AI之类的人,如果你认识我/在某个地方关注我,你就会知道。我日常都会使用LLM,就像今天这样,用来验证自己的想法、做代码审查、看看是否有比我设想更好的方案、探索那些处于我专业能力边缘的领域等等(大约两年前,当时用LLM编程还不算流行,我就写过一篇关于用LLM编程的博客文章:那时我就已经在用LLM写代码,而且一直没有停过,以后我得写个更新,但那不是本文的主题)。

但是,话说回来:当前的AI水平很有用,也很棒,但与人类智能相比依然远远落后,我想强调这一点,因为最近已经很难进行平衡的讨论了。

所以,今天我在为Redis的Vector Sets(向量集合)修复一个复杂的bug:在我暂停Redis相关工作期间,同事们引入了一项针对损坏的RDB和RESTORE payload的抵抗能力,即使数据的checksum通过了校验也会生效。这个功能默认是关闭的,但为有需要的人提供了一层额外的安全保障。

但是……有一个大如象的问题:为了让HNSWs(分层可导航小世界图)能快速保存到Redis的RDB中并快速加载回来,我序列化的是*图*结构本身,而不是element-vector pairs(元素-向量对),否则我就得把数据重新插入回HNSWs,那样会慢上大概100倍(!)。所以我把节点与其他节点之间的所有连接以整数形式存储,然后再将它们解析为指针,这是个很巧妙的技巧,效果也很好。但如果你把这一点和表示层的随机损坏混在一起,再加上我自己对HNSWs的改进要求节点之间必须有相互连接(我自己实现了一个具备许多有用特性的HNSW版本,但要启用其中许多特性,就需要相互连接),那么就可能会发生以下情况:

  1. 我们加载了损坏的数据,它显示A连接到B,但B不再连接到A(节点ID已损坏)。
  2. 我们删除节点B:由于相互性被破坏,我们不会清除从A到B的连接。
  3. 然后我们扫描图,当再次访问到B时去访问A:use-after-free :-D :-) :-|

所以在加载数据后,我需要检查每条连接都是相互的,而在最朴素的实现中,这将是O(N^2)的复杂度,对于每个节点,我们需要扫描所有层,在每一层扫描该节点的所有邻居,并通过扫描对方在该层的连接来检查对方是否也连回了本节点。很不理想。

人类 vs LLM

一开始,我实现了最朴素的方案,看看fuzzer(模糊测试器)是否就再也找不到这个bug了,确实有效,但一个包含2000万个向量的大型vector set的加载时间却从45秒变成了90秒左右。WTF。所以我打开了一个Gemini 2.5 PRO的对话,问LLM,嘿,这里我们能做些什么?有没有超级快的方法?

Gemini能找到的最佳方案是说:把邻居连接的指针排好序,这样就可以使用二分查找。哦,好吧,当然,我知道这个,我不太确定在16/32个指针的数组里这到底会更快还是更慢。于是我问,还有别的办法吗?没有,没有更好的方案了。

于是我跟它说:听着,如果我们在X层看到A连接B时,就在hash table(哈希表)中存入A:B:X(但我们总是对A和B排序使A>B,这样无论方向如何,连接都是一样的),当再次看到同一条连接时就把它清除,这次我们只需像在解析连接中ID到指针的映射时已经做的那样完整地扫描一遍,如果最后hash table不为空,我们就知道肯定存在某条非相互的连接,怎么样?

Gemini告诉我这是个不错的想法,但提到要用snprintf()来创建键,还有哈希耗时等等,不过,是的,这比我最初的方法(甚至比对指针排序)要好。我提醒它根本不需要snprintf()。我们完全可以直接用memcpy()把指针拷贝到一个固定大小的键里。它承认这样做是可行的,然后我又想到了一些事……

嘿,我跟Gemini说,如果对A:B:X使用一个固定的累加器怎么样?完全不用hash table。每次看到一条连接(A:B:X,也就是8+8+4字节)时,我们就把它xor到一个12字节的当前累加器里。如果同一条连接存了两次,就会相互抵消,所以最后如果寄存器非零,我们就知道有异常!不过我也预先跟Gemini提到,这个系统潜在地会有碰撞问题,需要评估一下。即使这个功能在Redis中通常是关闭的,当用户启用这类额外检查时,他们往往也期望能对攻击者故意构造的恶意payload有更多防护。

Gemini对这个想法相当赞赏,但还是说指针嘛……你知道的,结构上很相似,只差几个比特,所以如果存在三条异常连接L1、L2、L3,就有可能出现L1与L2的xor结果恰好等于L3的比特,从而导致假阴性(寄存器为零)。我也注意到分配器的行为往往非常可预测,外部很容易猜到。

我向Gemini询问改进这个方案的办法:它没什么好主意。然后我想,等等,我们其实可以用一个足够好且依然很快的哈希函数来哈希它,比如murmur-128之类的(对于这个任务,我们不需要它具备密码学特性),并向Gemini提出了以下方案:

  1. 取连接A:B:X,但使用通过/dev/urandom获取的seed来给所有键加上前缀,所以实际上我们得到的是S:A:B:X。
  2. 我们只需将murmur-128(S:A:B:X)的输出xor到128位的寄存器中。
  3. 最后,我们检查寄存器是否为0(所有连接均为相互连接)。

我让Gemini对此进行分析,它终于表示满意,说这样一来,无论是偶然找到恰好一起xor为0的孤立连接,还是外部攻击者想以有用的方式利用这一点,都要困难得多,因为“S”是未知的,还得控制指针等等,所有这些要凑在一起真的很难。而且,这个功能是一种需要手动启用的尽力而为的额外保护,正常情况下是关闭的,为了实用起见,它不应该带来太大的性能损耗。

好了,说了这么多:我刚刚完成分析就停下来写了这篇博客文章,我还不确定是否会采用这个方案(但很可能会),但是,人类的创造力依然更胜一筹,我们能够真正跳出框框思考,设想出那些奇怪却不精确、但可能比其他方案更有效的解法。这是LLM极难做到的。不过,为了验证我的所有想法,Gemini还是非常有用的,也许正因为有这么一个可以对话的“聪明的鸭子”,我才开始以这种方式来思考这个问题。

原文由 Salvatore Sanfilippo 发布

本文章由 muse-spark-1.2-contributor 进行翻译