转载本文请注明出处:https://yudonglee.me/deepseek-coder-explained/ | 作者:yudonglee

本文是 DeepSeek 论文专题系列的第 3 篇,详解 DeepSeek 公司 2024 年 1 月发表的 DeepSeek-Coder: When the Large Language Model Meets Programming — The Rise of Code Intelligence (arXiv:2401.14196)。这篇论文不像 DeepSeekMoE 那样提出新架构,但在训练数据组织方式上做了一项关键转换——从 file-level 训练升级到 repo-level 训练,配合 Fill-in-the-Middle (FIM) 双模目标和 16K 长上下文,让 1.3B / 6.7B / 33B 三档代码模型在发布时即位列开源 SOTA。其中 6.7B 模型已经追平 CodeLlama-34B,33B-Instruct 在 HumanEval 上超过 GPT-3.5-turbo。这篇论文也是 DeepSeek 系列里代码主线的起点,后续 Coder-V2、V3 内置 coding 能力、V4 的 agentic coding,都以此为根。

📚 DeepSeek 论文专题系列 · 全 18 篇
通用 LLM 主线LLM · V2 (MLA) · V3 · V3.2 (DSA) · V4 · 收官
Reasoning 主线Math (GRPO) · Prover · R1 · GRM · Math-V2
代码主线Coder ●
多模态主线VL · Janus
MoE 架构与工程MoE · ESFT · Aux-Loss-Free
Attention 演化NSA

一、为什么 DeepSeek 要把代码模型单独成线

W1 序言里我们把 DeepSeek 的 30+ 篇论文归为四条主线,第三条就是 代码(Code)主线

\text{Coder V1 (2024-01)} \to \text{Coder V2 (2024-06)} \to \text{V3 内置 coding (2024-12)}

为什么不把 coding 直接放进通用 LLM 训练?两个原因:

  1. 数据组织方式不同——自然语言文本的最大单位是文档/书章,而代码的最大语义单位是 repository(项目仓库)。一个函数的语义往往依赖同 repo 内其他文件定义的类、接口、常量。file-level 训练相当于把每个文件当独立文档处理,模型看不到跨文件的依赖。
  2. 数据配比不同——代码数据稀疏(GitHub 高质量代码远少于 CommonCrawl 的文本),且语种极度长尾(Python、Java、JavaScript 占大头,Rust、Solidity 等小语种数据极少)。通用 LLM 的 6:1 文本:代码混合解决不了小语种 coverage 的问题。

DeepSeek-Coder 给出的解法是:单独训练专门的代码模型,从数据采集起就以 repo 为基本单位。这条思路后来在 Coder-V2 里被进一步放大(涵盖 338 种编程语言),并在 V3 的 coding 能力建设中得到延续。

具体地看,DeepSeek-Coder 相对 CodeLlama(Meta, 2023-08)、StarCoder(BigCode, 2023-05)这两个同期开源代码模型贡献了什么?三点核心创新:

  1. Repo-level training with topological dependency sort:在一个 repo 内部按文件之间的 import/include 依赖做拓扑排序,再把整个 repo 拼成一条长序列训练
  2. FIM 双模混合(50% PSM + NTP):在标准 next-token prediction 之外,加入 Prefix-Suffix-Middle / Suffix-Prefix-Middle 两种 FIM 模式,50% 配比让模型同时具备代码补全与中段填空能力
  3. 16K 长上下文扩展:在 base model 之上再用 200B tokens 把 RoPE base 调整后做长上下文继续预训练,覆盖整个文件甚至小型 repo

下面我们按论文顺序展开。


二、模型家族与发布节点

DeepSeek-Coder V1 提供三档参数规模,每档都有 Base 与 Instruct 两个版本:

规模Layersd_modeln_heads训练 tokens
DeepSeek-Coder 1.3B242048162T
DeepSeek-Coder 6.7B324096322T
DeepSeek-Coder 33B627168562T

所有模型采用与 DeepSeek LLM 相同的 Transformer 骨架:Pre-norm + RMSNorm + SwiGLU + RoPE + GQA(33B 模型采用 GQA,1.3B 与 6.7B 采用 MHA)。架构本身没有引入新的组件——这是有意为之,DeepSeek-Coder 把所有研究火力都投到了数据与训练目标上。

数据 pipeline 上,DeepSeek-Coder 训练语料包含 87 种编程语言2T tokens,构成上是 87% 代码 + 13% 自然语言(中英双语),完全从零训练(不像 CodeLlama 是从 LLaMA-2 继续预训练)。

从零训练 vs 继续预训练:CodeLlama 选择从 LLaMA-2 起点做 500B tokens 代码继续预训练,DeepSeek-Coder 选择从零训练 2T tokens。后者数据量大约 4 倍、计算成本也高约 4 倍,但好处是 tokenizer、数据配比、长上下文从一开始就为代码优化。两条路线后来被 V2 / V3 系列同时印证:通用 LLM 内置 coding 走”继续训练”路线,专门代码模型走”从零训练”路线。


三、数据创新:Repo-Level 训练

3.1 file-level 训练的局限

绝大多数早期代码模型(CodeGen、Codex、StarCoder 等)都采用 file-level training:把每个源代码文件视为独立样本,截断到固定上下文窗口(通常 2K-4K)后送入训练。

这种做法的根本问题是:真实编程任务很少在单文件内闭合。一个 Python 工程通常有 models.pyutils.pyconfig.pyapi/views.py 等多个文件,写 views.py 时几乎一定要 import models.py 里定义的类。file-level 训练让模型看不到这种跨文件依赖,结果是:

  • 模型生成的代码经常 hallucinate 不存在的接口
  • 给出的 import 路径与实际 repo 结构不符
  • 在多文件协作 benchmark(如 CrossCodeEval、RepoBench)上表现远低于单文件 benchmark

3.2 DeepSeek-Coder 的解法:repo-level + 拓扑排序

DeepSeek-Coder 的数据 pipeline 完整流程是:

  1. 以 repo 为单位采集:从 GitHub 拉取整个 repository,而非散文件
  2. 依赖图构建:解析每个文件的 import / include / require 语句,识别同 repo 内其他文件的引用关系,构建 directed dependency graph
  3. 拓扑排序:在依赖图上做拓扑排序,保证被依赖的文件排在依赖它的文件之前
  4. 拼接成长序列:按拓扑顺序把所有文件拼成一条长序列,每个文件之间用特殊分隔符(路径标识)隔开
  5. 长上下文截断:用 16K 上下文窗口截断(很多小到中等 repo 可以完整放入一个样本)

伪代码示意:

# 简化版 repo-level 序列构造 def build_repo_sequence(repo): files = repo.list_source_files() graph = build_dependency_graph(files) # 解析 import / include sorted_files = topological_sort(graph) sequence = [] for f in sorted_files: sequence.append(f"<filename>{f.path}</filename>") sequence.append(f.content) sequence.append("<eof>") return tokenize(sequence)[:16384] # 16K window
Repo-level training: 文件依赖图 → 拓扑排序 → 拼接成 16K 长序列

这样做的好处是:

  • 模型在生成 views.py 时,真的看过 models.py 的内容(因为拓扑排序保证 models.pyviews.py 之前)
  • 跨文件的类型、函数签名、常量定义都进入了 attention 范围
  • 在 repo-level benchmark(CrossCodeEval、RepoBench)上获得显著提升

论文里展示的消融实验:用 1B 模型做对照,repo-level 训练相对 file-level 训练,HumanEval 提升 6.7%(30.5% → 37.2%),MBPP 提升 9.4%(44.6% → 54.0%)。注意 HumanEval 和 MBPP 都是单文件 benchmark,repo-level 训练能带来这样的提升说明它不只对跨文件场景有效——长上下文里积累的”周边知识”对单文件生成也有正向贡献。

3.3 一个工程上的细节:解析 87 种语言的依赖

要做拓扑排序,必须先能解析每种语言的依赖语法。Python 有 import、Java 有 import、C/C++ 有 #include、Rust 有 use、Go 有 import……每种语言的语法不同,路径解析规则也不同(相对路径、模块路径、package 别名)。

DeepSeek-Coder 论文里没有给出 87 种语言的具体解析规则,但工程上这是一个相当重的实现——需要为每种语言写一套依赖提取器。这也是为什么”repo-level 训练”听起来简单,但同期 CodeLlama / StarCoder 都没有完整做出来的原因。这部分工程红利最终转化为模型能力上的优势。


四、训练目标:FIM 双模混合

4.1 为什么需要 FIM

标准 next-token prediction (NTP) 只训练模型”看到前缀,预测下一个 token”,但实际编程任务里非常常见的是中段填空

def fibonacci(n): if n <= 1: return n return ??? # <- 这里要填

模型不仅看到了前缀(函数签名 + base case),还看到了后缀(return 之后的部分上下文,可能是测试或调用)。光靠 NTP 训练的模型无法直接利用后缀信息。

Fill-in-the-Middle (FIM) 是 OpenAI 在 2022 年提出的训练技巧(Bavarian et al., 2022):把一段连续 token 序列切成三段 prefix / middle / suffix,重新排列后训练模型预测 middle。

4.2 PSM 与 SPM 两种模式

DeepSeek-Coder 实现了 FIM 的两种排列:

  • PSM (Prefix-Suffix-Middle):训练时把序列重排为 <fim_prefix> Prefix <fim_suffix> Suffix <fim_middle> Middle <fim_end>,模型从左到右预测,看到 prefix 和 suffix 后生成 middle
  • SPM (Suffix-Prefix-Middle):把 suffix 放在 prefix 之前

数学上,对一段原始 token 序列 x_1, \ldots, x_n,随机选两个切点 i < j,则:

  • Prefix: P = x_1, \ldots, x_i
  • Middle: M = x_{i+1}, \ldots, x_j
  • Suffix: S = x_{j+1}, \ldots, x_n

PSM 模式下训练样本为:

[\text{fim\_prefix}], P, [\text{fim\_suffix}], S, [\text{fim\_middle}], M, [\text{fim\_end}]

模型仍然做标准的 left-to-right next-token prediction,但因为 prefix 和 suffix 都在 middle 之前,模型在生成 middle 的每个 token 时可以同时利用两侧上下文。

FIM PSM 模式:把原始 prefix/middle/suffix 重排为 (prefix, suffix, middle) 加上 sentinel token 后做标准 NTP 训练

4.3 50% FIM + 50% NTP 的配比选择

DeepSeek-Coder 的训练配比是:50% 样本走 FIM (PSM 模式)、50% 走标准 NTP。这个比例来自消融实验——100% FIM 会让模型在标准 code completion 上退化(因为它习惯了”看到后缀”才能预测),50/50 配比在 FIM 任务和补全任务之间取得平衡。

后来 V2-Coder 沿用了类似配比,业界(CodeLlama-7B、StarCoder)也基本收敛到 50% 这个值。

NTP 与 FIM 的本质区别:NTP 是 causal——模型只能看到左侧上下文;FIM 是 bidirectional-aware——通过 token 重排把”未来上下文”塞到当前位置之前,让 causal 模型间接获得双向信息。这是用最低成本(不改架构)达到了 BERT 类双向模型的部分能力。


五、长上下文:16K 训练 + RoPE base 调整

DeepSeek LLM 的默认上下文是 4K,但代码任务普遍需要更长的窗口(一个完整文件经常超 4K,repo-level 序列更是动辄上万 token)。

DeepSeek-Coder 用两步把上下文扩到 16K:

  1. Base 阶段(2T tokens):直接用 16K 窗口训练,但 RoPE 的 base 参数从 LLaMA 默认的 10000 调大,对应 NTK-aware 位置插值的思路——base 越大,RoPE 频率谱越拉长,模型在更长距离上仍能维持相对位置信号
  2. 继续预训练阶段(200B tokens):在 16K 窗口上再做 200B tokens 的继续预训练,专门强化长上下文场景

理论上 RoPE base 调整后模型能外推到 64K,但论文里给出的”可靠工作区间”是 16K——再长会出现性能下降。这与 LLaMA-2 长上下文论文的观察一致:位置编码外推的上限通常远小于理论值,实际产品上线一般取 1/3 到 1/2 的理论上限。


六、Instruct 阶段:2B tokens 的指令微调

Base 阶段结束后,DeepSeek-Coder 用 2B tokens 的指令数据对 Base 模型做 SFT(监督微调)得到 Instruct 版本。指令数据涵盖:

  • 代码生成(自然语言描述 → 代码)
  • 代码补全(部分代码 + 注释 → 完整代码)
  • 代码解释(代码 → 自然语言解释)
  • Bug 修复(错误代码 + 报错 → 修复代码)
  • 代码翻译(语言 A 的代码 → 语言 B 的代码)
  • 多轮对话(迭代式修改)

指令格式遵循 Alpaca-style:

### Instruction: Write a Python function to compute Fibonacci numbers. ### Response: def fib(n): …

这里 SFT 的细节比较常规,论文没有专门展开。真正决定 Instruct 模型质量的还是 Base 模型——好的 Base + 标准 SFT = 好的 Instruct,反过来不成立。


七、评测结果:开源 SOTA 与跨 size 比较

7.1 HumanEval 与 MBPP

HumanEval 与 MBPP 是当时最常用的两个 Python 函数级 benchmark。DeepSeek-Coder-Base 的成绩(论文表 5):

模型HumanEval (Python)MBPP
StarCoder-15B33.6%43.3%
CodeLlama-7B33.5%41.4%
CodeLlama-13B36.0%47.0%
CodeLlama-34B42.4%55.2%
DeepSeek-Coder-1.3B34.8%46.2%
DeepSeek-Coder-6.7B49.4%60.6%
DeepSeek-Coder-33B56.1%66.0%

关键观察:

  • DeepSeek-Coder-6.7B (49.4%) 已经超过 CodeLlama-34B (42.4%)——这是这篇 paper 最有冲击力的结果。同期 7B 量级的开源模型,前一个 SOTA 是 CodeLlama-7B 33.5%,DeepSeek-Coder 直接把 6.7B 拉到了 4.94 倍参数模型的水位线之上
  • DeepSeek-Coder-1.3B (34.8%) 已经追平 CodeLlama-7B (33.5%)——5.4 倍参数差距被抹平
  • DeepSeek-Coder-33B (56.1%) 超过 CodeLlama-34B (42.4%) 13.7 个百分点——同等参数规模下的代际差

7.2 多语言:HumanEval Multilingual 与 DS-1000

HumanEval-Multilingual 把 HumanEval 翻译到 Python、Java、JavaScript、C++、TypeScript、PHP、Bash 等 7 种语言。DeepSeek-Coder-33B 平均 50.3%,CodeLlama-34B 41.0%——领先 9.3 个百分点。

DS-1000(数据科学场景,pandas、numpy、scipy 等库的真实使用):DeepSeek-Coder-33B 40.2%,CodeLlama-34B 34.3%——领先 5.9 个百分点。

7.3 LeetCode Contest(防止数据污染的关键测试)

HumanEval 和 MBPP 已经发布多年,存在被预训练数据”污染”的风险(模型可能在训练集里见过题目+答案)。DeepSeek-Coder 团队额外构建了 LeetCode Contest 评测集——2023 年 7 月之后(即所有训练数据截止之后)的 LeetCode 周赛题目。结果:

  • DeepSeek-Coder-33B-Instruct:27.8%
  • GPT-3.5-Turbo:19.4%
  • GPT-4:48.1%

这个结果说明:DeepSeek-Coder-33B-Instruct 已经超过 GPT-3.5-Turbo,但与 GPT-4 仍有较大差距。这个相对位置在 2024 年初是非常强的开源代码模型水位。

7.4 数学与通用能力:13% 自然语言数据的红利

虽然 DeepSeek-Coder 是代码模型,但 13% 自然语言(含中英双语)数据让它在通用任务上表现也不弱:

  • MMLU:DeepSeek-Coder-33B 41.6%(CodeLlama-34B 29.4%)
  • GSM8K:DeepSeek-Coder-33B 60.7%(CodeLlama-34B 30.3%)
  • C-Eval:DeepSeek-Coder-33B 40.2%(CodeLlama-34B 7.0%)

这点很重要——很多代码模型为了专注于代码完全放弃自然语言能力,结果是用户必须维护”代码模型 + 通用模型”两套系统。DeepSeek-Coder 保留 13% 自然语言数据后,一个模型同时是不错的代码生成器和合格的双语对话助手。这种”代码主、通用兼”的数据配比后来被 Coder-V2 沿用。


八、与同期对手的差异:为什么 DeepSeek-Coder 跑得快

把 DeepSeek-Coder 与同期三个主要竞品对比:

维度CodeLlama (Meta)StarCoder (BigCode)WizardCoderDeepSeek-Coder
训练起点LLaMA-2 继续预训练从零训练CodeLlama + Evol-Instruct从零训练
训练 tokens500B(继续)1T2T
数据组织file-levelfile-levelfile-levelrepo-level + 拓扑排序
训练目标NTP + 部分 FIMNTP + FIMNTP(SFT-only)NTP + 50% FIM (PSM/SPM)
上下文16K8K16K (RoPE base 调整)
语言数主流几种8687
HumanEval-34B 同档(Python)42.4%56.1%

可以看到 DeepSeek-Coder 的领先不是来自某个单点突破,而是来自数据组织、训练目标、训练量三方面的同时优化。这是 DeepSeek 团队从 LLM 时代就形成的工程哲学——单点优化容易被追平,组合优化才能拉开代际差。后续 V2 / V3 / R1 都体现了这种”多点并进”的研究风格。


九、局限与衔接 Coder-V2

DeepSeek-Coder V1 是一个非常强的开源代码模型,但仍有几个明显局限:

  1. Dense 架构限制规模:33B Dense 推理成本比同等能力的 MoE 高得多。半年后发布的 Coder-V2 直接换成 MoE 架构(236B 总参 / 21B 激活),HumanEval 进一步提升到 90%+ 水位
  2. 语言数量仍偏少:87 种语言看起来不少,但实际 GitHub 上活跃的编程语言(包括 DSL、配置语言)有 300+。Coder-V2 扩到 338 种语言
  3. 缺少数学推理专项训练:虽然 GSM8K 60.7% 不算差,但与 DeepSeekMath / R1 这类专门的推理模型差距明显
  4. 指令数据偏少(2B tokens):相比同期 WizardCoder 用了大量合成数据,DeepSeek-Coder 的 Instruct 阶段比较克制——这也解释了为什么 Base 与 Instruct 差距不像 WizardCoder 那么大
  5. 没有 RLHF 阶段:直接 SFT 后即发布,没有 reward modeling + PPO/DPO。这个能力后来在 R1 系列里专门补足

Coder-V2 在 V1 基础上的关键改动(W 系列后续会专门展开)

维度V1V2
架构Dense (33B 上限)MoE (236B / 21B 激活)
训练 tokens2T6T
编程语言数87338
上下文16K128K
数学推理60.7% (GSM8K)90%+ (借助 DeepSeekMath 数据)

可以看到 Coder-V2 是把 V1 的所有维度都放大一个量级——这是 DeepSeek 一贯的”V2 是 V1 升级版”节奏(DeepSeek LLM → DeepSeek-V2、DeepSeek-VL → DeepSeek-VL2 也都是同样的形态)。


写在最后

DeepSeek-Coder V1 是 DeepSeek 系列里最不”花哨”但工程含量最高的一篇 paper:

  • 没有新架构(与 DeepSeek LLM 同骨架)
  • 没有新损失函数(NTP + FIM 都是现成的)
  • 没有新硬件优化

但它把三件已有的事做到了极致:repo-level 数据组织、FIM 双模训练、长上下文 + RoPE 调整。结果是一个 6.7B 的模型在最关键的 HumanEval / MBPP 上超过对手的 34B 模型,33B 模型在 LeetCode Contest 上超过 GPT-3.5-Turbo。

这种”用工程组合拳跑赢架构创新”的研究路线,是 DeepSeek 系列后来反复验证的方法论——V3 的 FP8、DualPipe 训练优化、R1 的 GRPO + Cold-Start,本质都是这类多点工程优化的成果。

下一篇 W5 我们详解 DeepSeekMath(arXiv:2402.03300),这是 DeepSeek 第一篇专门的推理模型论文,提出了影响整个行业的 GRPO 算法,并为后来的 R1 与 OpenAI o1 系列定下了 RL-for-reasoning 的范式。


参考资料

  1. Guo et al., DeepSeek-Coder: When the Large Language Model Meets Programming — The Rise of Code Intelligence, arXiv:2401.14196, 2024.
  2. DeepSeek-Coder GitHub repository:
  3. Bavarian et al., Efficient Training of Language Models to Fill in the Middle, arXiv:2207.14255, 2022.
  4. Rozière et al., Code Llama: Open Foundation Models for Code, arXiv:2308.12950, 2023.
  5. Li et al., StarCoder: may the source be with you!, arXiv:2305.06161, 2023.
  6. Luo et al., WizardCoder: Empowering Code Large Language Models with Evol-Instruct, arXiv:2306.08568, 2023.
  7. DeepSeek-AI, DeepSeek-Coder-V2: Breaking the Barrier of Closed-Source Models in Code Intelligence, arXiv:2406.11931, 2024.
  8. Chen et al., Evaluating Large Language Models Trained on Code (HumanEval), arXiv:2107.03374, 2021.

Loading