大型语言模型 (LLM) 正在改变软件开发。从自动补全整个函数到回答复杂的代码库级别问题,这些人工智能助手正迅速变得不可或缺。
但它们有一个致命弱点:** 上下文长度**。

当你让 LLM 处理一个大型项目时,你通常需要向它输入数万行代码。这种长上下文会引发一系列问题:

  • 中间信息丢失: 当重要 token 被淹没在海量文本之中时,模型可能难以捕捉到相关片段。
  • 生成缓慢: 注意力机制的计算复杂度随输入长度呈二次方增长,因此长输入会导致延迟飙升。
  • 成本高昂: 商业 API 按 token 数量计费,长上下文会迅速推高账单。

对于源代码来说,这个问题尤为严重。与普通文本不同,代码存在复杂的相互依赖性。一个文件中的函数可能对分散在其他几十个文件中的逻辑至关重要。为了节省成本而随机裁剪代码,可能会破坏可编译的结构并丢失关键约束。

现有的解决方案,如*检索增强生成 *(Retrieval-Augmented Generation, RAG) ,尝试只获取相关的片段。但 RAG 通常依赖于基于嵌入的文本相似度,这在查找名称相似的函数时很有效,却无法捕捉那些细微而隐蔽的依赖关系。

那么,我们是否能智能地压缩代码——精确保留关键部分,舍弃其余内容?

最近的一篇研究论文——《LongCodeZip: 为代码语言模型压缩长上下文》——提出了这样一个框架。它无需训练、与具体模型无关,且专为代码设计。利用源代码的结构和语义,它可实现高达 5.6× 压缩,而不降低 (甚至有时提升) 性能。

让我们看看它是如何工作的。


相似度的困境: 为什么 RAG 处理代码时常常失效

检索增强生成是处理长上下文的常见方法。它通过模型嵌入和比较片段,然后检索那些与目标查询或待补全代码“最近”的片段。

但在代码中,“相似度”有多种形式:

  • 词法层面: 共享变量名、关键字。
  • 结构层面: 匹配的函数签名。
  • 语义 / 依赖层面: 只有理解程序流程后才能发现的关联。

RAG 在捕捉词法和浅层结构匹配方面表现良好,但经常错过更深层的、基于依赖的联系——尤其是当这些联系是隐式时。

一张图表,比较了基于相似度的相关性和基于依赖的相关性。左侧,一个代码补全任务通过找到一个名称相似的函数而受益。右侧,另一个任务需要找到一个 ‘Config’ 类,该类与待补全的函数没有文本相似性,但对其实现至关重要。

图 1: RAG 能找到词法上相似的代码 (左) ,但未能识别关键且不显眼的依赖关系 (右) ,比如设置优化器所需的配置类。

例如,如果要补全 get_email_by_id,RAG 会很容易地找到 get_account_by_id——一个几乎完美的词法匹配。这就是相似度相关性

但在另一任务中——补全 train_model——必需的 Config 类位于别处,并且没有任何相同的标识符。如果不了解依赖关系,RAG 会错过它,并将无关代码排在更高优先级。结果就是补全不完整或不正确。

我们需要一种度量方式能够理解功能和依赖关系,而不仅仅是表面相似度。


LongCodeZip 简介: 一个从粗到细的压缩框架

LongCodeZip 通过衡量信息增益而非简单相似度来解决问题。它问的是: 哪些代码片段能最大程度降低模型的不确定性?

困惑度作为相关性信号

困惑度 (Perplexity) 衡量模型对一个序列的“惊讶”程度。如果添加一个上下文片段让模型对指令的困惑度降低——即更不感到“惊讶”——那么这个片段很可能很重要。

作者定义了近似互信息 (AMI) :

\[ AMI(c, q) = PPL(q) - PPL(q \mid c) \]

其中:

  • \(PPL(q)\): 指令本身的困惑度。
  • \(PPL(q|c)\): 给定上下文 \(c\) 时的困惑度。

更高的 AMI 意味着上下文 \(c\) 让模型更有信心——既能捕捉相似度相关性,也能捕捉依赖相关性。


阶段一: 粗粒度压缩 (选择相关函数)

第一阶段会过滤掉不相关的整个函数或类

  1. 函数级分块:
    在函数/类的边界处拆分代码,保持语法有效性,同时保留自包含的逻辑块。

  2. 指令感知排序:
    针对任务指令,使用 AMI 对每个代码块打分。

  3. 预算约束选择:
    贪婪地选择排名最高的代码块,直到填满粗略预算 (\(B_{\mathrm{coarse}}\)) 。未选的块用占位符替代,以保留整体文件结构。


阶段二: 细粒度压缩 (函数内部剪枝)

在选出最相关的函数后,LongCodeZip 会进一步裁剪它们:

LongCodeZip 框架概览,展示了两个主要阶段。第一阶段,粗粒度压缩,从代码库中选择相关函数。第二阶段,细粒度压缩,对选定函数的内容进行剪枝以适应 token 预算。

图 2: 第一阶段选择相关函数;第二阶段在函数内部进行代码块剪枝。

  1. 基于困惑度的代码块切分:
    将函数划分为语义块。在一个连贯块中,困惑度保持稳定;困惑度突增通常意味着新的逻辑单元。边界设在困惑度相对相邻区域急剧上升处。

  2. 自适应预算分配:
    并非所有函数重要性相同。

    • 极短函数 (<5 行) 完整保留。
    • 较长函数按基线保留率,结合归一化 AMI 和重要度参数 \(\beta\) 进行调整:
      \[ R_{\mathrm{biased}, i} = R_{\mathrm{base}} \cdot \left(1 + \beta \cdot (2 \times AMI_{\text{norm}, i} - 1) \right) \]
    • 对比率重新缩放,使总 token 数匹配最终预算。
  3. 动态代码块选择 (背包优化) :
    将每个代码块作为一个物品:

    • 价值 = AMI 分数
    • 重量 = token 长度
      在预算内选择,以最大化总价值,保留最重要的代码。

实验评估

研究团队在以下任务上测试了 LongCodeZip:

一张表格,总结了评估中使用的数据集,包括长代码补全、长模块摘要和 RepoQA,并显示了每个数据集的样本数量和平均上下文长度。

表 1: 基准任务的平均上下文长度约为 1 万个 token。

任务:

  • 长代码补全
  • 长模块摘要
  • RepoQA (代码库级问答)

基准方法: RAG (Sliding/Function) 、文本压缩器 (LLMLingua、LongLLMLingua) 、代码压缩器 (DietCode、SlimCode) 以及上/下界基准 (不压缩、无上下文) 。


RQ1: 压缩与性能

一张表格,展示了长代码补全任务的结果。在所有压缩方法中,LongCodeZip 取得了最高的编辑相似度 (ES) 和精确匹配 (EM) 分数,通常在更高的压缩比下,其性能与“不压缩”基准相当甚至更好。

表 2: LongCodeZip 一贯取得最高 ES/EM 分数,有时甚至超过“不压缩”基准——使用的 token 远少于其。

亮点:

  • 长代码补全任务中,它有时超越不压缩基准。
    例如: Seed-Coder-8B 的 ES 仅下降 0.93 点,但只用了约 18% 的 token (压缩 5.6×) 。
  • RepoQA 上,移除无关上下文简化了检索,在 GPT-4o 和 Claude-3.7-Sonnet 上的表现有时甚至超越不压缩基准。

一张表格,展示了 LongCodeZip 在 GPT-4o 和 Claude-3.7-Sonnet 等闭源模型上的性能。在代码补全任务上,它的性能几乎与未压缩的基准相当,而在 RepoQA 任务上则超越了基准,同时使用的 token 数量显著减少。

表 3: 在顶级闭源模型上同样表现优异。


RQ2: 消融实验——关键因素

一张来自消融研究的表格,显示了移除 LongCodeZip 关键组件后性能的下降幅度。用基于相似度或随机排序替代基于困惑度的排序会导致最大的性能下降。

表 4: 粗粒度困惑度排序是最核心的组件。

将 AMI 排序替换为相似度排序会导致 ES 下降约 8 点。移除细粒度优化 (自适应预算、背包算法) 则进一步损害性能,证明每个模块都不可或缺。


RQ3 & RQ4: 泛化与效率

  • 跨模型泛化:
    一个小型 0.5B 模型可以负责压缩,再将优化后的上下文传给更大的生成模型,效果几乎一致。
  • 效率:
    压缩仅有约 2.6 秒的开销,但带来巨大收益:
    • 生成时间由 15.7 秒 → 6.6 秒
    • 输入 token 减少 77% ≈ 成本降低约 77%

一条折线图,展示了性能 (ES 分数) 与剩余上下文百分比的关系。LongCodeZip 的性能曲线始终高于所有其他方法,尤其是在极高压缩比下优势明显。

图 3: 即便在极端压缩率下也能保持高 ES 分数。


案例研究: 补全 execute_blind

一个案例研究图,展示了待补全代码、在上下文中找到的相关函数、该函数各行的困惑度分布,以及压缩后成功实现正确补全的最终上下文。

图 4: 困惑度峰值界定语义块;背包算法保留最关键的块。

evaluate_blind 函数被识别为相关函数。困惑度分析将其切分为多个块;AMI 排序优先保留初始化 actioncall_name 的块。保留这些部分确保模型正确补全 execute_blind


结论: 更智能的代码长上下文处理

LongCodeZip 在用 LLM 处理大型代码库方面实现了重大突破。主要贡献包括:

  • 分层压缩: 函数过滤加代码块剪枝,在压缩率与语义完整性间取得平衡。
  • 困惑度驱动相关性: AMI 捕捉到基于相似度方法遗漏的深层依赖。
  • 实用且即插即用: 无需训练,模型无关,易于集成。

在不牺牲性能的前提下缩减上下文、减少生成时间、降低 API 成本,LongCodeZip 使 LLM 在真实工程中的应用更可行——尤其是在“整个代码库”无法放入上下文时。

有时候,少即是多。