论文干货 | 首篇代码生成大模型综述

分享一篇来自上海交通大学的最新代码生成大模型的论文综述。下面是对《A Survey on Language Models for Code》这篇论文综述的解读。

这篇文章的贡献:

  • 系统回顾了使用语言模型处理代码的最新进展,涵盖 50 多种模型、30 多种评估任务和 500 多项相关工作。
  • 将代码处理模型分为以 GPT 系列为代表的通用语言模型和根据特定目标对代码进行预训练的专用模型。
  • 讨论代码建模从统计模型和 RNN 到预训练 Transformers 和 LLM 的历史过渡,这与 NLP 的发展历程类似。
  • 探索 AST、CFG 和单元测试等特定代码特征及其在训练代码语言模型中的应用。
  • 确定代码处理领域的主要挑战和未来潜在方向。

Abstract

在这项工作中,我们系统地回顾了语言模型在代码处理方面的最新进展,涉及 50 多种模型、30 多种评估任务和 500 多项相关工作。我们将代码处理模型分为以 GPT 系列为代表的通用语言模型和专门针对代码进行预训练的专业模型,后者通常具有量身定制的目标。我们讨论了这些模型之间的关系和区别,并强调了代码建模从统计模型和 RNN 到预训练 Transformers 和 LLM 的历史性转变,这与 NLP 所走的路线如出一辙。我们还讨论了 AST、CFG 和单元测试等代码特有的特征,以及它们在训练代码语言模型中的应用,并指出了这一领域的主要挑战和潜在的未来方向。我们在github中保持开放和更新调查。

1 Introduction

​ 近年来,随着 BERT(Devlin 等人,2019 年)和 GPT(Radford 等人,2018 年)等预训练 Transformers (Vaswani 等人,2017 年)的出现,语言建模取得了显著进展。随着大型语言模型(LLM)扩展到千亿级参数,并开始显示出人工通用智能的早期迹象(Brown 等人,2020 年;Chowdhery 等人,2022 年;OpenAI,2023 年),它们的应用也超越了文本处理。由 Codex(Chen 等人,2021 年)开创的 LLM 在代码处理方面取得了令人瞩目的成果,催生了 GitHub Copilot 等商业产品以及 StarCoder(Li 等人,2023 年)和 Code LLaMA(Rozière 等人,2023 年)等开源数十亿代码模型。

​ 然而,预训练 Transformers 在代码处理中的应用可以追溯到仅用于解码器的自回归模型成为主流之前(Feng 等,2020;Liu 等,2020),而这一领域尚未得到全面回顾。为了弥补自然语言处理(NLP)界和软件工程(SE)界在语言模型应用这一主题上的差距,我们在本研究中对代码语言模型进行了一次全景式调查,涵盖 50 多种模型、30 多种下游任务和 500 多项相关工作。我们细分了代码语言模型的不同类别,从针对一般领域训练的大型模型到专门针对代码理解或生成训练的小型模型,不一而足。我们强调了这些模型之间的关系和差异,并重点介绍了将代码的特定特征(如抽象语法树或数据流)整合到语言模型中的方法,以及从 NLP 中改编的最新技术。

​ 与我们的工作相关,我们知道有几项关于类似主题的综述,其中有三项工作与我们同时进行(Hou 等人,2023 年;Zheng 等人,2023 年;She 等人,2023 年)。不过,这些研究要么侧重于 NLP 方面(Zan 等人,2023 年;Xu 和 Zhu,2022 年),要么侧重于 SE 方面(Niu 等人,2023 年;Hou 等人,2023 年;Zheng 等人,2023 年;She 等人,2023 年),并没有涵盖其他方面的模型、任务和挑战。例如,Zan 等人(2023 年)关注文本到代码生成的 LLM,而对软件工程界的其他评估任务讨论甚少。相反,Hou 等人(2023 年)和 She 等人(2023 年)全面回顾了 ASE 和 ICSE 等 SE 领域的作品,但只引用了 ACL、EMNLP、NeurIPS 和 ICLR 等深度学习和 NLP 领域的少量作品。

​ 因此,在这些工作的基础上,我们努力将两个社区的观点结合起来,并在整个工作中强调 NLP 与 SE 之间的融合。我们注意到,语言建模的高级主题最近已被引入代码处理,包括指令调整(Honovich 等人,2023 年;Xu 等人,2023 年;Luo 等人,2023 年)、填充目标(Tay 等人,2023 年;Li 等人,2023 年;Rozière 等人,2023 年)、缩放规律的重新构想(Hoffmann 等人,2022 年;Gunasekar 等人,2023 年;Li 等人,2023 年)、 2022;Gunasekar 等人,2023;Li 等人,2023)、架构改进(Shazeer,2019;Su 等人,2021;Dao 等人,2022)和自主代理(Qian 等人,2023;Hong 等人,2023),而SE要求则为这些技术提供了真实世界的测试平台,并推动LLMs的发展进入生产阶段。我们相信,对这些进展进行系统回顾将使这两个群体受益匪浅。

​ 这项工作的其余部分按照图 1 所示的分类法组织。在第 2 节中,我们首先介绍了语言建模和 Transformer 模型的基本原理,然后在第 3 节中,我们介绍了代码语言模型评估的背景,强调了从各种代码理解任务到更实用的文本到代码生成任务的历史过渡。在第 4 节中,我们讨论了大量已经展示了编码能力的 LLM,然后在第 5 节中,我们按其架构回顾了专门的、通常较小的模型,并特别关注了最近对填充目标、指令调整、强化学习和工程改进的应用。然后,在第 6 节中,我们将讨论代码的独特特征,这些特征是自然语言所不具备的,但已被用于帮助代码处理。在第 7 节中,我们回顾了 LLM 与软件开发之间的最新融合,最后在第 8 节中总结了这项工作,并强调了当前代码处理中面临的挑战。

在这里插入图片描述

2 Background

在本节中,我们将简要回顾基于 Transformer 的语言建模的基本原理,包括单向和双向模型的共同目标,以及 NLP 中一些流行的模型和设计。

2.1 Causal Language Modeling

单向语言模型(也称因果语言模型)使用链式法则将句子的概率分解为每个标记的条件概率的乘积。一段由 n n n个标记组成的输入文本 x = [ x 1 , x 2 , ⋯   , x n ] x=[x_1,x_2,\cdots, x_n] x=[x1,x2,,xn]被建模为:

P ( x ) = ∏ i = 1 n p θ ( x i ∣ x 1 : i − 1 ) ( 1 ) P(x)=\prod \limits _{i=1}^{n}p_{\theta}(x_i|x_{1:i-1})\quad (1) P(x)=i=1npθ(xix1:i1)(1)

其中, x 1 : i − 1 x_{1:i-1} x1:i1是输入中 x i x_i xi之前的标记的简称, θ \theta θ是模型的参数。在 GPT(Radford 等人,2018 年;Radford 等人,2019 年;Brown 等人,2020 年)和 LLaMA(Touvron 等人,2023 年;Touvron 等人,2023 年)等Transformer 解码器中,(1) 中的条件概率是通过在每个 Transformer 块的注意力矩阵中添加注意力掩码来建模的,以确保 x i x_i xi只能注意到之前的标记。在训练过程中,并行计算输入中所有标记的交叉熵损失,而在推理时,自回归生成新的标记。有关 Transformer 架构的更多详情,请参阅 Vaswani 等人(2017)。

2.2 Masked Language Modeling

与因果语言模型不同,双向语言模型的训练目的是获得更好的文本上下文表征,而不是自回归生成文本。在 vanilla Transformer 中,编码器部分可以关注标记的左侧和右侧语境。BERT(Devlin 等人,2019 年)更进一步,只训练了一个 Transformer 编码器。输入中随机选择的一组 M \mathcal{M} M标记被一个特殊标记 [MASK] 替代,从而得到一个有噪声的输入 x ^ \hat x x^,例如 [ [ C L S ] , x 1 , [ M A S K ] , x 3 , [ M A S K ] , x 5 , [ E O S ] ] [[CLS], x_1,[MASK], x_3, [MASK], x_5,[EOS]] [[CLS],x1,[MASK],x3,[MASK],x5,[EOS]],并通过最大化的方式训练模型来恢复原始标记:

∏ m ∈ M p θ ( m ∣ x ^ ) ( 2 ) \prod \limits _{m\in \mathcal{M}} p_{\theta}(m|\hat x)\quad (2) mMpθ(mx^)(2)

虽然这一目标要求模型对输入文本有深入的理解,以便对其进行重构,但它的训练效率却很低,因为只有一小部分标记(通常为 15%)被屏蔽(从而 “被训练”)。为了解决这个问题,Clark 等人(2020 年)提出了 ELECTRA 模型,该模型经过训练后可以分辨输入文本中的每个标记是否被类似 BERT 的模型所替代。

2.3 Denoising Objectives

​ GPT 式因果 LM 和 BERT 式双向 LM 各有优缺点。虽然 GPT 可用于自回归生成,但它缺乏输入文本的双向表示,因此不适合序列-序列(seq2seq)生成任务,如翻译和摘要。另一方面,BERT 可以生成双向表示,但其预训练仅用于掩码填充,而非生成。

​ vanilla Transformer 编码器-解码器架构结合了GPT和BERT各自的优点。T5(Raffel 等人,2020 年)就是这样一个用 span corruption 预训练的模型,它可以被看作是 MLM 的一个变体。在预训练过程中,输入文本的跨度会被哨兵标记(sentinel tokens)替换,其作用与 BERT 中的 [MASK]相同。噪声输入首先由编码器进行双向注意处理,然后由解码器自回归生成屏蔽跨距。从形式上看,如果对输入 x x x k k k个跨度进行采样以防止损坏,那么在 i = 1 , 2 , ⋯   , k i=1,2,\cdots,k i=1,2,,k时,通过用特殊标记 <extra_id_i> 替换每个跨度,就可以构建出噪声输入 x ^ \hat x x^,而目标 y y y则是通过将所有跨度与相应的哨兵前缀连接起来而构建的: [<extra_id_1>, s p a n 1 span_1 span1, ⋯ \cdots , <extra_id_k>, s p a n k span_k spank]。然后用标准的 seq2seq 目标对模型进行训练,即最大化

p θ ( y ∣ x ^ ) = ∏ i = 1 n y p θ ( y i ∣ x ^ , y 1 : i − 1 ) ( 3 ) p_{\theta}(y|\hat x) = \prod \limits _{i=1}^{n_y} p_{\theta}(y_i|\hat x, y_{1:i-1}) \quad(3) pθ(yx^)=i=1nypθ(yix^,y1:i1)(3)

​ Lester等人(2021 年)的研究表明,使用此类目标预训练的模型可以通过使用前缀语言建模目标进行额外预训练,即把文本分成两部分,用编码器处理第一部分,用解码器生成第二部分,从而适用于自回归语言建模。

​ Tay 等人(2023 年)认为,span corruption 也与 CLM 密切相关,因为人们可以将整个输入文本屏蔽为单个跨度,并训练解码器自回归地生成跨度。受这种关系的启发,他们提出了 UL2,它是多种跨度损坏目标的组合,这些目标在损坏率和跨度长度上各不相同。将其应用于编码器-解码器模型和解码器模型时,他们发现在相同的计算预算约束下,编码器-解码器模型的性能更好。其他研究也发现,这种编码器-解码器模型的性能通常优于纯因果解码器模型(Wang 等人,2022 年;Soltan 等人,2022 年)。

2.4 Auxiliary Objectives

​ 语言建模目标,如前面讨论过的 CLM 和 MLM,主要是训练模型捕捉标记级信息,在文档结构建模方面效果不佳。因此,通常需要添加辅助目标来帮助模型学习此类全局信息。BERT 在使用 MLM 的同时,还进行了下一句预测(NSP)的预训练,下一句预测被表述为一项二元分类任务,即预测输入中的两个句段在原始语料库中是否相邻。Lan 等人(2020)提出了一个更具挑战性的句序预测(SOP)任务,即通过交换两个相邻句子的顺序来构建负样本,而不是从其他文档中随机抽取一个句子。

​ 与此相关,Raffel 等人(2020 年)将 GLUE(Wang 等人,2018 年)等监督下游样本混合到 T5 的预训练数据集中,进行多任务预训练。不过,值得注意的是,由于他们将所有任务统一为文本到文本格式,其自我监督预训练和监督下游任务的训练目标是相同的。

2.5 Implementation Design

虽然大多数关于预训练语言模型的研究都集中在训练目标的设计上,但多年来,Transformer 架构本身的底层实现也在不断改进,以追求稳定性、性能和效率。

​ Vaswani 等人(2017)提出的原始 Transformer 模块的公式为:

h = L N ( A t t e n t i o n ( x ) + x ) , ( 4 ) h=LN(Attention(x)+x), \quad(4) h=LN(Attention(x)+x),(4)

y = L N ( F F N ( h ) + h ) , ( 5 ) y = LN(FFN(h)+h), \quad(5) y=LN(FFN(h)+h),(5)

其中, x x x是层的输入, y y y是层的输出。"Attention "是自注意子层,"FFN "是前馈子层,"LN "是层归一化(Ba 等人,2016 年)。

​ GPT-2(Radford 等人,2019 年)将层归一化移至每个 Transformer 子块的输入,以稳定训练:

h = A t t e n t i o n ( L N ( x ) ) + x , ( 6 ) h=Attention(LN(x))+x, \quad(6) h=Attention(LN(x))+x,(6)

y = F F N ( L N ( h ) ) + h , ( 7 ) y=FFN(LN(h))+h, \quad(7) y=FFN(LN(h))+h,(7)

而这种 pre-norm 从此成为 Transformer 解码器的标准做法。

​ GPT-J(Wang 和 Komatsuzaki,2021 年)修改了 Transformer 模块,以并行计算 FFN 子层和自注意子层,从而提高计算吞吐量:

y = x + F F N ( L N ( x ) ) + A t t e n t i o n ( L N ( x ) ) , ( 8 ) y=x + FFN(LN(x))+Attention(LN(x)), \quad(8) y=x+FFN(LN(x))+Attention(LN(x)),(8)

​ PaLM(Chowdhery等人,2022年)在LLM中引入了旋转位置嵌入(RoPE)和多查询注意力(MQA)。RoPE(Su 等人,2021 年)将每个自注意力层的键和查询乘以一个与位置相关的旋转矩阵,以注入位置信息,后来证明可以通过位置插值处理较长序列(Chen 等人,2023 年;Rozière 等人,2023 年)。作为 RoPE 的替代方案,Press 等人(2022 年)提出了 ALiBi 方案,根据键和查询之间的相对位置直接减弱注意力分数。这种位置嵌入方案后来被 BLOOM(Scao 等人,2022 年)采用。

​ 除了位置嵌入之外,Transformer 中另一个长期困扰研究人员的问题是,self-attention 的复杂度与输入序列长度成平方关系。一些研究成果,如 Reformer(Kitaev 等人,2020 年)、Linformer(Wang 等人,2020 年)、Performer(Choromanski 等人,2021 年)和 cosFormer(Qin 等人,2022 年)使用近似注意来降低这一复杂性,但它们大多以性能下降为代价。其他作品则从工程角度解决了这一问题。MQA(Shazeer,2019)在所有注意力头中共享同一组键和值,以优化内存与算术比率,并以较小的模型性能代价显著提高了推理速度。它的变体分组查询注意力(GQA,Ainslie 等人,2023 年)采取了中间路线,将注意力头分成若干组,并在每组内共享同一组键/值。与此对应,Dao 等人(2022 年)提出了 FlashAttention,它是自注意力的一种精确但改进的实现,通过平铺优化了加速设备上的 IO 操作,从而提高了内存效率。

3 Evaluation of Language Models for Code

过去十年间,软件工程界提出了各种评估任务来评估代码模型。CodeXGLUE(Lu 等人,2021 年)将大部分此类任务整合到一个基准中,涵盖了代码理解任务(如克隆检测、缺陷检测)和序列到序列生成任务(如代码修复、代码翻译、程序合成和代码总结)。然而,在 Chen 等人(2021 年)推出 HumanEval 和 Codex 之后,文本到代码的合成成为 NLP 界的焦点,并从此成为评估 LLM 的标准任务(图 2)。因此,我们首先在第 3.1 节中简要介绍了每项传统任务以及预训练语言模型在其中的应用,并在图 3 和图 4 中提供了每项任务的相关工作的综合列表。然后,我们在第 3.2 节中回顾了评估指标,并在第 3.3 节中更详细地研究了程序合成。最后,我们还在第 3.4 节中讨论了资源库级评估的最新趋势。在附录 A 中,我们列出了每项下游任务的基准。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.1 Downstream Tasks of Code Processing

按照软件工程的习惯,我们根据输入/输出模式对代码的评估任务进行分类,并将这些任务分成五个系列:文本到代码、代码到代码、代码到文本、代码到模式以及文本到文本。我们注意到,这种分类法与 NLP 中的理解-生成二分法相互交错,因为每个类别可能同时包含理解和生成任务,这一点将在第 3.1.6 节中讨论。

3.1.1 Text-to-Code

文本到代码任务将文本作为输入,并输出代码。

代码检索旨在根据自然语言查询检索相关代码,或从未加注释的语料库中挖掘平行文本-代码对。这项任务通常是通过计算查询和候选代码嵌入之间的相似度量来完成的,而双向语言模型(如 BERT)产生的上下文嵌入已被证明非常有用。Grazia 和 Pradel(2023 年)以及 Xie 等人(2023 年)对这一主题进行了全面评述。

代码合成旨在根据自然语言描述生成代码(通常是函数或方法)。这项任务可以看作是使用生成模型而不是检索模型进行代码检索的升级版。统计机器翻译(SMT)和神经机器翻译(NMT)模型已被广泛应用于这项任务,它们通常带有利用编程语言独特语法规则的增强解码器(Dong 和 Lapata,2016 年;Yin 和 Neubig,2017 年)。然而,基于 Transformer 架构的预训练语言模型改变了游戏规则,即使没有特定任务的微调,也能以自回归语言建模的方式直接生成源代码(Chen 等人,2021 年)。我们将在第 3.3 节更详细地讨论这项任务。

文本到 SQL 是代码合成的一种特殊情况(也可以说是一种更简单的情况),在这种情况下,模型的任务是从自然语言查询生成 SQL 命令。由于 SQL 的结构化特性(与 Python 和 C 等通用语言相比)以及在数据管理中的广泛应用,它一直是一个特别受关注的话题。关于这一主题的研究,我们可以参考 Kumar 等人 (2022) 和 Deng 等人 (2022)。

数学编程也是代码合成的一种特殊情况,即要求语言模型通过生成将由外部解释器执行的代码来解决数学推理问题。这项任务将推理过程从数值计算中抽象出来,因此在评估 LLM 时具有特殊意义。

3.1.2 Code-to-Code

代码对代码任务将代码作为输入,并输出代码。

​ 代码搜索是一项与代码检索类似的任务,与后者的区别仅在于输入的是现有的代码片段,通常使用的编程语言与目标语言不同。

代码补全旨在根据代码前缀补全一段代码。这本质上是将语言建模应用于代码,相关技术已逐步被引入:ngram、RNN 和 Transformer。不过,由于编程语言的结构化特性,许多早期研究发现语法辅助的统计模型表现更好(Bielik 等人,2016 年;Hellendoorn 和 Devanbu,2017 年),神经模型在 2018 年之后才成为主流(直观概述见图 3)。

​ 代码翻译的目的是将一段代码(通常是函数或方法)翻译成另一种编程语言。代码翻译与跨语言代码搜索之间的关系类似于代码合成与文本到代码检索之间的关系,SMT/MNT 模型也被广泛应用于这项任务。代码合成可以帮助程序员编写代码片段,而代码翻译则不同,它是迁移用过时语言编写的旧项目的重要技术。然而,我们尚未看到此类应用,因为面对此类项目,即使是最强大的语言模型的上下文窗口也相当有限。

代码修复,又称错误修复,旨在修复一段有错误的代码。与代码翻译一样,它也是一项传统的序列到序列生成任务,关于这一主题的调查报告比比皆是(Gazzola 等人,2018 年;Monperrus,2018 年;Zhong 等人,2022 年;Zhang 等人,2023 年;Huang 等人,2023 年)。

Cloze 测试是在 BERT 式预训练兴起之后,最近提出的一项代码处理任务。由于编程语言语义的特殊性,该测试通常会选择几个关键词,如 min 和 max(Feng 等人,2020 年)。

代码填充是继中间填充预训练(Bavarian 等人,2022 年)流行之后,最近提出的另一项任务。它是代码补全的一般化,不仅给出左侧上下文,还给出右侧上下文。不过,它与掐词测试的不同之处在于,掐词测试的目标只有一个标记,而代码填充的目标可以是整行甚至多行,这就需要解码器自动生成。

混淆是指对标识符(如变量、方法和类)进行重新命名的过程,例如将其命名为 v a r 1 , v a r 2 var_1,var_2 var1,var2或者 x , y x,y x,y等等通用名称,这是病毒检测、知识产权保护和代码缩减的一项重要技术(Collberg 和 Thomborson,2002 年;Murad 等人,2010 年;Vasilescu 等人,2017 年)。去混淆指的是反向过程,即从被混淆的程序中恢复有意义的标识符名称。混淆在语言模型中的应用很少,因为它可以很容易地静态实现,但近年来,去混淆已成为一个更受关注的主题,并已被采用为代码语言模型的预训练目标(Lachaux 等人,2021 年;Ding 等人,2022 年)。

单元测试生成的目的是为给定程序生成单元测试。在 Codex 和其他代码 LLM 兴起之前,这一领域的几乎所有工作都采用了非神经方法(见图 3)。然而,在 LLMs 时代,这项任务变得越来越重要,因为研究表明,目前用于评估 LLMs 程序合成能力的单元测试可能不够充分(Liu 等人,2023 年)。

断言生成是一项与单元测试密切相关的任务。给定一个程序和一组单元测试,这项任务的目的是生成断言(也称为软件工程中的谕令),以便使用单元测试来评估程序。这项任务通常不为 NLP 界所关注,因为用于评估 LLM 的程序合成任务通常涉及独立的竞争式方法,对于这种方法,只需断言程序输出与预期答案相等即可。

突变生成的目的是为突变测试生成给定程序的突变体,与单元测试生成和断言生成密切相关。特定单元测试和断言集未检测到的突变表示需要额外的测试用例或更好的断言(Fraser 和 Arcuri,2011 年)。最近,屏蔽源代码中的标记并从屏蔽语言模型的输出中采样已成为这项任务的常用方法。Papadakis 等人(2019 年)对这一主题进行了调查。

模糊测试旨在对给定的单元测试集进行突变,以生成新的测试用例,是与软件测试相关的另一项任务。虽然近期许多模糊处理工作都以深度学习库为目标,但很少有人利用语言模型来执行这一过程(见图 3)。

类型预测旨在预测 Python 和 JavaScript 等动态编程语言的类型。它已被用作代码语言模型的预训练目标(Wang 等人,2022 年),通常被简化为二进制标记任务,以预测代码中哪些标记符是标识符(Wang 等人,2021 年;Wang 等人,2021 年)。

3.1.3 Code-to-Text

代码到文本任务将代码作为输入,并输出文本。

代码摘要,也称为文档字符串生成,旨在为给定代码(通常是函数或方法)生成自然语言描述。这与代码合成恰恰相反,SMT/NMT 技术也同样得到了应用。Zhang 等人(2022 年)对这一主题进行了调查。

代码审查旨在实现同行代码审查过程的自动化,其形式多种多样。许多早期的研究将其表述为在提交时接受或拒绝修改的二进制分类任务,而另一些研究则利用信息检索技术从现有的评论库中推荐评论。不过,随着生成模型的能力越来越强,研究人员也将直接生成评论意见作为序列到序列的学习任务进行了研究。

标识符预测是预测代码中标识符名称的任务。由于这些名称被认为包含重要的语义信息,这项任务已被用于代码总结(Allamanis 等人,2016 年)以及代码模型的预训练(Wang 等人,2021 年;Niu 等人,2022 年)。

3.1.4 Code-to-Patterns

代码到模式任务对代码进行分类。

缺陷检测预测输入代码是否存在缺陷,是一项标准的单句分类任务。

克隆检测可预测两段代码是否相互克隆。在软件工程中,存在四种类型的代码克隆,其中最难识别的类型是语义克隆,即语法上不同但功能相同的代码。由于这项任务可被视为双句分类任务,BERT 风格的语言模型已被广泛应用于其中。
​ 由 Mou 等人(2016 年)推广的代码分类旨在预测一段代码在一组预定义标签内的功能。一个非常类似的任务是作者识别,即预测输入代码的作者。这两项任务都是标准的单句分类任务。

代码推理是最近引入的一项 LLM 评估任务,通常作为 MMLU(Hendrycks 等人,2021 年)等通用评估基准的一个子集。这项任务要求模型对代码或算法进行推理,并回答以多项选择形式提出的相关问题,问题范围可能包括概念理解、数值计算和复杂性分析。

3.1.5 Text-to-Text

文本到文本任务将文本作为输入,并输出文本。

文档翻译是对代码相关文档的自动翻译。由于机器翻译的模型、数据集和提示策略在 NLP 中非常丰富(Vaswani 等人,2017 年;Goyal 等人,2022 年;He 等人,2023 年),因此我们不详细介绍这项任务。

日志解析旨在分析软件产品产生的系统日志,例如将日志解析为结构化模板或从原始日志中发现异常。Zhu 等人(2019 年)对截至 2018 年的传统方法进行了调查,而 Zhang 等人(2023 年)也涵盖了更多最新方法。

3.1.6 NLP Point-of-View

​ 在前面列出的任务中,代码合成、代码翻译、代码修复、去混淆、单元测试生成、断言生成、突变生成、模糊处理、代码总结、代码审查和标识符预测都属于序列到序列生成任务。从形式上看,这些任务的每个实例都有一个源序列 x x x(例如一段源代码)和一个目标序列 y y y(例如其相应的摘要),语言模型的任务是最大化 (3) 所给出的条件概率,其中 θ \theta θ可以是一个纯解码器模型,也可以是一个编码器-解码器模型。在前一种情况下, x x x y y y被串联起来。在后一种情况下, x x x由编码器处理, y y y由解码器处理。

​ 代码补全和代码填充也是生成任务,它们与 (1) 和 (3) 中给出的两个预训练目标完全对应,只是代码填充只屏蔽了输入中的一个跨度。同样,"掐头去尾 "测试也是一项理解任务,其形式与(2)相同。

​ 缺陷检测、克隆检测、代码分类和类型预测都是序列分类任务。在这些任务中,对输入定义了一组标签 Y \mathcal{Y} Y,每个实例被分配一个标签 y ∈ Y y \in \mathcal{Y} yY(例如,缺陷检测的标签 Y = { 0 , 1 } \mathcal {Y} = \{0,1\} Y={0,1},而类型预测的标签 Y \mathcal Y Y可能是 {int, float, string, bool, others})。然后,模型的任务是最大化

p θ ( y ∣ x ) ( 9 ) p_{\theta}(y|x) \quad(9) pθ(yx)(9)

最后两项任务–代码检索和代码搜索–也属于理解任务。在这些任务中,每个源序列 x x x与一个正目标序列 y y y和一组负目标 y ˉ ∈ { y 1 , ⋯   , y k } \bar y \in \{y_1,\cdots, y_k\} yˉ{y1,,yk}。模型的任务是找到一个相似度量 s s s,使得 s ( x , y ) s(x,y) s(x,y)大于 s ( x , y ˉ ) s(x,\bar y) s(x,yˉ)

3.2 Evaluation Metrics

​ 在第 3.1 节提到的任务中,理解任务的形式与自然语言理解任务类似(Wang 等人,2018 年;Wang 等人,2019 年),同样通过准确率、F1 和平均互易等级(MRR)等指标进行评估,而短生成任务(如标识符预测)也通过精确匹配的准确率进行评估。代码到文本任务则采用文本生成的通用指标进行评估,如 BLEU(Papineni 等人,2002 年)、

​ 另一方面,涉及代码生成任务的评估则更为复杂。大多数早期研究都对语法正确性进行了评估,即可以成功解析的代的百分比。Chen 等人(2018)反对采用此类指标,并建议采用参考匹配度,即与参考完全相同的代的百分比。Ren 等人(2020)提出的 CodeBLUE 是 BLEU 的一种变体,它通过评估抽象语法树(AST)和数据流的重叠程度,将代码语法和语义考虑在内。

​ 然而,随着代码生成模型能力的不断提高,这些基于内容重叠的度量标准被认为是不够的(Rozière 等人,2020 年;Hendrycks 等人,2021 年;Austin 等人,2021 年),因为功能等同的代码片段在词形上可能存在巨大差异。因此,研究人员将注意力转向了功能正确性。Kulal 等人(2019 年)提出并经 Chen 等人(2021 年)改进的 pass@k,就是此类度量的一个流行例子,它是对模型在任意 k 个生成样本中通过程序所有单元测试几率的无偏估计。这一指标可以推广到 passn@k(Li 等人,2022 年),它将提交模型的数量限制为 n,但允许通过 k 个样本输入中给出的单元测试进行筛选。

3.3 Program Synthesis

​ 随着代码模型多年来的发展,研究人员逐渐将注意力转向了程序合成这一实际任务。CONCODE (Iyer 等人,2018 年)是这一领域的早期数据集之一,其中包括 10 多万个 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 年)。

3.4 Repository-Level Evaluation

​ 第 3.1 节和图 3 中讨论的大多数评估任务仅限于单个文件甚至单个函数,因为跨文件代码建模带来的挑战超出了大多数现有语言模型的能力。然而最近,位置插值技术(Chen 等人,2023 年;Rozière 等人,2023 年;Peng 等人,2023 年)将 LLM 的上下文窗口扩展到了成百上千的标记,使得在整个资源库中对代码建模进行上下文评估成为可能。有几项研究(Shrivastava 等人,2023;Ding 等人,2022;Zhang 等人,2023;Shrivastava 等人,2023)利用资源库级上下文研究了代码完成,Liu 等人(2023)提出了 RepoBench 来评估此类系统。最近,Bairi 等人(2023 年)研究了更具挑战性的版本库级 API 迁移和时态编辑任务,Jimenez 等人(2023 年)推出了相应的基准 SWE-bench。

4 General Language Models for Code

自从语言模型扩展到数千亿个参数(Brown 等,2020 年;Chowdhery 等,2022 年)以来,许多语言模型已经展示了非同小可的编码能力,即使它们不是专门为编码而设计或训练的。由 Codex 首创,研究人员还发现持续的代码预训练能显著提高语言模型的代码性能。

4.1 Off-the-Shelf Language Models

​ 大型语言模型通常要根据缩放定律在数以万亿计的词块上进行预训练(Kaplan 等人,2020;Hoffmann 等人,2022),而这样大量的文本数据往往是一个多样化的复合体,其中有不可忽视的代码部分。例如,Pile(Gao 等人,2021 年)的 800GB 原始数据集中包含 95GB 从 GitHub 抓取的代码,而多语言预训练数据集 ROOTS(Laurençon 等人,2022 年)的 1.6TB 复合数据中也包含 163GB 的代码,横跨 13 种编程语言。作为两个最大的开源预训练数据集,它们支持了许多具有编码能力的语言模型。例如,Chen 等人(2021 年)报告说,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 标记上训练的 13B 模型,而 Qwen(Bai 等人,2023 年)是一个在 3T 标记上训练的 14B 模型。它们在 HumanEval 上分别获得了 17.1 和 32.3 的通过率@1。然而,Li 等人(2023 年)证明,小到 1.3B 的模型也能获得与大得多的模型相媲美的编码能力,同时还能在一般文本处理上保持合理的性能,甚至表现出一些新兴能力(Wei 等人,2022 年),如思维链推理(Wei 等人,2022 年)。他们的模型Phi-1.5是在由ChatGPT生成的210亿字节教科书数据以及来自Stack Overflow和Refined Web(Penedo等人,2023年)的1亿字节过滤网络数据上训练出来的,在HumanEval上达到了41.4 pass@1的性能。这些模型的具体性能见表 1。

在这里插入图片描述

4.2 Language Models with Additional Pretraining on Code

​ 除了开创性的基准 HumanEval 之外,Chen 等人(2021 年)还通过 Codex 开启了代码 LLM 的时代,Codex 是在 1,000 亿额外代码标记上进行预训练的 GPT-3 检查点,也是最早的数十亿代码模型之一。在他们的工作之后,其他研究人员也通过额外的预训练对代码进行了专门的 LLM。Chowdhery 等人(2022 年)对 PaLM 进行了额外 78 亿个代码标记的训练,得到了 PaLM-Coder,在 HumanEval 和 MBPP 上创造了新的最高水平(表 1),后来被其后继者 PaLM 2-S* 打破,后者是 PaLM 2 的最小版本(Anil 等人,2023 年),在未公开的代码量上进行了进一步训练。同样,Lewkowycz 等人(2022 年)在 385 亿个 arXiv 论文和数学内容标记上训练 PaLM,而 Rozière 等人(2023 年)在超过 500 亿个代码标记上训练 LLaMA 2(Touvron 等人,2023 年),从而获得了 Code LLaMA,其在 HumanEval 上的性能超过了除 GPT-4 以外的所有以前的 LM(表 1)。Liu 等人(2023 年)通过多任务微调(MFT)进一步训练 Code LLaMA,推出了 CodeFuse-CodeLLaMA,在 HumanEval 上达到了 74.4 pass@1,甚至超过了 OpenAI(2023 年)发布的 GPT-4 的性能。

​ 尽管几乎所有这些模型都是用 CLM 预训练的变形解码器,但正如我们在第 2.5 节中所指出的那样,这些模型在结构上做了一些修改。所有这些模型都使用预规范,GPT-J 引入了并行注意,PaLM、GPTNeoX 和 Phi-1.5 后来也采用了这种方法。PaLM 在 LLM 中引入了 MQA 和 RoPE,现在大多数语言模型都采用了 RoPE,包括 GPT-NeoX、两代 LLaMA、Qwen 和 7B 版本的百川 2。然而,BLOOM 和 13B 版本的百川 2 使用 ALiBi 进行位置嵌入,而 LLaMA 2 和 Code LLaMA 则采用 GQA 而不是 MHA 或 MQA。在第 5 节中,我们将展示完全基于代码预训练的专门模型也紧跟这些进步。

5 Specialized Language Models for Code

由于 GPT 和 BERT 等预训练变换器在自然语言处理领域取得了显著的成功,软件工程界很快就采用了这种模型架构、学习范式和训练目标,以产生用于代码理解和生成的专门模型。在本节中,我们首先回顾了用于代码语言模型预训练的常用数据集(§5.1),然后按模型架构深入探讨了复杂的代码 LM 系列:纯编码器模型(§5.2)、编码器-解码器模型(§5.3)、纯解码器模型(§5.4)、UniLM(§5.5)和扩散模型(§5.6)。最后,在第 5.7 节中,我们还说明了将指令调整(Wei 等人,2022 年;Sanh 等人,2022 年;Chung 等人,2022 年)和强化学习(欧阳等人,2022 年)等最新技术应用于代码处理的当前趋势。表 3 提供了这些预训练模型的概况。
在这里插入图片描述

5.1 Training Dataset for Code

​ 用于预训练语言模型的文本数据通常是从网络上抓取的,必须经过细致且经常是激进的预处理(Raffel 等人,2020 年),而代码数据则是以整篇文档的形式从公共 GitHub 资源库中自然获取的。更妙的是,它们还带有现成的质量指标,如星级或分叉数(尽管 Allal 等人(2023 年)认为星级数与下游性能的相关性很差)。因此,许多大规模的代码预训练数据集已经问世,包括 CodeSearchNet(Husain 等人,2019 年)、CodeParrot(Tunstall 等人,2022 年)和 Stack(Kocetkov 等人,2022 年),总计分别有 20GB、50GB 和 3TB 的代码文档(表 2)。
在这里插入图片描述

​ 虽然这些数据集是用来训练代码模型的,但应该注意的是,代码归根结底是一种特殊形式的自然语言,因为大多数编程语言的词汇量只是英语的一小部分。此外,高质量的代码通常与自然语言注释或文档交错在一起,这也使模型能够获得某些关于一般文本表示的知识。事实上,在 CodeSearchNet 的 650 万个函数中,有 230 万个与自然语言文档配对,这使得模型可以明确地在此类双模数据上进行训练。

​ 与自然语言相比,从 GitHub 搜刮代码的另一个副产品是提交历史,它由提交前的代码、提交后的代码和描述提交的简短信息组成,可以松散地作为语言模型的指令。Muennighoff 等人(2023 年)利用这一特性构建了一个 2GB 的数据集 CommitPackFT,其中包含 742K 个代码指令数据样本,从而避免了构建自然语言指令所需的大量人力(Sanh 等人,2022 年;Wang 等人,2022 年)。

​ 除了双模训练和指令微调之外,构建代码数据集的另一个最新趋势是使用强大的模型(如 ChatGPT)合成数据。这种方法最初是为生成自然语言的指令数据而提出的(Wang 等人,2023 年;Honovich 等人,2023 年),而 Gunasekar 等人(2023 年)则更进一步,综合了 Python 教科书和编码练习中的 1B 标记来预训练一个 1.3B 的模型,在 HumanEval 上取得了最先进的结果,可与在更大的数据集上训练的更大的模型相媲美。

5.2 Encoders

​ BERT(Devlin等人,2019年)、RoBERTa(Liu等人,2019年)和ELECTRA(Clark等人,2020年)等预训练 Transformer 编码器在自然语言理解任务中取得了令人瞩目的成绩,这些方法问世后很快被引入代码处理领域。Kanade 等人(2020)在代码语料库上复制了 BERT 的训练过程,从而产生了 CuBERT,并展示了其优于 LSTM(Hochreiter 和 Schmidhuber,1997 年)和非预训练 Transformers 的性能。另一方面,Feng 等人(2020 年)在 CodeSearchNet 上使用 MLM 和 ELECTRA 的 RTD 训练 CodeBERT。他们还利用了 CodeSearchNet 中的显式文本代码对,并将其分别用作 BERT 输入的第一和第二段。在使用 CodeBERT 初始化用于序列到序列生成任务(如代码摘要)的 vanilla Transformer 的编码器部分时,他们观察到比非预训练基线有适度的性能提升。

​ 除了这些标准训练目标外,还有许多专门针对代码设计的辅助目标。GraphCodeBERT (Guo et al., 2021) 和 SynCoBERT (Wang et al., 2021) 都从源代码中提取图形(分别是数据流图和抽象语法树),并训练模型以预测节点之间的类型学关系,而 Syn-CoBERT 和 Code-MVP (Wang et al., 2022) 还以标记的形式在预训练阶段添加了类型推断。另一个共同目标是对比学习: SynCoBERT 和 Code-MVP 对输入的不同视图(如代码、注释、AST 和转换后的代码)进行对比,而 DISCO(Ding 等人,2022 年)则通过语义保护转换(如混淆)构建正样本对,并通过注入人工错误构建负样本对。

5.3 Encoder-Decoders

​ 在 NLP 领域,T5(Raffel 等人,2020 年)和 BART(Lewis 等人,2020 年)等预训练 Transformer 编码器-解码器也在过去几年的语言建模进展中留下了显著的印记。例如,T5 将所有文本任务统一为序列到序列的格式,并创下了 GLUE(Wang 等人,2018 年)和 SuperGLUE(Wang 等人,2019 年)的新纪录。与纯编码器模型相比,编码器-解码器自然更加强大,因为它们可以用于条件文本生成,而其编码器部分可以随时取出来执行需要纯编码器架构的任务,如回归(Tay 等,2023 年)。

​ 受编码器-解码器架构这些优势的启发,许多用于代码处理的此类模型已被提出。PyMT5 (Clement 等人,2020 年)和 Mastropaolo 等人(2021 年)在代码语料库上复制了 T5 的预训练和多任务微调过程,而 Ahmad 等人(2021 年)介绍了 PLBART,一种在 655GB Java、Pyhton 和自然语言组合数据上预训练的 BART。Lachaux 等人(2021 年)认为,对于编程语言来说,MLM 可能是一项过于简单的任务,因为标识符名称经常在单个上下文窗口中出现多次,因此他们提出了一种去混淆预训练目标,即训练模型将被混淆的代码转换回其原始形式。与此方法相关,我们注意到有意义的变量名也被发现对大型语言模型的代码生成过程有积极影响(Chen 等人,2022 年)。

​ 在这些早期工作的基础上,Wang 等人(2021 年)提出了 CodeT5,它的预训练方式包括:1)T5 原始的跨度破坏;2)标识符标记(代码输入中的每个标记都被标记为标识符或非标识符);3)屏蔽标识符预测(一种特殊形式的跨度破坏,其中所有标识符都被屏蔽);以及 4)文本到代码和代码到文本的生成。其后续版本 CodeT5+(Wang 等人,2023 年)从 UL2(Tay 等人,2023 年)中汲取灵感,在预训练中引入了因果语言建模(CLM),以及基于文本代码匹配的额外对比目标。

​ AlphaCode(Li 等人,2022 年)也是采用多重目标训练的,其中编码器采用 MLM 训练,解码器采用 CLM 训练,并对架构进行了修改,如浅编码器和深解码器、多查询关注(Shazeer,2019 年),而且比 CodeT5 大得多(多达 41B 个参数)。另一方面,NatGen(Chakraborty 等人,2022 年)以类似于去混淆的 "归化 "目标进行预训练:通过预定义操作(如循环转换、死码注入和变量重命名)生成语义等价但不自然的代码,然后对模型进行预训练,将这些不自然的代码翻译回其原始形式。我们注意到,其中一些模型是建立在以前的工作基础上的。例如,NatGen 是由 CodeT5 初始化的,而 CodeT5+ 的最大版本则是由纯解码器模型 CodeGen 初始化的(Nijkamp 等人,2023 年)。

​ 除了这些一般的预训练目标,一些研究还训练了 Transformer 编码器-解码器,重点关注代码翻译,这是 Transformer 模型在代码中的自然应用,因为 Transformer 架构最初是由 Vaswani 等人(2017 年)针对机器翻译(MT)提出的。然而,与自然语言不同的是,自然语言中存在大量两种或多种人类语言的并行语料,而代码的并行数据却很少。为了解决这个问题,Rozière 等人(2020 年)提出了 Transcoder,它首先用 XLM(Conneau 和 Lample,2019 年)预训练一个编码器,然后用这个编码器初始化一个 vanilla Transformer,并继续用去噪自动编码(DAE,Lewis et al、 2020)和反向翻译(Sennrich 等人,2016),而其后续工作(Szafraniec 等人,2023)也利用与语言无关的中间表征来增强这一过程,我们将在第 6 节中对此进行更详细的讨论。

​ 如表 3 所示,除了训练数据和目标之外,这些模型大多保持了 NLP 界提出的原始架构。例如,基于 BART 的模型使用后规范化和可学习的绝对位置嵌入,而基于 T5 的模型则使用其简化的相对位置嵌入和前规范化。

5.4 Decoders

​ 在GPT-3(Brown等人,2020)的巨大成功和上下文学习(in-context learning)的发现之后,纯解码器变换器模型已成为语言建模的主流(Rae等人,Hoffmann等人,Chowdhery等人,Scao等人,Touvron等人,Touvron等人,2021,2022,2022,2022,2023,2023,等等)。 2023)、PyCodeGPT(Zan 等人,2022)、Pangu-Coder(Christopoulou 等人,2022)、CodeGeeX(Zheng 等人,2023)、Phi-1(Gunasekar 等人,2023)、CodeFuse(Di 等人,2023)、CodeShell5 和 DeepSeek Coder6。在这些模型中,有人尝试过几种替代训练目标,如Pangu-Coder 中的 MLM 和 Masked CLM7,但发现与纯 CLM 训练相比,效果不佳。Zan 等人(2022 年)还提出在草图上进行持续训练,即模型先学会生成程序草图,然后再生成实际代码。值得注意的是,Gunasekar 等人(2023 年)提出了 Phi-1,这是一个 1.3B 的小型模型,训练数据集只有 7B 标记,包括来自 StackOverflow 的 6B 标记和 ChatGPT 生成的 1B 合成数据,但在 HumanEval 上达到了 50.6 pass@1 的成绩,在 MBPP 上达到了 55.5 pass@1 的成绩,可与 Code LLaMA 或 PaLM 2 等更大型(模型大小和训练数据大小)的模型相媲美。

​ 尽管 Christopoulou 等人(2022 年)报告称去噪目标在纯解码器模型中表现不佳,但也有其他研究成功地将去噪或多任务预训练与解码器架构结合起来。Incoder(Fried 等人,2023 年)、SantaCoder(Allal 等人,2023 年)和 StarCoder(Li 等人,2023 年)都采用了中间填充(FIM)目标进行训练,Fried 等人将其称为因果掩蔽(causal masking)(2023 年),这实质上是在纯解码器架构中采用了跨距破坏(Raffel 等人,2020 年)。这些填充目标的一个明显优势是,它们为模型注入了在推理时填充输入代码中间空白的能力,而 CLM 只允许自回归生成。不过,如表 4 所示,与 CodeGen 等仅有 CLM 的模型相比,这些目标也能提高下游任务的性能。

​ 观察表 3 可以发现,与其他模型架构相比,纯代码解码器模型一般都更严格地遵循了 NLP 的实践。值得注意的是,最近的三个模型–StarCoder、Phi-1 和 CodeFuse–也采用了 FlashAttention 来提高模型的吞吐量。

5.5 UniLMs

​ 继 NLP 领域的 UniLM(Dong 等人,2019 年)之后,代码处理领域的一些研究也对代码进行了第四系列变换器模型的预训练。 CugLM (Liu et al., 2020) 通过交替注意力掩码使用 CLM 和 MLM + NSP 进行训练,而 UniXcoder 则使用 CLM、MLM、Span Corruption(前缀 LM 风格)以及包括对比学习和文本代码相互生成在内的辅助目标进行训练。不过,这两个模型的规模都相对较小,这种结构是否适合代码处理还有待探索。

5.6 Diffusion Models

​ 目前,Transformer 架构在文本生成方面占据主导地位,但也有一些作品(Li 等人,2022 年;Lin 等人,2023 年)采用了计算机视觉中的扩散模型(Ho 等人,2020 年)来生成文本。 最近,CodeFusion(Singh 等人,2023 年)也将扩散模型引入代码建模,并证明在 3 个代码合成数据集上,75M 扩散模型的性能优于 StarCoder、CodeT5+ 和 GPT-3。

5.7 Instruction Finetuning and Reinforcement Learning for Code

​ 在自然语言处理中,在带有指令前缀的不同任务集上训练模型(称为指令微调)已被证明能够释放跨任务泛化能力(Ouyang 等人,2022;Chung 等人,2022;Iyer 等人,2022)。 起初,这些指令数据样本都是人工编译或从人群中获取的(Wei 等人,2022 年;Sanh 等人,2022 年),但后来的研究发现 LLM 生成的指令已经足够(Wang 等人,2023 年;Honovich 等人,2023 年)。

​ 继这些自然语言方面的研究之后,代码界的研究人员也将指令调整应用到了他们的模型中。Wang 等人(2023 年)利用 InstructGPT(欧阳等人,2022 年)生成的 20K 指令数据对 CodeT5+ 进行了微调,得到了 InstructCodeT5+。WizardCoder (Luo et al., 2023) 沿用 WizardLM (Xu et al., 2023) 的方法,将 20K 代码 Alpaca (Taori et al., 2023) 样本演化成 78K 数据集,并用它对 StarCoder 进行微调。Pangu-Coder 2(Shen 等人,2023 年)也使用 WizardLM 的 EvolInstruct 从 20K 代码 Alpaca 生成 68K 指令样本,但也通过 Rank Responses to align Test & Teacher Feedback(RRTF)引入了强化学习。另一方面,OctoCoder(Muennighoff 等人,2023 年)走的是另一条路,它使用 Git 提交历史作为指令数据,对 StarCoder 和 CodeGeeX2 进行微调。最近,CodeFuse(Di 等人,2023 年)也采用了多任务微调技术,并在其指令数据中明确引入了多个下游任务。这些经过指令微调的代码模型的性能也可以在表 4 中找到。

​ 在 NLP 领域,与指令微调密切相关的另一项技术是人类反馈强化学习(RLHF),它在使 LLM 与人类价值观保持一致方面发挥了重要作用(Ouyang 等人,2022;Bai 等人,2022)。强化学习的优点在于它可以将无差别的奖励信号纳入训练,如 BLEU(Bahdanau 等人,2017 年)和人类偏好(Christiano 等人,2017 年),但对齐 LLMs 所需的人类反馈往往涉及大量的注释工作。相比之下,将强化学习应用于代码模型具有天然的优势,因为编译器可用于为语言模型生成的代码样本自动生成反馈。

​ CodeRL(Le 等人,2022 年)就是这样一个模型,它为每个生成的程序定义了四个级别的奖励(即编译错误、运行时错误、单元测试失败、通过),以及由批评者模型估算的细粒度标记级奖励。演员模型是 CodeT5 的扩展,然后用 REINFORCE 算法(Williams,1992 年)进行训练。同样,CompCoder(Wang 等人,2022 年)和 PPOCoder(Shojaee 等人,2023 年)分别采用近端策略优化(Schulman 等人,2017 年)训练 CodeGPT 和 CodeT5,而 RLTF(Liu 等人,2023 年)则根据编译器提供的错误信息和位置提出了细粒度反馈,以及考虑到通过测试用例比例的自适应反馈。
在这里插入图片描述

6 Code Features for Language Models

​ 编程语言与自然语言的主要区别在于,前者被人为地定义为精确、明确的语言,并且需要在执行前进行无差错编译(或解释)。 这样,除了 CLM、MLM 和 Span Corruption 等词法操作外,在设计代码预训练目标时就有了更大的灵活性。在神经网络被引入主流 NLP 文献(Sutskever 等人,2014 年;Bahdanau 等人,2015 年)之前的过去几年中,MT 界的研究人员利用句法特征等文本的替代视图来提高 SMT 系统的性能(Galley 等人,2006 年;Chiang,2007 年),也可以观察到类似的趋势。然而,这些特征并不是普遍适用的,甚至没有达成共识,往往会导致系统非常复杂(例如,英语语音部分标记的标签集大小可能从几十到几百不等)。

​ 然而,编程语言在这些方面的表现要好得多。每种主流编程语言,如 C、Python 和 Java,都有现成的编译器工具包,可以轻松准确地提取语义信息,如抽象语法树(AST)、与语言无关的中间表示(IR),以及辅助信息,如每个标记的类型和控制/数据流图(CFG/DFG)。因此,在基于变换器的代码语言建模方面,许多研究都将这些特征纳入了训练程序。

6.1 Abstract Syntax Tree and Intermediate Representation

​ AST 是编译过程中最常见的中间结果之一,它将程序解析为一棵操作及其操作数的树。 在 Transformer 在代码处理领域普及之前,已经有 InferCode(Bui 等人,2021 年)等作品利用特殊的网络架构(如基于树的 CNN)处理这些表示,并通过预测子树进行自我监督预训练。

​ TreeBERT(Jiang 等人,2021 年)是将 AST 引入基于变换器的预训练-调整框架的首次尝试之一。它是一个使用树 MLM 和节点顺序预测进行预训练的变换器编码器-解码器,其中编码器将 AST 中的一组组成路径作为输入(每个标记都是一条路径,是其节点表示的连接),而解码器则将代码作为输入。然后,在解码器输入中,通过屏蔽路径表示法中的某些节点及其相应的代码标记来执行树 MLM,而节点顺序预测则是通过交换路径中的节点并用类似于 BERT 的 [CLS] 标记来预测。

​ 不过,TreeBERT 使用的方法比较复杂,扩展性不佳。后来的研究大多选择先将 AST 处理成文本序列,并将其视为输入的正常部分。例如,Wang 等人(2021 年)用深度优先遍历法处理 AST,并将其与代码和注释串联起来,然后用四个目标训练 SynCoBERT(与 TreeBERT 不同,它实际上是一个类似于 BERT 的纯编码器模型): 1) MLM;2) 标识符标记;3) AST 边沿预测(根据两个 AST 节点的点积预测这些节点之间是否存在边沿);4) 对 i) 代码和 AST 对,以及 ii) 文本和代码-AST 对进行对比学习。同样,SPT-Code(Niu 等人,2022 年)是一种转换器编码器-解码器,它将代码、顺序化 AST 和文本的串联作为输入,并通过以下方法进行预训练:1)span 破坏;2)codeAST 预测(NSP,其中一段为代码,一段为 AST);3)方法名生成,这是一种特殊形式的 span 破坏,其中方法名被屏蔽。不过,与其他工作不同的是,他们并不将文档字符串作为输入中的文本段,而是将代码中出现的所有方法名称串联起来,作为简洁的自然语言描述。同样,UniXcoder(Guo 等人,2022 年)在训练过程中也将扁平化的 AST 而不是源代码作为输入。

​ 在编译流水线中,AST 之后通常是与语言无关的中间表示,如 LLVM IR(Lattner 和 Adve,2004 年)。这些特征独立于特定的编程语言,因此适合作为翻译枢纽,就像低资源自然语言机器翻译中的英语一样(Leng 等人,2019 年)。Szafraniec 等人(2023 年)利用这一特点,通过对代码和 IR 以及从代码生成 IR 进行翻译语言建模(Conneau 和 Lample,2019 年),扩展了 Transcoder(Rozière 等人,2020 年)。他们还研究了其他目标,如 IR 反编译(即从 IR 生成代码)和 IR 枢轴(即从另一种语言的 IR 直接生成一种语言的代码),均显示出良好的效果。

6.2 Control Flow and Data Flow

​ 虽然 AST 和 IR 在某些任务(如代码翻译)中被证明是有用的信息,但它们本质上是静态的,就像源代码一样,可能无法捕捉到代码的语义属性,而这些语义属性只有在运行时才会显现出来(Wang 和 Su,2020 年)。 然而,这种语义包含在控制流和数据流等动态特征中。与 AST 类似,在预训练转换器(如 ProGraML 使用的消息传递神经网络)兴起之前,专门的网络也被用来处理此类信息(Cummins 等人,2021 年)。不过,与 AST 不同的是,即使在预训练变换器成为主流之后,也很少有作品关注这一方向。

​ GraphCodeBERT (Guo等、 它为流图中的变量创建了特殊的标记和位置嵌入,并将文本和源代码之后的变量序列串联起来构建模型输入,同时为代码段和变量段量身定制了关注掩码:当且仅当变量从代码标记中识别出来时,代码段和变量段的标记才能相互关注;对于变量段内的标记,如果数据流中存在从 v j v_j vj v i v_i vi的直接边,则允许 v i v_i vi关注 v j v_j vj。然后用 MLM 结合边缘预测和节点对齐对模型进行预训练,这两种方法都是通过两个标记表示(节点对齐时一个来自代码段,一个来自变量段,边缘预测时两个都来自变量段)的点积进行二进制分类来完成的。

6.3 Type

​ 除了 AST、IR 和数据流,类型信息也被用来帮助语言模型处理代码。例如,CugLM(Liu 等人,2020 年)在微调过程中使用类型信息来帮助预测单向 MLM(即具有单向注意屏蔽的 MLM)的标记:首先从最终转换器层的表示预测被屏蔽标记的类型,然后根据隐藏表示和预测的类型来预测标记本身。相比之下,CodeT5(Wang 等人,2021 年)和 SynCoBERT(Wang 等人,2021 年)的预训练目标都包括标识符标记,这可以被视为粗粒度类型预测。

​ 值得注意的是,Wang 等人(2022 年)在 Code-MVP 中集成了前述的许多特征:源代码、文档脚本、AST、CFG,以及通过标识符重命名、循环交换和死代码插入进行转换的源代码。模型由 GraphCodeBERT 初始化,然后通过 MLM、细粒度类型预测和不同视图(如文本与代码、代码与 AST 和代码与 CFG)的对比学习进行训练。

7 LLMs in Software Development

随着语言模型在软件工程基准上创造了新的记录,软件工程技术也反过来扩展了语言模型的边界,并随后将它们引入了现实世界的开发周期。

7.1 LLMs Extended with Coding Tools

​ NLP 界的研究表明,LLM 可以学会使用计算器、MT 系统和搜索引擎等外部工具(Thoppilan 等人,2022 年;Schick 等人,2023 年)。 因此,解释器已被用于在复杂推理任务中增强 LLM。PAL(Gao等人,2023年)和PoT(Chen等人,2022年)都用Python解释器扩展了用于数值计算的Codex,而ViperGPT(Surís等人,2023年)则通过调用视觉应用程序接口进一步扩展了Codex,以便从视觉输入中提取信息并回答相关问题。

​ 除了减轻抽象推理任务中数值计算的负担,解释器还能提供代码生成过程本身的反馈以及单元测试。CodeT(Bareiß等人,2022年)和TiCoder(陈等人,2023年)使用Codex生成单元测试,这些测试针对生成的代码样本运行,以提高模型的代码合成性能。同样,TransCoder-ST(Rozière et al. 在第 5.7 节中,我们还证明了单元测试的执行结果可以作为代码强化学习的自然监督信号。

​ 值得注意的是,2023 年 3 月,OpenAI 还发布了 ChatGPT8 的解释器插件,它可以接受用户的文件输入,根据用户指令生成代码,并通过实时执行提供反馈。Zhou 等人(2023 年)的研究表明,这一功能允许 GPT-4 进行自我调试。

​ 在 LLM 研究中,与工具使用密切相关的一个主题是作为智能代理的规划,这在理论和经验上都被证明可以增强 LLM 的能力(Feng 等人,2023 年)。Ruan 等人(2023 年)发现,LLMs 可以使用外部 SQL 生成器和 Python 生成器规划解决复杂任务,而 CodePlan(Bairi 等人,2023 年)则证明它们可以通过自适应规划执行存储库级编码。

​ 另一系列研究则利用 LLMs 创建代码生成的多代理系统,如自我协作(Dong 等人,2023 年)、ChatDev(Qian 等人,2023 年)和 MetaGPT(Hong 等人,2023 年)。在这些框架中,多个 LLM 被提示扮演不同的角色,如程序员、审查员和管理者。这些角色之间相互影响,将代码生成细分为不同阶段(如设计、编码、测试和记录),并合作完成复杂的任务。

7.2 LLMs Integrated into Software Development

​ 随着 LLM 交互式编码能力的提高,研究人员也开始将 LLM 集成到软件开发的每一个过程中。

​ 自动完成代码是语言模型在软件开发中最早的应用之一,因为它们只需要预测下一个标记的能力。甚至在语言模型扩展到数十亿参数之前,就已经将 Pythia(Svyatkovskiy 等人,2019 年)和 IntelliCode(Svyatkovskiy 等人,2020 年)等补全系统集成到了流行的集成开发环境中。

​ 不过,最近代码语言模型的应用已经超越了简单的代码补全。GitHub Copilot 可以说是最流行的人工智能代码助手之一,具有代码生成、漏洞检测和许可证管理等多种功能9,而 CodeFuse(Di 等人,2023 年)也将代码生成、代码翻译、代码注释和测试用例生成集成到了一个集成开发环境扩展中。然而,随着代码语言模型变得越来越大,其客户端部署和实时性能也提出了新的挑战。

​ 随着 LLM 的不断进步,在其基础上构建应用程序本身也逐渐成为一项重要任务。许多针对此类应用的开源框架已经发布,包括 LangChain、AutoGPT 和 WorkGPT。这些框架为开发人员提供了语言模型的抽象,甚至在本调查报告定稿的同时,它们也在积极革新整个软件开发过程。

8 Conclusion and Challenges

在这项工作中,我们系统地回顾了使用预训练 Transformer 语言模型处理代码的历史,并强调了它们与一般领域预训练模型的关系和比较。代码建模的发展与 NLP 的发展历程大致相同,从 SMT 模型到 NMT 模型,再到预训练 Transformer 的微调,最后到 LLM 甚至自主代理在实际生产中的少量应用。与自然语言不同的是,代码的特性使其很容易从其他视图中提取辅助信息,并利用解释器和单元测试进行自动反馈。

考虑到这些,我们确定了当前代码建模开发中的几个挑战:

​ 将代码 LLM 推向下一阶段的综合基准。广泛使用的 HumanEval 基准在代码 LLM 的发展过程中发挥了关键作用。不过,它的规模相对较小,而且其记分板已被篡改得近乎完美,不能完全反映真实世界的行为。人们还提出了许多其他的代码 LLM 基准,但它们仍不够全面,无法反映生产级的要求。社区迫切希望在 HumanEval 之后有一个新的标准基准,以进一步推动代码 LLM 进入下一阶段。

​ 获取高质量数据。随着 Gunasekar 等人(2023 年)在教科书数据上训练的 1.3B 模型实现了 SOTA 性能,我们相信在不久的将来,训练数据的选择和合成数据的利用将在自我监督预训练和监督微调方面变得更加重要。

将代码特征整合到语言模型中。正如我们在第 6.2 节中所指出的,CFG 和 DFG 还没有在代码语言建模中得到大规模应用。少数采用数据流的研究对模型的注意力掩码进行了修改,这严重限制了它们的跨任务泛化和扩展能力。我们认为,将这些特征无缝集成到文本输入中值得在未来进行研究。

​ 在更多代码下游任务中应用 LLM。正如我们在第3节中指出的,目前对LLMs编码能力的评估主要集中在程序合成方面,图3清楚地显示了与软件测试(即单元测试生成、断言生成、突变体生成和模糊处理)和去混淆相关的任务很少应用LLMs。此外,由于 LLMs 的上下文窗口目前还相当有限,程序合成和代码翻译等生成任务尚未应用到方法级以外的层面。在第 3.4 节中,我们列举了几项关于资源库级代码补全和时态编辑的工作,相信 LLMs 在更多资源库级任务中的应用将成为未来的研究热点。

​ 可供选择的模型架构和训练目标。在表 3 中,我们已经展示了许多代码语言模型在预训练时采用了专门针对代码的辅助目标,但这些模型都属于纯编码器或编码器-解码器系列,而纯解码器模型还有待于用其他目标来增强。此外,正如 Singh 等人(2023 年)所开创的那样,我们相信扩散模型将在未来的代码建模中找到自己的位置。

​ 为软件开发的全生命周期构建代码 LLM 生态系统。虽然学术界出现了大量的代码模型,但大多数都是作为集成开发环境插件部署在编码阶段,而忽略了软件开发生命周期的其他阶段。在第7.2节中,我们提到了几个鼓舞人心的例子,我们希望在软件开发的整个生命周期(从需求分析到DevOps)中看到更多代码有限元的应用,最终形成像PyTorch(Paszke等人,2019)和Hugging Face13那样的全面生态系统。

​ 与代码 LLM 相关的安全和道德问题。随着语言模型日益强大,它们也引发了安全问题,包括但不限于数据污染、有毒或有偏见的生成、个人信息泄露和幻觉。在软件开发中,部署这些模型时应格外谨慎,因为它们生成的代码可能包含导致灾难性结果的安全风险。预训练数据也正在成为一个敏感的道德话题,Kocetkov 等人(2022 年)在这个问题上迈出了有意义的一步,允许开发者从 Stack 中删除自己的代码。随着合成训练数据的普及,研究人员也应谨慎对待这种做法,因为用人工智能生成的数据训练人工智能模型的后果还有待大规模研究。

通过本调查报告,我们希望为语言模型在软件工程中的应用提供一个全局视角,并将这两个领域的研究联系起来。我们相信,当前如火如荼的语言模型研究将最终转化为现实世界的应用,引领人类走向更加光明的未来。

### 回答1: Spark Streaming 和 Flink 都是流处理框架,但在一些方面有所不同。 1. 数据处理模型 Spark Streaming 基于批处理模型,将流数据分成一批批进行处理。而 Flink 则是基于流处理模型,可以实时处理数据流。 2. 窗口处理 Spark Streaming 的窗口处理是基于时间的,即将一段时间内的数据作为一个窗口进行处理。而 Flink 的窗口处理可以基于时间和数据量,可以更加灵活地进行窗口处理。 3. 状态管理 Spark Streaming 的状态管理是基于 RDD 的,需要将状态存储在内存中。而 Flink 的状态管理是基于内存和磁盘的,可以更加灵活地管理状态。 4. 容错性 Flink 的容错性比 Spark Streaming 更加强大,可以在节点故障时快速恢复,而 Spark Streaming 则需要重新计算整个批次的数据。 总的来说,Flink 在流处理方面更加强大和灵活,而 Spark Streaming 则更适合批处理和数据仓库等场景。 ### 回答2: Spark Streaming 和 Flink 都是流处理框架,它们都支持低延迟的流处理和高吞吐量的批处理。但是,它们在处理数据流的方式和性能上有许多不同之处。下面是它们的详细比较: 1. 处理模型 Spark Streaming 采用离散化流处理模型(DPM),将长周期的数据流划分为离散化的小批量,每个批次的数据被存储在 RDD 中进行处理,因此 Spark Streaming 具有较好的容错性和可靠性。而 Flink 采用连续流处理模型(CPM),能够在其流处理过程中进行事件时间处理和状态管理,因此 Flink 更适合处理需要精确时间戳和状态管理的应用场景。 2. 数据延迟 Spark Streaming 在处理数据流时会有一定的延迟,主要是由于对数据进行缓存和离散化处理的原因。而 Flink 的数据延迟比 Spark Streaming 更低,因为 Flink 的数据处理和计算过程是实时进行的,不需要缓存和离散化处理。 3. 机器资源和负载均衡 Spark Streaming 采用了 Spark 的机器资源调度和负载均衡机制,它们之间具有相同的容错和资源管理特性。而 Flink 使用 Yarn 和 Mesos 等分布式计算框架进行机器资源调度和负载均衡,因此 Flink 在大规模集群上的性能表现更好。 4. 数据窗口处理 Spark Streaming 提供了滑动、翻转和窗口操作等灵活的数据窗口处理功能,可以使用户更好地控制数据处理的逻辑。而 Flink 也提供了滚动窗口和滑动窗口处理功能,但相对于 Spark Streaming 更加灵活,可以在事件时间和处理时间上进行窗口处理,并且支持增量聚合和全量聚合两种方式。 5. 集成生态系统 Spark Streaming 作为 Apache Spark 的一部分,可以充分利用 Spark 的分布式计算和批处理生态系统,并且支持许多不同类型的数据源,包括Kafka、Flume和HDFS等。而 Flink 提供了完整的流处理生态系统,包括流SQL查询、流机器学习和流图形处理等功能,能够灵活地适应不同的业务场景。 总之,Spark Streaming 和 Flink 都是出色的流处理框架,在不同的场景下都能够发挥出很好的性能。选择哪种框架取决于实际需求和业务场景。 ### 回答3: Spark Streaming和Flink都是流处理引擎,但它们的设计和实现方式有所不同。在下面的对比中,我们将比较这两种流处理引擎的主要特点和差异。 1. 处理模型 Spark Streaming采用离散流处理模型,即将数据按时间间隔分割成一批一批数据进行处理。这种方式可以使得Spark Streaming具有高吞吐量和低延迟,但也会导致数据处理的粒度比较粗,难以应对大量实时事件的高吞吐量。 相比之下,Flink采用连续流处理模型,即数据的处理是连续的、实时的。与Spark Streaming不同,Flink的流处理引擎能够应对各种不同的实时场景。Flink的实时流处理能力更强,因此在某些特定的场景下,它的性能可能比Spark Streaming更好。 2. 窗口计算 Spark Streaming内置了许多的窗口计算支持,如滑动窗口、滚动窗口,但支持的窗口计算的灵活性较低,只适合于一些简单的窗口计算。而Flink的窗口计算支持非常灵活,可以支持任意窗口大小或滑动跨度。 3. 数据库支持 在处理大数据时,存储和读取数据是非常重要的。Spark Streaming通常使用HDFS作为其数据存储底层的系统。而Flink支持许多不同的数据存储形式,包括HDFS,以及许多其他开源和商业的数据存储,如Kafka、Cassandra和Elasticsearch等。 4. 处理性能 Spark Streaming的性能比Flink慢一些,尤其是在特定的情况下,例如在处理高吞吐量的数据时,在某些情况下可能受制于分批处理的架构。Flink通过其流处理模型和不同的调度器和优化器来支持更高效的实时数据处理。 5. 生态系统 Spark有着庞大的生态系统,具有成熟的ML库、图处理库、SQL框架等等。而Flink的生态系统相对较小,但它正在不断地发展壮大。 6. 规模性 Spark Streaming适用于规模小且不太复杂的项目。而Flink可扩展性更好,适用于更大、更复杂的项目。Flink也可以处理无限制的数据流。 综上所述,Spark Streaming和Flink都是流处理引擎,它们有各自的优缺点。在选择使用哪一个流处理引擎时,需要根据实际业务场景和需求进行选择。如果你的业务场景较为复杂,需要处理海量数据并且需要比较灵活的窗口计算支持,那么Flink可能是更好的选择;如果你只需要简单的流处理和一些通用的窗口计算,Spark Streaming是更为简单的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

计小酱蟹不肉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值