23年11月来自上海交大和蚂蚁金服的论文“A Survey on Language Models for Code”。
本文系统地回顾大语言模型在代码处理方面的最新进展,涵盖了 50 多个模型、30 多个评估任务、150 多个数据集和 550 篇相关工作。代码处理模型分为 GPT 系列代表的通用语言模型和专门针对代码进行预训练的专用模型,这些模型通常具有定制的目标。讨论这些模型之间的关系和区别,并强调代码建模从统计模型、RNN 到预训练的 Transformers 和 LLM 的历史转变,这与 NLP 所走的路线完全相同。还讨论 AST、CFG 和单元测试等特定于代码的功能,以及它们在训练代码大语言模型中的应用,并确定该领域的主要挑战和潜在的未来方向。
近年来,随着预训练 Transformer(Vaswani ,2017)的出现,语言建模取得了显著进展,例如 BERT(Devlin,2019)和 GPT(Radford,2018)。随着大语言模型 (LLM) 扩展到数千亿个参数并开始显示出通用人工智能的早期迹象(Brown,2020;Chowdhery,2022;OpenAI,2023),它们的应用也超越了文本处理。由 Codex(Chen,2021)开创的 LLM 在代码处理方面取得了令人瞩目的成果,催生 GitHub Copilot 等商业产品和 StarCoder(Li,2023)和 Code LLaMA(Rozière,2023)等开源数十亿代码模型。
代码的预训练语言模型在本文分类如下:
在过去十年中,软件工程界提出了各种评估任务来评估代码模型。CodeXGLUE(Lu,2021)将大多数此类任务整合到一个基准中,涵盖代码理解任务(例如克隆检测、缺陷检测)和序列到序列生成任务(例如代码修复、代码翻译、程序合成和代码摘要)。然而,在 Chen(2021)推出 HumanEval 和 Codex 之后,文本到代码合成在 NLP 社区中成为关注的焦点,并从此成为评估 LLM 的标准任务(如图所示)。
如图所示是本文的架构:黑色:非神经方法。红色:非 Transformer 神经方法(如 LSTM)。橙色:基于 Transformer 编码器的方法(如 BERT)。紫色:基于 Transformer 的 seq2seq 方法(如 T5)。蓝色:基于 Transformer 解码器的方法(如 GPT)。灰色:其他基于 Transformer 的方法(如 UniLM)。对于代码合成,在此仅列出几个代表性基准。这里的“方法”不同于“目标”。例如,(Pearce2022)检查他们使用的生成代码是否是非神经的。
关于本文关于代码处理的评估任务部分架构,如下图所示:
按照软件工程的惯例,根据代码的输入/输出方式对代码的评估任务进行分类,并将这些任务分为五类:文本-到-代码、代码-到-代码、代码-到-文本、代码-到-模式和文本-到-文本。这种分类法与 NLP 中的理解-生成二分法是交织在一起的,因为每个类别可能包含理解和生成任务。
理解任务在形式上与自然语言理解任务相似(Wang et al., 2018; Wang et al., 2019),同样通过准确度、F1和平均倒数排名(MRR)等指标进行评估,而标识符预测等短生成任务也通过精确匹配的准确度进行评估。代码到文本任务使用文本生成的常用指标进行评估,例如BLEU(Papineni et al., 2002)。
另一方面,涉及代码生成的任务的评估更加复杂。大多数早期方法评估了句法正确性,即可成功解析的生成百分比。(Chen2018)反对这种指标,并建议改用参考匹配,即与参考完全相同的生成百分比。(Ren2020) 提出了 CodeBLUE,这是 BLEU 的一种变体,它通过评估抽象语法树 (AST) 和数据流的重叠来考虑代码语法和语义。
然而,随着代码生成模型多年来变得越来越强大,这些基于内容重叠的指标,发现是不够的 (Rozière,2020;Hendrycks,2021;Austin,2021),因为功能等效的代码片段在词汇形式上可能有很大差异。因此,研究人员将注意力转向了功能正确性。此类指标的一个流行示例是 pass@k,由 (Kulal 2019) 提出并由 (Chen 2021) 改进,它是模型使用 k 个生成样本中的任何一个通过程序所有单元测试的机会的无偏估计量。该指标可以推广到 passn@k (Li et al.,2022),它将模型提交的数量限制为 n,但允许通过来自 k 个样本的输入中给出的单元测试进行过滤。
随着代码模型多年来的进步,研究人员逐渐将注意力转向了程序合成这一实际任务。CONCODE(Iyer,2018)是该领域早期的数据集之一,其中包含 100K 多 Java 方法,并被纳入 CodeXGLUE 基准测试(Lu,2021)的子网。自 2021 年以来,社区已经见证了这项任务的大量数据集。其中大多数,包括 APPS(Hendrycks,2021)、HumanEval(Chen,2021)和 MBPP(Austin,2021),都专注于 Python,但最近的研究也将 HumanEval 扩展到其他编程语言(Cassano,2023;Zheng,2023;Muennighoff,2023)。 DS-1000 是一个更加现实的 Python 数据集,它专注于 NumPy 和 SciPy 等数据科学库,同时一些数学推理基准也已转换为编程任务,包括 MathQA-Python(Amini,2019;Austin,2021)和 GSM8K-Python(Cobbe,2021;Chowdhery,2022;Wang,2023)。
最近,位置插值技术 (Chen,2023;Rozière,2023;Peng,2023) 已将 LLM 的上下文窗口扩展到数十万个tokens,从而可以在整个存储库中对代码建模的评估进行上下文化。一些工作 (Shrivastava,2023;Ding,2022;Zhang,2023;Shrivastava,2023) 研究了利用存储库级上下文的代码完成, (Liu2023) 提出 RepoBench 来评估此类系统。最近,(Bairi 2023) 研究了存储库级 API 迁移和时间编辑等更具挑战性的任务,(Jimenez2023) 引入了相应的基准 SWE-bench。
由于语言模型扩展到数千亿个参数(Brown,2020;Chowdhery,2022),其中许多模型已经展示了非同寻常的编码能力,即使它们不是专门为代码设计或训练的。在 Codex 的开创下,研究人员还发现,对代码进行持续的预训练可以显著提高语言模型在代码上的性能。
现成的语言模型
大语言模型通常按照缩放定律对数万亿个tokens进行预训练(Kaplan,2020;Hoffmann,2022),如此大量的文本数据通常是一个多样化的复合体,其中包含不可忽略的代码部分。例如,Pile(Gao,2021)从其 800GB 原始数据集中包含从 GitHub 爬取的 95GB 代码,而多语言预训练数据集 ROOTS(Laurençon,2022)在其 1.6TB 的数据集中也包含涵盖 13 种编程语言的 163GB 代码。作为两个最大的开源预训练数据集,它们支持了许多具有编码能力的语言模型。例如,据 (Chen2021)报告,GPT-J(Wang & Komatsuzaki,2021)在 HumanEval 上表现出不平凡的性能,而 (Scao 2022 )报告了 GPT-NeoX(Black,2022)和 BLOOM 的类似结果。 LLaMA (Touvron ,2023 ) 的预训练数据集包括来自 GitHub 的 328GB 代码,在 HumanEval 上实现了 23.7 pass@1 性能,其后继者 LLaMA 2 (Touvron ,2023 ) 获得了更高的分数即 29.9。另一方面,闭源模型的表现通常更好。 LaMDA(Thoppilan,2022)和 PaLM(Chowdhery,2022)的预训练数据集分别包含 12.5% 和 5% 的代码,在 HumanEval 上实现了 14.0 和 26.2 pass@1 性能,而 GPT-4(OpenAI,2023 年)创下了惊人的 67.0 记录,在(Bubeck 2023) 报告的早期版本为 82,直到最近,这一记录仍然高于任何针对代码进行预训练或指令微调的专门模型。
最近,总体趋势是使用较大的数据集训练较小的模型,遵循修订后的缩放定律(Hoffmann,2022)。例如,百川 2 号(Yang,2023)是一个在 2.6T tokens上训练的 13B 模型,而 Qwen(Bai,2023)是一个在 3T tokens上训练的 14B 模型。它们在 HumanEval 上分别实现了 17.1 和 32.3 pass@1。然而,(Li 2023)证明,小至 1.3B 的模型可以获得与更大模型相当的编码能力,同时在一般文本处理方面保持合理的性能,甚至表现出一些涌现能力(Wei ,2022),例如思维链推理(Wei ,2022)。他们的模型 Phi-1.5 使用 ChatGPT 生成的 210 亿个教科书数据,还有 Stack Overflow 和 Refined Web 的 1000 亿个过滤后的网页数据,进行训练(Penedo,2023),并在 HumanEval 上获得了 41.4 pass@1 的性能。这些模型的具体性能如表所示。
对代码进行额外预训练的语言模型
除了开创性的基准 HumanEval 之外,(Chen 2021) 还通过 Codex 开启了代码 LLM 的时代,Codex 是 GPT-3 检查点,在 1000 亿个额外代码tokens上进行了预训练,是最早的数十亿个代码模型之一。继他们的工作之后,其他研究人员也在代码上专门设计了 LLM,并进行了额外的预训练。(Chowdhery 2022) 在 78 亿个额外代码tokens上训练 PaLM 获得 PaLM-Coder,在 HumanEval 和 MBPP 上创造了最先进水平,后来才被其后继者 PaLM 2-S* 打破,PaLM 2-S* 是 PaLM 2 的最小版本(Anil,2023),在未公开的代码上进行了进一步训练。同样,(Lewkowycz 2022) 在 385 亿个 arXiv 论文和数学内容的 token 上训练 PaLM,而 (Rozière 2023) 在超过 5000 亿个代码 token 上训练 LLaMA 2 (Touvron,2023) 以获得 Code LLaMA,其在 HumanEval 上的表现超过了除 GPT-4 之外的所有之前的 LM。(Liu2023) 进一步使用多任务微调 (MFT) 训练 Code LLaMA,以引入 CodeFuse-CodeLLaMA,在 HumanEval 上实现了 74.4 pass@1,甚至超过了 OpenAI (2023) 中发布的 GPT-4 的性能。
虽然几乎所有这些模型都是用 CLM 预训练的 Transformer 解码器,但在此过程中已经引入了一些架构修改。所有这些模型都使用了预规范,而 GPT-J 引入并行注意机制,后来被 PaLM、GPT-NeoX 和 Phi-1.5 所采用。PaLM 将 MQA 和 RoPE 引入 LLM,现在大多数语言模型都采用了 RoPE,包括 GPT-NeoX、两代 LLaMA、Qwen 和百川 2 的 7B 版本。然而,BLOOM 和百川 2 的 13B 版本使用 ALiBi 位置嵌入,而 LLaMA 2 和 Code LLaMA 采用 GQA 而不是 MHA 或 MQA。
随着 GPT 和 BERT 等预训练 Transformer 在自然语言处理领域取得显著成功,此类模型架构、学习范式和训练目标很快被软件工程的社区采用,用于生成专门用于代码理解和生成的模型。
如表是预训练模型的数据集统计分析:字节大小、文件数量和编程语言数量。在 CodeSearchNet 中,每个文件都是一个函数。
下表提供各种预训练模型的概述:
在自然语言处理中,使用指令前缀在多样化的任务集上训练模型(称为指令微调)已被证明可以释放跨任务泛化的能力(Ouyang,2022;Chung,2022;Iyer,2022)。起初,这些指令数据样本是手动编译或众包的(Wei,2022;Sanh,2022),但后来的研究发现 LLM 生成的指令就足够了(Wang,2023;Honovich,2023)。
继这些自然语言研究之后,来自代码社区的研究人员也将指令调整应用于他们的模型。(Wang2023) 使用 InstructGPT (Ouyang et al., 2022) 生成的 20K 指令数据对 CodeT5+ 进行微调,得到 InstructCodeT5+。WizardCoder (Luo et al., 2023) 遵循 WizardLM (Xu et al., 2023) 的方法,将 20K 代码 Alpaca (Taori et al., 2023) 样本演变为 78K 数据集,并使用它来微调 StarCoder。PanguCoder 2 (Shen et al., 2023) 也使用 WizardLM 的 EvolInstruct 从 20K 代码 Alpaca 生成 68K 指令样本,但也通过 Rank Responses 引入强化学习来对齐测试和教师反馈 (RRTF)。另一方面,OctoCoder (Muen-nighoff,2023) 采用了不同的路径,并使用 Git 提交历史作为指令数据来微调 StarCoder 和 CodeGeeX2。最近,CodeFuse (Di,2023) 也采用了多任务微调,并明确将多个下游任务引入其指令数据中。这些指令微调代码模型的性能也可以在下表中找到。
在 NLP 中,另一项与指令微调密切相关的技术是从人类反馈中强化学习 (RLHF),它在使 LLM 与人类价值观保持一致方面发挥了重要作用 (Ouyang,2022;Bai,2022)。强化学习的优点在于它可以将不可微的奖励信号纳入训练中,例如 BLEU(Bahdanau,2017)和人类偏好(Christiano,2017),但对齐 LLM 所需的人工反馈通常需要大量的注释工作。相比之下,将强化学习应用于代码模型具有天然优势,因为编译器可用于自动为语言模型生成的代码样本生成反馈。
CodeRL(Le,2022)就是这样一个模型,它为每个生成的程序定义了四个级别的奖励(即编译错误、运行时错误、单元测试失败、通过)以及由批评(critic)模型估计的细粒度 token 级奖励。然后使用 REINFORCE 算法(Williams,1992)训练参与者(actor)模型(CodeT5 的扩展)。类似地,CompCoder(Wang,2022)和 PPOCoder(Shojaee,2023)分别使用近端策略优化PPO(Schulman,2017)训练 CodeGPT 和 CodeT5,而 RLTF(Liu,2023)提出了基于编译器提供的错误信息和位置的细粒度反馈,以及考虑通过测试用例比例的自适应反馈。
编程语言和自然语言之间的主要区别在于,前者被人为地定义为精确且无歧义的,并且需要在执行之前编译(或解释)无错误。除了 CLM、MLM 和 Span Corruption 等词汇操作之外,这为设计代码的预训练目标提供了更大的灵活性。在神经网络被引入主流 NLP 文献之前的几年里,可以观察到类似的趋势(Sutskever,2014;Bahdanau,2015),当时 MT 社区的研究人员利用文本的其他视图(例如句法特征)来提高 SMT 系统的性能(Galley,2006;Chiang,2007)。但这些特征并不是普遍适用的,甚至没有得到一致认可,而且往往会导致系统非常复杂(例如,英语词性标注的标签集大小可能从几十个到几百个不等)。
然而,编程语言在这些方面表现得更好。每种主流编程语言,如C、Python和Java,都带有现成的编译工具包,可以轻松准确地提取语义信息,如抽象语法树(AST)、语言无关的中间表示(IR),以及辅助信息,如每个token的类型和控制/数据流图(CFG/DFG)。因此,在基于Transformer的代码语言建模中,许多工作已将这些特性纳入其训练过程中。
随着语言模型在软件工程基准上创下新的记录,软件工程技术也反过来扩展了语言模型的边界,并将其带入现实世界的开发周期。
NLP 社区的研究表明,LLM 可以学习使用外部工具,例如计算器、MT 系统和搜索引擎(Thoppilan,2022;Schick,2023)。因此,解释器已用于增强 LLM 在复杂推理任务中的能力。PAL(Gao,2023)和 PoT(Chen,2022)都使用 Python 解释器扩展了 Codex 以进行数值计算,而 ViperGPT(Surís,2023)通过调用视觉 API 从视觉输入中提取信息并回答相关问题对其进行了进一步扩展。
随着 LLM 交互式编码能力的提升,研究人员也开始将其集成到软件开发的每个过程中。自动代码补全是语言模型在软件开发中最早的应用之一,因为它们只需要预测下一个token的能力。甚至在语言模型扩展到数十亿个参数之前,就已经将 Pythia(Svyatkovskiy,2019)和 IntelliCode(Svyatkovskiy,2020)等补全系统集成到流行的 IDE 中。
然而,最近,代码语言模型的应用已经超越简单的代码补全。 GitHub Copilot 可以说是最受欢迎的 AI 代码助手之一,具有代码生成、漏洞检测和许可证管理等多种功能,而 CodeFuse (Di et al., 2023) 也将代码生成、代码翻译、代码注释和测试用例生成集成到单个 IDE 扩展中。然而,随着代码语言模型变得越来越大,它们的客户端部署和实时性能也带来了新的挑战。
目前代码建模的挑战如下:
- 全面的基准测试,推动代码 LLM 进入下一阶段。广泛使用的 HumanEval 基准测试在代码 LLM 的演进中起着关键作用。然而,它相对较小,其记分牌已被操纵到近乎完美,这并不能准确反映现实世界的行为。已经提出了许多其他代码 LLM 基准测试,但它们仍然不够全面,无法反映生产级要求。社区渴望在 HumanEval 之后出现新的标准基准测试,以进一步推动代码 LLM 进入下一阶段。
- 获取高质量数据。随着 (Gunasekar 2023 )使用基于教科书数据训练的 1.3B 模型实现 SOTA 性能,在不久的将来,无论是对于自监督预训练还是监督微调,训练数据的选择和合成数据的利用都将变得更加突出。
- 将代码特征集成到语言模型中。CFG和DFG尚未在代码语言建模中大规模使用。少数使用数据流的工作会改变模型的注意掩码,这严重限制了它们的跨任务泛化和扩展能力。将这些特性无缝集成到文本输入中是未来值得研究的。
- LLM 在更多代码下游任务中的应用。目前对LLM编码能力的评估集中在程序合成上,与软件测试(即单元测试生成、断言assertion生成、突变mutant生成和模糊测试)和反混淆相关的任务很少使用LLM。此外,由于LLM的上下文窗口目前非常有限,程序合成和代码翻译等生成任务尚未在方法级别之外应用。 LLM 在更多存储库级任务中的应用将成为未来的热门研究方向。
- 替代模型架构和训练目标。许多代码语言模型都是使用特定于代码的辅助目标进行预训练的,但这些模型都属于仅编码器或编码器-解码器架构系列,而仅解码器模型尚未通过替代目标进行增强。此外,正如 (Singh2023) 开创的那样,扩散模型将来会在代码建模中找到立足之地。
- 为软件开发的全生命周期构建代码 LLM 生态系统。虽然学术界已经见证了大量的代码模型,但大多数模型都是在编码阶段作为 IDE 插件部署的,而忽略了软件开发生命周期的其他阶段。代码 LM 在软件开发的整个生命周期中将得到更多应用,从需求分析到 DevOps,最终形成像 PyTorch (Paszke et al., 2019) 和 Hugging Face 那样的全面生态系统。
- 与代码 LLM 相关的安全和道德问题。随着语言模型的不断发展,它们也引发了安全问题,包括但不限于数据污染、有毒或有偏见的生成、个人信息泄露和幻觉。在软件开发中,这些模型的部署应格外谨慎,因为它们生成的代码可能包含导致灾难性后果的安全风险。预训练数据也正在成为一个敏感的道德话题,(Kocetkov2022) 允许开发人员从 Stack 中删除他们的代码,朝着这个问题迈出了有意义的一步。随着合成训练数据的普及,研究人员也应该谨慎行事,因为使用人工智能生成的数据训练人工智能模型的结果,尚未得到大规模研究。