🧑‍💻

代码模型

 
给定一个LLM (base model/sft model), 我现在要进行SFT, 我应该怎么准备数据, 能让使用者满意.
用什么model / 如何进行SFT / 数据来源和组织是什么 / 如何定义使用者满意.
 

用什么model

从以下3个里面选,其他的模型不用考虑了。
  1. Llama3+ 系列 ❌ 不推荐
  1. Deepseek-Coder系列(可用)
  1. Qwen-Coder系列 ✅ 推荐
如何判断一个model到底处于什么能力阶段
1. 基于benchmark的判断方法. 这里不要看官方的report number, 而是自己用社区常用的default prompt template进行humaneval 和mbpp上的测试. 如果你发现(1) llm的base和instruct版本对于prompt都及其敏感(超过10%的误差), [例如CodeLlama系列] (2) LLM的绝对数值偏低(Humaneval低于25, mbpp低于50) [例如Llama系列], 那么此时我的建议是直接跑路,不要在上面做SFT或者reasoning的实验.不然后续有意外之喜.
2. 基于SFT training的判断方法. 某些非code models的instruct version效果非常好(接近80的humaneval), 但是一训练就崩, 此时可以直接跑路.
3. Internal clustering方法. 这里可以采用[1]中Figure5类似的方法,笔者自建了不同function的code pairs, 如果完全无法进行区分, 那么可以跑路.
4. 快速调入局部最优陷进的速度的方法. 混合了codeexercise和其超长扩展版本, 如果SFT过程中LLM快速收敛且3epoch后期就装死, 那么可以认为其本身能力有偏差.
如何选择模型的version和大小(本节主要针对资源有限的高校研究者)
一般而言, 开源model 会提供1B~70B+的一系列model, 笔者了解到大部分研究者能接触到的仍旧是3090/4090/A100 , 因此, 7B左右的model是最合适的
 

如何SFT

PEFT 还是 FULL (魔鬼的代价)
关于PEFT, 本文不做过多介绍, 简而言之, 以LoRA为代表的一系列能够” huge savings in compute and storage” 的方法. 这里有一篇非常好的论文 [6], 笔者摘录一些核心观点(Finding99):
PEFT和Full都不向LLM添加知识, 而只是简单的pattern-copy
Full SFT受到dataset影响更大, 导致token shift更严重, 从而产生知识偏差导致幻觉.
是否注入了知识取决于对知识的定义, 银行客服的”您好, 亲爱的顾客”这句话如果被认为是知识, 那么SFT就是可以注入的, 反之则很难注入. 在数据量有限(80w以内, 长度较短, 无CoT等扩展)的情况下, 从已有的base llm representation space进入另一个稳定的representation space是非常困难的.
总而言之, 除非是专门做PEFT, 否则一般还是用Full进行训练.
 
混合general IT数据的影响
对于绝大多数模型而言, 在code SFT datasets上训练以后都会产生在其他任务上性能下降的情况. 因此, 往往会需要用通用sft数据进行补偿式训练.
在多阶段SFT的最后使用IT (general场景下)进行补偿式训练总是能获得更好的效果.
模型不同, IT数据和code SFT数据的混合对于结果的影响也不同.
🎯
苦涩的教训: 错误观点:如果一个LLM只是为了做特定的任务(e.g., code generation). 例如CodeX, 那么解决问题的方案应该是简单的: 不要考虑其他任务上的效果, 如果加了general IT数据能让code generation的效果提升, 那么就加,否则不加. 教训:必须加通用数据。
训练epoch
2轮训练有助于model提升效果. 轮数更多则可能导致性能下降.
多轮的loss 计算问题.
2轮conversation的loss到底是算两轮还是最后一轮.
两轮
Code SFT和General SFT 的区别
计算时扩展定律永远成立:在code上 “more token more gain” 
code models仍旧不够好的时候, 重点在于生成语法正确 语义正确 功能正确 的可执行代码. 而当model足够好的时候, 重点应该是如何让model 能够兼容不完美的人类. 永远不要高估自己而低估模型.
🎯
Takeaways:
1. 能用全参数微调就不要用 lora 等方法.
2. 只要模型能力足,有数据,有资源 就用1~2轮数据训练. Loss计算可以只看最后一轮.
3. 模型能力足够, 可以不用IT数据进行补偿式训练.
4. 在general场景下的SFT经验和code上有显著不同, 这是因为两者本质上的优化目标, 理论最优奖励方式不同.

数据来源和组织是什么

数据筛选

先看看不同paper对于这个问题的认识是如何演进的:
LLaMA2: Quality Is ALL You Need. 用几万条标注清晰的SFT数据就够了.
LIMA: 别说那么多, 1k完全人工编写的就够了.
YI:不到10k, 统一回答格式, 不和pretrain 未涵盖的topic冲突.
Llama3: 合成数据为主, Data pruning更重要
Nemotron-4(nvidia): 10k用来做SFT,用多样化的prompt模板来生成.
Qwen2.5: Expanded SFT dataset
Deepseek-V3: 150w, 包括reasoning data (来自R1)和 Non-Reasoning Data (V2.5+人工)
如果还不够清晰地话, 可以进一步看看不同model对于SFT的描述:
Nemotron-4中的认知是: Supervised fine-tuning enables models to learn how to interact with users in a dialogue format. => 这也是为啥要做 Synthetic Dialogue Generation 的原因
 
SFT 数据的 diversity 到底意味着什么?
  • Group1: 将<Instruction, Response> 中的Instruction固定, 而对Response做mutation(即生成10份不同的答案)
  • Group2: 将<Instruction, Response> 中的Response固定, 而对Instruction做mutation(即生成10份不同的问题)
最终的结果是:
  • Group2在benchmark上的表现大幅度下降(humaneval-15), token shift及其严重. 但是实际体验还不错.
  • Group1在benchmark上表现尚可, 但是loss曲线[笔者注:虽然SFT的loss没啥信息量,但是偶尔还是有用的]和一般的SFT训练大有不同, 实际体验在同等temp的情况下, 明显会出现更多的明显和用户无关的回答.
回到主题, 我们可以看到这里的发展有一个明显的变化. 从最开始的尽可能收集多的SFT到后面选用少量而高质量的数据, 再到发现base model性能提升以后合成数据远远好于SFT数据, 最终几乎放弃用少量SFT数据,而是用大量的, topic coverage更高的数据集来进行训练.
 
SFT data selection (在code) 已经只具备学术研究的价值了.
为什么价值有限? 因为以上工作大多follow以下流程: 设计一个selection方法 => select以后用各种cluster或者topic 去告诉大家diversity/quality更好 => 训练以后在benchmark 上表现相当(or better) => 太好了, 这个方法有效.
因此, 本质上这些都是面向benchmark的selection. 笔者曾经做过以下两个实验,供读者参考:
实验一:
  • Model1: Original 10W的 SFT data + 重复了10遍的关于某内部API使用的 INST, RESPONSE pair. (共计100010)
  • Model2: Original 10W的 SFT data
  • Model3: CaR从10W 精选的1000 examples.
结果发现: 其在humaneval/mbpp以及LCB上表现数值几乎一样. 然而, 后两者无法正确回答该API相关的问题
实验二: 简单从10W数据中选出和benchmark中最相似的500个example进行SFT, 在benchamrk上的结果大幅攀升.
 
🎯
苦涩的教训:data selection是一个 llm-as-judge 的子任务。完全不需要设计新的算法,而是利用通用模型的能力来完成任务。
我们应该怎样收集 & 收集什么样的数据呢?
  • Educational Code: 即具有教育意义的代码. 笔者在phi[17] 系列论文中最早看到了这一说法. Phi-3: Our training data of consists of heavily filtered web data (according to the “educational level”) from various open internet sources, as well as synthetic LLM-generated data.
  • Medium-size Code: 即拥有完整功能(self-contain)的代码, 而非 print(“hello, wwoold”)这样的
  • Correct code: 即可以确定是正确的代码, 这一点至关重要. 一条能够被verify的训练数据好过一百条无法被verify的数据.
具体做法:
  • 直接在开源的SFT数据集上regenerate. 这实际上是用的最多的方法, 笔者称之为反复套娃. 这部分既包括对于一些数量庞大的SFT dataset的二次筛选(例如wildchat[18]这种及其不规范的. 也包括对于OSS-Instruct这样的code SFT dataset的重新采样) 这里社区最近开源了可验证的Code SFT dataset kodcode [19], 虽然数量比较少, 但是质量相当不错.
  • 使用现成的数据作为基本的prompt, 然后利用已有的各种生成pipeline自己进行生成. 这个比较适合于想同时提升业务代码生成效果以及刷KPI的读者. 一般而言, 对于Code domain, 笔者建议读者从以下3个方面考虑框架的效果[此处作者不考虑效率, 默认计算资源足够].
    • 涉及到Diversity的粒度. 这里有concept/ topic/ repo / length.
    • 是否涉及到依赖于多个LLM的反复校准, 即一个答案是否经过了多个LLM的修改润色. 一般而言, 流程越长, cost越高, 得到的数据效果越好. [但是相关文章汗牛充栋]
    • 是否依赖于外部数据/代码执行的结果. 这部分分为两个层次: (1)仅有 syntax level correctness. 这个基本上所有的框架都会支持,如果不支持可以直接考虑pass (2) semantic level correctness. 目前主流的办法还是基于 testcase的, 这里就涉及到一个test case coverage导致的fp问题(后续我们会在讨论benchmark那一节详细讨论). 并且由于许多生成的代码本身就无法判断其运行结果是否正确(感谢莱斯定理), 因此一个带有沙箱环境验证的数据生成框架应该是首选的.
opencoder的sft数据策略:
第一部分:
  1. Realuser_instruct: 来自 GPT conversation histories like ShareGPT and WildChat. 说白了就是质量千奇百怪但是数量繁杂的真实对话.
  1. Largescale_diverse_instruct: 基于 CommonCrawl的种子生成的SFT dataset
  1. Filtered_infinity_instruct: 基于 指令选择和指令进化 在大量数据上生成的dataset.
stage1里面的其实就是把质量低但是数量众多的SFT dataset里面和code相关的片段全部拿出来进行训练.
第二部分:
  1. educational_instruct: 以 algorithmic corpus 为seed 生成三元组(instruction, code, test case)的数据
  1. evol_instruct: MagicCoder-Evol-Instruct-110k, 纯Instruction tuning数据
  1. mceval_instruct: 来源于MCEVAL [21], 核心要点在于Cross-lingual Code Transfer技术, 即在不同的PL之间进行转换.
  1. package_instruct: Python package-related questions, seed 是python文档.
conclusion:
  • 多个stage的分开训练是必要的. [这里笔者单独说一句, 只有当两个stage都满足数量巨大(超过50k)且面向general 场景时, 该结论才成立.]
  • 一般而言, 数据是先通用(数量多)后专用(数量少)
  • 当无法确定diversity的时候, 从多个source采样
 
程序合成
面向SFT dataset的code synthesis技术流派简介:
传统程序合成流派简介 (PL角度): 这其实是PL领域一个长期存在的问题, 以往的解决方案很多还是基于rules的, 不过一般而言, 他们解决的问题也是某个language在某个domain上效率不高/效果不好的问题, 通常设计新的DSL解决, 笔者在此不赘述了.
早期尝试: 用NLP社区的方法直接迁移到code领域上. 此时, 由于此前的code related data还是CodeXGLUE中按照code task进行区分的, 缺乏一个进行SFT的数据, 因此仿造 Evol-Instruct [22] 的思路, 这里的工作主要focus在如何让instruction更加的diverse上. 借助已有的框架, 生成code SFT dataset.
通过限定topic和concept的方式: 以 Nemotron-4 [24]为例, 通过预先设定各种concept和domain, 结合各种难度, 从而让LLM自己生成相关的SFT dataset, 下图为初学者level的一个prompt示例:
用code inspire code的尝试: 以OSS-Instruct [23]的提出为界限, 研究者发现和NLP不同, code数据质量的瓶颈很大程度上更加依赖于原本作为种子的code. 换言之, 很多code如果不加以重新生成就直接使用, 会导致质量奇差无比. 因此就想到了用Github等open-source software的数据作为seed 去query LLM, 拿到可用于训练的数据.
增加考虑verification的尝试: 这部分的考虑又可以分为前/中/后三种不同的介入时机:
  1. 前期: 以difficulty作为筛选的原则. 即在判断某个item是否需要进一步rewrite然后加入到SFT dataset中. 笔者记得qwen-coder采用了这种方式.
  1. 中期: 在生成过程中, 是否需要生成对应的verification [23]. (即test case) 从而帮助接下来的数据筛选. [这里有很多文章引用了以往SE community的各种testing技巧, 例如下图]. 特别的,这里生成了数据并不意味着要利用生成的verification => 实际上, 据笔者观察, 单纯地让LLM在生成<Instruction, Response> pair的过程中同时生成 testcase,也能够提升数据的质量.
  1. 后期: 这部分工作严格意义上应该不叫作dataset synthesis而应该叫做improvement, 即已经生成好的数据, 通过 以下几种方式的组合套娃来进一步提升质量: (1) 是否将code用于query LLM生成更多的testcase (2)是否使用多个不同的LLM或者多次采样 (3) 是否涉及到各种反思/循环结构反复调整数据集.
 
直接面向私域数据, 结合内容与代码结构的尝试[20]: 笔者简单讲述设计该方法的原因: 在和同行的交流中, 我们发现不论是以OSS-INST这种以code inspire code的方式, 还是各种topic的方式, 都非常依赖于LLM本身的能力. 并且, 随着规模的上升, 这些方法的diversity会快速下降. 尽管更换LLM有助于缓解这个问题, 但是笔者还是决定更换一个思路. 结合RL, 笔者坚信只要资源足够多, 允许LLM自己做verification, 就一定能生成数量巨大且质量很高的数据, 然而, 问题在于: 有多少token是被浪费了. 或者更直接一些: 人类在过去五十年在code中包含的知识, 应该如何被高效地提取出来? 笔者认为通过”抽象” [此抽象非彼抽象, 指代对于一般规律的统一表达], 能够让我们在资源有限的情况下, 帮助限定LLM生成数据的内容. 同时, 由于PL本身是高度形式化的语言, 因此通过预先指定AST/CFG的方式, 能够进一步控制生成数据的难度. 总而言之, 笔者认为LLM在代码生成上远远好于人类, 生成高质量的代码的关键在于如何找到驴前面的胡萝卜以及朝哪个方向走.
 
 
参考:
zhuanlan.zhihu.com