AIAny. ← Back to AIAny

Lesson notes · DeepSeek-AI · thirteen authors · one corpus recipe

DeepSeek-Coder

In early 2024 the strongest code models sat behind an API. This one is the open answer — and its edge isn't size. It's how the training data is built. We'll walk a tiny five-file project through the two moves that let a 33B open model read across files and fill blanks in the middle of a function.

Subject Open code language models Date Jan 25, 2024 Reference arXiv 2401.14196 Recipe 2T tokens · 87 languages · 16K window

Daya Guo · Qihao Zhu · Dejian Yang · Zhenda Xie · Kai Dong · Wentao Zhang · Guanting Chen · Xiao Bi · Y. Wu · Y.K. Li · Fuli Luo · Yingfei Xiong · Wenfeng Liang — core contributors ordered alphabetically. DeepSeek-AI · Key Lab of HCST, Peking University.

The core idea

Train on the repository, not the file.

Almost every code model before this one was fed source code as a pile of loose files, shuffled and cut into fixed-length windows. That throws away the one thing real engineering runs on: a file rarely stands alone — it imports other files, and the definitions it uses live somewhere else in the project.

DeepSeek-Coder's bet is that this organization matters as much as raw scale. Two moves carry the whole paper: build the corpus at the repository level, ordering files so that whatever a file imports comes before it; and train not only left-to-right but with a fill-in-the-middle objective, so the model learns to complete code with context on both sides.

A file rarely stands alone. So the corpus shouldn't either.

PART A

From orbit

What ships, and where the real bet is hiding.

Step 1 · The black box

A code model — and the corpus is where the bet lives.

From the outside DeepSeek-Coder is the model you'd expect: a decoder-only Transformer that takes some code as context and predicts what comes next. It ships in four sizes — 1.3B, 6.7B, 33B, plus a 5.7B mixture — each with a base version (raw pretraining) and an instruct version (fine-tuned to follow requests).

def total(cart): return sum( code context in DeepSeek-Coder 1.3B · 6.7B · 33B item.price for item in cart) completion out
Our running specimen: cart.py from a tiny shopping-cart project. This little repo follows us from the corpus all the way to the benchmarks.

Trained from scratch on 2 trillion tokens across 87 languages, the 33B base model tops open peers and clears closed models like Codex and GPT-3.5 on standard code tests. But sizes and scores are the visible part. The interesting question is why a from-scratch open model closed that gap — and the answer is on the input side, not the architecture side.

Architecture, in one breath: decoder-only Transformer, RoPE positions, SwiGLU, trained with AdamW. The 33B adds grouped-query attention for inference speed — machinery it shares with most 2024 LLMs, so we won't dwell on it here.

Step 2 · The silent bug

Chop a repo into loose files and the definitions vanish.

The model reads context and predicts code. So everything rides on what context it saw during training. Here's the habit DeepSeek-Coder set out to break.

Look at our specimen. cart.py computes a total — but the two names it leans on, catalog.Product and money.usd, are defined in other files. That's normal Python: you import what you need. The trouble starts when a corpus is built one file at a time.

# cart.py import catalog, money p = catalog.Product(...) label = money.usd(p.price) # catalog.py class Product: ... # money.py def usd(n): ... the definitions cart.py depends on live in other files
Follow the two dashed arrows. If the corpus is cut file-by-file, cart.py can land in a training window with catalog.py and money.py nowhere in sight — the model reads a use with no definition.

This is the silent bug. Standard code corpora shuffle files and pack them into windows, so a model routinely sees catalog.Product used without ever seeing class Product. It can memorize surface patterns, but it never gets to learn the dependency — the thing real projects are made of.

So the fix writes itself: before packing anything, arrange each project's files so that whatever a file imports appears earlier in the sequence. Definitions first, uses second. The rest of Part B is how you do that automatically, for millions of repositories.

Step 3 · The corpus pipeline

Five stages turn raw GitHub into ordered training samples.

We know the goal now — put a file's imports before it. Zoom out one level: that reordering is a single stage inside a five-stage assembly line that turns raw repositories into training data.

The team crawled public repositories created before February 2023, kept 87 languages, and pushed everything through the line below. The star of this page is the third box — dependency parsing — which we open up for the rest of Part B.

Stage 1

Crawl

Public repos, 87 languages, created before Feb 2023.

Stage 2

Filter

StarCoder-style rules drop low-quality files — down to ~32.8% of raw size.

Stage 3 ★

Parse deps

Read imports, sort each project so dependencies come first.

Stage 4

Dedup

Near-duplicate removal — at the repo level, not the file level.

Stage 5

Screen

Compiler + quality model, then n-gram scrub against the test sets.

Final tally: 798 GB, 603 million files, 87 languages. Roughly 87% source code, 10% code-related English (GitHub Markdown, StackExchange), 3% Chinese text. The pipeline picture is redrawn from Figure 2 of the paper.

PART B

The corpus machine

Two ideas, both hands-on: order the files, then cut a hole.

Step 4 · Reading the imports

Every import is an arrow; count the arrows into each file.

Stage 3 promised to put imports first. To do that automatically, the parser first turns the whole project into a picture: a dependency graph.

The rule is deliberately simple — scan each file for invocation lines (import in Python, using in C#, #include in C) with regular expressions. Each import becomes a directed arrow drawn from the file being imported to the file that imports it. Read an arrow as “this end must come first.”

config.py imports: 0 money.py imports: 0 catalog.py imports: 1 cart.py imports: 2 checkout.py imports: 2
The specimen as a graph. The two files outlined in cinnabar import nothing — natural starting points. “imports: N” is a file's in-degree: how many other files it needs.
The one number that matters

A file's in-degree is just how many files it imports. In-degree 0 means “needs nothing” — safe to place anytime. cart.py has in-degree 2: it can't go down until both catalog.py and money.py are already placed.

Step 5 · Sort it by hand

Place a file only after everything it imports is down.

You have the graph and you have in-degrees. That's everything you need to do the sort yourself — no algorithm required yet, just the one rule: a file is ready the moment all of its imports are already placed.

Build the training sample below. Ready files glow; blocked ones stay dim until you clear what they need. The order you produce is exactly what gets concatenated into one training document — with a # path comment stitched onto each file so the model always knows where it is.

Click files in a legal order — imports before importers

The single training document you're assembling

Nothing placed yet — start with a file that imports nothing.

Two files are ready: config.py and money.py.

What to notice: flip the checkbox and catalog.py's in-degree jumps to 2 — it stops being ready until money.py is down too. Change the imports, and the legal orders change with them. There is usually more than one valid answer.

Step 6 · The receipt, and two catches

Pick the least-blocked file — cycles and islands included.

The moves you just made by hand have a name: a topological sort. Here is the paper's Algorithm 1, and it is nothing more than a receipt for the rule you already used — take a ready file, place it, then relax whatever it was blocking.

The subtle word is smallest, not zero. A textbook topological sort demands an in-degree of exactly 0 and gives up if none exists. Real import graphs aren't so polite — two files can import each other, forming a cycle where nobody ever reaches 0.

This is the spot that took me a couple of reads to see clearly. By always taking the least-blocked file instead, the algorithm never stalls: on a clean project it behaves exactly like the ordinary sort you did in Step 5, and on a tangled one it still produces a reasonable order instead of throwing up its hands.

Catch 1 · cycles

a.py b.py

Each imports the other. “Smallest in-degree” still moves — “zero” would freeze.

Catch 2 · islands

Unconnected clusters are found first, then each is sorted separately.

The label

# shopcart/cart.py def total(cart): ...

A # path comment rides along, so location survives into training.

Simplification we're glossing: the parser only tracks import-style calls via regex — it isn't a full type resolver, so some real dependencies slip through. And near-duplicate removal (Stage 4) runs on the whole concatenated repo as one unit, precisely so this ordering isn't torn apart by deduping individual files.

Step 7 · Fill in the middle

Cut a hole, move the tail up front, learn to fill the gap.

Ordering fixes what the model reads. The second idea fixes which direction it reads. Plain next-token training only ever predicts forward — but in an editor you usually have code on both sides of your cursor.

So during pretraining, half the documents get rewritten. Take a line from our cart.py, slice it into three pieces — a prefix, a middle, and a suffix — then reorder them so the middle comes last. Now “predict what's next” means “fill the hole, having read both sides.” That's Fill-in-the-Middle (FIM).

Slide the two cuts — the hole moves, the training sequence rebuilds

The line, with the hole you carve out

2
5

The reordered training sequence — PSM mode

What to notice: whatever you cut, the coloured middle always lands at the end, right after the ⟨fim_end⟩ marker — that's the only place the model has to produce. The prefix and suffix are just context it reads first. PSM and SPM only change the order of that context.

The paper tests 0%, 50%, and 100% FIM. A 100% rate wins the infilling benchmark but hurts ordinary left-to-right completion — a real trade-off — so DeepSeek-Coder settles on a 50% PSM rate, applied per document before packing. The real markers are <|fim▁begin|>, <|fim▁hole|>, <|fim▁end|>.

PART C

Does it hold up

The scores, and the one number that pays back Part B.

Step 8 · The scoreboard

A 33B open model clears Codex and GPT-3.5.

Recipe in hand, the payoff is blunt: on the standard code tests the biggest base model beats every open peer and the closed models the paper set out to catch.

The bars compare DeepSeek-Coder-Base 33B against CodeLlama-Base 34B — a near-identical parameter budget — on three code suites, plus one instruct-model result against GPT-3.5-Turbo.

Higher is better · pass@1, greedy decoding

HumanEval avg · 33B-Base
vs CodeLlama-34B
50.3%
41.0%
MBPP · 33B-Base
vs CodeLlama-34B
66.0%
55.2%
DS-1000 · 33B-Base
vs CodeLlama-34B
40.2%
34.3%
LeetCode · 33B-Instruct
vs GPT-3.5-Turbo
27.8%
23.3%
1.3B beats 16BOn single-line infilling the 1.3B base scores 70.4% mean, edging StarCoder-16B — data quality over parameter count.
6.7B ≈ 34BThe 6.7B base performs on par with the five-times-larger CodeLlama-34B.
Instruct > GPT-3.5After instruction tuning, the 33B clears GPT-3.5-Turbo on HumanEval (69.2% vs 64.9% avg).
Honest ceilingGPT-4 still leads (76.5% HumanEval). This closes the open/closed gap; it doesn't erase it.

Numbers from Tables 3–6. The 33B is DeepSeek-Coder-Base unless marked Instruct; CodeLlama figures were re-run by the authors under one shared harness for a fair comparison.

Step 9 · The payoff for Part B

Turn repo-ordering off and cross-file accuracy drops.

Those scoreboard suites are mostly single-function puzzles — they can't actually see whether repository ordering helped. So the paper runs the one test that can: complete a line that depends on another file.

This is the clean ablation. Same model, same retrieval setup, measured on CrossCodeEval — then the whole thing retrained on a plain file-level corpus with the Part B ordering switched off. The bars are exact-match accuracy; cinnabar keeps the repo-level ordering, grey drops it.

0 10% 20% Java 17.7 16.6 TypeScript 14.0 13.2 C# 16.2 14.5
Table 7, DeepSeek-Coder-Base 6.7B with retrieval. In every language the repo-ordered corpus wins — direct evidence that Steps 4–6 did real work. Python (not shown) is roughly flat, since its cross-file signal is weaker.
The loop closes

This is the whole thesis, measured. The only thing that changed between the two bars is whether the corpus was ordered by the dependency graph you sorted by hand in Step 5. Order the files, and the model gets better at the one task single-file training can't teach.

Step 10 · Say it back

The recipe, in your own words.

You built the corpus and read the payoff. Try the five questions below before opening each answer — if the sort and the hole both feel obvious now, the paper has landed.

Why order files inside a repository at all?

Because a file's definitions live in the files it imports. Order imports first and the model reads a definition before its use, so it can actually learn the cross-file dependency instead of guessing from surface patterns — the gap you saw in Step 2.

What is a file's in-degree, and why does it drive the sort?

It's how many files that file imports. In-degree 0 means it's safe to place now; every time you place a file, the files that imported it lose a blocker and their in-degree drops. Keep taking the least-blocked file and imports always land first.

Why “smallest in-degree” instead of “zero”?

Real projects have import cycles — two files needing each other — where nothing ever hits zero. Taking the least-blocked file keeps the sort moving through cycles, and behaves identically to a textbook topological sort on clean, acyclic projects.

What does Fill-in-the-Middle actually change?

It rewrites half the documents so the middle chunk sits last, after the prefix and suffix. “Predict next” becomes “fill the hole given both sides” — the infilling an editor needs. The cost: a 100% rate would hurt plain completion, so it's kept at 50%.

Where did the corpus recipe actually show up in the scores?

In the cross-file test of Step 9. Retraining without repo-level ordering drops exact-match accuracy on Java, TypeScript and C# — the languages where files lean on each other most. Single-function benchmarks miss this entirely.

What did this page deliberately skip?

The later DeepSeek machinery. This is a dense model; the mixture-of-experts routing, latent attention, and RL-for-reasoning arrive in V2, V3 and R1 — each taught on its own page. Here the whole story is the corpus and the objective.

“We attempt to organize the pre-training data at the repository level to enhance the pre-trained model's understanding capability within the context of cross-files within a repository.” — the paper, DeepSeek-AI, 2024
The profound impact

The open code recipe that the whole series was built on.

DeepSeek-Coder made one argument stick: a from-scratch, open, code-first model can catch the closed leaders if the corpus is built with care. That argument carried straight into the models that followed.

2024 · same paper, §5

DeepSeek-Coder-v1.5

Restarted from the general DeepSeek-LLM 7B and kept training — trading a sliver of coding for large gains in math and language. The lesson: the best code models sit on strong general ones.

2024 · the series

DeepSeek-V2 / V3

The open lineage that followed traded dense layers for sparse mixture-of-experts and latent attention — different machinery, each explained on its own page.

2025 · reasoning

DeepSeek-R1

Reinforcement-learned reasoning, built on the same open, from-scratch conviction that started here — a topic for its own walkthrough.

The corpus was the quiet idea; the open, from-scratch stance was the loud one — and it set the tone for everything DeepSeek shipped next.