Cursor博客 - 每秒编辑 1000 个 Token

August 23, 2024

一种全新的模型和推理方法,能够以每秒1000个单词的速度进行高精度、全文件代码编辑。

像 GPT-4o 这样的前沿模型在处理大规模的代码编辑时常常力不从心,会出现效率低下、准确性不足以及延迟过高等问题。

这种缺陷在编写代码的 Agent 中尤为明显。准确地编辑数百行代码可能需要多次调用模型,有时甚至会导致 Agent 陷入无限循环。即使是小的、孤立的编辑也充满了错误:

图1:SWE-Agent尝试进行一个简单的编辑,但由于持续的语法错误,它尝试了七次后放弃了。

最糟糕的是,现有模型在进行大型编辑时速度很慢,这会打断程序员的思路。

我们训练了一个专门针对重要版本的全文件代码编辑任务的模型,称为"快速应用"(fast apply)。

复杂的代码编辑可以分解成两个阶段:计划和应用。

在 Cursor 中,计划阶段采用与强大的前沿模型进行聊天交互的形式。将更改应用于当前文件应该是简单直接且即时的。

alt text 图2:我们想要"Apply"代码的变更。它无法简单地复制/粘贴,因为它描述了整个类的变化。

我们的快速 Apply 模型在性能上超越了GPT-4和GPT-4o,并将准确性/延迟曲线的帕累托边界向前推进。我们在 70B 模型上使用了一种针对代码编辑优化的推测性解码变体(称为推测编辑),实现了约每秒 1000 个 token(约每秒 3500 个字符)的速度。

这意味着与 Llama-3-70B 的原始推理相比,我们实现了约 13 倍的速度提升;与我们先前部署的 GPT-4 推测编辑相比,提升约为 9 倍。

图 3:上方是我们新推出的带有推测性编辑的 70B 模型。下方是同样带有推测编辑的 GPT-4 Turbo。在这种情况下,对于较小的文件,速度提升接近4-5倍。

默认情况下,我们让语言模型根据当前文件、对话历史记录和当前代码块生成完全重写后的文件。

在这篇文章中,我们将解释我们如何训练和评估我们的新模型。我们将展示为什么我们选择重写文件而不是使用差异文件(diffs),以及推测编辑如何给我们带来如此惊人的速度提升。

评估提示重写

我们构建了一个包含约450个全文件编辑的评估集,这些文件都在400行以下,然后使用Claude-3 Opus作为评分器来衡量几种提示模型的性能。

在数十个精心挑选的例子中,基于Opus的评分结果与我们的评估结果更加一致。

alt text 图4:提示的评分指南

这些分数可能偏向于Claude模型的输出。但这些分数也与我们对模型的定性评估相符。

alt text

图5:令人惊讶的是,claude-3-sonnet 的表现优于 gpt-4-turbo。而 gpt-4o 的表现与 gpt-4-turbo 相似。

我们假设 Claude 的优异表现是后期训练的结果。Claude 模型在助手消息中输出了数千行代码 (LOC),而 GPT-4 则省略了代码,并用 ... 或注释来表示缺失的区域。

GPT-4 较差的表现也可能归因于不相关的更改。GPT-4 会删除注释掉的代码和不必要的换行符。它倾向于"修复/清理"不相关的代码。

速度测量

我们测量的速度是:

alt text

这种方式的优势在于:

  1. 标准化了不同分词器之间的速度
  2. 针对各种提示/生成的标记长度,提供了一个我们关心的单一数字(而不是同时计算 TTFT 和生成字符/秒)
  3. 给出了生成速度的一个最低下限,因为延迟包括 TTFT。对于大多数分词器和文本,一个token是 3-4 个字符,所以将字符/秒除以 4 可以得到token/秒的下限。

alt text 图6:图中右上角代表更佳的表现。opus、sonnet、gpt-4o 和 haiku 处于帕累托边界上。

当我们将 gpt-4-turbo 的推测编辑速度提升计算在内时: alt text 图7:使用推测编辑情况下,gpt-4-turbo 的性能与 gpt-4o 相似。gpt-4o 目前还无法进行推测编辑,否则 gpt-4o-spec 将成为速度领先者。

Diff 模型

为什么我们让模型重写整个文件,而不是建议使用 diff 呢?

我们发现语言模型在处理 diff 格式的编辑时遇到了一些困难,可能的原因如下:

  • 在较少的 Token 中思考 - 当输出 Token 增加时,模型有更多的前向传递机会来确定正确的解决方案。diff 迫使模型用更少的标记进行思考。
  • Diff 格式超出了训练数据分布 - 在预训练和特别是后期训练中,模型可能见过更多的完整代码文件,而不是代码的 diff。
  • 输出行号 - 如果一个分词器将数字序列(如 123)视为一个 Token,则模型必须在单个(通常是第一个)输出 Token 中决定 diff 的正确行号。此外,众所周知,模型在数行号方面表现不佳。

Aider 的 diff 格式启发,我们消除了行号问题。我们采用一种非标准的 diff 格式,模型提出的 diff 块作为搜索/替换块:

@@ ... @@
 function binarySearch(arr, x) {
-    let low = 0, high = arr.length - 1;
-    while (low <= high) {
-        let mid = Math.floor((low + high) / 2);
-        if (arr[mid] === x) {
-            return mid;
-        }
-        low += 1;
-    }
-    return -1;
+    let low = 0, high = arr.length - 1;
+    while (low <= high) {
+        let mid = Math.floor((low + high) / 2);
+        if (arr[mid] === x) {
+            return mid;
+        } else if (arr[mid] < x) {
+            low = mid + 1;
+        } else {
+            high = mid - 1;
+        }
+    }
+    return -1;
 }

我们替换所有以 - 或 + 或 . 开头的行。diff 中有冗余的 - 和 + 符号,这使得 diff 解析系统对模型的小错误具有鲁棒性。

除了 Claude Opus 之外,大多数模型都无法输出准确的 diff。

alt text

图8:claude-3-opus-diff 是使用改进版 Aider diff 格式提示的 Claude Opus。它在速度和准确性上都优于 gpt-4-turbo-spec。gpt-4o-diff 虽然达到了中位速度 2476 字符/秒,但由于性能远差于 claude-3-haiku(平均评分4.18),因此未在图中显示。

训练

claude-3-opus-diff 应用模型虽然比 gpt-4-turbo-spec 更快更准确,但仍然太慢。

由于无法在任何 Anthropic 的模型中构建推测性编辑,所以我们需要训练和部署一个高性能的自定义模型。

合成数据

我们从少量的"快速应用" prompts 和大量的 cmd-k prompts 开始。

alt text 图9:一个 cmd-k prompt 及其对应的生成结果。Cmd-k 允许你指导模型在选定区域进行编辑。

cmd-k prompt 与我们快速应用所需的数据形式类似。它包含一个编辑指令,以及当前文件中选中的代码。

对于每个编辑指令,我们让 GPT-4 根据当前文件生成响应,然后让语言模型"apply"这个变更。我们使用一个小型的"真实"应用输入数据集来生成更多高质量的应用数据点。最后,我们以 80/20 的比例将这些数据集合并,得到我们的微调数据。

alt text

图10:我们用于生成快速应用微调数据集的数据管道。完全合成的数据质量较低。例如,在上面的示例中,选择范围(我们舍弃的部分)对于准确编辑来说至关重要。

模型训练

我们训练了 Deepseek Coder Instruct 和 Llama 3 模型系列。为了改进我们的微调数据集,我们:

  1. 对小文件进行降采样,因为它们在训练集中的比例过高( 小于100行代码 )。
  2. 对每个文件名的训练样本数量进行降采样。
  3. 对那些导致无操作的数据点进行降采样。

alt text 图11:我们使用每字节比特数来标准化不同分词器之间的损失值。Deepseek 模型按文件名采用了更激进的过滤策略。

我们发现我们最好的模型(llama-3-70b-ft)几乎能够媲美claude-3-opus-diff,并且性能超过了gpt-4-turbo和gpt-4o。

alt text 图12:Llama-3-70b-ft在性能上超越了gpt-4-turbo-spec 在评估中,所有三个微调模型都超过了gpt-4-turbo的表现,但我们能明显感觉到微调后的deepseek-33b和llama-3-70b之间的差异。Llama-3-70b的表现比其他微调模型和gpt-4-turbo都要好。其他微调模型的实用性不够理想,通常还不如GPT-4好用。

推测性编辑

我们最大的成功来自于我们自定义的推测性解码算法,称为"推测性编辑"。它能够实现完整文件重写的效果,同时速度最高可提升9倍。

在进行代码编辑时,我们在任何时刻都对草稿标记有很强的先验认知,因此我们可以使用确定性算法而不是草稿模型来推测未来的标记。

我们与Fireworks合作部署了我们的快速应用模型,该模型具有强大的推测性编辑支持。他们拥有出色的推理引擎,并为我们的自定义推测逻辑构建了API支持。

这使得推测性编辑在Llama-3上带来的性能提升比GPT-4更大,相比最快的模型能获得4-5倍的速度提升。

alt text

图13:推测性编辑为我们的微调模型带来了巨大的速度提升。

未来方向 长上下文训练 - 我们正在进行长上下文训练,目标是能够重写最多2500行的文件。简单的RoPE位置ID线性缩放效果并不理想 -- 目前社区对Llama 3 70b的长上下文微调也存在同样的问题。

知识蒸馏 - 我们也希望将当前模型的"快速应用"能力蒸馏到更小的模型中,特别是llama-3-8b。对于较大的文件来说,更小模型带来的低延迟将变得更加重要。

更高的准确性 - 使用新部署模型的数据进行某种形式的在策略强化学习,应该能带来额外的性能提升。

除了在聊天中的实用性外,快速应用还是更复杂代码生成系统的关键构建模块。随着模型在推理/规划方面能力的提升,低延迟应用带来的好处将越来越多!

如果你还没有试过,一定要在Cursor中体验这个功能!这是展现产品精致度和深度的一个小例子。

这篇博文是我们在Anysphere进行的应用研究的一个典型例子。我们构建像推测性编辑这样的特定应用推理加速技术,训练和评估特定任务的模型,并将这些功能打包成实用特性提供给用户。

我们正在招聘研究工程师和软件工程师!你可以在这里了解更多关于Anysphere的信息。

附加挑战

我们已经简要介绍了推测性编辑的工作原理,但还没有详细解释完整的算法。

作为一个练习,尝试用OpenAI的API来实现推测性编辑。(提示:对于大多数OpenAI账户来说,这只能用davinci-002或babbage-002模型来实现,所以你的解决方案应该只使用这些模型)

如果你想要更大的挑战,我们建议你在vllm中实现推测性编辑。对于最困难的版本,可以尝试在TensorRT-LLM中实现。

我很期待看到你的解决方案!欢迎在Twitter上分享给我。