OPEN CODER : THE OPEN COOKBOOK FOR TOP -TIER CODE LARGE LANGUAGE MODELS
Abstract
大型语言模型(LLMs)在代码领域已经成为不可或缺的工具,包括代码生成、推理任务和代理系统等多个方面。虽然开放获取的代码LLMs的性能越来越接近专有模型,但适合严格科学研究的优质代码LLMs,特别是那些具有可复现数据处理管道和透明训练协议的模型,仍然很有限。这种稀缺性是由于各种挑战造成的,包括资源限制、伦理考虑以及保持模型先进性的竞争优势。为了填补这一空白,我们推出了Open-Coder,这是一款顶级代码LLM,其性能不仅可与领先模型相媲美,而且还是研究社区的“开放食谱”。与大多数之前的努力不同,我们不仅发布了模型权重和推理代码,还发布了可复现的训练数据、完整的数据处理管道、严格的实验消融结果以及详细的训练协议,以供开放科学研究使用。通过这一全面的发布,我们确定了构建顶级代码LLM的关键要素:(1)针对数据清洗和去重的方法优化启发式规则,(2)回忆与代码相关的文本语料库,以及(3)在退火和监督微调阶段的高质量合成数据。通过提供这种开放程度,我们旨在拓宽对顶级代码LLM所有方面的访问,让OpenCoder既作为一个强大的模型,又作为一个开放的基座,以加速研究进程,并使代码AI的可复现进步成为可能。
Introduction
OpenCoder 的发布旨在解决以下三个关键问题:
- 提供研究基准:OpenCoder 为机械可解释性和代码 LLM 数据分布研究提供了一个精心策划且透明的强基准,方便学者们进行深入分析。
- 深入探究训练数据:OpenCoder 的发布将推动对更强大的代码 LLM 预训练和指令数据收集流程的深入研究,以提升模型的性能。
- 促进开源社区发展:OpenCoder 的开放性将激发开源代码 LLM 社区的发展,促进代码 AI 的可复现进步。
OpenCoder 的主要优势:
- 透明性:OpenCoder 提供了完整的训练数据流程、可复现的数据集、中间检查点和训练协议,确保研究的可重复性和可验证性。
- 高质量数据:OpenCoder 使用了经过精心清洗和去重的数据,包括 RefineCode 数据集和代码相关的网络数据,保证了模型的性能和泛化能力。
- 先进的训练方法:OpenCoder 采用了两阶段指令微调策略,并结合了高质量合成数据,使模型在理论和实践编码任务上均表现出色。
- 开源:OpenCoder 是完全开源的,任何人都可以访问和使用其代码和数据,促进代码 AI 领域的开放合作和创新。
Pretraining Data
OpenCoder 的预训练数据由两部分组成:原始代码数据和代码相关的网络数据。
1. 原始代码数据:
- 主要来自 GitHub,截止到 2023 年 11 月。
- 也包含来自 The Stack v2 的非 GitHub 数据。
- 经过数据清洗和去重,保留 607 种不同编程语言的文件。
2. 代码相关的网络数据:
- 主要来自 Common Crawl 数据集。
- 经过人工标注,提取与代码相关的文本数据。
- 包含来自 FineWeb、Skypile 和 AutoMathText 的数据。
原始代码数据
数据处理流程:
- 预处理:
- 排除文件大小超过8MB的文件,因为这些文件通常是非文本文件,需要大量计算资源。
- 根据文件扩展名,只保留与编程语言相关的文件类型。
- 过滤掉低质量或低容量的文件类型。
- 去重:
- 精确去重: 使用SHA256哈希值来识别和删除完全相同的代码文件。
- 模糊去重: 使用MinHash和LSH算法来识别和删除高度相似的代码文件。
- 转换:
- 版权信息移除: 删除代码文件开头的版权信息,例如 “Copyright Intel Corporation © 2014-2016”。
- 个人身份信息 (PII) 减少: 使用正则表达式识别和替换PII信息,例如密码、电子邮件地址等。
- 过滤:
- 基于代码特性,设计启发式规则来过滤低质量代码,例如:
- 过滤掉自我包含性差的代码文件。
- 过滤掉逻辑结构差或缺失的代码文件。
- 移除与标准格式差异较大的代码文件。
- 针对不同编程语言,设计了更具体的过滤规则,例如:
- Python: 过滤掉函数数量过多且函数体过短的文件。
- C/C++: 过滤掉无法解析为AST的文件。
- JavaScript: 过滤掉导入语句比例过高的文件。
- 基于代码特性,设计启发式规则来过滤低质量代码,例如:
- 数据采样: 对部分语言数据进行下采样,以平衡数据集规模。主要是Java与HTML,最终得到730B tokens预训练数据。
使用PCA to visualize the embeddings extracted from Code-BERT (Feng et al., 2020) for The Stack V2 and RefineCode, and observe a clear distinction between these datasets. The Stack V2数据表现出更多的异常值,而RefineCode的嵌入向量则看起来更为紧密地聚集在一起。此外,在分析异常值数据后,我们观察到异常值通常表现出许多低质量模式,例如纯文本注释、仅包含十六进制的数据以及缺乏计算逻辑的过短代码,这些模式可能会扭曲预训练数据集的分布,最终阻碍预训练的效率。
代码相关的网络数据
这部分数据主要来自 Common Crawl 数据集,并经过人工标注和筛选,以提高数据质量。
1. 数据来源:
- 主要来自 Common Crawl 数据集,这是一项大规模的网页抓取项目,包含海量网页数据。
- 也包含来自 FineWeb、Skypile 和 AutoMathText 的数据,这些数据集是专门收集与代码和数学相关的文本数据。
2. 数据处理流程:
- 人工标注:
- 为了从 Common Crawl 中提取高质量的代码相关数据,作者首先标注了 50 万个高质量的代码样本文本,作为训练 fasttext 模型的种子数据。
- 这些数据经过训练后,被用于识别和召回 Common Crawl 中的代码相关文本。
- 领域发现:
- 通过统计分析召回数据的域名 URL,将具有相同基础 URL 的网页视为同一域名,并定义包含超过 10% 代码相关网页的域名为代码相关域名。
- 作者还手动标注了 Common Crawl 中与代码和数学相关的域名,例如 Stack Overflow、GitHub 等。
- URL 标注:
- 对代码相关域名中的 URL 进行人工标注,确定哪些 URL 与代码内容相关。
- 例如,作者将 Stack Overflow 上的所有内容都标注为计算机技术问题,并将与这些 URL 匹配的样本纳入代码种子语料库。
- 迭代优化:
- 通过迭代的方式,不断优化种子语料库的质量和多样性,最终获得约 220GB 的代码相关网络数据。
最终,得到了一个高质量的代码预训练数据集 RefineCode,包含大约 9600 亿个token。数据源的组成如表 2 所示,而不同程序语言的分布则展示在图 4 中。有关不同程序语言数据组成的更多细节,请参考附录 F。为了证明 RefineCode 的有效性,我们使用 RefineCode 的数据和 The Stack v2 的训练子集分别训练了一个 15 B 和 600 B的代码大语言模型。图 1 的结果表明,与 The Stack v2 相比,RefineCode 显著提高了训练效率,突显了我们数据集的优越性。
Annealing Data
OpenCoder 的训练过程分为三个阶段:通用预训练阶段、退火阶段和指令微调阶段。在本节中,我们将重点介绍退火阶段的数据,包括数据来源、处理方法和优势。
1. 数据来源:
- 原始数据分布 (Original Distribution Data): 为了保证退火阶段的数据分布与预训练阶段相似,确保模型知识的连贯性,84% 的退火数据来自 RefineCode 的原始数据分布。
- 算法语料库 (Algorithmic Corpus): 为了提升模型的自包含性,并使其更适应实际场景中常见的独立任务,我们从原始预训练数据中采样包含关键词“LeetCode”、“def solution”或“class solution”的数据,构建算法语料库。
- 合成数据 (Synthetic Data): 为了进一步提升模型的能力,OpenCoder 在退火阶段引入了高质量的合成数据,包括:
- 高质量代码片段 (High Quality Code Snippet): 受益于 CodeExercises 数据集的启发,我们使用算法语料库作为种子数据,利用强大的 LLM 生成一系列独立函数及其对应的测试用例。只有通过测试用例的样本才被保留在退火阶段的数据集中。
- 代码教材 (Code Textbooks): 为了使模型能够从多个角度理解代码,我们基于 hqcode 数据集构建了教育性文本片段。LLM 对数据集中的代码进行分析,提取并阐述抽象代码知识,从而帮助模型从不同角度学习代码。
2. 数据处理方法:
- 数据混合 (Data Mixture): 退火阶段的数据混合策略旨在平衡不同类型数据的影响,确保模型在不同方面的能力都能得到提升。具体而言,原始数据分布占据主导地位,算法语料库和合成数据作为补充,共同提升模型的表现。
- 数据清洗 (Data Cleaning): 与预训练阶段类似,退火阶段的数据也进行了严格的清洗,包括去除版权信息、个人身份信息等,以确保数据的质量和安全性。[25-26]
Pretraining
模型架构
- 模型规模:OpenCoder提供了两个不同规模的模型,分别是1.5B参数和8B参数,与Llama-3.1-8B架构类似。
- 1.5B模型:包含24层,隐藏层维度为2240,注意力头数为14,键/值头数为14,支持上下文窗口大小为4096。
- 8B模型:包含32层,隐藏层维度为4096,注意力头数为32,键/值头数为8,支持上下文窗口大小为8192。
- 激活函数:两个模型均使用SwiGLU激活函数。
- 词汇表大小:96640,使用了特定的分词器。
训练数据
- 数据来源:训练数据包括中文、英文以及607种编程语言,具体编程语言列表见附录E。
- 数据量:
- 1.5B模型:在2万亿个token上进行了4个epoch的训练,随后在额外的1000亿个token上进行了退火训练。
- 8B模型:在2.5万亿个token上进行了3.5个epoch的训练,随后在额外的1000亿个token上进行了衰减阶段的训练。
训练过程
- 学习率调度:
- 两个模型均使用WSD(Warm-up and Step Decay)学习率调度策略。
- 1.5B模型:在80亿个token上进行2000步的warm-up,峰值学习率为3e-4,warm-up后保持不变,随后在退火阶段指数衰减至1e-5。
- 8B模型:在80亿个token上进行2000步的warm-up,峰值学习率与1.5B模型相同。
- 批大小:
- 1.5B模型:微批大小为4,全局批大小为1024。
- 8B模型:微批大小为1,TP(Tensor Parallelism)为2,序列长度为8192,全局批大小为1024。
- 训练框架:使用Megatron-LM,支持分布式优化和DDP(Distributed Data Parallel)梯度重叠。
- 训练硬件:
- 1.5B模型:在256个H800 GPU的集群上训练,总耗时109.5小时,相当于28034 GPU小时。
- 8B模型:在512个H100 GPU的集群上训练,总耗时187.5小时,相当于96000 GPU小时。
Post Training
数据来源
指令微调阶段的数据来源多样,包括开源的指令数据集、教育性指令合成数据、与包(Package)相关的指令合成数据,以及大规模多样指令合成数据。这些数据集的组合旨在提供丰富的任务类型和高质量的训练样本,以提升模型在不同场景下的表现。
具体数据集
- 开源指令数据集(Open-source Training Data)
- Evol-Instruct:一个开源的指令数据集,包含多种编程语言的任务指令。
- Infinity-Instruct:一个大规模的指令数据集,通过语言采样生成多语言的代码片段。
- McEval:一个用于多语言代码评估的指令数据集。
- WildChat:包含真实用户查询的对话历史数据,通过语言模型提取与代码相关的对话内容。
- Code-290k-ShareGPT:一个包含290k代码片段的ShareGPT数据集,用于生成代码相关的对话历史。
- 教育性指令合成数据(Educational Instruction Synthesis)
- 通过高质量的代码片段生成教育性的编程任务和解决方案。这些数据旨在提升模型对编程任务的理解和解决能力。
- 使用高分值的代码片段作为种子数据,生成包含任务描述、分析、解决方案和测试代码的完整指令。
- 与包相关的指令合成数据(Package-related Instruction Synthesis)
- 针对特定编程语言的包(如Python的NumPy、pandas等)生成与包使用相关的指令数据。
- 通过分析常用库的API签名和使用示例,生成高质量的编程问题和解决方案,帮助模型更好地理解和使用这些库。
- 大规模多样指令合成数据(Large-scale Diverse Instruction Synthesis)
- 通过大规模的文本数据生成多样化的编程任务,涵盖不同的编程语言和任务类型。
- 使用语言模型从网页中提取相关句子作为种子数据,生成多样化的编程任务和解决方案。
两阶段指令微调的动机
在开发代码语言模型时,特别是在计算机科学和软件开发领域,模型需要在理论知识和实际编程任务两方面都表现出色。理论知识包括算法、数据结构、网络原理等,而实际编程任务则涉及代码生成、代码补全和代码调试等。为了实现这一目标,作者提出了两阶段指令微调策略:
- 第一阶段:专注于理论知识,通过合成与理论计算机科学相关的问答对(QA pairs),使模型能够更准确地回答关于算法、数据结构等理论问题。
- 第二阶段:专注于实际编程任务,通过使用高质量的代码数据,提升模型生成和处理代码的能力。
第一阶段:理论知识微调
- 数据来源:
- RealUser-Instruct:包含真实用户查询的指令数据,通过语言模型提取与代码相关的对话历史。
- Large-scale Diverse-Instruct:通过大规模文本数据生成多样化的编程任务,涵盖不同的编程语言和任务类型。
- Filtered Infinity-Instruct:从Infinity-Instruct数据集中提取与代码相关的高质量指令数据。
- 训练细节:
- 训练1个epoch,批大小为4096。
- 学习率(LR)为2e-5,warmup步数为100。
- 使用余弦学习率调度器(cosine learning rate scheduler)。
第二阶段:实际编程任务微调
- 数据来源:
- McEval-Instruct:用于多语言代码评估的指令数据集。
- Evol-Instruct:开源的指令数据集,包含多种编程语言的任务指令。
- Educational-Instruct:教育性指令合成数据,通过高质量的代码片段生成教育性的编程任务和解决方案。
- Package-Instruct:与包相关的指令合成数据,生成与特定编程语言的包使用相关的指令数据。
- 训练细节:
- 训练3个epoch,批大小为512。
- 学习率为5e-5,warmup步数为100。
- 使用余弦学习率调度器。
Experimental Results
5.1 Evaluation on Base Models(基础模型评估)
- 评估目标:评估OpenCoder基础模型(未经过指令微调)在代码补全任务上的表现。
- 评估基准:
- HumanEval:一个Python代码补全基准,包含164个编程任务。
- MBPP:一个包含1000个编程任务的基准,用于评估模型的代码生成能力。
- BigCodeBench:一个综合性的代码生成基准,涵盖多种编程语言和任务类型。
- 评估结果:
- OpenCoder在这些基准测试中表现优异,尤其是在HumanEval和MBPP上,其性能超过了大多数开源模型,接近甚至超过了某些闭源模型。
- 例如,OpenCoder-8B基础模型在HumanEval上的Pass@1指标达到了66.5%,在MBPP上达到了63.4%,在BigCodeBench上的表现也非常出色。
5.2 Evaluation on Instruct Models(指令微调模型评估)
- 评估目标:评估经过两阶段指令微调后的OpenCoder模型在实际编程任务中的表现。
- 评估基准:
- LiveCodeBench:一个用于评估复杂算法任务的基准,包含来自LeetCode、AtCoder和CodeForces的题目。
- MultiPL-E:一个扩展到多种编程语言的HumanEval基准,用于评估模型在不同语言上的代码生成能力。
- McEval:一个用于多语言代码评估的基准。
- MdEval:一个用于多语言代码调试的基准。
- 评估结果:
- OpenCoder在这些基准测试中表现出色,尤其是在LiveCodeBench和MultiPL-E上,其性能显著优于其他开源模型。
- 例如,OpenCoder-8B指令微调模型在LiveCodeBench上的平均通过率达到了23.2%,在MultiPL-E上的平均通过率达到了71.0%。
Analysis
6.1 Analysis of the Deduplication Level(去重水平分析)
- 实验设计:比较了文件级去重和仓库级去重对模型性能的影响。
- 实验结果:
- 文件级去重保留的数据量较少,但训练效率更高,模型在下游任务上的表现更好。
- 仓库级去重虽然保留了更多的数据,但模型性能较差,且训练效率较低。
- 结论:文件级去重是处理大规模代码数据集的高效方法。
6.2 Analysis on the Importance of High-quality Data in the Annealing Phase(退火阶段高质量数据的重要性)
- 实验设计:比较了使用高质量退火数据和不使用高质量退火数据的模型性能。
- 实验结果:
- 使用高质量退火数据的模型在下游任务上的表现显著优于不使用高质量数据的模型。
- 结论:退火阶段的高质量数据对模型性能至关重要。
6.3 Analysis on the Effect of GitHub Stars(GitHub星数的影响)
- 实验设计:比较了使用基于GitHub星数过滤的数据和原始数据的模型性能。
- 实验结果:
- 使用基于星数过滤的数据的模型在训练损失上较低,但下游任务性能较差。
- 原始数据虽然包含更多低质量代码,但模型性能更好。
- 结论:基于星数过滤数据可能会降低数据多样性,不利于模型性能提升。
6.4 Analysis on the Two-Stage Instruction Tuning Strategy(两阶段指令微调策略分析)
- 实验设计:比较了单阶段微调和两阶段微调的模型性能。
- 实验结果:
- 两阶段微调策略在理论知识和实际编程任务中都能显著提升模型性能。
- 例如,在Evalplus和CodeArena基准测试中,两阶段微调的模型表现优于单阶段微调的模型。
- 结论:两阶段微调策略能够使模型在理论知识和实际编程任务中达到更好的平衡。