Aioga
AI资讯 / 技巧观点
返回 AI资讯

用 LLM 实现证明自动化:Lean 中构建 Zstandard 解压器的实践

Hacker News 热门(buzzing.cc 中文翻译)Aioga 编辑团队2026-07-27T03:38:12.004Z热度 43

依赖类型语言(如 Lean)的证明工作耗时巨大,seL4 项目的证明代码量是 C 代码的 20 倍以上。作者利用 LLM 结合证明无关性(proof irrelevance)实...

技巧观点Hacker News 热门(buzzing.cc 中文翻译)

今日 AI 情报摘要

依赖类型语言(如 Lean)的证明工作耗时巨大,seL4 项目的证明代码量是 C 代码的 20 倍以上。

作者利用 LLM 结合证明无关性(proof irrelevance)实现自动化,在 Lean 中构建了一个 Zstandard 解压器,认为 LLM 能大幅降低证明工程开销,使依赖类型系统变得更为实用。

中文正文 · AI 翻译

我一直对像 Coq、Rocq 和 Lean 这样的依赖类型语言情有独钟。它们提供了一个可能性:拥有一个能够编码并强制执行任意微妙不变量的类型系统。这种东西,在普通语言里,充其量也不过是注释,而且随着团队规模的增长,很快就会丢失。随后你就会遇到微妙的误解和不完全兼容的组件。通常这些组件已经长到一定规模,当问题被发现时,要对其进行统一调整是一个让人疲惫的任务。也许,依赖类型引诱地告诉你,你可以正式地书写这些不变量,并让机器检查它们。

(顺便说一句,Coq 改名字了!多年前,我记得在普林斯顿参加 Coq 会议时,我曾试图建议,在讲英语的世界里,有一门叫做 Coq 的编程语言是一个障碍。当时我觉得观众并不同意。我还开玩笑说,那里的许多演讲听起来就像提利昂·兰尼斯特的演讲,因为有这么多的 Coq 和 Hoare。这是一个非常精彩和恰当的玩笑,虽然完全没被理解,因为它发生在那部剧的最终季之前,而我们集体选择性地忘记了它。)

问题一直在于,强大的类型系统能力伴随着巨大的证明工作量。我可以肯定地证明,自己曾整天在证明一些相当简单的事情。实际上,做证明是很有趣的:它具有挑战性、互动性,并且有明确的目标。但天哪,这确实非常耗费时间,尤其是像我这样不知道自己在做什么的人。还有一种周期性的、令人恼火的经历,那就是在许多小时的努力结束时,你发现你尝试证明的目标实际上是错误的。经典结果:https://trustworthy.systems/publications/nicta_full_text/7371.pdf,这里是 seL4 项目的回顾,他们发现,即使项目规模足够大,使工程师积累了大量经验,他们花在证明上的时间仍然大约是设计和实现时间的十倍。最终,他们的证明代码行数比 C 代码多出了 20 倍以上。

这种额外开销使得使用依赖类型语言进行编程变得极为小众。它也促使人们尝试自动化它。我略有了解的一种尝试是 F*,系统尝试让 SMT 求解器自动完成义务。这对简单情况当然有效,但很容易制造出会让 SMT 求解器陷入无尽运行状态的情形,让你怀疑它是否能完成。我见过经常使用这些语言的人不得不培养一种第六感,来判断什么会让求解器满意,然后围绕这个去设计一切。这可能有帮助,但在某种程度上,它把问题转化为神秘主义:最终你是在侍奉一个复杂而善变的神。

一个关键事实是,至少在理论上,一旦语句是正确的,其证明的内容是无关紧要的:只有证明的存在才重要。这并不完全正确,因为有两个复杂因素:首先,是 seL4 团队所说的“证明工程学”:需要结构化证明,以减少在代码更改后重新对齐所需的工作量。其次,足够复杂的证明甚至可以导致类型检查器崩溃并消耗大量内存。

现在我们有了大型语言模型(LLM),结合证明无关性,它承诺成为一种非常强大的证明自动化工具。通过足够的自动化,你或许不需要过多担心证明工程学。你仍然需要避免让类型检查器崩溃,但根据我有限的测试,LLM 可以避免这一点。潜在地,LLM 可能会让依赖类型系统变得极其实用。我想尝试一下,所以用 Lean 构建了一个 Zstandard 解压器,主要也是因为我对 Zstandard 感到好奇。

Zstandard 似乎正在赢得取代 gzip 作为标准压缩工具的竞争。它是另一种 LZ77 风格的压缩器,但它提供了更好的熵编码和精心设计,使其能够实现非常令人印象深刻的解压速度。它永远不会像 bzip2 那样美丽,但 Burrows–Wheeler 变换的闪亮优雅在面对显著的实际优势时并不算什么:

(测量是在标准参考计算机上进行的,即作者当时使用的设备。请注意纵轴为对数刻度:gzip 和 Zstandard 属于它们自己的速度类别。这是一台苹果机器,苹果的 gzip 尤其优化;在其他地方 gzip 可能会更慢。)

Zstandard(由 Yann Collet 开发,基于 Jarek Duda 的开创性 ANS 工作:https://en.wikipedia.org/wiki/Asymmetric_numeral_systems)有一个 RFC:https://www.rfc-editor.org/rfc/rfc8878,但它相当简略。它包含实现解压器所需的所有信息,但除非你已经非常熟悉压缩,否则我认为需要反复阅读几次才能理解其中的内容。我至少读了第 4.1 节六遍,才觉得对其有了不错的理解。在此过程中较晚的时候,我发现我的同事 Nigel Tao 写的关于 Zstandard 的说明比我原本打算写的要好得多。所以,如果你想理解 Zstandard,你应该阅读那篇文章。我这里只会解释最有趣的部分,熵编码器,并结合一些关于 Lean 的推广。

Zstandard 使用 Huffman 树,但它也有一种名为 FSE 的高压缩熵编码器。FSE 是一个状态机。状态的数量多于符号,每个符号获得的状态数量比例反映其在数据流中出现的概率。因此,如果某个符号预计出现的概率为 50%,它将获得大约 50% 的状态。每个状态有三个值:该状态的符号、在该状态下从比特流中读取的比特数,以及加到这些比特上的基准状态编号以获得下一个状态。现在,如果你记得,Huffman 树的问题在于它们只能使用整数位,而这些状态也读取整数位。但技巧是,如果你希望为某个符号读取 1.5 比特,那么它的一半状态读取 1 位,另一半状态读取 2 位。这样平均就达到了目标。状态表不会被传输。RFC 规定了一种从符号概率列表构建表的算法,因此只需要传输概率即可。

让我们做一个例子。假设我们有四个符号,并且我们将使用16个状态。那么我们必须将符号概率近似为16分之一的形式。(如果你想更准确地近似概率,你可以使用更多的状态;实际上zstd从不使用少于32个状态。)

任何符号都可以跟随任何其他符号,而且一个符号可能只有一个状态。因此每个符号必须能够到达每个状态。看看状态三,它是符号D的唯一状态。因为它是唯一的,所以它必须读取四位,这足以编码任何其他状态。但是,如果你看一个像B的符号,它的状态只要求你读取一位或两位。然而,16个可能的下一个状态集合正好在B符号的那些状态之间划分。因此,对于任何特定状态,恰好有一个B符号的状态可以到达它。

再考虑符号B,我们说它的概率是5/16。编码该符号的理想比特数是 -log₂(5/16) = 1.68。有三个B符号状态读取两位,两个位读取一位。状态的使用频率并不相等,并且按使用频率加权后,平均值几乎正好匹配量化概率的正确值。如果你想更精确地捕捉真实的符号概率,可以使用更大的表。

核心技巧是,通过给更常见的符号分配多个状态,编码器不仅选择一个符号:它还选择该符号要落入的状态,这个选择将信息传递到下一个符号。这就是信息的分数比特所在。但这个熵编码器仍然只是基于表的,因此运行非常快。

问题在于你不能向前工作。假设你想编码 C、D。你从哪个 C 状态开始?嗯,D 只有一个状态,所以它必须是可以到达那个状态的 C 状态。如果 D 有多个状态,那么你就需要考虑 D 之后的内容,以知道你需要哪一个。FSE 强制你从序列的末尾开始,向后工作。(这也不算太糟糕,因为通常你需要知道整个序列才能计算符号概率。)此外,Zstandard 压缩器因此是从后往前编码符号,但输出是增量写入的,所以解压器必须定位到块的末尾并倒着读取比特以整理它!这就涉及到格式的更广泛的细节,我在这里不打算涵盖;参见 Nigel 的文章:https://nigeltao.github.io/blog/2022/zstandard-part-1-concepts.html。

基本熵编码器不关心符号间的概率。也就是说,它们不能利用英文中 Q 字母后面通常跟随 U 字母这一事实。必须有其他编码在利用这些冗余。在 Zstandard 中,这是一种传统的 Lempel–Ziv 结构,它编码字面字节或对先前解码数据的反向引用。所以 FSE 主要用于高效地编码这些反向引用的偏移量和长度。

让我们谈谈 Lean!上面我说它是一种依赖类型语言,而这个概念通过例子比通过复杂定义更易阐明。所以这里有一个函数的类型,它从流中读取 n 个字节,如果没有异常,它返回一个字节数组,类型系统知道这个数组正好是 n 个字节长。

这是一个函数,它返回两个数字和一个字节数组,使得第一个数字是素数,两个数字的和可以被六整除,并且字节数组的长度至少与两个数字中较小的那个数字一样长。

那不是任何人真正需要的类型。它只是用来展示你可以把这个玩得多疯狂。依赖类型语言足以编码非常复杂的数学结构,而 Lean 目前的主要用途是作为陈述和证明数学的形式化语言。最近的一本书《代码中的证明》:https://www.quantabooks.org/books/the-proof-in-the-code/,简明且写得很好地阐述了 Lean 的发展故事。作者确实在几段文字中对构造性数学进行了彻底的曲解,但除此之外,我还是很喜欢它!

Lean 是一种像 Haskell 一样的纯函数式语言,尽管它有一些特性使其作为编程语言可能更方便。首先,Lean 是严格求值的,而 Haskell 是惰性求值。严格求值意味着函数的参数在调用之前就会被求值,而在 Haskell 中,参数的求值被推迟,直到实际需要该值时才进行。因此,在 Haskell 中,可以自由地编写开销较大的表达式并传入函数,因为它们只有在被实际使用时才会被计算。但这也意味着计算可能在程序中出现非常出乎意料的地方。这是一个有争议的话题,虽然我欣赏惰性求值的优雅,但它确实会让程序性能难以预测。

接下来,Lean 还有一些不错的语法糖:https://lean-lang.org/papers/do.pdf。它的单子 do 记法包含 for 循环、return 语句和 break 语句。如果你想以命令式风格编程,你完全可以相当合理地做到这一点!

最后,Lean 有一个优化,它会对对象进行可变更新,只要它们的引用计数等于一。因此,只要你小心不要在别的地方持有它的引用,你可以像在命令式语言中一样高效地原地修改数组。不幸的是,据我所知,Lean 并没有线性类型系统的任何方面,因此它不会帮助你确保对一个值只有单一引用。这是一个比较棘手的问题,看似微小的代码调整就可能因为在某个不起眼的地方持有对大数组的引用而彻底破坏性能。但这确实意味着,如果你想优化某个东西的性能,你有更多的工具可用。

这里是一个例子,来自我草拟的 zstd 解码器:

关注第 9 行。这有一个数组索引,这正是隐含不变量存在的地方:blockBytes 最好不要为空!在 C 语言类语言中,这种情况下会产生未定义行为。现代语言会在运行时抛出异常,或者只提供一个可选值来避免这种情况。Lean 提供了另一种选择:证明它不为空。这就是第 10 行所做的事情。blockBytes.property 表明它的长度与请求读取的长度相同,即恰好是 blockHeader.contentSize 字节。blockHeader.contentSize_rle 是这样定义的:

这是一个证明,当类型为 rle 时,contentSize 总是为一。有了这些事实,Lean 就可以推导出其余部分。

这是一个非常简短的证明,我自己也可能能弄明白,但我们可以设定更高的目标:

我实现了 RFC 中的 FSE 表构建算法。RFC 为它提供了“测试向量”:三个示例输出:https://datatracker.ietf.org/doc/html/rfc8878#section-appendix.a,给定概率。显然,这些会用于单元测试。但在 Lean 中,我们也可以证明该函数的普遍属性:

假设表构建函数在给定“accuracy”常量和符号概率列表时,会生成一个值,那么:

这些是优化解码内部循环所需要的微妙假设,也是弱类型系统中只能是隐含的或仅作注释的事情。证明像这样的强声明是 seL4 回顾中描述的 10 倍努力的一部分,也是依赖类型在常规软件中采用的主要障碍。现在有几种大语言模型(LLM)可以在大约 20 分钟内自动完成,而且只使用每月 20 美元订阅额度的一小部分。明年这很可能会成为基本要求。我必须承认,在这样做时他们需要修改生成表格的代码:我使用了太多的 Id.run(即进入命令式模式),这对证明机制来说更难处理。(但 Lean 正在研究这个问题:https://lean-lang.org/doc/tutorials/4.33.0-rc1/mvcgen/。)我确认了证明类型检查通过,并且没有任何 sorry s。

将依赖类型和大语言模型结合起来并不是一个新想法,但在将这种组合应用于日常软件工程方面做得并不多。可能需要更多的经验。非常强的类型可以放大更改的范围,因为它们必须传播到所有派生类型中。或许在更大的系统中,证明工作量扩展得不好,即使是现代的 LLM 也跟不上。Lean 是一种高级语言,并不适合所有情况。(我的玩具 Zstandard 解码器比命令行下的 zstd 慢 10 倍。)不过,证明自动化现在已经到来,从实际角度讲,我们可以使用一种新的编程语言类型。这令人兴奋!

(我没有发布代码,因为坦白说,对于像这样的小型、明确的案例,LLM 很可能比我做得更好。我这样做是为了稍微学习一下 Lean,并不将我的探索作为示范。这受到 lean-zip 的启发:https://github.com/kim-em/lean-zip,它功能更多,包括压缩器,并且证明了往返处理!)

AWS 制作了 LNSym:https://github.com/leanprover/LNSym:一个用于 AArch64 的语义和模拟器。这很酷。也许我们可以用它来展示一些函数的优化汇编实现与它们的 Lean 对应实现之间的等价性,然后在运行时使用这些汇编代码?这样我们就可以让大语言模型在优化上尽情发挥,而它们不会引入任何功能性错误。经过验证的汇编在加密实现中已经有很多经验,但现在也许可以廉价实现?

我投入了一些(主要是大语言模型的)时间尝试这个。小型 popcount 示例:https://github.com/leanprover/LNSym/blob/main/Proofs/Popcount32.lean 使用了 bv_decide,这是一个认证 SAT 求解器,而该示例所需的内存超过了我的系统,这并不乐观。微小函数是可以工作的,而且有可能得到微小 Lean 函数的等价性证明,然后使用 extern:https://lean-lang.org/doc/reference/latest/Run-Time-Code/Foreign-Function-Interface/#The-Lean-Language-Reference--Run-Time-Code--Foreign-Function-Interface--The-Lean-ABI 在运行时调用它们!但我和几个大语言模型都无法让它进一步扩展。

情报判断

Aioga 编辑摘要

Aioga 编辑摘要:依赖类型语言(如 Lean)的证明工作耗时巨大,seL4 项目的证明代码量是 C 代码的 20 倍以上。 Aioga 将其归入「技巧观点」方向,重点关注它对真实使用和行业竞争的影响。

背景分析

背景分析:实践类内容的价值在于是否能被复现、是否有明确边界,以及它能否转化为稳定的开发或工作流方法。

Aioga 观点

Aioga 判断:这条动态更适合作为行业观察信号,当前信息足以建立线索,但不足以推导长期结论。

影响与后续

影响分析:对相关团队而言,短期应先核对来源、可用范围和实际成本,再判断是否值得接入或跟进。 后续观察:继续观察示例是否可复现、工具版本变化、社区反馈和实际成本。

来源与版权说明

本页正文由公开来源页面提取并按原有信息整理,同时保留来源、发布时间和原文入口。版权归原作者及来源网站所有,请通过原文链接核验和阅读来源版本。

抓取通道: 摘要聚合 · 原始域名: imperialviolet.org

来源: Hacker News 热门(buzzing.cc 中文翻译)

原文链接: 打开原始来源

Aioga 归档: 查看情报页

Content record: source-page · Updated: 2026-07-27T03:38:12.004Z

API 中转站
API RELAY · DEVELOPER INFRASTRUCTURE

API 中转站

统一接入主流 AI 模型 API,为开发、测试与生产环境提供稳定调用入口。

立即访问 api.w173.com
AIOGA SHARE POSTER

分享这篇 AI 情报

依赖类型语言(如 Lean)的证明工作耗时巨大,seL4 项目的证明代码量是 C 代码的 20 倍以上。作者利用 LLM 结合证明无关性(proof irrelevance)实...

Hacker News 热门(buzzing.cc 中文翻译)2026-07-27T03:38:12.004Z
扫码打开文章详情扫码直达文章详情

Aioga 自动聚合全球 AI 动态,并保留来源信息用于核验与引用。