我正在参加「掘金·启航计划」
导语
本文介绍了如何从存储库中获取对大型代码语言模型进行代码生成有帮助的Prompt的一些探索,整体过程比较朴素,就是使用一些规则或者设计网络选取存储库中的内容,但效果惊艳。作者的结论发现,即使是不带逻辑顺序的混合上下文也能大幅度提升最终的表现。
- 会议:ICML 2023
- 链接:arxiv.org/pdf/2206.12…
摘要
随着大型语言模型(LLMs)在代码中的成功及其作为代码助手的使用(例如在GitHub Copilot中使用的Codex),引入领域特定知识的提示设计过程的技术变得重要。本文提出了一个称为Repo-Level Prompt Generator的框架,它学习使用提示提案(Prompt Proposal)生成特定示例的提示。提示提案从整个代码库中获取上下文,从而包含代码库的结构和其他相关文件的上下文(例如导入文件和父类文件)。该技术不需要访问LLM的权重,使其适用于只能访问LLM黑盒的情况。实验表明,所提出的提示提案构建的oracle情形下相对于Codex提供了36%的性能提升。此外,本文还展示了当训练模型来预测提示提案时,可以比Codex和其他基线获得显着的性能提升。
1 简介
尽管Code LLMs越来越受欢迎,但没有工作系统地解决与源代码相关的不同方面的提示生成。其中一个问题是,当涉及到代码时,提示中要放置的相关上下文不仅可以来自当前文件,还可以来自外部,例如导入文件、父类、文件夹内的文件和API文档。由于LLMs的提示具有有限的上下文长度,因此领域特定的理解指导选择相关上下文变得越来越重要。本文通过提出Repo-Level Prompt Generator(RLPG)来解决这个问题,它在生成提示时同时考虑代码库的结构和代码库中文件中的相关上下文。在RLPG中,从代码库中选择来自哪里和什么来自提示由一组提示提案指定。例如,Prompt Proposal可以是从第一个导入文件中获取所有标识符。这些Prompt Proposal允许提示工程师在提示设计过程中引入他们的领域专业知识。本文的Prompt Proposal是离散的,但并没有为每个示例固定一个特定的提示提案,而是根据示例预测最佳Prompt Proposal。
本文通过提出一种名为Prompt Proposal Classifier(PPC)的神经网络来实现预测最佳Prompt Proposal。因此,RLPG允许引入领域专业知识,同时通过学习的神经网络实现自动的例特定提示生成。需要注意的是,虽然在NLP中有自动提示生成的一些技术,但这些技术需要更新LLM的一些或所有权重。然而,最强大的LLMs不公开提供(例如,OpenAI仅通过API提供Codex的生成输出,并没有提供模型权重和数据的访问权限),这使得这些技术在这种情况下不太有用。RLPG通过假设仅具有LLM的黑盒访问权限来生成提示,解决了这个限制。即使在可以访问模型权重的情况下,RLPG也提供了一种适应代码库级上下文而无需反复微调模型的方法。这在适应包含专有或小众软件的代码库时可能特别有用,这些软件LLM在训练过程中的机会有限。
本文重点关注于IDE中的单行代码自动完成任务(如图1),这反映了用户正在编辑现有文件的场景,这意味着后面可能有代码行。图1提供了方法示例。PPC以当前文件中空缺位置(光标位置)、当前文件所属的代码库和一组存储库级Prompt Proposal作为输入,并预测Prompt Proposal。示例中,预测的Prompt Proposal对应于从MaximizingGibbsSampler.java中获取的方法名称和主体(mg.before空缺位置表示可能调用导入文件中的方法)。Prompt Composer使用来自预测Prompt Proposal的上下文与默认的Codex上下文(即当前文件中空缺位置之前的代码)结合起来。生成的提示包括方法名称InitializeToAssignment(来自提示提案上下文)和方法CurrentAssignments(来自默认Codex上下文),从而成功预测目标空白(上面的棕色框)。
本文主要贡献如下:
- 提出了一个名为Repo-Level Prompt Generator(RLPG)的框架,该框架学习根据示例生成提示,而无需访问LLM的权重。
- RLPG允许同时使用代码库的结构和来自代码库中所有文件的相关上下文,从而提供了一种将领域知识纳入提示生成过程的机制。
- 在单行代码自动完成任务中,实验结果惊人的好。
2 Repo-Level Prompt Generator (RLPG)
2.1 Repo-Level Prompt Proposals
作者认为Prompt Proposal是一个函数,它将目标hole的位置和hole所属的存储库作为输入,并返回Prompt Proposal上下文(由Prompt Proposal的上下文组成的字符串)。为此,只需要确定两个要素:
- 从哪里取Prompt(Where?)
- 取什么做Prompt(What?)
对于第一个问题,作者共列举了十个可能的地方:
- 当前文件;
- 父类文件;
- Import的文件;
- Sibling文件(即同一个目录下的其他文件);
- Similar Name文件(即通过一些分词后包含相同词的文件);
- 子类文件;
- Import文件的父类文件;
- Import文件的Sibling文件;
- Impact文件的Similar Name文件;
- Import文件的子类文件;
对于第二个问题,作者也列举了七个取的内容:
- Post Line,即后面的行;
- Identifiers,即所有的变量名;
- Type Identifiers,即所有的类型名;
- Field Declarations,域声明;
- String Literals,所有的字符串值;
- Method Names,所有方法的名字;
- Method Names and Bodies,方法以及其函数体实现;
结合上面的两个维度,作者一共设计了63类Prompt Proposal;
2.2 Prompt Proposal Classifier (PPC)
PPC实质上就是一个多标签的二分类器,用来决定哪些Prompt Proposal对最终成功填出target hole有帮助。整体的训练目标就是这些每个hole预测的loss的加和。
同时作者给出了两种模型的变体:
- RLPG-H:定义hole window表示hole附近的上下文,使用一个预训练模型F来编码并取出[CLS]作为这个window的表示,之后通过两层MLP得到最终的分类;
- RLPG-R:直观地说,如果hole window包含的变量(例如标识符)与Prompt Proposal上下文中的变量相似,那么hole可能会出现在Prompt Proposal上下文的某个地方。为此,作者使用Multi-head Attention模块来建模这一相似度,将hole window的向量表示作为Query,Prompt Proposal的上下文表示向量作为Key,(Value和Key一致)
2.3 Prompt Composer
Prompt Composer将来自PPC所选定的提示提案的上下文与Codex默认使用的上下文结合起来。为了适应固定长度的提示,该研究采用了动态上下文分配策略,该策略允许将Prompt Proposal上下文剩余的部分分配给默认Codex上下文。对于所有Prompt Proposal,作者将总上下文长度的一半分配给Prompt Proposal上下文,其余的分配给默认Codex上下文。对于后续行,作者还分配了总上下文长度的四分之一和三分之三给Prompt Proposal上下文。如果Prompt Proposal上下文或默认Codex上下文大于其分配的上下文长度,将对其进行截断。
3 实验与结果
3.1 数据集构建
数据集来自合法的Github存储库,统计指标如下:
3.2 实验细节
作者选取的是code-davinci-001引擎的CodeX模型,预训练模型F采用CodeBERT或GraphCodeBERT。不过这些模型的最大输入长度远小于CodeX,所以PPC中获得Prompt Proposal上下文表示时要截断。RLPG的计算复杂度和可扩展性也很好,作者使用了OpenAI的Codex API来收集用于训练PPC的基准数据。在训练过程中,RLPG-R变体的计算复杂度明显低于微调Codex。在推理过程中,使用缓存信息可以避免额外的计算复杂度。此外,RLPG的Prompt Proposal是基于通用的概念,可以适用于其他编程语言,并且框架提供了灵活性来整合新的Prompt Proposal。作者认为,与在潜在空间中执行提示工程的技术(Soft-prompting)相比,RLPG可以更方便地表达人类的意图,并且不需要访问LLM的权重。
作者一共选取了9种方法进行比较:
- CodeX,即默认的模型;
- Oracle,即使用Ground-truth Prompt Proposal的模型;
- Fixed Prompt Proposal,使用固定的某一种(最成功的)Prompt Proposal的模型;
- RLPG-H and RLPG-R,使用PPC预测的Prompt Proposal模型;
- RLPG-BM25,使用BM25作为Proposal Proposal选择依据的模型,不使用PPC;
- File-level BM25,使用BM25作为Proposal Proposal选择依据的模型,搜索文档的完整上下文组成;
- Random,随机选择一段Prompt Proposal;
- Random NN,选择经过PLM编码后与hole window在向量空间最近的那个Prompt Proposal;
- Identifier Usage,取最近的标识符,并从存储库中的任何地方取该标识符的使用窗口(所述使用行上方和下方的两行,包括所述使用行)。
对于评估指标,使用成功率success rate (SR)。
3.3 结果
实验结果如下图,作者主要关注于这样两个问题:
- [RQ1]-生成由不同于默认CodeX上下文的代码上下文组成的提示是否有用?如果是,什么上下文是有用的?
- [RQ2]-对于每个target hole,是否有自动选择提示符的方法?如果是,该系统相对于CodeX的表现如何?
对于问题1,作者发现将Prompt Proposal上下文(存储库中其他文件中的上下文)与默认Codex上下文相结合可以显著提高性能。表2显示了oracle的性能,可以看到,在所有数据分割中,Prompt Proposal相对于Codex的性能有显着的提升(测试分割最高可达36%)。这些结果可能会令人惊讶,因为Codex并没有针对除默认Codex上下文以外的上下文组成的提示进行训练。更令人惊讶的是,在大多数情况下,提示由不带逻辑顺序的混合上下文组成,甚至可能看起来不像是一个有意义的代码块(例如,包含字符串文字列表的同级文件之后是默认Codex上下文或后置后缀行而不是之后)。 这些结果可能表明,只要相关上下文(在我们的情况下,以Prompt Proposal的形式呈现的存储库级别知识)以任何形式出现在提示中,就可以非常有效。
对于问题2,表3呈现了测试数据的成功率以及相对提升的百分比。可以看出,所有的RLPG变体以及固定的提示提案都显著提高了Codex的性能。随机基线要么更糟糕,要么与Codex相当。标识符使用是一个很好的基线,但仍然比固定的提示提案或RLPG表现差。作者发现,文件级别的BM25虽然比Codex好,但表现不如使用某些语义上有意义的上下文概念的方法(例如方法体或字段声明)。然而,当将BM25与提示提案上下文结合使用(RLPG-BM25)时,性能有了很大的提升。所有基于RLPG的方法都比固定的提示提案更好,显示出使用RLPG生成特定于示例的提示的价值。然而,学习型RLPG的两个变体,即RLPG-H和RLPG-R,都优于RLPGBM25,凸显了学习PPC的重要性。请注意,即使我们将标识符使用视为一个单独的Baseline,仍可以将其视为提示提案之一,从而进一步提高RLPG的性能。
作者还探索了Top-K的成功率和不同的Prompt Proposal来源对于最终结果的影响,如图2所示。
最后,作者探索了使用编辑距离作为评估指标和使用code-cushman-001作为基准模型的实验表现,如下表所示。可以看到,所提出模型对于另一个模型也同样适用,展现了框架的泛化性和可拓展性。
4 相关工作
略
5 讨论
Code LLM系统应该谨慎部署,它们可能存在生成不安全代码或包含敏感信息。在本文提交之后,LLMs的输入上下文长度已经增加,例如GPT-4支持32k个token。通过扩展上下文长度,可以考虑在提示中包含整个存储库的内容。然而,在实践中,软件存储库通常更长。在本文的数据集(去重后),作者观察到70.22%的存储库包含超过32k个token。值得注意的是,除了当前存储库之外,还有其他相关上下文来源,例如API文档、教程或相关存储库,可以帮助代码自动完成。RLPG通过新的提示提案提供了一种整合这些额外上下文来源的机制。因此,无论代码生成模型的上下文长度如何,RLPG提供了一个有价值的方法来确定哪些上下文是相关的,并应该包含在提示中。随着GPT-4上下文长度的增加,作者预计会减少提示提案上下文的截断,从而可能导致使用RLPG获得更大的改进。
总的来说,本文提出了RLPG框架,它可以学习自动生成特定于示例的提示,而无需访问LLM的权重。RLPG利用存储库的结构以及存储库中其他文件的上下文,使用一组易于理解的提示提案。在本工作中,仅从一个Prompt Proposal中提取上下文。未来的研究方向包括学习模型自动从多个提示提案中组合提示。其他有趣的方向包括将用户的反馈纳入RLPG中,以及将RLPG扩展到多行代码自动完成。