CREATE DEEPSEEK · FROM SCRATCHRead the paper ↗

ARCHITECTURE. CODE. BUILD. TRAIN.

Create DeepSeek
from scratch.

Understand, build, and train a DeepSeek-style large language model—from scratch.

Follow a token through DeepSeek. See how attention, memory, and experts work together. Then modify small pieces of the real code and test your predictions.

“I’ll just read one paper.”

Me, 23 references later:

Charlie Day gesturing at an elaborate wall of papers and connecting strings
Reaction GIF · Charlie’s conspiracy board
01 Explanation02 Example03 Task04 Result

The papers used in this course

These are the real first pages. Open a paper to read the source.

We build small educational implementations inspired by published DeepSeek mechanisms. The main path follows the V4.1 report through architecture, attention memory, expert routing, and Engram. A tiny trained baseline and runnable exercises accompany the theory. A preliminary SmolLM2 experiment compares task adaptation with n-gram memory and LoRA; full DeepSeek reproduction remains beyond this course.

Watch the course introduction

COURSE ROADMAP · THEORY FIRST

Understand the architecture, then test its design choices

Our backbone is the DeepSeek-V4.1-Flash technical report. Start with what each component does and what information it passes onward. Work through memory, attention, expert routing, and Engram with concrete examples. Then read a result from the paper and try the optional code and experiments. You can follow the theory without running a training job.

READ AND EXPLORE 1 · See the whole architectureFollow the encoder, decoder, attention, experts, and learned memory on the paper’s diagram. 2 · Distinguish weights, states, and cachesFollow one input and identify which information is learned, computed, or saved for later. 3 · Understand what caching reusesSee why old keys and values remain valid when the sequence grows. 4 · Compare three attention design choicesCompression, sharing, and sparse reading solve different parts of the cost problem. 4b · Store the main cache in FP4Try rounding and shared scales; separate the main cache from the local FP8 cache. 5 · Follow sharing and sparse selectionCalculate two reads from the same memory, then follow the indexer and hierarchical selection. 5b · Rebuild missing local cacheFollow a saved conversation through global-cache reuse and approximate local replay. 6 · Route a token through expertsSee what the router chooses, how outputs combine, and what happens when workloads are uneven. 6b · Mix residual streams in one passMove an input-mixing decision earlier and see which repeated memory read disappears. 7 · Understand Engram lookup memoryFollow n-grams, tables, collisions, and contextual gates through the existing separate clips and explanations. 7b · Draft and verify several tokensSeparate proposing tokens, checking them, and choosing how many to verify. 7c · Construct useful training tasksTry three candidate fixes against weak and stronger tests to see what a reward can miss. 8 · Read the evidence in the paperInspect a real comparison, understand what it supports, and identify what remains untested.
Optional practice: code and experiments already available
  1. Trace the NumPy attention implementation. Fixed random weights isolate the calculation.
  2. Inspect a real small training run. Code, synthetic dataset, settings, weights, loss curve, and samples are saved.
  3. Check caching correctness and measure storage. Runtime measurements are specific to this small CPU setup.
  4. Read the official packed-table code and fix an offset.
  5. Try the illustrative Engram lab. Its table examples are not trained model results.
Later chapters: planned, not implemented yet
  1. Training this architecture (§3.1.2, §4): attention-sharing training, data construction, and the training setup described in this report.
  2. Post-training and reasoning effort (§5): expand from the task-construction lesson into reward design and how the paper controls reasoning effort.

Our trained baseline uses a small synthetic command grammar. It does not reproduce DeepSeek’s training or establish natural-language performance. Paper results, illustrative examples, and our own measurements are labeled separately throughout the course.

01 / ARCHITECTURE

How DeepSeek’s main components work together

BEFORE THE WALKTHROUGH · WHAT DOES “MEMORY” MEAN HERE?

A KV cache saves the keys and values attention will read again

When generating another token, attention needs information from earlier positions. Instead of recalculating their keys and values at every step, we keep those vectors in a KV cache. It is temporary working storage for the sequence being processed.

KEYS · KUsed to calculate attention weights
VALUES · VCombined using those weights

The new token’s query scores the stored keys. Attention turns those scores into weights and combines the stored values. A key or value is a vector of numbers, not a saved sentence or a finished answer.

Is the encoder just building a cache?

The encoder does the computation first: attention and expert layers produce a contextual vector for each position. “Final hidden states” means the vectors after the encoder’s last layer, not just the final token.

ENCODERInput → attention and experts → hidden-state vectors
DECODER’S GLOBAL CACHETransform those vectors → keys and values → decoder attention reads them

This is the decoder’s global KV cache. The decoder also keeps separate local attention caches. Engram is different again: it retrieves learned vectors from model parameters, rather than caching keys and values calculated for this input.

See cached generation step by step ↓
50-second architecture walkthrough · AI narration · Original paper figure with highlights
Read or edit the video narration

Here is the architecture we'll unpack. It looks like a lot of boxes, so let's follow one job at a time.

Text enters through embeddings. Images take a separate vision path before joining the language model.

The encoder computes a vector for each position. We turn these vectors into the decoder's global keys and values: its K V cache.

Attention reads earlier context. DeepSeek combines local attention with compressed global memory, and shares that global memory across selected layers.

The mixture of experts routes each token to selected networks. We will examine how that routing works.

Engram adds learned memory for short token groups. Context controls how much that memory contributes.

At the output, D Spark drafts several future tokens. The main model verifies the draft before accepting it.

We won't build everything at once. First, follow our tiny attention model, understand its cache, and measure it. Then explore DeepSeek's changes.

This is the original DeepSeek V4.1 Flash architecture figure. Move through the four steps to see what each component does and where its output goes next.

Use this as the map for the course. For now, identify the jobs: process the input, read earlier context, retrieve extra memory, and produce output. We will unpack the equations and code in the relevant chapters.

DeepSeek architecture: causal encoder, decoder, shared attention caches, Engram, vision encoder, and DSpark
Original DeepSeek-AI Figure 3. The blue outline is a reading aid added by this course.
1 / 4
CAUSAL ENCODER–DECODER

The encoder’s outputs become the decoder’s global KV cache.

The encoder processes the input through attention and expert layers. Its last layer produces a vector for each position. Learned projections turn those vectors into global keys and values that the decoder can read.

Input → encoder hidden-state vectors → decoder global keys and values.

Next, distinguish the information moving through this diagram: learned weights, temporary hidden states, and the keys and values saved for this input. We will use that distinction throughout the attention and Engram lessons.

Follow the information through the model →

01A / MODEL INFORMATION FLOW

What information moves through DeepSeek—and where does it live?

A model answers one token at a time, but it does not keep every kind of information in the same place. Some numbers are learned once during training. Some are calculated for this request. Some are cached because the next token will need them. This distinction explains both DeepSeek’s CED architecture and why “memory” can mean several different things.

MAKE A PREDICTION

Predict which state changes when one prompt token changes

Choose an answer, then reveal the reasoning.

Identify four places where model information lives

The labels below describe different jobs. A vector is an array of numbers. It can participate in language behavior after training, but it is not a little sentence with a literal meaning stored inside it.

1 / TRAINED ONCE

Model parameters

Embeddings, attention projections, expert weights, and other learned weights are updated during training. At ordinary inference they are fixed. They specify how the model transforms an input; they are not a cache for this one conversation.

Persists across requests

2 / COMPUTED NOW

Hidden states

Every layer computes a contextual vector for each token position. These temporary activations depend on this input and this position. A hidden state is a working representation used for the next calculation, not a word definition.

Exists for this forward pass

3 / KEPT FOR THE NEXT TOKEN

Input-dependent global KV

Keys and values derived from this sequence let later queries read earlier context without rebuilding those entries. In DeepSeek V4.1-Flash CED, decoder global KV is projected from the final encoder hidden states.

Grows with this sequence

4 / LEARNED, THEN LOOKED UP

Engram table

Engram is a learned conditional-memory module. Token n-grams choose addresses in learned embedding tables; the current hidden state controls how much the retrieved value contributes. It is neither the prompt’s KV cache nor a database of literal facts.

Parameters, addressed by input
Useful test: if it would be different for a different prompt, it is request-specific. If training changes it and ordinary inference does not, it is a parameter.

Separate prompt computation from decoder memory with CED

A conventional decoder-only transformer produces each layer’s KV from that layer’s own hidden states. The V4.1-Flash report describes a 20-layer causal encoder followed by a 20-layer decoder. For the decoder’s global attention, the KV entries are instead projected from the final encoder state using decoder-layer-dependent learned projections.

INPUT TOKENSThe · tide · roseIllustrative tokens
CAUSAL ENCODERFinal hidden statesComputed from this prompt
DECODER GLOBAL KVLayer-specific projectionsBuilt from final encoder states
DECODER QUERYRead context, score next tokenOwn query per decoder layer

This is a conceptual data-flow diagram, not a claim that one vector has one human-readable meaning. The arrows show where tensors are computed and reused.

GLOBAL ATTENTION

The decoder can share global context

CED lets the decoder obtain global KV from encoder outputs during prefill. CSA2 can then share global KV and indexer keys across selected layers. Later layers still have their own queries, so sharing memory does not make their attention outputs identical.

LOCAL ATTENTION

Each layer still has separate local KV

Sliding-window attention needs keys and values derived from each layer’s current hidden state. The report calls this layer-wise local KV. Decoder SWA KV is separate from the global cache and is generated for decoding through bounded replay; it is not the same object merely stored in a different box.

ONE OPERATION · WORKED EXAMPLE + UNIT TEST

Turn an encoder output into a cache entry

The encoder produces a vector. That vector is not yet a key or a value. Learned projections turn it into numbers that attention can use. Here we isolate that one operation.

STEP 1

Start with one encoder output

h = [2, 3]

These are two features of one token’s state, not two different tokens.

STEP 2

Apply the key projection

K = 2 × 1 + 3 × 2 = 8

Our invented key weights are [1, 2].

STEP 3

Apply the value projection

V = 2 × (−1) + 3 × 1 = 1

Our invented value weights are [−1, 1]. The same input produces two different results.

Encoder state [2, 3] → projections → K = 8, V = 1

Change one thing and predict the result

Now change the encoder output to [3, 3]. Keep the projection weights fixed. What are the new key and value?

Enter both numbers, then check.

See the Python unit test

These assertions check the worked input and the changed input. If an assertion fails, the function did not produce the expected result.

def make_cache_entry(h):
    return h[0] + 2*h[1], -h[0] + h[1]

assert make_cache_entry([2, 3]) == (8, 1)
assert make_cache_entry([3, 3]) == (9, 0)

The browser runs JavaScript calculations. The Python version is downloadable below.

Download all three Python examples and tests

The projection weights are learned parameters, fixed during this inference example. Cache entries depend on the input. These scalar K/V projections teach the idea; V4.1 uses larger, compressed representations and additional operations.

Follow an input from token IDs to decoder memory

Use the sequence [The, tide, rose]. These are teaching tokens, not actual tokenization and not a model prediction. The cards are a dependency walkthrough, not an exact execution schedule.

STEP 1 / INPUT BECOMES VECTORS

Look up learned token embeddings

Thetiderose

The token IDs select learned embedding rows. The selected parameter rows persist across requests; the resulting vectors are new temporary states for this sequence. In the released implementation, rotary position information is applied later to attention components rather than simply added as a position-embedding vector here.

CHANGES WITH PROMPTInput activation vectors
LEARNED / FIXED AT INFERENCEEmbedding table
1 / 6

Identify what each tensor represents

“Tensor” is the general name for a shaped collection of numbers; a vector is one simple case. A hidden-state tensor can have a position axis and a feature axis. A KV cache adds storage for positions, layers, and heads. These shapes tell the program how to multiply, score, retrieve, and combine numbers. They do not give an individual coordinate a reliable literal label such as “the ocean feature.”

Why not say a hidden state “contains the meaning” of a token?

It is reasonable shorthand to say a state carries context relevant to later computation. But the model’s behavior comes from many numbers interacting through learned projections and nonlinear operations. Treating one state or one coordinate as a clean symbolic sentence overstates what the tensor representation guarantees.

Separate report facts from the teaching model

Report-specific: V4.1-Flash uses CED, has 20 encoder and 20 decoder layers, projects decoder global KV from the final encoder states, retains layer-wise local SWA KV, and integrates two Engram modules. Illustrative: our three-word sequence, visual cards, and compact labels are a simplified way to follow the flow. They do not expose a released activation trace or execute the production model.

Official report: §2.2 CED, p. 9 ↗ · §2.3.1 CSA2 and Figure 4, p. 10 ↗ · §2.4.2 Engram, p. 13 ↗ · §3.2.2 bounded replay, p. 20 ↗.

Next: see cached generation step by step →

03 / ATTENTION AND MEMORY · THE MECHANISM

How generation reuses earlier keys and values

56 seconds · Why earlier keys and values can be reused · AI narration
Read or edit this clip’s narration

Suppose our prompt is, the cat sat. We'll treat each word as one token and follow one attention layer.

The layer calculates a key and a value for each position. Attention uses keys to calculate weights, then combines the values.

Now we append, down. Do we need to calculate the first three keys and values again?

Look at cat, in position two. Causal attention lets this position read The and cat. It cannot read sat, which comes later.

After we append down, cat can still read only The and cat. Its token, its position, and the earlier tokens are all unchanged.

No earlier position gains a new allowed input. Its representation stays the same through the causal layers. With fixed weights, its keys and values stay the same too.

So we keep those three pairs in the K V cache, and calculate a new query, key, and value only for down.

The new query reads all four keys. Its attention weights combine all four values to produce the new attention output.

We reuse the old keys and values, but still read them. That is the work the cache saves.

Generation extends the sequence one token at a time. Re-running the full prefix would calculate earlier keys and values repeatedly. A KV cache keeps those tensors so each new query can read them again.

FIRST / WHAT REPEATS?

The old tokens have not changed

Focus on “cat,” the second token in our example. Before adding “down,” it can attend to “The” and “cat.” It cannot attend to “sat,” because that position comes later.

BEFORE: The · cat · satAt “cat”: can read [The, cat]. Cannot read [sat].
AFTER: The · cat · sat · downAt “cat”: can read [The, cat]. Cannot read [sat, down].

Appending “down” gives the earlier position no new information it is allowed to read. Its token and position also stay the same. With fixed model weights, the same allowed inputs give the same representation through the causal layers, so the earlier keys and values remain valid.

The same argument applies to the other old positions: “The” still sees only “The,” and “sat” still sees “The cat sat.” The new “down” position can see all four tokens, so it needs new computation.

WITHOUT A CACHERecalculate K/V for all four positions
WITH A CACHEReuse three pairs. Compute the fourth.

The new query still reads all four key/value pairs in this full-attention example. We avoid recalculating the old pairs; we do not skip reading them.

1 / PROCESS THE PROMPT

Compute the prompt once, then keep its keys and values

For our three-token example [12, 37, 9], run causal attention and save K₀, K₁, K₂ and V₀, V₁, V₂. The last position’s output scores the next token. Processing the prompt is often called prefill.

FOLLOW ONE SEQUENCE · ILLUSTRATIVE TOKEN IDs

Read [12, 37, 9]

REUSED K/VNone yet
NEW K/VPositions 0, 1, 2

Save three keys and three values. The final prompt position produces scores for the next token.

Cache after this step: 3 positions.

This diagram follows the mechanism; it does not execute the model. Tokens 21 and 6 are chosen examples, not predictions from trained weights.

2 / APPEND ONE TOKEN

A new query reads old and new memory

Suppose the next token is 21. At position 3, compute its query, key, and value. Append the new key and value to the cache. Its query attends to keys and values at positions 0 through 3, producing scores for the following token.

The earlier keys and values are reused, but attention still reads them. Caching removes repeated work; it does not make the attention step independent of context length.

3 / CHECK WHAT MUST STAY THE SAME

Caching should preserve the calculation

With the same fixed weights, token sequence, and position IDs, the cached and full-prefix paths should produce matching next-token scores within numerical tolerance. Test this before trusting performance measurements.

Our one-layer example has one pair of K/V tensors. A conventional multilayer transformer caches keys and values for each attention layer. Later we will examine DeepSeek’s changes to their size and ownership.

We now know what the cache stores and why it grows. Next, compare three ways to change attention: store a smaller representation, share memory across layers, or read fewer entries. The optional cache experiment later in the course measures the ordinary caching behavior we just explained.

Compare three attention design choices →

BETWEEN CACHE AND SHARING / THREE DESIGN CHOICES

How CSA2 compresses, shares, and selects attention memory

When generation adds a token, the cache avoids rebuilding earlier keys and values. Long contexts still make the cache large, and reading every old entry can still be costly. DeepSeek-V4.1-Flash’s CSA2 separates three choices that are easy to mix up.

Keep the verbs separate.

Compress changes the representation or number of global entries. Share changes which layers keep the same global memory. Select chooses addresses to read from memory that already exists. These are teaching categories, not a quotation of the report’s formal taxonomy; CSA2 combines several mechanisms. None proves an answer-quality or latency improvement on its own.

ONE TINY, SWITCHABLE ACCOUNTING MODELILLUSTRATIVE

Follow one change at a time

Each tab isolates one change against the same baseline: 8 earlier positions, 4 layers, and 2-byte toy numbers. It is a counting aid, not DeepSeek’s released cache layout or a benchmark.

BASELINE / STORE AND READ EVERYTHING

Four layers each keep a full global cache.

Each layer stores 8 positions as a toy K-and-V pair: 8 × 2 vectors × 8 values × 2 bytes = 256 bytes. Four independent owners store 1,024 bytes. Each query reads all 8 global positions.

Global storage
1,024 B
Global entries read
8 of 8
What changed?

Nothing yet. This gives the reference count for the three choices.

Storage counts show only this toy global cache. They exclude local-window KV, indexer keys, scales, allocator padding, weights, and computation. “Entries read” counts candidate global memory entries, not wall-clock time.

01

CHOICE 1 / COMPRESS THE GLOBAL MEMORY

Store a compact latent instead of a full key-and-value pair per position

A normal cache can carry a separate key and value vector for every position in every layer. At long context lengths, the number of positions multiplies a wide representation.

two source positions→learned pooling or projection→one global latent entry

CSA2 has an explicit compressor. With a compression ratio above 1, the reference implementation pools consecutive token representations using learned, channel-wise softmax gates; at ratio 1 it uses a normalized projection without pooling. The report says V4.1’s encoder CSA2 uses ratio 2, while its decoder CSA2 uses ratio 1. So “compressed attention” does not mean every V4.1 global entry represents two tokens.

toy dense pair: 2 vectors × 8 values × 2 B = 32 B / position
toy latent entry: 1 vector × 8 values × 2 B = 16 B / position
if 2 positions → 1 entry: 16 B / 2 source positions

What it changes: stored representation and, when pooling is used, entries per source position. What it does not decide: how many layers own that memory or how many entries attention reads. The trade-off is information bottleneck: a pooled entry must carry useful information about its source positions, and the model has to learn that representation.

Why this is not a generic MLA claim

This lesson describes the V4.1 CSA2 report and its released reference model. MLA, CSA, and other DeepSeek versions use related vocabulary but have different layouts and schedules. Do not transplant this toy equation or V4.1’s ratio schedule into another architecture.

02

CHOICE 2 / SHARE GLOBAL MEMORY ACROSS LAYERS

Let a group of layers read one global memory owner

Even a compact global entry becomes expensive if many layers each store their own version for the same sequence. That is a storage-ownership problem, separate from the width of one entry.

Fullbuilds global KV + indexer K
↓
Reindexsame memory, new selection
↓
Reusesame memory, same selection

CSA2 assigns each layer a mode in advance. A Full layer produces main KV, indexer K, and fresh Top-K indices. Reindex reuses the latest main KV and indexer K but forms its own indexer query and selection. Reuse also keeps the latest compatible Top-K addresses, skipping new index scoring.

toy independent owners: 4 layers × 256 B global memory = 1,024 B
toy one owner shared by 4 layers: 1 × 256 B = 256 B global memory

What it changes: the number of stored global-cache owners. What stays layer-specific: main queries, attention outputs, and local sliding-window KV. A later layer is not allowed to be treated as if it had produced the owner layer’s cache; the architecture is trained with this sharing relationship. The trade-off is representational flexibility: layers in a group read the same global memory basis.

Global and local memory are different branches

In V4.1, selected global entries are read together with each layer’s own SWA (sliding-window-attention) KV. The local branch has a fixed window size in the released configuration, so its storage is bounded as the context grows; it is not the cross-layer global cache being shared here.

03

CHOICE 3 / SELECT BEFORE READING

Use an indexer to choose a small global read set

Sharing a cache reduces duplicate storage, but a query facing a million global entries would still have an enormous set to score and read. Cache bytes alone do not tell us this reading cost.

global entries
1 2 3 4 5 6 7 8
indexer
scores addresses
→Top-K read set
2 · 5 · 8
→main attention
combines selected values

CSA2’s lightweight indexer scores global entries using indexer queries and keys, then returns Top-K addresses. Main attention reads those selected global entries together with local SWA KV. Reindex can make a fresh Top-K list from shared memory; Reuse can keep the preceding compatible list. The addresses are not the attention weights or the layer output.

toy global memory stored: 8 entries (unchanged)
toy global entries read by main attention: 8 → Top-3
V4.1 report configuration: Top-512 global entries per query

What it changes: the global entries supplied to main attention for that query. What it does not automatically change: global storage; the candidates still exist in the cache. The trade-off is retrieval error: a useful entry outside Top-K is unavailable to that attention read. The report’s hierarchical indexer further limits where later decoder indexers search, but the first Full-mode decoder indexer still scans the causally visible global context.

Why “fewer reads” is not a measured speedup here

A smaller read set is an algorithmic count, not a latency claim. Actual speed depends on kernels, hardware, cache layout, sequence length, candidate gathering, and the rest of the model. The report provides its own system results; this lesson does not infer a speedup from the toy counts.

RESEARCHER MOMENT

The useful token didn’t make Top-K.

Attention: “Where did it go?”

Confused John Travolta looking around
YOUR TURN · ONE DECISION

You read 3 entries. How many remain stored?

Our toy cache holds 8 entries. The indexer selects 3 for this query. We have not compressed or evicted anything.

Choose an answer to see the explanation.

PUT THE THREE KNOBS TOGETHER

One sentence per mechanism

Compress
Make each global memory item smaller, and sometimes make fewer items for the same source positions.
Share
Keep one compatible global memory item for several layers instead of one copy per layer.
Select
Give attention a small set of global addresses to read for the current query.

The research question is empirical: under matched training and evaluation, how do these restrictions affect quality, memory, and throughput? Byte arithmetic explains a mechanism. It does not answer that question.

Primary source: DeepSeek-V4.1 technical report §2.3, Figure 4, and Figure 5 ↗ · released reference model ↗

There is another storage choice inside each cache entry: how precisely to store each number. Next, see why V4.1 uses four-bit storage for its main cache.

Explore FP4 cache precision →

V4.1 PAPER · §2.4.4 · MAIN KV CACHE

How DeepSeek stores its main KV cache in four-bit numbers

We have changed how many memory entries exist and which layers share them. There is another question: how many bits do we spend on each number inside an entry? A long sequence contains so many cached numbers that saving a few bits per number can add up.

1 / START WITH THE STORAGE PROBLEM

Same number of entries. Fewer bits per value.

Imagine one small block containing 16 cached values. Using eight bits per value takes 128 bits, or 16 bytes, for those values alone. Four bits per value takes 64 bits, or 8 bytes. Two four-bit values fit inside one byte.

8-bit values
12345678910111213141516
16 bytes of value data
4-bit values + one scale
1│23│45│67│89│1011│1213│1415│16scale
8 bytes of value data + 1 scale byte = 9 bytes

This compares raw 8-bit value data against the illustrated four-bit block plus its scale. It is not a complete comparison of two production cache layouts: the 8-bit format may need its own metadata, and an implementation may add alignment or padding.

2 / WHY THIS LOSES INFORMATION

Four bits cannot preserve every original value

Four bits provide 16 bit patterns. A stored code chooses from a small set of representable values. An original value between two choices must be rounded. Reading the code later recovers the rounded value, not the discarded detail.

ORIGINAL3.2
→
STORE NEAREST CHOICE3
→
READ BACK3.0

Here we assumed a scale of 1. The difference is −0.2. That error matters because attention uses these cached values in later calculations. Smaller storage is useful only if the resulting model still performs well.

3 / GIVE A BLOCK A SHARED SCALE

The code chooses a level; the scale sets its size

Instead of letting the code stand for a fixed value, multiply it by a scale shared by a group of values. A scale of 0.5 turns the level 3 into 1.5. A scale of 2 turns it into 6. This lets the same small codebook cover different ranges.

reconstructed value = stored level × shared scale

A larger scale reaches larger magnitudes, but the spacing between neighboring values also grows. A smaller scale gives finer spacing near zero, but its largest representable magnitude is smaller. Values beyond that range are clipped. One scale must work for the whole block.

TRY THE TRADE-OFF

Move a value and watch what survives storage

We use the E2M1 value levels with a manually chosen scale. This demonstrates rounding; it does not run DeepSeek’s scale-selection kernel or measure model quality.

STORED LEVEL3
RECONSTRUCTED3.00
ERROR (READ − ORIGINAL)−0.20

Gold marker: original value. Blue ticks: representable values. Green tick: selected value.

Which values can E2M1 represent?

Before scaling, its nonnegative magnitudes are 0, 0.5, 1, 1.5, 2, 3, 4, and 6. The sign bit allows negative values too. Positive and negative zero use two encodings, so the 16 bit patterns represent 15 distinct numerical values. Our visual merges the two zeros. Exact midpoint ties in this teaching widget choose the lower numerical value; hardware rounding rules can differ.

4 / CONNECT IT TO THE ACTUAL PAPER

What V4.1 changes—and what it keeps at higher precision

Main KV: E2M1 + block scale

The report uses one E4M3 scale per 16 channels, without NVFP4’s second-level global scale. That is 4 bits per value plus 8 scale bits shared across 16 values: 4.5 bits per value before layout overhead.

Quantize after RoPE

Both positional and non-positional components use this format. Cached values are dequantized before attention; storing four-bit values does not mean every attention operation uses four-bit arithmetic.

Local SWA cache: FP8

The local sliding-window cache stays FP8 because the paper reports greater sensitivity to quantization. The main and local branches make different precision choices.

Train with quantization

The authors introduce quantization-aware training during post-training. The model can adapt to quantization effects; changing a cache format after training is a different experiment.

Why can they omit the second-level global scale?

The paper compares the format’s available magnitude range with the cache values it needs to store. It reports observed magnitudes around 10, well inside the available range. That supports omitting this scale in their design; it does not remove rounding error or guarantee the same choice works for arbitrary tensors.

CHECK YOUR UNDERSTANDING

Is “half the bits” the same as “twice as fast”?

The cache takes less space, but attention still has to read it, reconstruct values, and do the rest of its work.

Reveal the answer

No. Reduced storage can help memory capacity and data movement. Runtime also depends on dequantization, kernels, hardware, and the workload. The report describes this main-KV change as a storage optimization. A speed claim needs an actual timed comparison.

Source: DeepSeek-V4.1-Flash, §2.4.4: FP4 Main KV Cache. The browser arithmetic is an illustrative calculation, not a reproduction benchmark. Source and calculation notes.

We have reduced the bits inside each entry. Next, return to the separate question of how several layers can share global memory while computing different outputs.

Continue to cross-layer cache sharing →

05 / SHARING MEMORY

How layers share the attention cache

60 seconds · Architecture choice, global cache sharing, separate queries and local caches · AI narration · Original paper figure
Read or edit this clip’s narration

We just reused a cache across generation steps. Now let's reuse a global cache across layers.

Normally, each attention layer stores its own keys and values. Three layers means three separate caches, with potentially different contents.

As the sequence grows, every separate cache takes more memory. So can selected layers read from one shared global cache?

But those layer caches are not identical. Deleting two caches from an existing model can change its answers. Instead, we design the layers to share memory, and train the model with that design.

DeepSeek's C S A two is designed for this. Within a sharing group, one layer builds the global cache. Later layers reuse it.

They don't copy its output. Each layer computes its own query, which can assign different attention weights to the shared entries.

And each layer still keeps its own local cache. Only the global cache is shared in this example.

Here is the paper's diagram. Main K V is the global cache. Main Q and S W A K V are calculated separately in each layer.

So sharing reduces global cache storage. It does not remove the later layers' attention computation.

In a conventional transformer, each attention layer maintains its own keys and values. DeepSeek’s CSA2 lets groups of layers share global memory. A later layer can select different entries, or reuse the previous selection.

The distinction matters: sharing a cache does not mean sharing the attention output. Each layer still computes its own main query and keeps its own local sliding-window cache.

Earlier short explanation · optional
Watch the mechanism · 18 seconds · AI narration
THE IMPORTANT DIFFERENCE

Train layers to share; do not assume their caches match

Across generation steps, earlier causal states stay unchanged, so their stored keys and values can be reused. Across layers, the vectors can differ. Replacing a later layer’s own cache with an earlier one changes its inputs and can change the model’s answers.

DeepSeek builds sharing into CSA2 and trains with that architecture. Later layers learn to use shared global memory while keeping their own queries and local caches. This is a model design choice, not an exact shortcut for any existing trained transformer.

Could we prove model quality just by counting saved bytes?

No. Cache size tells us how much memory is stored. To assess answer quality, we need a trained model and evaluation data. The interactive examples here explain the mechanism; they do not measure DeepSeek’s quality.

COMPARE THE THREE MODESINTERACTIVE

How Full, Reindex, and Reuse layers use shared memory

Click through the modes. Follow what changes—and what stays shared.

GLOBAL KV + INDEXER KEYS one sharing group
SELECTED ENTRIES
→
Layer’s own query + local KVAttention → this layer’s output
Build memory + select entries

A Full layer constructs global KV and indexer keys, then chooses which entries its query will attend to.

Illustrative entries and sequence. Modes are assigned to layers in advance; the network has multiple sharing groups.

Source: §2.3.1 and Figure 4 ↗

Why can the output change if the memory stays the same?

The query determines the attention weights. Different layer-specific queries can give different weights to the same keys and values. The local attention entries are also layer-specific. Reusing memory or selected indices therefore does not force the output vectors to be identical.

ONE SHARED MEMORY · TWO READS

The same KV memory can give two different outputs

Sharing keys and values saves storage, but it raises a useful question: if two layers read the same two memory entries, must they get the same answer? No. Each read can use a different query, which changes the attention weights before the values are combined.

NUMERICAL MINI LESSONINTERACTIVE

Keep K and V fixed. Change only Q.

This is a two-dimensional toy calculation. The numbers illustrate the mechanism; they are not trained weights or word meanings.

SHARED KEYS KK₁ = [1, 0]   K₂ = [0, 1]
SHARED VALUES VV₁ = [2, 0]   V₂ = [0, 2]

Before revealing the calculation: with Query A = [√2, 0], which output coordinate do you predict will be larger?

Choose a prediction first.

Illustrative 2D attention only. Real attention uses learned, higher-dimensional vectors and can include many entries.

06 / INDEXER AND ATTENTION

How the indexer selects entries before attention reads them

We now know how layers can read shared memory. But a long context can contain far more entries than we want attention to read at once. The next job is to select a smaller set.

AI narration · the exact spoken script appears below
Read or edit the video narration

Sharing memory saves storage. But the model still needs to decide which parts to read.

That’s where the indexer comes in: it selects a smaller set of entries for attention to use.

Selecting the entries and computing attention over them are two separate steps.

Now the difference between Reindex and Reuse becomes clearer.

Reindex makes a fresh selection from the shared memory.

Reuse keeps the previous selection, but the layer still performs its own attention computation.

Reusing the selection does not mean copying the previous layer’s answer.

Reading the numerical example on screen

The entries change from 2, 5, 7 to 1, 4, 8 at Reindex. Reuse keeps 1, 4, 8. Two different main queries then produce different attention weights over those same entries.

The displayed weights and outputs are calculated from a tiny dot-product attention example. They are not measurements from DeepSeek. The diagram isolates the global branch; the real layer also has its own local KV.

For reproducibility: keys = [[2, 0], [0, 2], [1, 1]], values = [[1, 0], [0, 1], [0.5, 0.5]], queries A = [0, 1] and B = [1, 0]. We compute softmax(QKᵀ / √2), then multiply by V.

Source: §2.3.1 and Figure 4 ↗

1 / CHOOSE WHERE TO LOOK

The indexer returns addresses

Imagine eight cached entries. The indexer chooses entries 1, 4, and 8. Those three numbers tell attention where to read. They are not the attention output.

8 cached entries → indexer → [1, 4, 8]

ONE OPERATION · WORKED EXAMPLE + UNIT TEST

Use indexer scores to retrieve the right rows

The indexer scores cache positions, but attention needs the values at those positions. A small implementation mistake is to pass the scores onward instead of retrieving the rows.

STEP 1

Score four cache positions

Position:  0    1    2    3
Score:    0.2  0.9  0.1  0.8

Keep the two positions with the highest scores. We use zero-based positions, as in Python.

STEP 2

Keep the positions, not the score values

Selected positions = [1, 3]

The selected scores are 0.9 and 0.8. Those are not row addresses.

STEP 3

Retrieve values at those positions

Values: [10, 20, 30, 40]
Read:   [20, 40]

Attention can now combine these selected values using its own attention weights.

Scores → selected positions → retrieved values → attention

Change one thing and predict the result

Raise the score at position 0 to 0.95. Keep all other scores and values unchanged. Which two values are now retrieved, in descending score order?

Enter both numbers, then check.

See the Python unit test

These assertions check the worked input and the changed input. If an assertion fails, the function did not produce the expected result.

def select_values(scores, values, k=2):
    ids = sorted(range(len(scores)),
                 key=lambda i: (-scores[i], i))[:k]
    return [values[i] for i in ids]

assert select_values([.2,.9,.1,.8], [10,20,30,40]) == [20,40]
assert select_values([.95,.9,.1,.8], [10,20,30,40]) == [10,20]

The browser runs JavaScript calculations. The Python version is downloadable below.

Download all three Python examples and tests

This example isolates selecting and gathering global-cache rows. Values are scalars only to make the indexing visible. It does not implement DeepSeek’s learned indexer or the subsequent attention calculation. Ties use lower position first.

2 / COMPUTE WHAT TO READ OUT

Attention combines the selected values

The main query scores the keys at those addresses. The resulting attention weights determine how much each value contributes. A later layer can reuse [1, 4, 8] and still produce a different weighted sum with its own query.

Query + keys at [1, 4, 8] → weights
Weights × selected values → attention output

This diagram isolates global memory. The real layer also reads its own local cache.

Check yourself: Reuse keeps [1, 4, 8]. Does it copy the output?

No. It reuses the addresses. The layer still calculates attention with its own query. Reindex goes one step further and chooses fresh addresses.

We reduced the number of entries attention reads. But finding those entries can still require a search across the whole context. Next, we reduce that search.

How the indexer narrows its search →

07 / HIERARCHICAL INDEXING

How block selection narrows the entries searched by later layers

Selecting 512 entries sounds cheap. But if every layer first scores a million entries to find them, selection itself becomes expensive. DeepSeek limits where later indexers search.

How the indexer narrows a full context to blocks, then individual entries · AI narration
Read or edit the video narration

Selecting a few entries does not make the search cheap. The indexer could still score the whole context.

In DeepSeek’s decoder, the first Full layer scans every visible global position.

It groups positions into blocks, scores each block by its highest score, and builds a candidate pool from the winning blocks.

Later Reindex layers search only this pool, each choosing its own top five hundred and twelve entries.

The pool is bigger than the final selection, so different layers can choose different entries.

The later searches are bounded by the pool size. But the first full scan still happens.

This restriction is used during training too, so the later indexers learn under the same constraint.

What is shared—and what is still searched?

The candidate pool is built for each query by the decoder’s first Full-mode layer. It includes every position in the selected blocks, rather than just that layer’s highest-scoring individual entries. Later Reindex layers can therefore choose different final Top-512 sets within the same pool. Reuse layers skip new indexing.

The paper gives an example of 2,048 selected blocks with 8 positions each: 16,384 candidate positions. This is larger than a final set of 512 entries. The animation instead uses 24 → 8 → 3 so that you can follow the entries.

Only the later indexers have a search size bounded by the fixed candidate pool. The initial Full layer still scans the causally visible global context. This does not make every operation in the model constant-cost.

DeepSeek Figure 5: initial full-range indexing produces a shared block candidate pool, then later Reindex layers select their own Top-512 entries
Original Figure 5 · DeepSeek technical report

Source: §2.3.2 and Figure 5 ↗

FOLLOW A SMALL EXAMPLE

24 positions → 8 candidates → 3 selected entries

First scan: group 24 positions into six blocks of four. Score each block using its highest-scoring position, then keep two blocks. All eight positions in those blocks enter the candidate pool.

Full context:    [1–4] [5–8] [9–12] [13–16] [17–20] [21–24]
Winning blocks:       [5–8]                [17–20]
Candidate pool:       5 6 7 8 17 18 19 20
Later layer A:        5   7      18
Later layer B:          6          19 20

Later searches: each Reindex layer scores only those eight candidates and chooses its own three entries. The two example selections above are illustrative, not measured model outputs.

The trade-off: an entry outside the pool cannot be selected by those later indexers, even if it would have been useful. The model must learn to work with that restriction.

Check yourself: does this remove the first full scan?

No. Building the candidate pool still needs the first scan. The saving comes from restricting later searches to a smaller pool.

We have followed how attention chooses information from this input. Next, see what happens when a later conversation turn finds the global cache but its local cache is missing. Then we will switch from attention to expert routing.

Follow a missing local cache →

V4.1 PAPER · §2.2 · §3.2.1–3.2.2 · DEPLOYMENT TRADE-OFF

Why DeepSeek rebuilds part of a cache instead of saving everything

A cache miss does not have to mean “start the whole conversation again.” DeepSeek-V4.1-Flash keeps the long-lived part that is useful across turns, then rebuilds a small local state when it is missing. The report attributes a persistent-cache footprint around one eighth of V4’s to removing persistent SWA KV and further compressing global KV. The rebuild is a deliberate approximation—not ordinary exact KV caching.

LONG-LIVEDGlobal KV

Global context can be useful when a later turn refers back to an old tool result or document. V4.1 keeps this cache persistently so a matching prefix can be reused.

SHORT-LIVEDLocal SWA KV

Sliding-window attention needs nearby layer-wise keys and values. They are valuable while a session is active, then quickly lose their reuse value after a turn or session ends. Section 3.2.1 places them in a separate distributed host-DRAM pool with a minutes-scale TTL: absent from persistent storage, not absent everywhere.

1 / FIRST SEPARATE TWO MEANINGS OF “REUSE”

Appending a token can preserve ordinary causal cache entries exactly

In a standard causal transformer, an old position cannot attend to a newly appended future token. With fixed weights, token IDs, and positions, its allowed inputs do not change. Its old key and value therefore remain valid. Reusing those entries is an exact-in-principle equivalence to recomputing the full prefix, subject to normal numerical tolerance.

This fact does not say that every way of restoring a multi-layer local cache is exact. A local key/value at an upper layer depends on hidden states beneath it. Those lower-layer states can themselves depend on an earlier local window. Dependencies accumulate through the stack.

2 / THE EXPENSIVE VERSION OF A RESTART

A local window has a deeper theoretical reach through layers

Let L be the number of layers and nwin the SWA window. The report says exact reconstruction of the SWA KV for all layers requires replaying the most recent L × nwin tokens. Each layer can pass information one local window farther back, so keeping only one window of input does not, by itself, recreate the same stacked states.

EXACT RECONSTRUCTIONL × nwin replayed tokenspreserves the full stacked local dependency
BOUNDED REPLAYnwin replayed tokenstruncates local attention at the replay boundary

Why accept the shorter path? The paper cites prior work indicating SWA’s effective receptive field can be much smaller than the theoretical nwin × L/2 decoder reach. That is motivation and an empirical hypothesis, not a proof that one local window exactly reconstructs every model state.

INTERACTIVE TOY CACHEILLUSTRATIVE · NO TIMINGS

Follow one prefix through save, reuse, replay, and continuation

This toy uses an eight-position prefix, four local layers, and a selectable replay segment. Its numbers are not DeepSeek settings or a measurement. An exact recovery would replay the smaller of the available prefix and the theoretical stacked span. The widget only exposes what persists, what is recomputed, and what the bounded pass intentionally leaves out.

The toy local window equals the selected segment, so shorter choices make the replay boundary more visible.

1 / SAVE THE FIRST TURN

The prefix creates global and local state.

A completed prefix produces global KV plus local SWA KV. The local part can serve the active conversation, but is not chosen for long-lived persistent storage.

PERSISTENT STOREGlobal KV: positions 0–7
ACTIVE RUNTIME POOLSWA KV: positions 5–7
OLDER PREFIXRECENT EDGE
stored global KVlocal state recomputed nowoutside the bounded local replay
TOY PERSISTENT LOCAL KV0 positions
TOY BOUNDED REPLAY3 positions
TOY EXACT REPLAY USEDmin(8, 4 × 3) = 8 positions
THEORETICAL STACKED SPAN4 × 3 = 12 positions

3 / ENCODER: A PREFIX-CACHE RECOVERY PATH

Reuse global KV; regenerate only the local state that is missing

For an encoder cache hit with missing encoder SWA KV, the system replays the last nwin tokens of the cached prefix together with the uncached suffix. The replayed prefix regenerates only SWA KV. Its cached global KV is reused without recomputing or overwriting it; the new suffix produces both global and local KV.

The consequence is precise: replayed prefix state is approximate, and global and SWA KV for the new suffix can depend on where the cache hit occurred. The report says its experiments found negligible response-quality degradation, while also stating that these states are not mathematically identical across cache-hit positions.

4 / DECODER: A DIFFERENT LOCAL-STATE JOB

Build decoder SWA KV for decoding, not for prefix persistence

CED lets decoder global KV be projected from final encoder hidden states. Decoder SWA KV is different: each decoder layer derives it from that layer’s own hidden states, and the first decode steps need it. V4.1 does not cache decoder SWA KV. At every prefill, it feeds the last nwin prompt tokens’ encoder outputs through the decoder under the same local truncation and keeps the resulting decoder SWA KV only for decoding.

That decoder reconstruction is also explicitly non-equivalent to a full decoder forward pass. The authors report negligible response-quality impact and say they simulated the replay during post-training so the model could adapt to it.

CHECK THE CLAIM

Does a sliding window make bounded replay exact?

A later layer’s local key/value can depend on a lower layer that reached beyond the replay boundary.

Reveal the answer

No. Ordinary causal caching can exactly reuse already computed entries. Bounded replay deliberately cuts off local attention before the replay start, so it approximates a stacked SWA state. Its case is empirical: the report finds negligible degradation in its tested settings, but warns approximate state reconstruction can still cause degradation in untested boundary cases.

Sources: DeepSeek-V4.1-Flash §2.2: Causal Encoder-Decoder; §3.2.1–3.2.2: persistent KV management and SWA Bounded Replay. Source and scope notes.

Global cache sharing, FP4 storage, and bounded local replay address different costs. Together they reduce stored state, but each has its own correctness and quality boundary.

08 / EXPERT ROUTING

How V4.1 routes tokens through experts

V4.1-Flash uses Mixture-of-Experts (MoE) layers to increase available feed-forward capacity without asking every token to run every feed-forward network. The saving is selective computation, not a promise that every selected expert has a human-readable subject.

01 / WHY SPARSITY EXISTS

See how sparse routing limits per-token work

A dense Transformer layer sends every token through one large feed-forward network (FFN). An MoE layer stores many smaller FFNs, called experts, and a router selects a small subset for each token. An expert is a learned FFN with weights and nonlinearities. It is not a person, and selection does not guarantee a stable label such as “the math expert.”

Dense FFNone FFN runs

Every token uses the same feed-forward weights.

MoE FFNsmany exist; a few run

The router can choose different routed experts for different tokens.

Parameter counts answer “how much is stored.” Activated parameters and FLOPs answer part of “how much arithmetic this token asks for.” Neither alone measures end-to-end latency.

02 / FOLLOW ONE TOKEN

Trace one token through routing and combination

At an MoE layer, a token already has a hidden-state vector h. The router scores the routed experts from h, keeps the top k, runs those FFNs, and adds their weighted outputs. This happens token by token; neighbouring tokens can take different routes.

  1. 1Hidden stateone vector for this token
  2. 2Router scoresone score per routed expert
  3. 3Top-k selectiononly selected routed FFNs run
  4. 4Weighted sumcombine their outputs
What does “weighted” mean?

If experts A and C are selected with weights 0.7 and 0.3, their outputs contribute as 0.7 × A(h) + 0.3 × C(h). The exact scoring function and whether top-k weights are normalized are model details. Do not silently assume a softmax or normalized weights.

03 / INTERACTIVE TOY ROUTER

Choose a toy token and follow each calculation

This is a fixed, two-dimensional teaching example: four routed experts, top-2 selection, and explicitly normalized toy weights. It is not learned, measured, or extracted from DeepSeek-V4.1-Flash. The toy FFN outputs stay fixed across the choices so you can isolate the routing arithmetic; real FFN outputs also depend on their input state.

Router raw scores

Top-2 selected by score. In this toy, their selected scores are divided by their sum.

Selected paths

Weighted output

Predict before changing the token

Will the same experts always win? No. The router receives the token’s current hidden state, which has been shaped by its surrounding context and prior layers. The labels here are just names for four toy FFNs, not evidence of real expert identities.

04 / WHAT V4.1-FLASH SPECIFIES

Identify the shared and selected routed FFNs

The V4.1-Flash report’s §4.2.1 architecture configuration says each MoE layer has 1 shared expert and 384 routed experts; 6 routed experts activate for each token. The accompanying official reference implementation makes the intended composition concrete: it sums the weighted outputs of selected routed FFNs, then adds the shared FFN output for every token.

Shared expertruns for every token
384 routed expertsrouter selects 6 for this token
add outputsback into the layer path

The official reference implementation uses correction biases to help choose experts, while retaining the original scores as output weights. It also has separate correction biases for image-span tokens. The report’s §2.1.1 multimodal routing section describes modality-specific bias updates to balance image and text loads separately during training.

V4.1’s Causal Encoder–Decoder makes prefill and decode different execution modes. The report gives 552B backbone parameters, 8B activated parameters per prefill token, and 16B per decode token for the full architecture. Those figures are not only this MoE layer and should not be reverse-engineered from “6 of 384.”

05 / THE FAILURE MODES

Check whether compute savings fix the bottleneck

Routing imbalance

If too many tokens choose the same few experts, those experts become overloaded while others idle. A batch may wait for the crowded path. Load balancing tries to spread use; it does not make every token equally expensive.

Communication and dispatch

When experts are distributed across devices, tokens may need to be grouped, sent to the right device, computed, and returned. Those transfers and synchronization can matter to wall time.

Capacity is not a semantic guarantee

Even if routes correlate with patterns in analysis, the experts are trained jointly and can change roles. A route is a numerical decision, not a certified domain assignment.

SEE THE BOTTLENECK

Twelve tokens. Four experts. One very busy expert.

This toy routes each token to one expert so the workload is easy to count. V4.1 selects six routed experts per token; this is only an illustration of uneven load.

Both examples assign 12 tokens. Changing the distribution is not a measured speedup or an implementation of DeepSeek’s balancing algorithm. In the paper, text and image tokens have separate correction biases for selection; original routing scores still weight the outputs.

RESEARCHER MOMENT

384 experts available.

The router watching every token choose the same six.

Roz from Monsters Inc watching sternly
Check yourself: does sparse routing guarantee lower latency?

No. It can reduce the amount of expert arithmetic for a token, but latency also depends on batching, routing balance, kernel efficiency, device placement, memory traffic and communication.

MoE changes how the feed-forward computation is allocated. It does not replace attention, KV-cache design, or the rest of the Transformer system. Next: how residual streams are mixed between blocks →

Sources and claim boundaries: see expert-routing-sources.md. The V4.1 configuration claims come from the saved DeepSeek-V4.1-Flash report and official reference implementation; all four-expert numbers above are explicitly illustrative.

V4.1 PAPER · §2.4.1 · RESIDUAL-STREAM SCHEDULING

How Single-Pass mHC avoids rereading residual streams

A Transformer normally carries one residual vector between blocks. The original DeepSeek-V4 mHC carries n residual streams per token. That gives the model a learned way to combine streams before a block and to distribute a block’s output afterward. DeepSeek-V4.1-Flash changes the timing of one set of coefficients so a deployment kernel can avoid rereading the same residual data.

Prerequisite: residual streams

Think of Xl as a small stack of vectors travelling from block l to block l+1. A residual connection preserves information by adding or transforming a carried state around a block. mHC keeps several such carried streams, then uses token-specific coefficients to make one block input and the next stack. This lesson explains the data dependency; it does not require the original mHC paper.

ONE OPERATION · WORKED EXAMPLE + UNIT TEST

Combine two residual streams into one block input

Before studying when mHC predicts its mixing coefficients, first calculate what mixing does. Each residual stream is a vector. The input mix weights corresponding coordinates, then adds them.

STEP 1

Start with two streams

Stream A = [2, 0]
Stream B = [0, 4]

Both vectors have two coordinates, so their corresponding coordinates can be combined.

STEP 2

Weight each stream

0.25 × A = [0.5, 0]
0.75 × B = [0,   3]

These illustrative coefficients tell us how much each stream contributes to this input mix.

STEP 3

Add coordinate by coordinate

Mixed input = [0.5, 3]

We get one two-coordinate vector. We do not concatenate the streams into a four-coordinate vector.

Two streams → weighted sum → one block input

Change one thing and predict the result

Keep the same two streams, but set both mixing coefficients to 0.5. What are the two coordinates of the mixed input?

Enter both numbers, then check.

See the Python unit test

These assertions check the worked input and the changed input. If an assertion fails, the function did not produce the expected result.

def mix_streams(a, b, wa, wb):
    return [wa*x + wb*y for x, y in zip(a, b)]

assert mix_streams([2,0], [0,4], .25, .75) == [.5,3]
assert mix_streams([2,0], [0,4], .5, .5) == [1,2]

The browser runs JavaScript calculations. The Python version is downloadable below.

Download all three Python examples and tests

This is only the input-mixing operation, with invented coefficients and two streams. It is not all of mHC, does not derive its coefficients, and does not specify every normalization constraint. The lesson below explains how the actual architecture schedules this operation.

1 / THE ORIGINAL DEPENDENCY

The current input mix waits for a full reduction

For block l, the coefficient predictor H(Xl) produces three token-wise objects: Al mixes the n streams into the input for the current block; Bl mixes streams into the next residual stack; and Cl distributes the block output into that stack. The predictor includes normalization and projection.

Xl+1 = BlXl + ClFl(AlXl)

The obstacle is subtle. Each tile of Xl can help predict Al, but the finished Al is unavailable until all hidden-dimension tiles have contributed to the reduction. Therefore a kernel cannot use the first tile to form AlXl yet. It must come back and read Xl again for input mixing. This is a bandwidth and scheduling issue, not an algebra mistake.

DEPENDENCY WALKTHROUGHORDINARY mHC

Follow two illustrative residual streams through the timing

Toy model: two streams and two hidden tiles. Real mHC has n streams and vectors of width d; the illustration exists only to make the ordering visible.

STEP 1 OF 4

Read tile 1 of Xₗ

2 / MOVE ONLY THE INPUT-MIX COEFFICIENT

Block l consumes Al−1, then predicts Al

Single-Pass mHC shifts only the input-mixing coefficient by one block. The already available Al−1 mixes each tile of Xl for the current block input while that same tile contributes to predicting the new coefficients. Bl and Cl are still predicted from Xl and still belong to the same block’s update. They are not shifted.

Xl+1 = BlXl + ClFl(Al−1Xl)

Now no tile waits for a reduction before it can be mixed into the input: its needed Al−1 arrived from the previous block. In the same traversal, the kernel accumulates what it needs to predict (Al, Bl, Cl) for the current block. The new Al is saved for the next block’s input mix.

3 / WHAT THE PAPER COUNTS

One architectural shift enables a one-pass deployment kernel

The report first gives an ideal lower bound of (2n + 2)d activation-memory traffic: read the n input streams plus the preceding block output, then write the n next streams plus the current mixed input. Its original multi-pass implementation has three sequential mHC kernels; including input pre-norm, it totals (4n + 4)d. A partially fused two-pass version would total (3n + 2)d, because it still rereads Xl for the current Al mix.

ORIGINAL IMPLEMENTATION(4n + 4)dincludes pre-norm
TWO-PASS REFERENCE(3n + 2)dstill needs one reread
SINGLE-PASS MEGA-mHC(2n + 2)dattains the lower bound

“Halves” here means the paper’s comparison from the original (4n + 4)d implementation to Single-Pass Mega-mHC’s (2n + 2)d. It is not a claim that the shift halves the two-pass count, halves model FLOPs, or guarantees a twofold end-to-end speedup.

4 / MODEL CHANGE, NOT A FREE COMPILER TRICK

The shifted schedule changes which input mix each block applies

The authors say the shift causes negligible performance degradation empirically. They keep the existing multi-kernel implementation for pre-training because the shift changes the model’s computation, then use the fused Mega-mHC kernel for deployment. In other words, exact fusion optimizes execution for a chosen formula; Single-Pass changes that formula and must be evaluated as an architectural choice. The report’s qualitative statement is not a general proof for another model, sequence length, or training recipe.

Optional compact notation

At each token, Al ∈ R1×n, Cl ∈ Rn×1, and Bl ∈ Rn×n. Thus A collapses a stack of n streams to one block input, while C lifts the block output back across n streams. The shapes explain why the toy has two streams; it does not claim the production model uses two.

CHECK YOUR UNDERSTANDING

Which coefficient must already exist when a tile of Xl arrives?

In the Single-Pass schedule, the tile is immediately mixed into block l’s input.

Choose an answer.

Primary source: DeepSeek-V4.1-Flash technical report, §2.4.1 Single-Pass mHC ↗ · source and scope notes.

The key lesson is a dependency schedule: preserve the current residual data, use the previous input-mixing decision, and make the current decision for the next block.

08 / ENGRAM

How Engram retrieves n-gram memory and uses context to control it

The paper we are learning from

We will trace Engram from its research idea to its implementation, then test small pieces ourselves. Start with the mechanism; return to the paper when you want the complete experiments and equations.

Read the Engram paper ↗ · Open PDF ↗

Conditional Memory via Scalable Lookup: A New Axis of Sparsity for Large Language Models — Peking University and DeepSeek-AI.

Actual first page of the Engram paper, showing its title, authors and abstract
ENGRAM · VIDEO SCRIPT✓ Fully approved · Text + video

Part 1 · Can we use this information?

Sometimes, two or more tokens appear together frequently. For example, “New York.” In our example, these are two separate tokens. We see them together in “flights to New York,” “weather in New York,” and “hotels in New York.” Can we use this information somehow?

Here’s one idea: have a separate memory table for groups of tokens that appear together frequently. These groups are called n-grams. Just as an individual token can be represented by an embedding vector, we can also associate a whole group of tokens, like “New York,” with an embedding vector.

New opening · 28 seconds · exact two-paragraph script above · AI narration
PART 2 · ALTERNATIVE APPROACHDraft · Not yet approved

Why not make “New York” one token?

Part 2 · AI narration and animated examples · Separate clip

Text edits save automatically. The recorded video stays unchanged until we render an updated version.

SPOKEN SCRIPT · EDITABLE

Before we dive deeper into n-grams, let's consider an alternative.

Since New York appears frequently, why not make it a single token? We could also add a token for New York City.

Each new token would have its own learned embedding. New York gets one vector. New York City gets another.

When the tokenizer uses the whole phrase as one token, the model processes fewer positions. That is a real benefit.

Engram takes a different approach. Keep New, York, and City as separate tokens, and look up extra memory for groups of those tokens.

At York, look up New York. At City, look up York City and New York City. These are different groups, with separate lookups.

Both approaches use learned vectors. The difference is where we put them: in the token vocabulary, or in an additional memory module.

Engram can add memory without adding new vocabulary tokens. It doesn't shorten the sequence, and its lookups have a cost. Neither approach is automatically better.

PART 3 · ONE MEMORY LOOKUPDraft · Not yet approved

From “New York” to a stored vector

Part 3 · Simple lookup · AI narration

Edit the text below with Edit text. Your changes are saved; the video changes when we render a new revision.

Spoken script · Read or edit the exact narration

Let's keep New and York as two separate tokens. When we reach York, take that token and the one immediately before it. Together, they form the two-gram New York.

We want to retrieve a learned vector for this group. Our memory table contains many rows of vectors. How do we choose which row to read?

We use a hash function: a repeatable rule that takes the group's token IDs and returns a row number. For our example, New York leads to row twenty-eight.

Now read the vector already stored in row twenty-eight. The hash chooses the address. It doesn't create the vector's values.

With the same token IDs and the same hash settings, New York leads to row twenty-eight again. We can calculate the address directly, without searching through the whole table.

In a real model, training learns the values stored in the rows. The hash determines where to look; training determines what is stored there.

For now, that's the whole lookup: token group, row number, stored vector. You don't need the hash arithmetic to follow this mechanism.

But there is a catch. Can a different token group lead to the same row? Let's look at that next.

READ IT STEP BY STEP

We want a vector for the group.

The group “New York” gives the model a way to retrieve extra memory. This example follows just one lookup, so we can see exactly where its vector comes from.

1 / FORM THE GROUP

Stop at York. Look one token back.

NewYorkCity · later

At the position of “York,” the 2-gram is “New York”: the current token and its immediate predecessor. “City” is not part of this group.

2 / USE THE IDS

Replace the labels with identifiers.

TOKENNew → 72
TOKENYork → 18

These made-up IDs identify the tokens. The pair is [72, 18], in that order. An ID is an integer label; an embedding is a vector of learned values.

3 / CHOOSE THE ROW

A repeatable rule gives us an address.

A hash function takes the group’s token IDs and returns a valid row number. In this example, “New York” leads to row 28.

GROUPNew · York
→
RULEHash
→
ADDRESSRow 28

With the same IDs and hash settings, we get row 28 again. We calculate where to look directly; we do not search every row for the phrase.

Optional: see the toy arithmetic

Our toy table has 101 rows, numbered 0 through 100. A hash is a repeatable rule that converts our input pair into one of these addresses.

31 × 72 + 18 = 2250
2250 = 22 × 101 + 28
Keep the remainder → row 28

“mod 101” means keep the remainder after dividing by 101. Removing 22 whole groups of 101 leaves 28. We use the remainder so the address fits inside the table.

This is a teaching hash. DeepSeek uses a different hash, token-ID normalization and much larger tables.

4 / READ THE ROW

The vector is already there.

COMPUTED ADDRESS28
→
STORED CONTENTS[0.2, −0.5, 0.8]

We read the three example numbers stored in row 28. The hash did not generate them. It only selected their storage location.

5 / SEPARATE THE TWO JOBS
HASH CALCULATIONWhere to look

The input IDs determine the row address.

TRAININGWhat to store

The model learns the numbers inside the memory table.

With the same IDs, hash and table size, we read the same row again. Its contents can change during training. Once training is finished and the weights are fixed, that row’s stored vector stays the same.

Only one lookup is shown. Actual Engram retrieves multiple vector pieces, combines them, and uses context to control their contribution. Our tokenization, IDs, formula, table size and vector values are illustrative.

Source: Engram paper · Sparse retrieval via hashed n-grams.

PART 4 · WHY MULTIPLE LOOKUPS?Draft · Not yet approved

Why share vectors—and why use multiple tables?

Part 4 · The problem, the compromise, and the reason for multiple tables · AI narration
Spoken script · Read or edit the narration

We want the model to remember useful information about token groups, like New York and ice cream. The straightforward idea is to give every group its own row, containing its own learned vector.

But the number of possible groups grows quickly. Just one hundred token IDs give us ten thousand possible ordered pairs. Adding a third token gives a million possible groups. A separate row for every combination becomes expensive.

So we choose a smaller table, and use hashing to assign groups to its rows. But now there are more possible groups than rows. Therefore, some different groups must share a row. That sharing is the compromise we make to limit storage.

Suppose New York and ice cream both reach row twenty-eight. There is only one vector stored in that row. Both groups retrieve exactly that same vector. We are not storing two separate vectors inside the row. This is a collision.

But that shared vector alone cannot tell us which group supplied it. During training, both groups can influence the same stored values. The rest of the model still has the original tokens, but this one memory lookup has lost a distinction.

So how can we distinguish more groups without storing a completely independent vector for every group? Here's an idea: build each representation from shorter pieces, and reuse those pieces in different combinations.

To do that, use two tables. Each table stores vector pieces, and each has its own hash rule. One hash rule plus its table is called a hash head. Both heads receive the same token group as input.

In our example, both groups retrieve piece A from the first table. But the second table gives New York piece B, and ice cream piece C. One shared piece no longer means that all the pieces match.

Join the pieces together. New York gets A followed by B. Ice cream gets A followed by C. These are different combined vectors, even though the first piece is shared.

Why not just make one table bigger? Let's hold storage fixed at four hundred numbers. One table could store one hundred vectors of four numbers each. Alternatively, two tables could each store one hundred pieces of two numbers each. Both designs use four hundred numbers and return four numbers per group.

The single table offers one hundred row choices. The two tables offer up to one hundred times one hundred address combinations. That's ten thousand possible combinations, made from shared pieces, not ten thousand independently learned vectors.

That's the reason for multiple hash heads: more combinations from limited memory. It is still a compromise. Different groups can collide in every head, so uniqueness is not guaranteed. Engram combines multiple lookups; it doesn't give every possible group its own private vector.

THE REASON FOR EACH STEP

START WITH THE GOAL

Ideally, every group would have its own learned vector.

We want useful memory for groups such as “New York” and “ice cream.” A direct design would give each group a private row. But the number of possible groups grows rapidly, so storing all of them independently becomes expensive.

Therefore, we use a limited table. The compromise is that some different groups share a row—and retrieve exactly the same stored vector from that row.

1 / THE STORAGE PROBLEM

Why not give every group its own row?

With just 100 token IDs, there are already 100 × 100 = 10,000 possible ordered pairs. Each extra token in a group multiplies the number of possible combinations again.

Storing a separate vector for every possible group quickly becomes impractical. Hashing lets us choose a table size we can afford, even when there are more possible groups than rows.

2 / THE CONSEQUENCE

Some groups must share a row.

Imagine fitting four different groups into only three row addresses. At least two groups must get the same address. They remain different groups: sharing a row does not make their tokens or meanings identical.

GROUP ANew · York → 28
GROUP Bice · cream → 28

This example is a hash collision. Both groups read the same stored vector from this head. That vector alone cannot distinguish the two inputs, although the model still has the original token sequence.

One row. One stored vector.

Row 28 contains [0.2, −0.5, 0.8]. If two groups select row 28 in this table, both retrieve those exact same numbers. There are not two private vectors hidden inside that row.

During training, both groups can influence these shared values. This may help both or cause interference; the vector alone cannot identify which group supplied it.

“New York” and “ice cream”

Both arriving at row 28.

Two Spider-Man characters pointing at each other
3 / ANOTHER CHANCE TO DISTINGUISH THEM

Give the same group a second lookup.

Use a different hash rule with a separate table. Each such route is a hash head. Both heads take the same group IDs as input; the second head does not take the first head’s row number.

GroupHead 1 · table 1Head 2 · table 2
New · YorkRow 28Row 71
ice · creamRow 28Row 30

These groups collide in head 1 but reach different rows in head 2. Hash heads are memory lookup routes, not attention heads.

WHY COMBINE PIECES?

More combinations without a private vector for every group.

Instead of selecting one whole vector, select shorter pieces from separate tables and join them. “New York” can use [A, B], while “ice cream” uses [A, C]. Sharing A no longer forces the entire vector to match.

Same storage budgetOne tableTwo tables
Stored numbers100 rows × 4 = 4002 × 100 rows × 2 = 400
Returned numbers42 + 2 = 4
Possible address choices100Up to 100 × 100 = 10,000

The 10,000 combinations reuse the same pieces. They are not 10,000 independently learned vectors. The hashes need not reach every combination, and different groups can still receive the same combination.

This is a small storage illustration, not a measurement of DeepSeek’s model quality or an exact model configuration.

4 / COMBINE THE PIECES

One shared piece does not force everything to match.

In the new two-table example, each piece has two numbers. Table 1 returns A = [0.2, −0.5] for both groups. Table 2 returns B = [0.6, 0.1] for “New York,” and C = [−0.3, 0.7] for “ice cream.”

Joining the pieces gives each group a longer vector. Their first pieces match, but the second pieces differ. The complete retrieved vectors therefore differ in this example.

In Engram, these pieces are also combined with lookups for other group lengths before projection and context gating. We are isolating one group length here.

5 / THE TRADE-OFF

More routes reduce a risk; they do not guarantee uniqueness.

Multiple hash heads reduce the chance that two groups share every lookup address. A collision in one head can be compensated for by distinctions in others.

Groups can still collide in all heads. Different addresses also do not guarantee different learned values. Additional tables require memory, so their sizes and number must be chosen within a budget.

Optional: how the example addresses were produced

These are made-up token IDs: New = 72, York = 18, ice = 1, cream = 98. Both teaching tables have 101 rows. Head 1 uses (31 × first ID + second ID) mod 101; head 2 uses (33 × first ID + second ID) mod 101.

Head 1 returns 28 for both pairs. Head 2 returns 71 and 30. The displayed vectors are invented; these are not DeepSeek’s actual IDs, hashes or weights.

Source: Engram paper · Multi-head hashing.

PART 5 · LET CONTEXT CONTROL MEMORYDraft · Not yet approved

We found a vector. How much should we use it?

Part 5 · A separate clip with AI narration · Context, key, value and the gate
Spoken script · Read or edit the narration

We can now retrieve memory for a group of tokens. But finding a vector doesn't mean we should always use it at full strength. The lookup only sees a short group, not the whole situation.

Take flights to New York and weather in New York. At York, the two-gram is identical. With fixed model weights and hash settings, that particular lookup returns the same vector. But the surrounding context is different.

The model already has another representation at this position: its current hidden state. Earlier layers have used the preceding text to build it. So we have local memory on one side, and a context-dependent state on the other.

Here's the idea: let that context control how strongly memory contributes. Think of a volume knob. Turning it down weakens the memory signal; it doesn't fetch a different row.

First, combine the retrieved pieces, including the other group lengths. Learned transformations turn that combined memory into two vectors. The key is used to check compatibility with the hidden state. The value is the information we might add.

Next, compare the key with the current hidden state. Engram normalizes them and turns their compatibility score into a gate between zero and one. This is learned behavior, not a hand-written rule about flights or weather.

Then multiply every number in the value by the gate. For a made-up value of two and minus one, a gate of zero point eight gives one point six and minus zero point eight. A gate of zero point one leaves only zero point two and minus zero point one.

Finally, add that scaled value to the existing hidden state. In this one-branch illustration of the DeepSeek implementation, the original state stays, and memory contributes an update. The gate doesn't rewrite the stored vector.

So hashing decides where to look. The context gate decides how much the retrieved information contributes. It can suppress an unhelpful signal, but it cannot guarantee that a collision is repaired or that the memory is correct.

1 / THE PROBLEM

A short group does not tell us the whole situation.

“Flights to New York” and “weather in New York” end with the same two-gram. For fixed weights, IDs and hash settings, that two-gram retrieves the same memory pieces in both sentences.

But we are asking about different things. A useful lookup is only a candidate contribution. It should not automatically influence every context equally.

CONTEXT Aflights to [New · York]
CONTEXT Bweather in [New · York]

We are isolating the two-gram here. Longer groups such as “to New York” and “in New York” differ, so the complete combined memory need not be identical.

2 / WHAT THE MODEL ALREADY HAS

The hidden state brings the context.

At the York position, the model has a hidden-state vector. Earlier layers have processed the preceding text, so this state can differ between the two examples.

Engram can compare its retrieved memory with that state. This motivates a gate: a learned strength control for the memory contribution. It uses context available so far, not future words.

3 / PREPARE THE MEMORY

A key for comparison. A value for the update.

First join the retrieved pieces from the hash heads and group lengths. Two learned transformations create a key vector and a value vector.

The key is compared with the hidden state. The value is what we might add to that state. These transformations also adapt the memory width to the model’s working width. The raw table vector does not have to match the token embedding width.

KEYCompare with context
VALUEInformation to add
4 / CONTROL THE STRENGTH

Compute a gate, then scale the value.

Engram normalizes the key and hidden state, compares them, and converts the score into a number between 0 and 1. A small gate weakens the contribution; a larger gate lets more through.

The gate is learned through training. It is not a hand-written rule, and 0.8 does not mean an 80% chance that a fact is correct.

5 / TRY IT

Move the gate. Watch the update change.

Keep the made-up memory value [2, −1] and hidden state [0.5, 0.5] fixed. Only change the gate below.

STORED EXAMPLE VALUE[2, −1]
×
GATE0.80
=
MEMORY UPDATE[1.60, −0.80]
EXISTING STATE + MEMORY UPDATE[2.10, −0.30]

Toy arithmetic, not model activations. You control the gate manually here; the real model computes it. Slider endpoints 0 and 1 illustrate the limiting cases of the sigmoid.

6 / ADD, DO NOT REPLACE

Keep the existing state and add a scaled update.

For one residual branch in the DeepSeek implementation: new state = existing state + gate × value. With a gate of 0.8, [0.5, 0.5] + [1.6, −0.8] = [2.1, −0.3].

This changes the current hidden state. It does not overwrite the memory table during inference, change the hash address, or fetch a replacement row.

Check your understanding: if the gate is almost zero, what happens?

Almost no memory update is added. The original hidden state still passes through. The lookup has already happened; the gate controls its contribution.

THE LIMIT

A gate can suppress noise. It cannot promise a repair.

If a collision makes the memory unhelpful, the contextual comparison may reduce its influence. That is a learned tendency, not a guarantee. Suppressing a vector does not reconstruct a missing private vector for the original group.

Optional: original Engram paper versus the DeepSeek implementation

The original Engram paper uses normalized key–state similarity followed by a sigmoid, then refines the gated value with a short causal convolution before residual addition. The DeepSeek implementation used in this course applies a signed square root to a weighted normalized similarity before the sigmoid and adds the gated value directly.

Its parallel residual branches have separate keys and gates and share the value. The main diagram shows one branch so that the addition is easy to follow.

Sources: Engram paper · Context-aware gating · DeepSeek report · Engram. Checked against the course’s saved inference-model.py, Engram.forward.

READ THE REAL RESULTSWritten companion · Official figures

What the official Engram README shows

We have built the intuition. Now connect it to DeepSeek’s actual diagrams and reported experiments. These are figures from the original Engram research repository, not results from our toy examples or benchmarks of DeepSeek-V4.1.

Source: deepseek-ai/Engram · Official README. Explanations below are our reading guide.

1 / SEE THE GATE IN ACTION

Darker red means a stronger gate.

The color shows the gate at each token position. Look at “Alexander the Great” and “Diana, Princess of Wales.” Engram reads groups ending at the highlighted position; the color is not a score for an isolated word.

Engram gate heatmap over English and Chinese example sentences
Original README case study. Darker red corresponds to a larger gate, closer to 1.
Open full-size image ↗

A larger gate lets more of the retrieved value contribute. It does not measure factual correctness, and this visualization does not prove that every useful phrase always gets a large gate.

Quick check: does a red “Great” mean only “Great” was looked up?

No. The suffix groups end there—for example, “the Great” and “Alexander the Great.” The displayed gate acts on the combined retrieved memory.

2 / CONNECT THE PARTS

Read the architecture from the bottom upward.

Start with token groups, follow the hash arrows into embedding tables, then follow Concat into the learned projections. The hidden state joins the comparison path, controlling the memory contribution.

Original Engram architecture with hashed two-grams and three-grams, concatenation, projections and convolution
Original Engram architecture. Follow the arrows rather than trying to understand every label at once.
Open full-size image ↗

The Conv block belongs to the original Engram design. The DeepSeek implementation discussed in Part 5 uses direct gated addition without this convolution. These are related designs, not identical diagrams.

3 / ASK AN EXPERIMENTAL QUESTION

How much capacity should go to memory?

The README’s central question is how to split capacity between MoE computation and Engram memory. The left plot holds the study’s budget fixed and varies that split. Lower validation loss is better.

Engram scaling plots showing U-shaped allocation curves and loss against embedding slot count
Left: capacity allocation under two compute budgets. Right: scaling the number of memory slots.
Open full-size image ↗

On the left, 100% means pure MoE. Moving left assigns more of the sparse parameter budget to memory. Both curves have their best region between the extremes: more memory is not automatically better. The two colored curves use different vertical scales.

On the right, more embedding slots reduce loss over the tested range. This is a separate experiment with growing memory capacity, not the same fixed-budget comparison.

Research task: what would you compare in our own experiment?

Compare a baseline with a memory-augmented model, then add a baseline with a similar total parameter budget. Keep data and evaluation fixed. Otherwise, extra parameters could explain a gain that we attribute to the memory mechanism.

4 / Open the pre-training benchmark table

Compare the MoE-27B and Engram-27B columns first. The README reports a comparison matched on total parameters and training FLOPs. Read the arrows: lower is better for loss; higher is better for the listed accuracy-style scores.

Full original pre-training benchmark comparison of Dense, MoE and Engram models
Original benchmark table, preserved at full resolution. Open the image to read individual rows.
Open full-size image ↗

The reported improvements extend beyond knowledge questions to reasoning, code and math. These are the authors’ results under their training setup; our small reproduction has not established the same gains.

5 / Open the long-context results

The authors also test 32k-token contexts. LongPPL measures perplexity, where lower is better. RULER includes retrieval and other long-context tasks, where higher scores are better.

Original Engram 32k long-context results, including LongPPL and RULER
Original long-context table. Parentheses identify pre-training steps and loss before context extension.
Open full-size image ↗

Compare the training conditions as well as the scores: the table includes different step counts and compute comparisons. The README’s proposed explanation is that local memory leaves more attention capacity for other dependencies. Treat that as an interpretation to investigate, not something the table alone proves.

6 / WHAT WE CAN RUN NEXT

The repository includes a mechanism demo.

The README provides engram_demo_v1.py, a standalone demonstration of the data flow. It uses placeholders for Attention, MoE and mHC. Running it is useful for learning the module; it does not reproduce the trained model or the benchmark tables.

Read the official demo code ↗

Another README idea worth testing later: because token IDs determine the addresses, the system can prepare memory reads ahead of time. The reported low overhead for host-memory offloading depends on the tested system; it does not make memory access free.

Figures reproduced unchanged from deepseek-ai/Engram. Copyright notice and Apache-2.0 license retained. Saved README · Figure sources. Retrieved September 20, 2026.

FIRST CODE INVESTIGATIONPredict → change → run → explain

Two tables, one array. What keeps them separate?

You do not need to write a whole model. We will inspect five lines from the official demo, change one operation, and see why it matters.

1 / SEE THE MECHANISM

Give head A three rows and head B four rows. The implementation packs both into one seven-row array. Head A starts at global row 0. Head B starts at global row 3.

Global 0A0
Global 1A1
Global 2A2
Global 3B0
Global 4B1
Global 5B2
Global 6B3

Both heads may request their own local row 1. That means global row 1 for A, but global row 4 for B. The offsets [0, 3] perform that translation.

2 / READ THE REAL CODE

This is an unchanged excerpt from MultiHeadEmbedding.forward in the official Engram demo. Here input_ids are already hash-row indices—do not confuse them with the original vocabulary token IDs.

    def forward(self, input_ids: torch.Tensor) -> torch.Tensor:
        shifted_input_ids = input_ids + self.offsets
        output = self.embedding(shifted_input_ids)
        
        return output

Official source · lines 320–324 ↗ · Saved source ↓

Line 2 adds each head’s starting offset. Line 3 reads those rows from the packed embedding array. The result contains one vector per head.

3 / PREDICT, THEN CHANGE ONE LINE

What if we remove the offsets?

In this toy run, A1 stores [0.2, −0.5] and B1 stores [0.6, 0.1]. Both heads request local row 1. Predict what the broken version will retrieve.

Make a prediction, then run. No model download required.

This browser exercise runs a small JavaScript equivalent with fixed arrays. It does not execute PyTorch or train a language model. The official code above remains unchanged.

4 / EXPLAIN WHAT HAPPENED

Without offsets, both indices stay at 1, so both read A1. The code runs, but head B silently reads the wrong region. With offsets restored, the indices become [1, 4], retrieving A1 and B1.

This is an implementation bug, not the intended hash collision we discussed earlier. Collisions happen within a head’s assigned table. The offsets preserve the separation between heads.

“It runs without errors.”

One head is reading the wrong table.

This is fine dog sitting in a burning room
Your review task: what should a test check?

Check that each shifted index falls inside that head’s own allocated range. For this example, A must use rows 0–2 and B rows 3–6. Checking only the output shape would miss this bug.

Download the tiny Python exercise ↓

Run it with python3 packed_lookup.py. This dependency-free teaching version lets you change the arrays and inspect the same address calculation. It is not the full official PyTorch module.

YOUR QUESTION · ALTERNATIVE APPROACH

Why not make “New York” one token?

We can. A tokenizer could represent the whole phrase with one token. That can shorten the sequence. Engram takes a different approach: keep the token sequence and add memory lookups for groups within it.

One segmentation, or overlapping groups?

Consider these three toy tokens:

NewYorkCity
ONE TOKENIZATION CHOICE[New York] [City]

Another choice is [New] [York City], or one token for the entire phrase. A tokenization picks one sequence of pieces.

OVERLAPPING MEMORY LOOKUPSKeep all three tokens

At York: look up New York.
At City: look up York City and New York City.

Engram can use those overlapping patterns at their respective positions. It does not need to replace them with a single vocabulary token.

What changes when we scale this up?

QuestionAdd phrase tokensUse Engram memory
Sequence lengthCan become shorter when phrases merge.The original sequence length stays the same.
Overlapping phrasesA tokenization chooses one segmentation.Several overlapping groups can be looked up.
Where capacity growsThe vocabulary grows. In a standard language model, added tokens are also additional possible next-token outputs.A separate memory table grows; the input/output vocabulary can remain unchanged.
Cost and compromiseMore vocabulary entries and training coverage to manage; fewer sequence positions may save work.Extra storage, lookup and gating work. Hashing bounds the table size but allows collisions.

No automatic winner: this is an architectural trade-off, not evidence that Engram always beats a larger tokenizer. Compare prediction quality, memory use and runtime under matched budgets.

Does an n-gram have to be frequent?

No. An n-gram is any contiguous group of n tokens. Frequent patterns motivate the opening, but Engram’s mechanism also hashes groups that are rare. It does not first check a list of frequent phrases.

Source: Engram architecture: hashed n-grams and unchanged input/output vocabulary. Tokenization examples and the comparison are explanatory illustrations.

Optional reference · earlier detailed Engram walkthrough

DETAILED MECHANISM WALKTHROUGH · EARLIER RECORDING

2 min 23 sec · Token groups, hash collisions, multiple heads and context gating · AI narration

READ · TRY · UNDERSTAND

N-grams, one small step at a time.

See why DeepSeek uses them, build a few yourself, then follow one into memory.

0 of 6 checks completed

Explore in any order. Your completed checks are saved in this browser.

01 / THE PURPOSEReady to try

Give a familiar pattern a memory lookup.

DeepSeek uses short token patterns to retrieve learned information from memory. This gives the model a direct lookup alongside its transformer computations.

THE MECHANISMEngram

The memory module.

ITS INPUTAn n-gram

A group of consecutive tokens.

PATTERNNew · York
ENGRAMLook up a table row
RESULTA learned vector

A vector is a list of numbers learned during training. The network uses these numbers; the table does not return a sentence about New York.

Which name refers to the memory module?

02 / BUILD A GROUPReady to try

Include York, then look backward.

We are processing York in this example. It is the last token inside each group, even though the sentence continues.

IloveNewYorkthis tokenCitylater
2-gram
NewYork
3-gram
loveNewYork
4-gram
IloveNewYork
Keep the orderNo skipped tokensNo future tokens

The groups overlap. The number n is just the number of tokens in the group.

We pretend each word is one token. A real tokenizer can split words into pieces or include spaces and punctuation. The same grouping rule still applies.

Which 2-gram belongs to York?

What if the whole prompt is already available?

The causal rule still applies at each position. Even during prompt processing, City is excluded from York’s n-grams.

Reversing the order changes the pattern too: York · New and New · York are different 2-grams.

03 / MOVE THE WINDOWReady to try

The groups move with the token.

Select City to see the windows shift right. Then select love to see what happens near the beginning.

CHOOSE THE TOKEN BEING PROCESSEDTRY IT

Current position: York. Tokens to its right are excluded.

○ Try City○ Try love

PAD fills a missing earlier position. At love, the 3-gram is PAD · I · love.

Padding does not borrow future tokens or wrap around the sentence.

Boundaries and all the groups in a sentence

The reference implementation also stops looking backward at masked image positions. Text across that boundary is replaced by padding for the lookup.

All unpadded 2-grams in this example are I · love, love · New, New · York, and York · City. At one position, however, Engram forms just one group of each length.

04 / FIND THE ADDRESSReady to try

Calculate where to look.

Engram represents the group with token IDs. A repeatable calculation called a hash turns these IDs into a table row address.

Toy hash: (31 × first ID + second ID) mod 101

CALCULATED ROW28
RETRIEVED VECTOR[0.2, −0.5, 0.8]

31 × 72 + 18 = 2250 = 22 × 101 + 28

Try reversing the IDs. Does the address change?

It calculates an address directly; it does not search every row for a similar phrase.

Different groups. Same memory address.

The n-gram is the input token group. Row 28 is a storage address. These are two different things.

[72, 18]2250 = 22 × 101 + 28
[1, 98]129 = 1 × 101 + 28

Both calculations leave a remainder of 28, so both groups read row 28 and get its vector. They have not become the same n-gram.

Think of two people assigned the same locker number: different people, one shared locker. This accidental overlap is called a hash collision.

Why can it happen? This toy table has only 101 rows, but there are many more possible token pairs. Some pairs must share an address. Sharing a row does not mean the groups have the same meaning.

These IDs, vectors and hash are made up. “mod 101” means take the remainder after division by 101. DeepSeek uses multipliers, XOR and prime-sized table ranges instead.

What happens before hashing?

A fixed mapping merges token IDs whose decoded text normalizes alike, including some capitalization variants. Hashing uses these compressed IDs. The mapping reduces the ID space; it does not shorten the n-gram or remove positions.

The same compressed group produces the same addresses within one Engram module. Different modules can use different mappings to table addresses.

05 / USE MORE THAN ONE LOOKUPReady to try

Eight routes for each group length.

Each n-gram length uses eight hash heads. A head here is one lookup route into its own table range.

2-gram12345678
3-gram12345678
4-gram12345678

Two groups that share a row in head 1 can reach different rows in head 2. The model combines all the retrieved vectors, so a collision in one head does not force the entire memory representation to match.

They do not guarantee unique entries for every phrase. These are hash heads, not attention heads.

3 group lengths × 8 heads = how many row lookups per position, in one module?

06 / USE THE MEMORYReady to try

Same lookup. Different contribution.

The hash decides where to look. Training learns what numbers each row contains.

The retrieved pieces are combined and projected. A gate uses the model’s broader context to control how much of the resulting value is added.

EXAMPLE VALUE[2, −1, 0.5]
×
GATE0.50
=
ADDED CONTRIBUTION[1, −0.5, 0.25]

Toy value, one residual branch. You control this slider; in the real model the gate is computed from the hidden state and retrieved key.

With fixed weights, the same pattern retrieves the same vectors in that module. Its contribution can still change with context.

Does a 4-gram limit the whole model to four tokens of context?

Sources: DeepSeek report §2.4.2 and the official implementation: build_compressed_token_map, NgramHashState.forward and Engram.forward. Examples are instructional.

Video transcript · exact narration

Engram gives the model a learned memory for short token patterns. An n-gram is the token group used to choose a memory address. The memory stores learned vectors, not paragraphs or a database of facts.

Take I love New York City. For this example, pretend each word is one token. While processing York, the two-token group is New York. The three-token group is love New York. Every group ends with York, the token being processed. City is still excluded.

Move to City, and all the windows move with it. The two-token group becomes York City. Near the start, there are not enough earlier tokens, so the longer groups use padding.

How does a group find its memory? Give New York the example IDs seventy-two and eighteen. Our toy hash multiplies the first ID by thirty-one, adds the second, and takes the remainder after division by one hundred and one. That selects row twenty-eight.

Now reverse the IDs. The same calculation selects row twenty-four. Token order matters. The hash calculates where to look; training learns the numbers stored at that address.

But a different pair, one and ninety-eight, also selects row twenty-eight. Watch the arithmetic: both totals leave the same remainder. These are still different n-grams. They simply share a memory address, so this one head returns the same vector. That is a hash collision.

Why can that happen? Our toy table has only one hundred and one rows, but many more possible token pairs. Some must share a row. Like two people assigned the same locker: same locker number does not mean the same person, or the same meaning.

Engram uses several hash heads. Two groups can collide in one head but reach different rows in another. Combining the retrieved vectors helps distinguish them. It does not guarantee that every possible group gets a unique representation.

DeepSeek uses eight heads for each group length: two, three, and four tokens. That is twenty-four row lookups at each position in one Engram module. These are hash heads, not attention heads. Our tiny hash was just a teaching example.

The retrieved vectors are combined, then projected into a key and a value. The key is compared with the model's current hidden state to calculate a gate. That controls how much of the value is added.

Here is a toy value. A gate of point two adds a small contribution; point eight adds a larger one. The same token group retrieves the same memory within a module, but context changes how much it contributes. Four-token lookups do not limit the whole model to four tokens of context.

And the addresses depend on token IDs, so they can be calculated early. Engram can fetch its vectors while other model computation runs. That is the whole path: form the group, calculate addresses, retrieve vectors, and let context control their contribution.

Implementation details behind the animation

Engram hashes compressed token IDs. For each module, the orders 2, 3 and 4 each have eight hash heads. Retrieved embeddings are concatenated and projected into keys and a value. The diagrams use illustrative IDs and vectors, not real model activations.

The official implementation normalizes the hidden state and key, forms a weighted dot product, then applies a signed square root and sigmoid to obtain the gate. Each residual branch has its own key and gate; the animation draws just one branch. The value is multiplied by the gate before being added to that branch’s hidden state.

Same lookup means the same compressed token pattern within the same module and fixed model weights. Hash collisions are possible. This memory contains learned embedding vectors, not a database of documents or guaranteed factual answers.

The paper describes two Engram modules at layers 1 and 14 (zero-indexed). Deterministic addresses allow host-memory prefetching; transfers for the first module overlap computation in the first Transformer block. This is an overlap opportunity, not a claim that every transfer is fully hidden.

Source: §2.4.2 ↗ · official inference implementation, Engram.forward

V4.1 PAPER · §2.4.3 · SPECULATIVE DECODING

How DSpark drafts and verifies several tokens

Ordinary generation chooses one token before it can calculate the next token’s distribution. DSpark uses a small drafter to propose several next tokens, then asks the main model to check a selected number of them together. The draft can be wrong, so checking it is essential before accepting its tokens.

1 / START WITH THE BOTTLENECK

See why target decoding waits on each token

For an autoregressive target model, the next distribution is conditioned on the already committed prefix. After sampling one token, its KV state and the prefix change, then the model can score the next position. Hardware can parallelize the matrix work within one forward pass, but it cannot know the sampled next token before that pass finishes. Long answers therefore repeat a latency-sensitive decode loop.

COMMITTED PREFIX… “The result”
→
TARGET PASSis
→
TARGET PASSready
→
TARGET PASSnow

This is a dependency bottleneck, not evidence that one target pass equals one fixed amount of wall-clock time. Batch size, context length, kernels, cache state, and serving load all matter.

2 / PROPOSE FIVE POSITIONS

See how DSpark drafts five positions together

The V4.1 report describes a drafter with three Transformer blocks and a sliding attention window of 128 tokens. One pass produces base logits for five draft positions in parallel. “Parallel” here means the five position representations are computed together; it does not mean the proposed words are assumed independent.

The lightweight Markov head models dependencies among the draft tokens. In the released reference, each sampled proposed token contributes a Markov embedding that adjusts the next position’s logits. That gives later proposals information about earlier proposals without rerunning all three draft blocks for every position. The confidence head has a different job: it predicts a conditional acceptance probability for each position.

3 / SCHEDULE A PREFIX, THEN VERIFY IT

How confidence estimates guide draft length

Let aⱼ mean the confidence estimate for position j, conditional on the earlier proposed positions being accepted. The scheduler can turn these into an estimated probability that the whole prefix through j survives: Sⱼ ≈ a₁ × a₂ × … × aⱼ. A low value late in a draft can make extra verification work unattractive. The report says DSpark combines these estimates with profiled engine-throughput curves to choose a verification length dynamically for each request under the current system load.

TRY THE SCHEDULER

Change the estimates; inspect expected accepted draft tokens

Illustrative arithmetic only. The sliders are invented confidence estimates. For each candidate length L, the widget shows E[AL] = Σj=1L Sj: the expected number of accepted draft tokens before any correction. It divides this by the explicitly invented cost CL = 1 + 0.45L arbitrary units (one fixed unit plus 0.45 per verified position). Neither formula is DSpark’s production scheduler.

ILLUSTRATIVE CHOICE3 positions
EXPECTED ACCEPTED DRAFT TOKENS2.31
E[A] / INVENTED COST0.98
PrefixConditional estimateSurvival SjExpected E[AL]Cost CLE[A] / C

Confidence is a learned forecast of acceptance, not a certificate that a token is correct. The product is an estimate based on conditional forecasts, not an observed verification result. The displayed E[A] excludes correction tokens and any bonus token a particular sampling procedure may generate after rejection. A real serving policy also accounts for target-model work, queueing, request shape, the sampling rule, and measured engine profiles. So DSpark does not imply a free five-times speedup.

4 / KEEP THE TARGET MODEL IN CHARGE

How proposed tokens are checked before acceptance

The drafter produces tokens from a draft distribution; the target model evaluates the proposal. A correct speculative decoder must use an acceptance-and-correction procedure consistent with the target sampling method, commit only the verified prefix, and resume from the corrected state after a rejection. A high confidence estimate cannot replace that procedure.

Run a deterministic match-prefix illustration

This button compares two fixed word lists and commits their shared prefix. It makes the control flow visible, but it is deliberately not a sampling-distribution-preserving speculative-decoding algorithm: it has no probability ratios, rejection correction, target sampling, or model state.

5 / TRAINING BOUNDARY

When DSpark is trained and which weights change

Unlike the jointly pretrained MTP module discussed for DeepSeek-V3, the report says V4.1 introduces DSpark in a dedicated stage after backbone pre-training. That stage trains only DSpark while the backbone is frozen. During post-training, DSpark continues alongside the backbone, but gradients from the DSpark objective do not propagate into the backbone. The stated purpose is to keep the drafter aligned with the evolving policy while avoiding DSpark-objective updates to backbone parameters.

The official minimal inference repository exposes a DSpark forward path, including a block-size configuration and Markov/confidence heads, but its README says generation itself is plain autoregressive sampling. It is useful for studying tensor structure; it is not the production verification scheduler.

Sources: DeepSeek-V4.1-Flash report, §2.4.3 DSpark · source and teaching-scope ledger.

V4.1 PAPER · §5.1.1 · BUILDING TRAINING TASKS

How DeepSeek constructs tasks that are worth training on

An agent’s training task needs more than a question. It needs a working environment and a way to distinguish a real solution from an accidental shortcut. Otherwise, a higher reward can mean the agent got better at passing a bad check.

PROBLEMWhat should change?

A precise user request.

ENVIRONMENTWhere can it act?

A runnable starting state and tools.

VERIFICATIONWhat counts as success?

Checks for the requested behavior and what must stay intact.

The report describes tasks as a problem–environment–verification triplet. Its coding pipeline checks whether a project can run and be verified, builds an isolated environment, has multiple agents attempt it, and separately inspects and repairs the task. Difficulty and correctness both matter.

1 / A TASK THAT LOOKS EASY TO CHECK

“Make shipping free for orders of $50 or more.”

In our invented shop, shipping currently costs $5. The requested change is free shipping when the subtotal is at least $50. Smaller orders should still cost $5. We will use a tiny JavaScript function so the entire verification problem is visible.

STARTING FUNCTION
function shipping(subtotal) {
  return 5;
}

This is our teaching task, not an example reported by DeepSeek. There is no training job or LLM running in this widget.

2 / A WEAK CHECK REWARDS A WRONG SOLUTION

One passing example is not the whole requirement

Suppose the evaluator checks only a $60 order. Returning zero for every order passes that check. But it also makes a $20 order free, which the user never asked for.

TRY TO FOOL THE TEST
return 0;

3 / CHECK THE FIX AND WHAT MUST NOT BREAK

Why the paper asks for two kinds of evaluation points

Fail-to-pass

A $60 order should become free. The starting implementation fails this requirement; a correct change makes it pass. The $50 boundary needs its own check too.

Pass-to-pass

A $20 order should still cost $5. This behavior already works and must keep working after the change. It catches the “make everything free” shortcut.

These three cases exercise the requested change, its boundary, and one behavior that must stay intact. They do not exhaust every possible subtotal. A real application might also specify currencies, rounding, invalid input, discounts, or other existing behavior. Tests have to follow those requirements, not silently invent them.

4 / AUDIT THE TASK, NOT ONLY THE SOLVER

A failed attempt can reveal a broken environment

The report’s independent quality inspection looks for environment problems, factual mistakes, mismatches between the description and checks, and exploitable evaluation. It removes solution leakage during environment construction and sends failed inspections through repair and verification again.

  1. ConstructProblem, runnable environment, checks.
  2. AttemptDifferent agents try the task.
  3. InspectExamine both the task and the attempts.
  4. RepairFix flaws, then verify again.

If every agent fails because a dependency cannot install, the task is not necessarily an excellent hard problem. If every agent succeeds by returning a constant, it is not necessarily evidence of strong reasoning. Those outcomes tell us to inspect the environment and evaluator.

Does the report claim architecture research no longer matters?

No. In §5.1, the authors describe keeping their post-training optimization recipe conventional while focusing on tasks, environments, and data. That statement concerns this post-training work. The same report introduces architectural changes elsewhere. It does not establish that architecture improvements are exhausted.

Primary source: DeepSeek-V4.1-Flash §5.1.1, Large-Scale Agent Task Synthesis. The shop function and browser tests are original illustrative examples. Source notes.

Next: interpret the paper’s measured results →

10 · READ A REAL PAPER RESULT

What V4.1’s benchmark comparison tells us

Table 1 asks a concrete question: does the finished V4.1-Flash base model retain strong capability while using its new long-context efficiency design? It compares three finished base models in DeepSeek’s shared internal evaluation setting. Read the scores as evidence about the final package, then keep separate the question of which design choice caused them.

Evidence boundary

The V4.1-Flash report does not publish a numerical, one-change-at-a-time architecture ablation for CSA2, FP4 KV caching, or SWA Bounded Replay. Section 4.3 instead gives Table 1: a comparison of DeepSeek-V4-Flash-Base, DeepSeek-V4-Pro-Base, and DeepSeek-V4.1-Flash-Base. We will read that actual core-paper result as a bundled architecture-and-training comparison, not call it an ablation.

Do fewer active parameters always mean lower scores?

Can V4.1-Flash reduce its long-context serving footprint while retaining or improving base-model capability? The architecture is designed around CED, CSA2 cross-layer cache reuse, FP4 main KV caching, and other changes. Table 1 asks whether the final package performs competitively against earlier DeepSeek base models; it does not tell us which component caused each score.

PREDICT BEFORE READING

Predict whether every benchmark score must fall

Predict one metric where V4.1-Flash improves over V4-Flash and one where it may not. Then separate the observed rows from any explanation for them.

Reveal the reported pattern

V4.1-Flash is higher on MMLU-Pro, BigCodeBench, HumanEval, MATH, and LongBench-V2 in the selected rows below; it is lower on MGSM. The reveal is deliberately mixed: “the new model wins” is too coarse. The paper also says a score gap of at most 0.3 counts as the same level, so its LongBench-V2 difference of 0.5 is narrow even by its own convention.

Which model and training choices changed?

REPORTED AS SHAREDInternal evaluation framework and the same evaluation setting
THE COMPARISONDeepSeek-V4-Flash-Base versus DeepSeek-V4.1-Flash-Base
WHAT CHANGED TOGETHERArchitecture, backbone size, activated parameters, data curation, and native multimodal training
OUTCOMESBenchmark scores; higher is better for every selected metric

Table 1 explicitly supports comparability of the evaluation procedure. It does not make the training histories identical. The report itself attributes results in part to substantial pre-training data-curation improvements and notes that V4.1 introduces native multimodal training.

Read selected results from Report Table 1

Vendor-reported values from DeepSeek-V4.1-Flash Report Table 1, PDF p. 24. This selected subset is not our result.
Reported Table 1 rowV4-Flash-BaseV4.1-Flash-Base
Activated parameters13B8B / 16B
MMLU-Pro (EM), 5-shot ↑68.374.1
BigCodeBench (Pass@1), 3-shot ↑56.860.6
HumanEval (Pass@1), 0-shot ↑69.579.4
MATH (EM), 4-shot ↑57.461.1
MGSM (EM), 8-shot ↑85.780.2
LongBench-V2 (EM), 1-shot ↑44.745.2

The activated-parameter row is not a score and the 8B / 16B notation has a context: the report describes 8B activation during prefill and 16B during decode. Do not average it, and do not infer end-to-end cost from it alone.

RESEARCHER MOMENT

Changed the architecture AND the training data.

Me giving all the credit to my favorite layer.

Obama placing a medal on another Obama in a self-congratulation meme
YOUR TURN · ONE DECISION

Which claim can Table 1 support?

V4.1-Flash-Base scores 74.1 on MMLU-Pro; V4-Flash-Base scores 68.3. Several architecture and training choices changed.

Choose an answer to see the explanation.

WHAT THE EVIDENCE SUPPORTS

Within DeepSeek’s reported common evaluation setting, V4.1-Flash-Base exceeded V4-Flash-Base on several selected knowledge, code, math, and long-context rows while using 8B activated parameters in prefill and 16B in decode versus 13B for V4-Flash-Base. It also scored lower on MGSM. This supports the narrow descriptive claim that the final V4.1 package had a strong, but non-uniform, reported capability profile alongside its stated efficiency design.

WHAT IT CANNOT ESTABLISH

It cannot assign the MMLU-Pro gain, the MGSM drop, or any other row to CED, CSA2, FP4 KV, Engram, parameter count, or data curation individually. It does not report seeds, confidence intervals, or run-to-run variance for these table rows. It is also an internal evaluation framework, so the table alone cannot establish external generalization. Architecture explanations are hypotheses until a matched intervention tests them.

Define an FP4-versus-FP8 follow-up experiment

QUESTION

For one fixed checkpoint, does serving the main KV cache in FP4 instead of FP8 measurably change long-context quality?

PLAN

Run the identical checkpoint, prompts, decoding settings, sequence lengths, and hardware with both cache precisions. Keep the backend configuration as comparable as possible and document precision-specific kernels. Repeat on representative long-context tasks and on the same sampled prompts.

MEASURE

Pre-register task accuracy and loss as quality metrics; log KV bytes per token, latency, and failures separately. Measure short and long contexts because a cache trade-off may appear only at length.

DECISION RULE

Report paired differences and their spread. Call any conclusion limited to this fixed checkpoint: it tests inference-time cache precision, not whether quantization-aware training would change the outcome.

This is a proposed experiment, not a run performed in this course.

Primary source: DeepSeek-AI, “DeepSeek-V4.1-Flash: Pushing the Limits of KV Cache Compression” ↗, §4.3.2 and Table 1, PDF pp. 23–24. Architecture context: §2.1–2.4, PDF pp. 7–14. Vendor-reported; no experiment in this lesson was run.

02 / OUR STARTING IMPLEMENTATION

Follow one forward pass through our small attention model

The architecture above is our destination. To understand its memory choices, first inspect a model small enough to follow in a few lines. This is the actual NumPy implementation used by the upcoming cache experiment.

What exists today: one causal attention layer with random, fixed weights. It accepts token IDs and returns next-token scores. It has no feed-forward block, normalization, or training loop, so it is an attention test model, not a complete DeepSeek reproduction or a trained chatbot.

INPUT[12, 37, 9]Three example token IDs
EMBED3 × 32 numbersToken + position vectors
ATTENDQ, K, VOnly read current and earlier positions
OUTPUT256 scoresAt the last position, score the next token
1 / REPRESENT THE INPUT

Each position starts with a 32-number vector

The toy vocabulary has 256 IDs. For [12, 37, 9], the code selects three token embeddings and adds the embeddings for positions 0, 1, and 2. The token IDs in this experiment are synthetic; we are not using a text tokenizer here.

x = E[tokens] + P[:len(tokens)]
2 / READ THE PREFIX

Attention combines information from visible positions

The code transforms those vectors into queries, keys, and values. A query scores the keys; softmax turns those scores into weights for mixing the values. The causal mask blocks future positions.

q, k, v = x @ WQ, x @ WK, x @ WV
scores = q @ k.T * scale
scores[np.triu_indices(len(tokens), 1)] = -np.inf
hidden = softmax(scores) @ v
3 / SCORE THE NEXT TOKEN

Read the scores at the final position

The output projection produces 256 scores per position. For this three-token prefix, the last row scores what could come after token 9. With untrained weights, those scores are not useful language predictions. They are still useful for checking that two implementations perform the same calculation.

logits = hidden @ WO @ E.T
# logits[-1] contains the next-token scores.

Excerpts from our runnable teaching implementation: Download Python code · Experiment configuration.

This fixed-weight model isolates the attention calculation. Next, we add the missing transformer components in a separate PyTorch baseline and train it. Afterward, we return to the simpler NumPy model to isolate cache behavior.

Train the small baseline →

BUILD AND TRAIN · OUR FIRST LEARNED MODEL

Train a small language model before changing its architecture

The previous model showed where the numbers flow. Now we need numbers that have actually learned something. Otherwise, changing its memory can only tell us about mechanics—not whether it predicts better.

We train a small causal transformer on a synthetic language of commands. This is our experimental starting point, not a DeepSeek checkpoint or a test of general language ability.

47 seconds · Actual training measurements · AI narration and transition sounds
Read or edit the video narration

We have traced the architecture. Now we need a model we can actually train, before testing whether a new mechanism helps.

Our starting point is a small causal transformer. Two attention blocks, feed-forward layers, and next-token prediction. No Engram or experts yet.

We train it on a tiny language of commands about shapes and colors. Training and validation use different sentences, but share the same grammar.

Here is the recorded run. Training loss measures prediction on the training sentences. Validation loss measures separate sentences without updating the weights.

Compare against simple guessing too. A word-frequency baseline predicts common words without reading the context. Beating it is more informative than beating uniform guessing alone.

Now repeat the run with another training seed. Keep the data split and training budget fixed. This gives us a baseline to compare future architecture changes against.

Token + position embeddings↓Two causal transformer blocks
Layer normalization · attention · residuals · MLP
↓Final normalization → next-token scores
1 / GIVE IT A TASK WE CAN INSPECT

Predict the next word in a command

Our sentences combine shapes, colors, directions, and actions. We generate the data ourselves, split complete sentences into training and validation sets, and save both lists so you can inspect them.

move the red circle left once then paint it blue

The validation sentences are different, but they use the same small grammar and familiar words. Success here means learning this controlled distribution. It does not establish general language understanding or out-of-distribution reasoning.

2 / TURN THE SENTENCE INTO TRAINING PAIRS

Each position predicts the token immediately after it

INPUT TO THE MODEL<bos> move the red circle
TARGET, SHIFTED BY ONEmove the red circle left

At “red,” the target is “circle.” The causal mask prevents that position from reading “circle” in the input. We can score all these next-token predictions in one forward pass while keeping future words hidden from earlier positions.

What would go wrong without the causal mask?

An earlier position could read the word it is supposed to predict. A low training loss would then be misleading: during real generation that future word is not available. The code checks that changing later tokens leaves earlier predictions unchanged.

3 / UPDATE THE WEIGHTS

Score predictions, calculate gradients, take one update

# Simplified training loop; full runnable source below.
logits = model(inputs)
loss = cross_entropy(logits, next_tokens, ignore_padding=True)
optimizer.zero_grad()
loss.backward()
optimizer.step()

The loss penalizes putting too little probability on the actual next token. Gradients tell the optimizer how the current weights contributed to that loss. The update changes the embeddings, attention, and feed-forward layers together.

Validation uses the same scoring rule on held-out sentences, with no optimizer update. We use it to check whether improvement extends beyond the training sentences.

API reference: PyTorch cross-entropy loss ↗. The snippet above is teaching pseudocode.

4 / INSPECT OUR RECORDED RUN

Did the predictions improve on held-out sentences?

Read the validation curve first. Lower cross-entropy means the model assigns more probability to the observed next tokens on average. It is not an accuracy percentage; a value of 1 does not mean “100% wrong.”

Loading the saved CPU run…

The slider replays recorded measurements. It does not train a model in your browser. Values come from one seeded CPU run; they are not multi-seed research results.

Measured training and held-out validation cross-entropy over optimizer steps, with constant uniform and train-unigram references
Actual measurements from our tiny trained baseline. Reference lines help distinguish learning from a weak guessing strategy.

What happened: validation loss fell from 3.370 to 0.572. Most improvement happened by step 100. More updates did not steadily improve it: step 500 scored 0.565, while step 600 scored 0.572. We report the preselected final step rather than silently selecting the best-looking checkpoint.

Why compare with a word-frequency baseline too?

Uniform guessing gives every vocabulary item equal probability. A word-frequency baseline can already improve on that by predicting common words more often, without reading the preceding context. Beating only uniform guessing would be a weak result.

5 / COMPARE WHAT IT GENERATES

The same prompts, before and after training

These are saved greedy continuations, not hand-written model answers. A plausible example is useful to inspect, but the validation set is stronger evidence than a few selected generations. The dataset permits multiple valid continuations, so there is not always one correct next word.

6 / RUN IT, THEN CHANGE ONE THING

Repeat the run with a different random seed

Download the experiment, run the reference configuration, then change only the model/training seed while keeping the data split fixed. Compare final validation loss and the generated examples. This tests whether our result depends strongly on one initialization and training order.

See the README in the downloadable experiment for the exact run command.
What should stay fixed in the comparison?

Keep the training and validation sentences, architecture, number of updates, batch size, and learning rate fixed. Change the training seed only. If you also change the data split or model size, you can no longer attribute the difference to that seed.

We now have a model whose weights have been trained. Next, isolate generation mechanics: understand cache reuse, then measure it in the separate, simpler NumPy model. Later architecture comparisons can build on this trained baseline.

Why generation can reuse earlier keys and values →

04 / ATTENTION AND MEMORY · MEASURE OUR STARTING POINT

How context length changes KV-cache memory and generation time

We have seen how the cache avoids recomputing earlier keys and values. But it also keeps those tensors in memory. Before studying DeepSeek’s compressed and shared caches, measure this simpler implementation so we have a concrete reference.

Need the setup? Our model · How cached generation works. The measurements below are from the untrained teaching model, not DeepSeek.

1 / QUESTION

What changes when the context becomes twice as long?

During cached decoding, each earlier position keeps a key and a value so the next token can reuse them. We will measure tensor bytes directly, then measure runtime separately.

2 / PREDICT

Make two predictions before reading the result

Choose both predictions first.

UNCACHED STEP[1][2][3][4] → recompute all positions
CACHED STEPstored K/V for [1][2][3] + compute [4]
Cache storage grows with saved positions. Runtime also depends on implementation and hardware.
3 / BRIEF EXPLANATION

The cache stores intermediate keys and values, not extra tokens or facts. In this one-layer float32 model, every saved token uses 32 key numbers plus 32 value numbers: 64 × 4 bytes = 256 bytes. Real models multiply this by their layer and head structure.

4 / READ RELEVANT CODE

Simplified pseudocode; the downloadable source includes the output projection and returns the updated cache.

def cached_step(token, position, cache):
    x = token_embedding[token] + position_embedding[position]
    query, key, value = x @ W_query, x @ W_key, x @ W_value
    keys = append(cache.keys, key)
    values = append(cache.values, value)
    return attention(query, keys, values)

Code review question: after each forced token is appended, do cached and full-prefix decoding score that exact same sequence at the same position? Check every step: one matching prefix output can still hide an off-by-one position or cache-index bug.

Now we can ask what to change. DeepSeek’s architecture compresses global memory and shares it across selected layers. The next lesson explains the sharing mechanism; our tiny benchmark has not implemented it yet.

Continue to shared attention caches →

09 / BUILD THE MECHANISM

Run a tiny Engram.

Change the input. Inspect the memory. Test a collision.

Every address, vector and gate below is calculated by runnable JavaScript in your browser. Start with the sentence, then follow its numbers through the module.

A working mechanism, with untrained weights.

The tables and projections use fixed, seeded random numbers. We have not trained a language model. This lab demonstrates the data flow; its outputs have no learned meaning.

01 / BUILD THE INPUT

Which token are we processing?

Click a token. Each group includes that token and its immediate predecessors. Future tokens stay out; missing earlier tokens become PAD.

Choose a group to inspect below. All three group lengths still feed the module. PAD uses ID 0; this toy tokenizer ignores capitalization when assigning IDs.

Try it: click City, then I. Watch the groups move and padding appear.

02 / RETRIEVE THE VECTORS

A calculated address reads an actual table row.

The previous diagram used handpicked vectors. Here the rows contain reproducible random numbers, so row 28’s numbers differ. Every head has its own table for each group length. “Row 5” in head 1 is a different storage location from “row 5” in head 2.

Combine all three group lengths → project

KEY
VALUE

Try it: switch from eight heads to one. The concatenated input and projected key/value change. Within a fixed configuration, repeating the same input gives the same result.

03 / LET CONTEXT CONTROL THE CONTRIBUTION

Change the hidden state—not the stored memory.

We hold the retrieved key and value fixed while rotating a hypothetical hidden-state vector. Here, alignment with the key opens the gate more.

Aligned · 0°Opposite · 180°
CALCULATED GATE
HYPOTHETICAL HIDDEN STATE
VALUE × GATE: ADDED CONTRIBUTION

Output = hidden state + added contribution:

See the exact gate calculation

Our toy rule is gate = sigmoid(3 × cosine(hidden, key)). The vectors’ angle determines cosine similarity; sigmoid turns that score into a number between 0 and 1.

We choose this hidden state directly. It is not produced from your sentence by a Transformer. DeepSeek’s gate uses its actual hidden state, learned scaling, normalization and a signed square root; this simplified gate teaches the dependency, not that exact implementation.

04 / RUN A CONTROLLED COLLISION EXPERIMENT

How much can more heads distinguish?

We enumerate 256 distinct pairs: every combination of IDs 1 through 16. For each pair, record its row in one head, or its sequence of eight row addresses.

An “address pattern” is the full list of row addresses across the selected heads. Two groups sharing that whole list retrieve the same vectors in this toy module.

A real collision from this run

This comparison gives eight heads eight times as many table entries. It isolates the mechanism; it does not prove that more heads are better at a fixed memory budget, or that language-model accuracy improves.

What this experiment can—and cannot—tell us

With very small tables, even eight heads can share the complete address pattern. More heads do not guarantee uniqueness. Our deliberately simple polynomial hash also has structure that a production hash need not share.

Distinct address patterns are an address-level measurement. They do not prove that projected vectors remain distinct, that the model learned useful features, or that it predicts text better. Those require training and separate measurements.

05 / TAKE THE CODE WITH YOU

Small enough to read. Ready to change.

The core has no packages, server calls or AI-generated answers at runtime. It tokenizes the toy sentence, hashes the groups, reads seeded tables, projects the vectors and calculates the gate.

Download the runnable JavaScript core ↓
Run the same calculation locally with Node.js
const E = require('./engram-lab-core.js');
const tokens = E.tokenize('I love New York City');
const groups = E.groups(tokens, 3); // York is position 3.
const memory = E.retrieve(groups, 8, 101);
console.log(memory.groups); // Every address and retrieved vector.
console.log(E.contextGate(memory.key, memory.value, 0));
console.log(E.contextGate(memory.key, memory.value, 180));
console.log(E.collisionExperiment(17));

Save this snippet as run.cjs beside the downloaded file, then run node run.cjs.

How this differs from DeepSeek

We split words on spaces, assign toy IDs, use a polynomial hash and three-number vectors, and inspect one residual branch. There is no trained tokenizer, compressed-token mapping, actual DeepSeek hash, production-size table or language-model training here. The learned tables in a real model are replaced by fixed random initialization.

The preserved mechanism is: causal n-gram groups → several table lookups → concatenation and projection → a context-dependent gated addition.

Next research step: train these table rows and projections on a tiny prediction task, then compare held-out results against a matched baseline.

OUR RESEARCH · LIVE EXPERIMENT NOTEBOOK

Can n-gram memory adapt a frozen language model?

Take a pretrained language model. Keep its original weights fixed. Can we teach it a task by training a small memory module, and how does that compare with LoRA?

Work in progress · Results below are our measurements on SmolLM2-135M, not DeepSeek benchmarks. Our adapter is Engram-inspired; it is not an exact reproduction of the paper.

Measured results: LoRA outperforms our n-gram adapter on banking classification and WikiText prediction; both improve over their baseline
Actual measurements. Text bars average three seeds; classification uses seed 42. Click to open the full-size graph.
1 / WHAT CHANGES?

Same pretrained model, two ways to adapt it

Frozen SmolLM2-135M
  ├─ LoRA: train small updates in attention layers
  └─ N-gram memory: train tables + the network that reads them

Original model weights stay fixed in both cases.

LoRA has 460,800 trainable adapter parameters. Our memory module has 460,352. It reads 2-token and 3-token groups through two hash heads each, then injects a gated memory contribution before the eighth Transformer block. This compares two complete adaptation designs; their placement differs as well as their mechanism.

2 / COMPLETED: CLASSIFICATION

Read a banking message and choose its category

“I am still waiting on my card?” → Card arrival
77 possible categories · real BANKING77 customer questions

All three methods train the same kind of classification head, with identical initialization. The baseline trains only that head. LoRA and memory also train their adapters. We compare 1,540 training examples with 9,229; each uses a separate 770-example validation set and the official 3,080-example test set.

Held-out test accuracy · seed 42 · higher is better
Method1,540 training examples9,229 training examples
Frozen model + classifier52.86%77.14%
LoRA + classifier80.62%89.94%
N-gram memory + classifier63.73%85.42%

LoRA wins both comparisons. Memory improves over training only a classifier, but that does not establish an advantage over LoRA. Each method received four epochs and the same two learning-rate choices; validation selected the checkpoint before test scoring.

Does the trained memory actually contribute?

In the larger run, Engram scores 85.42%. Disabling its adapter drops accuracy to 55.49%; shuffling its memory rows drops it to 71.04%. Both keep the classifier trained with the adapter, so neither is a replacement for the separately trained baseline.

Six test messages exactly match normalized training text in the larger split. Excluding them gives 77.10% baseline, 89.92% LoRA, and 85.39% Engram. The ranking stays the same.

Trained memory scores 85.42 percent accuracy; shuffling its rows scores 71.04 percent; disabling its adapter scores 55.49 percent with the same trained head

Explore the measured results and checks ↗ · BANKING77 source ↗

3 / TEXT GENERATION

Keep the language model’s normal next-token output

SmolLM2 is an autoregressive language model. For this experiment, we keep its original output vocabulary and train on real WikiText-2 passages. There is no 77-category classification head. The original model, LoRA model, and memory model predict unseen text and continue the same prompts.

Prompt: “The history of artificial intelligence”
Original model → saved continuation
LoRA          → saved continuation
N-gram memory → saved continuation

The first pilot uses 512 blocks of 256 tokens for training, 64 validation blocks, and 128 test blocks from the official separate splits. Both adapters get two epochs and two learning rates. We select by validation loss, then report test perplexity: lower means better average probability assigned to the real next tokens. This is a bounded pilot, not a full WikiText benchmark.

Three completed runs (seeds 42, 43 and 44) average 32.01 perplexity for the original model, 17.11 for LoRA, and 24.15 for n-gram memory. The training, validation and test text stays fixed across these runs. Lower is better: both adapters improve next-token prediction, and LoRA performs better here. The report includes every continuation from the fixed prompt list, including weak or repetitive outputs.

WikiText dataset ↗ · Base model ↗

4 / COMPLETED: SWAPPABLE SPECIALISTS

Can we switch between finance and code memories?

We trained separate n-gram modules for real FiQA financial discussions and CodeSearchNet Ruby functions, while keeping SmolLM2 and its language-model output head frozen. Each domain had 65,536 training tokens, separate validation and test text, and three training seeds. Code repositories were kept separate across splits. These are custom text-prediction tests, not the original datasets' retrieval benchmarks.

Mean test perplexity across three seeds · smaller module · lower is better
Loaded moduleFinancial textCode
No module31.5323.44
Finance memory30.4223.25
Code memory31.7519.64

The matching specialist improves predictions. Switching finance → code → finance restores exactly the same predictions. Here we swap the complete module: its tables and the learned network that reads them. The tokenizer stays unchanged, and we choose the domain manually.

Our hypothesis did not beat the baseline

Matched small LoRA adapters scored 26.78 on financial text and 13.31 on code, outperforming n-gram memory. Larger n-gram tables did not improve this training run: 30.56 on finance and 19.89 on code. LoRA also won within the frequent-token-group buckets. N-gram adaptation disturbed general text less, but also learned less; we have not established a better retention trade-off.

What happens if we switch only the tables?

A separate one-seed diagnostic put the code tables into the finance reader. Code perplexity was 23.29, compared with 19.64 for the complete code module. The specialty is not carried entirely by the tables. A shared reader explicitly trained with multiple tables is a motivated next experiment; we have not run it yet.

More rows cost storage, even when lookup count stays fixed

Our small and large modules contain 460,352 and 1,843,392 parameters. Their saved files are 1.84 MB and 7.38 MB. Both retrieve four vectors per token, so increasing rows does not enlarge the reader's matrix operations. However, our dense AdamW implementation uses more gradient and optimizer memory and performs more table-update work. Peak training allocation was approximately 882 and 904 MiB. Shared GPU contention prevents a clean speed claim.

32 training trials and all 24 selected-module evaluations completed. This is a small frozen-model adaptation study, not a reproduction of DeepSeek pretraining or evidence of reliable financial advice or executable code. Prior pretraining exposure is unknown. Finance documents may be related across splits despite duplicate filtering.

Experiment protocol · Results and cost explanation · Recording script · Raw measured results

5 / NEXT RESEARCH QUESTIONS

Test usefulness, cost and what breaks

  • Repeatability: text and swappable-specialist runs with seeds 42, 43 and 44 are complete.
  • Verifiable generated answers: add held-out questions with automatically checkable answers.
  • Retention: general-text checks are complete. A comparison at equal task improvement is still needed.
  • Cost: report trainable parameters, adapter bytes, memory, training time and generation speed.
  • Swapping: complete-module restoration passed. Next, train one shared reader with multiple switchable tables.

The completed comparisons above establish bounded prediction and swapping results; shared-reader training and verifiable answer tests remain future work. The GPU is shared, so timing measurements are affected by contention. Current generation recomputes the full prefix for every method; it does not measure optimized KV-cache decoding. Transfer between different model backbones is a separate, untested question.

6 / REPRODUCE AND SHARE

Keep the article, code and evidence together

This course is the main research write-up: question, mechanism, setup, results, failures, and runnable code. Once the experiments are stable, we can produce a PDF report and a short post linking back here. We are testing a task-adaptation approach, not claiming to have invented n-gram memory.

Experiment code and recorded results on GitHub ↗

EXTRA / MEMORY CALCULATOR

Calculate how cache sharing changes storage

Before training anything, isolate the storage question. Imagine eight layers whose global caches would otherwise have equal size. Change how many separate cache owners they use.

Separate caches
8 units
Shared caches
2 units
75%less global-cache storage
in this simplified calculation

Calculated, not measured. Equal-sized caches; excludes model weights, local KV, compression differences, and runtime overhead. This is not DeepSeek’s reported memory reduction.

YOUR FIRST RESEARCH QUESTION

How much sharing can the model tolerate?

Storage arithmetic cannot answer that. A useful next experiment would train matched small models with different sharing schedules, then compare held-out loss and memory under the same training budget.

COURSE CHECKPOINT

Review the architecture and choose your next experiment

You have worked through how V4.1 stores information, processes tokens, and checks proposed outputs. Use this checkpoint to review those ideas, then choose one small experiment.

TRY AN EXISTING LAB

Pick one small mechanism to inspect

Calculate cache-sharing arithmetic, run the tiny Engram data-flow lab, or inspect the recorded trained baseline. Each has a declared scope; none builds the full V4.1 model.

Self-check: are cache, model weights, and attention the same thing?

No. Model weights are learned parameters reused across inputs. A KV cache is temporary state computed for the current sequence. Attention uses a query to score cached keys, turns those scores into weights, and combines cached values. The cache is what attention can read; it is not the weights or the attention output.

Upcoming lessons: attention-sharing training, pretraining data, and reasoning-effort control from the V4.1 report. Our small baseline already demonstrates a training loop; reproducing full DeepSeek training remains beyond the work shown here.