前言
本文精讲代码生成的发展史与其背后的技术原理,总计4个部分
- 第一部分 GitHub copilot的起源:Codex
- 第二部分 微软GitHub copilot
- 第三部分 Code Llama
- 第四部分 CodeGeex
第一部分 GitHub copilot的起源:Codex
我们在这篇文章《ChatGPT技术原理解析:从RL之PPO算法、RLHF到GPT4、instructGPT》中的2.5节有提到,“2021 年7月,OpenAI发布Codex的论文《Evaluating Large Language Models Trained on Code》,其中初始的Codex是根据120亿参数的GPT-3变体进行微调的,且通过对159GB的Python代码进行代码训练,后来这个120 亿参数的模型演变成OpenAI API中的code-cushman-001,具备较强的代码/推理能力”
接下来,我们来看下Codex背后的原理到底是怎样的,即其是如何一步一步训练出来的
1.1 Codex效果的评估
1.1.1 HumanEval:评估Codex的数据集
为了准确地对Codex进行基准测试,OpenAI创建了一个包含164个原始编程问题的数据集,并进行了单元测试
- 这些问题评估语言理解、算法和简单的数学,这个评估框架即指的是HumanEval
- 这164个编程问题全部是手写的,非网上公开的。每个问题包括一个函数签名、文档、函数的实现和几个单元测试,平均每个问题7.7个测试。
We evaluate functional correctness on a set of 164 handwritten programming problems, which we call the HumanEval dataset. Each problem includes a function signature,docstring, body, and several unit tests, with an average of 7.7 tests per problem.
对于这些任务来说,手写是很重要的,因为Codex模型是通过GitHub上的代码训练的,而如果测试的问题是网上公开的,那很可能从GitHub上获取的训练数据集可能已经包含了对应的测试问题及其答案(毕竟本来是要评估模型的回答能力,结果模型直接看到答案了,就没法准确评估了)
It is important for these tasks to be hand-written, since our models are trained on a large fraction of GitHub, which already contains solutions to problems from a variety of sources.
当然,回过头来,如今因为这份数据集公开已久,其实网上也有很多里面的答案了,所以还是会有这个数据泄露问题,成了目前国内各大模型关于评估各自代码能力的开卷考试
为了解决该测试集中的一个个编程问题,让Codex生成了多个答案,并检查其中是否有通过单元测试的样本
- 如果仅用其中一个样本答案,12B参数的Codex就能解决28.8%的问题,但如果通过一些独立的、正确实现的函数再对Codex进行微调,所得到的Codex-S模型用单个样本/答案解决了37.7%的问题(we fine-tune Codex on standalone, correctly implemented functions. The resulting model, Codex-S, solves 37.7% of problems with a single sample)
- 而300M参数的Codex(相当于BERT large版本的大小)则能解决13.2%的问题
- 相比之下,6B参数的GPT- J (Wang & Komatsuzaki, 2021)在同一数据集上取得了11.4%的成绩
- 而不做任何微调的GPT模型的成绩都接近0%
当然,如果对于某个编程问题,让Codex生成100个答案的话,那Codex-S能够为77.5%的问题生成至少一个正确的答案,这一结果表明,可以通过启发式排序来选择准确的代码样本,而不是充分评估每个样本(This result suggests that accurate code samples can be selected via heuristic ranking instead of fully evaluating each sample)
事实上,我们发现对数概率均值最高的样本通过了44.5%问题的单元测试(Indeed, we find that the sample with highest mean log-probability passes unit tests for 44.5% of the problems)
1.1.2 pass@k度量的计算逻辑
代码的生成模型主要通过与参考解决方案匹配样本来进行基准测试,其中匹配可以是精确的或模糊的(如BLEU分数)。然而Ren等人(2020)发现BLEU在捕获特定于代码的语义特征方面存在问题,并建议对分数进行一些语义修改,说白了,BLEU在基于匹配的代码指标方面存在缺陷
- Kulal等人(2019)使用pass@k度量来评估功能正确性,其中每个问题生成k个代码样本,如果有其中任何一个样本/答案是正确的,则认为问题已解决
但每一个问题都针对其对应的k个答案都看一下有没有正确的话,这个过程显得比较繁琐,即便是搜索引擎给出用户搜某个问题的答案时,用户也不会看完显示出来的所有答案,而是根据答案的排序从上至下一个一个看,如果找到了合适的,则不再往下翻 - 所以,最终为了评估pass@k,我们为每个任务生成n≥k个样本(在本文中,使用n = 200和k≤100),统计通过单元测试的正确样本c≤n的数量,并计算无偏估计量「Instead, to evaluate pass@k, we generate n ≥ k samples per task (in this paper, we use n = 200 and k ≤ 100), count the number of correct samples c ≤ n which pass unit tests, and calculate the unbiased estimator」
然后每一次从里面随机采k个出来,看其中有没有符合要求的答案
其中,E是针对所有的问题算一个值,然后做平均,c是指生成的n个答案里面有c个是正确的
是指在n个里面取k个
是在所有非正确的答案n-c中选k个
两者一相除,则意味着选取的k个没有一个是正确答案,1减去这个相除的结果,则代表选取的k个中至少有一个是正确答案 - 由于上述的计算公式涉及到比较大的数相乘,所以可以用这个式子来近似:
为方便大家对上述pass@k的计算过程有更好的理解,故我还是推导下其计算公式
通过最后的式子可知,式子中第二项的分子有2n-c-k项相乘,其分母也有2n-c-k项相乘,很容易导致相乘的结果超出整数所能表示的范围
下面单独分析一下这个式子
然后把上式拆成两项,可得
最后的结果相当于便是论文中的写法,即,从而也就与论文中给出的稳定版的代码一致了
def pass_at_k(n, c, k): """ :param n: total number of samples :param c: number of correct samples :param k: k in pass@$k$ """ if n - c < k: return 1.0 return 1.0 - np.prod(1.0 - k / np.arange(n - c + 1, n + 1))
1.2 Codex的训练全流程
1.2.1 通过159G的Python代码微调GPT3
因为GPT3只能解决一些相对简单的代码问题(因为其训练数据中并没有特别多的代码),但解决不了相对复杂的代码问题,故Codex通过对12B参数大小的GPT3进行微调
其训练数据集是在2020年5月从GitHub上托管的5400万个公共软件库中收集的,一开始有179 GB的Python代码,但随着过滤掉了可能是自动生成的文件,平均行长度大于100,最大行长度大于1000,或者包含一小部分字母数字字符,最终数据集大小为159 GB
1.2.2 训练参数与采样方法核采样
Codex使用与GPT模型相同的学习率,具有175步线性热身和余弦学习率衰减。我们使用具有β1= 0.9,β2= 0.95,=10−8,权重衰减系数为0.1的Adam优化器,对总共1000亿个token进行训练「We train Codex using the same learning rate as the corre-sponding GPT model, with a 175 step linear warmup andcosine learning rate decay. We train for a total of 100 billiontokens, using the Adam optimizer with β1 = 0.9, β2 = 0.95, = 10−8 , and a weight decay coefficient of 0.1.」
之后,有个问题是在预测当前序列的最后一个词时,可以选取概率最大的词(softmax最高的值),但没法全局最优且不具备多样性(因为每次局部最优不代表最终全局最优,且每次都是取概率最大的词,则无论采样多少次,答案都是唯一没有多样性),对此
- 当然 可以使用束搜索来做改进,即一次性获取多个解,比如32个或128个
- 而论文中用的是核采样,预测的各个词根据概率从大到小排序,选取前些个概率加起来为95%的词
第二部分 微软GitHub Copilot
2.1 Your AI pair programmer
据GitHub copilot官方介绍的页面称
- 74%的开发者能够专注于更令人满意的工作、88%的人觉得工作效率更高、96%的开发人员在处理重复性任务时速度更快
- GitHub Copilot适用于任何语言,包括Java, PHP, Python, JavaScript, Ruby, Go, c#或c++
2.1.1 根据注释转换为代码
GitHub Copilot通过OpenAI的Codex演变而来,在大量公共源代码上进行了培训。它既擅长编写自然语言,也擅长编写代码,因此实际上它可以为您完成注释。在下面的例子中,我们首先让它不全我们的注释,然后,一行一行地,完成注释所要求的的功能
2.1.2 创建单元测试
GitHub Copilot的一个重要用例是减少编写单元测试的一些苦差事。比如当我们实现了一个计算两个列表的公共前缀的代码后(we already have an implementation of a function that computes the common prefix of two lists),我们想要测试它
为此,我们导入单元测试包,然后我们开始编写一个测试函数,让Copilot生成asserts,我们只需按Tab键就可以接受这些断言
2.1.3 创建SQL查询
比如我们还可以通过GitHub Copilot把嵌入式SQL生成Go(只需将模式显示为CREATE TABLE语句),具体如下图
// 待更
第三部分 Code Llama:Meta于23年8月底发布
3.1 Code Llama:通过代码数据集微调Llama 2
3.1.1 三个模型:Code Llama、Code Llama - Python、Code Llama - instruction
Code Llama 基于特定的代码数据集在Llama 2上进一步微调训练而成,可以根据代码和自然语言提示生成代码,它还可帮助用户进行代码补全和调试代码
且Code Llama 的开源协议与 Llama 2 一样,免费用于研究以及商用目的, 其对应的论文为《Code Llama: Open Foundation Models for Code》,此为其GitHub 地址
- Code Llama 系列有三类模型:
基础模型Code Llama
专门的Python化模型Code Llama - Python(其基于Code Llama在 Python 代码的 100B token 上进一步微调)
指令遵循模型Code Llama - instruction(Code Llama - Instruct 是 Code Llama 的指令微调和对齐变体,能够更好地理解输入提示) - 且三个参数版本的 Code Llama 模型都使用了 500B 的代码 tokens 和代码相关数据进行训练,同时,每个模型都有三个不同参数规模的版本,参数量分别为 7B、13B 和 34B
并且支持多种编程语言,包括 Python、C++、Java、PHP、Typescript (Javascript)、C# 和 Bash - Code Llama 稳定支持了最高 10 万 token 的上下文生成
3.1.2 Code Llama 的训练数据集
下表为 Code Llama、Code Llama-Python 的训练数据集
- 7B 和 13B 基础和指令模型也经过了 FIM(fill-in-the-middle)训练,从而允许将代码插入到现有代码中,这意味着它们可以支持开箱即用的代码补全等任务
- 有了这三种模型,不同的服务和延迟要求都能得到满足。例如,7B 模型可以在单个 GPU 上运行;34B 模型能够返回最佳结果并提供更好的编码辅助,但就速度而言,较小的 7B 和 13B 模型速度更快,更适合低延迟任务,例如实时代码补全
3.1.3 长上下文微调:类似位置插值微调(fine-tuning by position interpolation)
对长序列的有效处理是基于transformer的语言建模的一个主要研究课题(Vaswani et al., 2017)。基本的建模挑战是外推,即在训练时间以外的序列长度上进行操作,以及有利于在短到中等长度输入上进行训练的注意力传递的二次复杂度(Effective handling of long sequences is a major topic of research in transformer-based language model-ing (Vaswani et al., 2017). The fundamental modeling challenges are extrapolation, i.e., operating on sequencelengths beyond those seen at training time, and the quadratic complexity of attention passes which favors training on short-to-medium length inputs.)
- 对于Code Llama,我们提出了一个专用的长上下文微调(LCFT)阶段,在该阶段中,模型呈现16,384个token序列,高于Llama 2和初始代码训练阶段使用的4,096个token。通过将处理长序列所花费的训练时间限制在微调阶段,获得了远程能力,而不会显著增加训练模型的成本
For Code Llama, we propose a dedicated long context fine-tuning (LCFT) stage in which models arepresented with sequences of 16,384 tokens, up from the 4,096 tokens used for Llama 2 and our initial codetraining stages. By limiting the training time spent on processing long sequences to a fine-tuning stage, wegain long-range capabilities without significantly increasing the cost of training our models. - 我们的策略类似于最近提出的位置插值微调(Chen等人,2023b),并且我们确认了修改Llama 2基础模型中使用的旋转位置嵌入的旋转频率的重要性(Su等人,2021)。然而,我们并没有像Chen等人(2023b)那样线性地降低频率,而是改变了它们的基周期
Our strategy issimilar to the recently proposed fine-tuning by position interpolation (Chen et al., 2023b), and we confirmthe importance of modifying the rotation frequencies of the rotary position embedding used in the Llama 2foundation models (Su et al., 2021). However, instead of downscaling frequencies linearly as Chen et al.(2023b), we change the base period from which they are derived
具体来说,在旋转嵌入中,位置n的query和key向量xn受到线性变换RΘd,n xn,其中RΘd,n是一个分块对角矩阵,其形式为(Specifically, with rotary embeddings, the query and key vectors xn at position n are subject to a linear transformation RΘd,n xn, where RΘd,nis a blockdiagonal matrix with entries of the form)
而d表示嵌入维数。旋转频率计算为θi=θ−2i/d,为了进行微调,我们将基准周期θ从10000增加到1,000,000。这种增加允许处理更大的序列,并减少对短距离注意力的偏差「d denotes the embedding dimension. Rotation frequencies are computed as θi = θ−2i/d , and we increasethe base period θfrom 10,000 to 1,000,000 for fine-tuning. This increase allows for processing much largersequences and reduces bias towards short-distance attention (see Appendix F.1 for further discussion).」
我们的实验证实,Code Llama模型不仅在微调期间使用的增加的序列长度内有效,而且进一步显示外推能力,不仅提供了多达 100000 个上下文 token 的稳定生成,所有模型的训练 token 序列也高达 16000「Ourexperiments confirm that Code Llama models are not only effective within the increased sequence lengthused during fine-tuning, but further show extrapolation capabilities and exhibit stable behavior on very longsequences of up to 100,000 tokens (Section 3.3).」
// 待更
3.2 Code Llama 性能如何
Meta 使用了 HumanEval 和 MBPP(Mostly Basic Python Programming)两个编码基准进行测试。我们已经知道,HumanEval 测试模型基于文档字符串(docstrings)完成代码的能力,而MBPP 则测试模型基于描述编写代码的能力,具体而言
- Code Llama 的不同版本在 HumanEval 和 MBPP 数据集上的一次生成通过率(pass@1)都可以超越 GPT-3.5
- 另外,在 15000 个 Unnatural 指令上微调的 Code Llama-Python 34B 版本在 HumanEval 数据集上的 pass@1 接近了 GPT-4(62.2% vs 67.0%)
Meta 没有发布这个版本,但通过一小部分高质量编码数据的训练实现了明显的效果改进
// 待更
第四部分 从CodeGeex到CodeGeex2
4.1 CodeGeex
关于大型预训练代码模型,除了OpenAI的Codex,还包括
- DeepMind AlphaCode (Li等人,2022)、Salesforce CodeGen (Nijkamp等人,2022)、Meta InCoder (Fried等人,2022)和Google PaLM-Coder-540B (Chowdhery等人,2022)
- CodeGen-16B、gpt - nex - 20b、InCode-6.7B和GPT-J-6B
很快,推出了ChatGLM的智谱AI发布了CodeGeeX,参数规模为13B,在23种编程语言的大型代码语料库上进行了预训练,且支持集成到Visual Studio Code、JetBrains等编译器里
4.1.1 codegeex的核心架构:transformer解码器基础上
与最近的预训练模型(如GPT-3 (Brown等人,2020)、PaLM (Chowdhery等人,2022)和Codex (Chen等人,2021)类似,CodeGeeX遵循生成式预训练(GPT)架构(Radford等人,2018),采用自回归(编程)语言建模的解码器风格
CodeGeeX的核心架构是一个39层的transformer解码器
- 在每个transformer层中,我们应用了多头自注意力机制(Vaswani et al., 2017),然后是MLP层,以及层归一化(Ba et al., 2016)和残差连接(He et al., 2016)
- 使用了GELU(高斯线性单元)操作的近似(Hendrycks and Gimpel, 2016),即FastGELU,它在Ascend 910 AI处理器下效率更高
此外,通过采用GPT范式,我们在大量未标记的代码数据上训练模型。其原理是迭代地将代码token作为输入,预测下一个token,并将其与ground truth进行比较,具体来说
- 当给定任何长度为的输入序列:,codegeex都会预测下一个token的概率,使得,其中表示模型的所有参数,是词汇量
- 通过将其与真实分布(即ground-truth token的one-hot向量进行比较,我们可以优化如下Cumulative cross entropy loss
-
原始的GPT模型使用pooler function来获得最终输出。我们在所有其他transformer层之上使用一个额外的查询层(Zeng et al., 2021),通过attention获得最终的嵌入(如上图所示,he input of the top query layer replaces the query input by the query embedding of position n + 1)。最后的输出再乘以词嵌入矩阵的转置,得到输出概率(The final output is multiplied by the transpose of word embedding matrix to get the output probability)
对于解码策略,CodeGeeX支持贪婪、温度采样、top-k采样、top-p采样和束搜索(greedy, temperature sampling, top-k sampling, top-p sampling, and beam search)
最后,detokenization的操作再将把选中的token ID变成一个实际的单词
4.1.2 codegeex的训练数据与训练细节
训练语料库包含两部分
- Pile包含GitHub上超过100颗星星的公共存储库的一个子集,我们从中选择了23种流行的编程语言的文件,包括c++, Python, Java, JavaScript, C, Go等。我们根据每个文件的后缀和它所属的存储库的主要语言来识别其编程语言。CodeParrot是BigQuery的另一个公共Python数据集
- 第二部分是直接从GitHub公共存储库中抓取的Python、Java和c++的补充数据,这些数据在第一部分中没有出现。我们选择至少有一个星,总大小在10MB以内的仓库,然后我们过滤掉:1)平均每行字符超过100个,2)自动生成的,3)字母比例小于40%,4)大于100KB或小于1KB的文件,且按照PEP8标准对Python代码进行格式化
为提高训练效率
- 采用了8路模型并行训练和192路数据并行训练,启用了ZeRO-2优化器(Rajbhandari等人,2020),以进一步减少优化器状态的内存消耗。最后,每个节点的微批大小为16,全局批大小达到3072
To increase training efficiency, we adopt an 8-way model parallel training together with 192-way data parallel training, with ZeRO-2 (Rajbhandari et al., 2020) optimizer enabled to further reduce the memory consumption of optimizer states. Finally, the micro-batch size is 16 per node and the global batch size reaches 3,072. - 具体来说,我们使用Adam优化器(Kingma and Ba, 2014)来优化下述方程中的损失
模型权重均采用FP16格式,但为了更高的精度和稳定性,我们使用FP32进行层归一化和softmax。该型号大约需要27GB的GPU内存。我们从初始学习率1e-4开始,并应用余弦学习率衰减Specifically, we use Adam optimizer (Kingma and Ba, 2014) to optimize the loss in Equation 2.The model weights are under FP16 format, except that we use FP32 for layer-norm and softmax for higher precision and stability. The model takes about 27GB of GPU memory. We start from an initial learning rate 1e-4, and apply a cosine learning rate decay by:
4.2 CodeGeeX2:基于 ChatGLM2 架构加入代码预训练
4.2.1 CodeGeeX2:通过600B代码预训练且最终+107% > CodeGeeX
据CodeGeeX2的GitHub页面所称,其是多语言代码生成模型 CodeGeeX (KDD’23) 的第二代模型。不同于一代 CodeGeeX(完全在国产华为昇腾芯片平台训练)
- CodeGeeX2 是基于 ChatGLM2 架构加入代码预训练实现(至于ChatGLM2的介绍详见此文的第三部分:ChatGLM两代的部署/微调/实现:从基座GLM、ChatGLM的LoRA/P-Tuning微调、6B源码解读到ChatGLM2的微调与实现)
- 得益于 ChatGLM2 的更优性能,CodeGeeX2 在多项指标上取得性能提升(据称,+107% > CodeGeeX;仅60亿参数即超过150亿参数的 StarCoder-15B 近10%)
更多特性包括:
- 更强大的代码能力:基于 ChatGLM2-6B 基座语言模型,CodeGeeX2-6B 进一步经过了 600B 代码数据预训练,相比一代模型,在代码能力上全面提升,HumanEval-X 评测集的六种编程语言均大幅提升 (Python +57%, C++ +71%, Java +54%, JavaScript +83%, Go +56%, Rust +321%),在Python上达到 35.9% 的 Pass@1 一次通过率,超越规模更大的 StarCoder-15B
- 更优秀的模型特性:继承 ChatGLM2-6B 模型特性,CodeGeeX2-6B 更好支持中英文输入,支持最大 8192 序列长度,推理速度较一代 CodeGeeX-13B 大幅提升,量化后仅需6GB显存即可运行,支持轻量级本地化部署
- 更全面的AI编程助手:CodeGeeX插件(VS Code, Jetbrains)后端升级,支持超过100种编程语言,新增上下文补全、跨文件补全等实用功能。结合 Ask CodeGeeX 交互式AI编程助手,支持中英文对话解决各种编程问题,包括且不限于代码解释、代码翻译、代码纠错、文档生成等
- 更开放的协议:CodeGeeX2-6B 权重对学术研究完全开放,填写登记表申请商业使用
我们开发了支持 VS Code、 IntelliJ IDEA、PyCharm、GoLand、WebStorm、Android Studio 等IDE的 CodeGeeX 插件。在插件中,可以更直接地体验到 CodeGeeX2 模型在代码生成与补全、添加注释、代码翻译及技术问答方面的能力为开发效率带来的提升。欢迎在IDE中下载 CodeGeeX 插件获得更加全面的AI编程体验,详情见CodeGeeX主页
4.2.2 CodeGeeX2的快速使用
4.2.2.1 使用transformers
快速调用CodeGeeX2-6B
from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("THUDM/codegeex2-6b", trust_remote_code=True)
model = AutoModel.from_pretrained("THUDM/codegeex2-6b", trust_remote_code=True, device='cuda')
model = model.eval()
# remember adding a language tag for better performance
prompt = "# language: Python\n# write a bubble sort function\n"
inputs = tokenizer.encode(prompt, return_tensors="pt").to(model.device)
outputs = model.generate(inputs, max_length=256, top_k=1)
response = tokenizer.decode(outputs[0])
>>> print(response)
# language: Python
# write a bubble sort function
def bubble_sort(list):
for i in range(len(list) - 1):
for j in range(len(list) - 1):
if list[j] > list[j + 1]:
list[j], list[j + 1] = list[j + 1], list[j]
return list
print(bubble_sort([5, 2, 1, 8, 4]))
4.2.2.2 启动 Gradio DEMO
python ./demo/run_demo.py
usage: run_demo.py [-h] [--model-path MODEL_PATH] [--example-path EXAMPLE_PATH] [--quantize QUANTIZE]
[--chatglm-cpp] [--fastllm] [--n-gpus N_GPUS] [--gpu GPU] [--cpu] [--auth] [--username yourname]
[--password yourpassword]
[--port PORT] [--listen ADDRESS]
# 若要启用身份验证,请先启用--auth,然后定义--username与--password,如:
python run_demo.py --auth --username user --password password # 若要监听所有地址请指定 --listen 0.0.0.0
支持使用 ChatGLM.cpp 量化推理加速:
python ./demo/run_demo.py --quantize 4 --chatglm-cpp
4.2.2.3 启动FAST API
python ./demo/fastapicpu.py
usage: fastapicpu.py [-h] [--model-path MODEL_PATH] [--listen ADDRESS] [--port PORT] [--workders NUM] [--cpu] [--half] [--quantize QUANTIZE] [--chatglm-cpp]
# --cpu启用cpu --half启用.half()
支持使用 ChatGLM.cpp 量化推理加速,同样添加 --quantize 4 --chatglm-cpp
参数即可。
4.2.2.4 API使用示例
curl -X POST "http://127.0.0.1:7860" \
-H 'Content-Type: application/json' \
-d '{"lang": "Python", "prompt": "# Write a quick sort function"}'
// 待更