大语言模型在代码生成领域取得了令人瞩目的进展。本文整理自字节跳动产品研发和工程架构部的代码智能助手架构师刘夏在 AICon 2024 北京 的演讲《代码生成 Copilot 产品的应用和演进》,聚焦基于大语言模型的代码生成技术,深入探讨了代码补全和代码编辑这两种典型的应用形态。同时,还分析了当前代码补全面临的挑战和局限性,阐述了代码编辑是如何在交互和构建方法上实现创新。内容经 InfoQ 进行不改变原意的编辑。
此外,大会还进一步策划了《低代码与 AI 结合》专题,来自腾讯、网易、蚂蚁集团的技术专家,将在现场深度探讨在低代码环境中集成智能决策、自动化流程,以及构建灵活、高效的应用系统。目前,大会议程已全部上线,感兴趣的同学请锁定大会官网:https://archsummit.infoq.cn/2024/shenzhen/schedule。
首先,回顾一下代码生成 Copilot 这种产品形式。当我们谈论代码生成 Copilot 或者 Copilot 这个词时,不得不提到 GitHub 在 2021 年 6 月推出的 GitHub Copilot。这个产品不仅拥有一个响亮的名字,而且定义了一种新的 AI 产品的范式。GitHub Copilot 在 2021 年 6 月推出了技术预览版,随着不断的迭代,其效果令人印象深刻,使人们意识到将大语言模型应用于代码生成领域具有巨大的潜力。业界也开始迅速构建类似的产品,无论是在模型还是产品上都取得了快速的迭代。
这里有一个关键问题:为什么是 GitHub Copilot 引爆了这个热点?实际上,将自然语言处理(NLP)技术应用于代码生成并不是一个新概念,例如 TabNine 这样的产品在 GPT-2 时代就已经将其应用于代码补全。那么,GitHub Copilot 究竟有何特别之处呢?我们想要从几个方面和维度来探讨这个问题。
首先,我想提到团队,GitHub Next 是这个产品的孵化团队。GitHub Next 是一个具有研究属性的团队,他们的任务是探索未来软件开发的新方式。如果访问他们的官网,你会发现许多有趣的项目,其中就包括 Copilot。团队主要由程序分析师、软件工程师以及研究员组成,他们持续关注的一个重要话题是如何实现通用的代码生成。
接下来,我想谈谈一个重要的契机,那就是 2020 年 6 月 GPT-3 的问世。由于 GitHub 现在是微软的子公司,而微软与 OpenAI 有着深入的合作,GitHub 团队很早就获得了 GPT-3 的预览版,并对其能力感到非常兴奋。他们认为必须利用 GPT-3 在代码生成领域做出一些创新,因此与 OpenAI 紧密合作,基于 GPT-3 迭代开发出了专门用于代码的大型语言模型 Codex。随后,他们对 Codex 进行了持续的微调训练,打造了专属的模型。一个强大且优秀的基础模型实际上决定了产品的上限,因此 GPT-3 的出现对这款产品的贡献是巨大的。
有了模型之后,团队开始思考应该开发什么样的产品形态。根据 GitHub 的分享,他们最初的想法是开发一款 Chatbot,即一款能够解答编码过程中遇到的任何问题并提供代码的对话聊天产品。但他们很快发现,尽管知识库中大部分问题都能得到回答,但只有大约 20% 的回答是正确且被接受的。尤其是在 GPT-3 时期,ChatGPT 还要两年后才出现,他们意识到这种 Chatbot 产品的效果并不理想。如果大部分时候给出的答案都不是用户想要的,用户对产品的信任度会很低。于是他们决定先采用代码补全这种容错率更高的产品形态,一方面代码补全是个开发者使用频率非常高的功能,也有很强的依赖性,更重要的是开发者对于这个功能的预期是给出建议而不是 100% 准确的答案。
选择好产品形态后的一个要素是交互方式。GitHub Copilot 放弃了传统 IDE 中从下拉列表选择补全建议的交互,而是选择了用 Ghost Text 进行展示,用 Tab 键进行采纳,继续输入则取消推荐。这种交互方式发挥了模型在多行补全上的优势,推荐代码和已有代码融为一体,方便开发者快速基于上下文判断是否采纳。
代码补全产品的一个技术挑战是实现低延迟,Jetbrains 在开发传统的补全功能时甚至要求在 150ms 内出现推荐列表以达到最佳的开发者体验。因为专业开发者的输入速度通常较快,过高的延迟会失去很多推荐的机会或者迫使用户停顿等待。GitHub Copilot 在大语言模型的推理速度和工程链路上进行了优化,让一个基于云端推理的 LLM 应用做到 500ms 左右的平均延迟。
如果说基座模型决定了产品能力的上限,那么提示工程所做的努力就是去逼近这个上限。通过研究开发者日常开发中会关注的上下文,在 prompt 中加入文件路径、相似代码、浏览记录等信息,让模型在代码补全方面的表现大幅提升,如今这些提示工程上的实践也被大家广泛应用。
字节跳动在内部探索代码生成的过程中,面临多种优化选择:可以在模型层面进行优化,也可以选择在工程链路上优化,或在交互体验上进行改进。团队需要灵活地做出决策。
随着大语言模型的发展,特别是从 2023 年开始,这个领域开始受到广泛关注,新的模型和产品层出不穷。为了迭代和优化模型,字节跳动首先建立了自己的评测方法和自动化评测系统。这涉及到模型选型的决策,快速评估训练过程中的 checkpoint 效果,以及产品上线后如何收集线上反馈,包括用户编辑过程中的正反馈和负反馈。字节跳动还建立了一个完整的数据链路,以决定哪些数据被采纳,哪些被丢弃,并实施 A/B 测试系统来验证不同的 prompt 策略、参数配置,甚至是新模型的上线效果。字节跳动的自研大语言模型也已经发布,团队逐渐切换到这个自研模型上。基于此,字节跳动引入了对话方式,使代理模型能够理解整个工程结构,并根据实际情况生成代码。此外,还引入了多点代码编辑推荐功能,这是一个较新的功能。今天的分享将围绕三个重点进行详细分析:
构建自研评测体系的重要性;
如何科学定义产品指标;
A/B 测试的重要性。
构建自研评测体系的重要性在于,它可以帮助我们避免使用不恰当的评测指标,如 HumanEval,它可能无法准确反映模型在实际应用中的表现。HumanEval 通过完成人工编写的算法题并运行单元测试来评估模型,虽然模型在测试的分数可能很高,但这并不意味着模型在代码补全产品中的表现就一定好。例如,GitHub Copilot 在 HumanEval 上的得分可能不高,但其用户体验仍然出色。
自建评测集可以避免数据泄露问题,确保题目和答案不会被模型提前接触到。同时,自建评测集可以引入真实项目中的跨文件上下文,这对于评估模型能否合理利用上下文信息至关重要。此外,自建评测集还可以引入大量公司内部代码,因为开源代码与内部代码的使用场景和分布可能存在显著差异。评测体系还需要包括基于单元测试的验证方式,因为同一功能可能有多种不同的代码实现方式,而单元测试可以更准确地验证生成代码的正确性。
最后,安全的自动化评测系统对于模型迭代至关重要。它不仅可以通过执行结果来验证代码的正确性,还可以防止模型生成有害代码,如删除根目录或造成大量内存分配等问题。高效的沙箱测试环境和高并发支持对于大规模的评测也是必不可少的。通过这样的评测系统,我们可以在训练过程中对不同 checkpoint 的模型效果进行评估,从而为模型选型和迭代提供有力支持。
在科学地定义指标时,我们需要考虑代码补全流程中的各个环节,并确保所选指标能够准确反映产品优化的需要。一个有效的指标应该能够指导整个链路的优化,帮助我们识别瓶颈并进行相应的调整。采纳率是一个常被提到的指标,它通常定义为采纳次数除以推荐次数。虽然这个定义简单,但它并不是一个好的指标。首先,采纳率容易被操纵。例如,如果减少推荐次数,只在非常确定的时候去帮你补一个分号,采纳率就会提高,但这并不意味着产品的实际效果有所提升。其次,采纳率没有很好地拆解推荐和采纳过程中的具体因素,无法明确指出是推荐更快了,还是其他因素导致采纳次数增多。
体验指标是另一个需要考虑的方面。当用户在使用代码补全产品时,如果一个 Tab 操作就能接受推荐的代码并完成工作,这自然会带来良好的用户体验。体验指标可以反映用户对产品的满意度,但它并不直接指导产品优化的方向。在定义指标时,我们需要更细致地考虑如何反映产品的实际性能和用户体验,同时避免指标被操纵,并确保指标能够指导我们进行有效的产品迭代和优化。
在探讨如何科学地定义指标时,引入了 CPO(Character per opportunity)这一指标,它是由一家专门从事代码补全产品的公司提出的。CPO 的计算公式由五个因子相乘得到:尝试率、反馈率、采纳率、每次采纳平均的 token 数以及 token 的平均字符长度。
尝试率指的是用户在编辑器中进行操作时,AI 提供建议的频率。例如,如果用户敲击键盘 10 次,但只有 6 次触发了对模型的请求,尝试率就是 6/10。这个指标反映了 AI 实际为用户提供建议的次数。
反馈率考虑了 AI 给出补全建议时存在的延迟问题。如果因为延迟太高,开发者已经进行了其他操作,那么即使推荐返回了也没有意义。如果发起 6 次请求,最终只有 3 次被展示,反馈率就是 3/6。
采纳率是大家熟悉的指标,即用户接受推荐的次数与推荐次数的比值。例如,三次推荐中只有一次被采纳,采纳率就是 1/3。
引入每次采纳平均的 token 数和 token 的平均字符长度这两个参数,是为了衡量不同长度代码带来的价值。不同的语言模型有不同的分词器,因此需要计算每个 token 平均的字符长度。例如,ChatGPT 的词表较大,平均一个 token 可以生成的字符数可能大于其他模型。
CPO 指标的计算公式是这几个因子的乘积,它衡量的是在每次有机会向用户推荐时,推荐了多少字符给用户。这个指标不仅可以衡量产品给开发者带来的价值,还可以拆解到整个链路的各个部分进行优化。例如,可以通过优化模型推理性能,提高反馈率,或者在代码注释中提供推荐来优化尝试率。此外,当线上出现问题时,CPO 指标也可以用来分析可能存在的问题所在。
A/B 测试在产品开发过程中扮演着至关重要的角色。尽管离线评测可以帮助我们进行模型选型,但一个模型是否真正有效,还需要通过线上测试来验证。有时候,一个模型在评测中得分很高,但这并不代表它在线上的实际表现同样出色。例如,一个非常强大的模型如 GPT-4,可能会因为高延迟而影响用户体验。
A/B 测试还可以帮助我们确定各种参数配置的合适值。比如,如果一个模型支持 16K 的上下文长度,是否就应该使用完整的 16K 呢?实际上,如果上下文过长,可能会导致整体延迟增加,影响用户体验。因此,需要通过 A/B 测试来找到最合适的上下文长度。
此外,A/B 测试还可以验证新的提示工程策略的效果。例如,如果我们在模型中加入了函数签名或其他包结构信息,是否真的能提升效果?模型是否能够有效利用这些上下文?以及为了采集这些上下文信息而引入的额外延迟,是否值得?这些问题都需要通过 A/B 测试来验证。
最后,A/B 测试还可以帮助我们发现并改进产品指标。假设我们最初使用的是采纳率作为指标,但在进行 A/B 测试后,我们发现延迟提高后,采纳率反而增加了。这种情况可能表明我们的指标存在问题,需要重新考虑和调整。
代码补全的进化形式可以被视为代码编辑推荐。大语言模型擅长生成下一个 token,这与代码补全或续写任务非常契合。然而,传统的代码补全主要针对编写全新代码的场景,而软件工程师在日常工作中不仅需要编写新代码,还需要编辑现有代码,包括重构和删除代码。在这些场景下,传统的补全功能可能无法高效地满足需求。在编辑现有代码时,简单地删除一行然后重新编写是低效的。理想情况下,我们希望模型能够自动完成新增、删除、替换等操作,从而提高代码编辑的效率。因此,代码编辑推荐作为代码补全的进化,能够更好地适应软件工程师在实际工作中的各种代码操作需求,提供更加全面和智能的代码辅助功能。
代码编辑推荐的概念涉及到一种更高级的代码辅助功能,它不仅包括传统的代码补全,还涵盖了对代码进行更深层次的理解和编辑。例如,假设你写了一个 log 函数,该函数用于打印一个 message,并且有两个函数作为调用方来使用这个 log 函数。
如果你决定给 log 函数添加两个新的参数,比如 sourceMethod 和 level,用以打印出对应的方法名称和日志等级,这时你实际上需要执行两个后续操作:首先,在 print 语句中添加新参数,以便能够打印出这些新信息;其次,在所有的调用方中也添加这些新参数,确保它们能够传递正确的值给 log 函数。
在这种情况下,代码编辑推荐的目标是让模型在你添加完新参数后,能够自动帮你完成剩余的内容。理想状态下,当你完成添加参数的操作时,模型已经预测出你需要在 print 语句中加入这些参数,并且在你移动到调用方时,模型已经知道你接下来需要在这些调用点添加新参数。
在 Go 语言中,如果你有一个结构体并且希望它在多线程环境下保持线程安全,通常会引入互斥锁(mutex)来实现。在这种情况下,你需要在结构体的初始化(new)、设置(set)和获取(get)方法中添加锁操作。智能的代码编辑推荐系统应该能够预测到你接下来需要进行的操作。例如,当你在 new 函数中添加锁时,推荐系统可以自动提示你在 set 和 get 方法中也添加相应的加锁代码。当你的光标移动到相应的方法上时,推荐系统就可以给出这些建议。
数据构建和模型训练是提升代码生成能力的关键环节。模型的能力来源于数据,尤其是 Git 仓库中海量的 commit 数据,这些数据包含了丰富的用户编辑信息。
现有的模型训练并没有充分利用这些数据,因为它们往往包含噪音,例如在 commit 信息中夹带无关内容。因此,需要通过启发式规则或模型来过滤掉这些噪音,提取出有相关性和逻辑关系的编辑操作。
在编辑过程中,修正 Lint 错误是一个常见任务,这些错误信息及其修复方式也是非常宝贵的数据资源。在训练模型时,通常会选择一个基于大型代码表示模型作为基础,并通过持续训练和 SFT(Supervised Fine-Tuning)等方法让模型理解代码变更的差异。
模型在修正代码时可能会出现过度编辑的情况,即模型可能会过于激进地进行不必要的修改。因此,需要采取措施抑制这种行为,确保模型的编辑是恰当和准确的。
在进行中的优化方面,我们认识到目前的交互体验和展示方式可能并非最理想的状态。我们认为,集成在集成开发环境(IDE)中并进行一些 UI 上的定制,可能会带来更好的用户体验。
此外,我们已经在内部支持了对链接错误(Link Error)和警告(Warning)的修复功能。这是一个重要的进步,因为它能够帮助开发者更快速地解决编译时遇到的问题。
我们还在探索光标移动的自动识别和推荐功能。目前,模型通常需要等到开发者的光标移动到特定位置后才能进行预测和推荐。我们希望优化这一点,让模型在开发者完成编码时就能预测下一步可能的编辑位置,并直接提供相应的推荐。这样的优化将进一步提升代码编辑的流畅性和效率。
对于代码生成模型来说,一个明显的趋势是能够处理更长的上下文。理想情况下,模型能够理解整个代码仓库的内容。目前,K 级别和 M 级别的上下文可能还不够,模型需要能够无限地处理上下文信息。谷歌等公司已经提出了相关计划。但随着上下文的增长,保持推理速度不降低也是一个挑战,需要维持在几百毫秒的水平。一些公司如 Magic.dev 和 Supermaven 正在探索使用非 Transformer 架构来实现这一点。
对于产品形式,完全自主的 Agent 可能不太适合复杂的任务开发。程序员有时可能想用自然语言或注释来描述编码意图,但由于自然语言的局限性和文档编写的困难,最好的做法可能是 AI 与开发者通过交互的方式反复构思确认,并迭代完成复杂功能的开发。
AI 应该更智能地识别人类的意图,例如通过编辑位置的预测来主动参与编码过程,提前帮助预判并提供推荐。虽然这个概念比较抽象,但最近出现了一些体现这一思路的例子。Replit 公司开发的代码修复 Agent 展示了 AI 作为一个虚拟协作者参与交互过程的能力。在多人协同的 IDE 中,AI 能够发现错误并以协作者的身份帮助修正,这是一种有效的主动式 AI 交互方式。
明尼苏达大学的研究 “Sketch Then Generate” 展示了一种人与 AI 交互持续迭代的方法。通过编写有结构化的注释来指导模型,这些注释可以与代码的实体、符号、方法关联起来,先构建代码架构,然后逐步指导模型生成更多细节和代码。
代码生成 Copilot 的未来将更加注重上下文理解、交互式产品开发、智能意图识别和人机协同工作,以实现更高效和智能的代码生成和编辑体验。
6 月份,我们团队将把融合了团队技术和创新理念,基于内部大量研发实践打磨的 AI 编程工具 MarsCode 对外发布,旨在为广大开发者提供更便捷和高效的编程体验。
扫码添加 MarsCode 小助手,获取抢先体验名额
距离 ArchSummit 深圳开幕倒计时 2 天,6 月 14 日 -15 日,一起探索大模型时代的软件架构最佳范式。如您感兴趣,可点击「阅读原文」查看更多详情。购买票数越多,享受的优惠也就越丰厚,可以联系票务经理 17310043226 , 锁定最新优惠,期待与你的现场交流~