<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Maitreya - Notes</title>
<link>https://maitreyasuin.github.io/blog/</link>
<atom:link href="https://maitreyasuin.github.io/blog/index.xml" rel="self" type="application/rss+xml"/>
<description>A blog built with Quarto</description>
<generator>quarto-1.9.38</generator>
<lastBuildDate>Mon, 13 Jul 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>The KV Cache: Build It, Break It, Measure It</title>
  <dc:creator>Maitreya Suin</dc:creator>
  <link>https://maitreyasuin.github.io/blog/posts/kv-cache/</link>
  <description><![CDATA[ 






<p><a href="https://colab.research.google.com/github/maitreyasuin/blog/blob/main/notebooks/kv-cache/kv-cache.ipynb">📓 Google Colab Notebook</a></p>
<blockquote class="blockquote">
<p><strong>TL;DR</strong> - Autoregressive generation re-runs the transformer over the <em>entire</em> prefix for every new token. Causal attention makes most of that work redundant: past tokens never look at future tokens, so their keys and values never change. Cache them, and each step processes <strong>one token</strong>. We build this from scratch on GPT-2, prove it’s mathematically exact, break it in the three ways everyone breaks it, and measure a <strong>1.4-3.2×</strong> wall-clock speedup on a free Colab T4. Then we confront the two prices: the cache becomes the memory bottleneck of modern inference, and our simple implementation quietly copies gigabytes it already had.</p>
</blockquote>
<p><em>If you know attention already, skim to §3; if you build inference systems, §§5–8 are for you. Everything runs in <a href="https://colab.research.google.com/github/maitreyasuin/blog/blob/main/notebooks/kv-cache/kv-cache.ipynb">one Colab notebook</a> on a free T4.</em></p>
<section id="the-whole-post-in-one-plot" class="level2">
<h2 class="anchored" data-anchor-id="the-whole-post-in-one-plot">The whole post in one plot</h2>
<p>Run GPT-2 in the most obvious generation loop - full forward pass over the whole sequence, every token - and time each step. Then turn the KV cache on and do it again.</p>
<p><img src="https://maitreyasuin.github.io/blog/posts/kv-cache/latency.png" class="img-fluid"></p>
<p>Without the cache, the per-token latency is a rising line: token 400 costs visibly more than token 1, because each step reprocesses a longer prefix. With the cache, the line goes flat. Same model, same weights, same output tokens - provably the same, as we’ll see - at a fraction of the cost.</p>
<p>The rest of this post is the why, the how, the three common bugs, and the bill.</p>
</section>
<section id="the-problem-generation-is-a-loop-that-forgets-its-a-loop" class="level2">
<h2 class="anchored" data-anchor-id="the-problem-generation-is-a-loop-that-forgets-its-a-loop">1. The problem: generation is a loop that forgets it’s a loop</h2>
<p>A decoder-only LLM generates one token at a time. The naive loop:</p>
<div id="33f93144" class="cell" data-execution_count="1">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1">tokens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> prompt</span>
<span id="cb1-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_new_tokens):</span>
<span id="cb1-3">    logits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model(tokens)            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># forward over the WHOLE sequence</span></span>
<span id="cb1-4">    tokens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tokens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> [sample(logits[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])]</span></code></pre></div></div>
</div>
<p>At step 1 we run the model over <img src="https://latex.codecogs.com/png.latex?t"> tokens. At step 2, over <img src="https://latex.codecogs.com/png.latex?t%7B+%7D1"> tokens - of which <img src="https://latex.codecogs.com/png.latex?t"> are <em>identical to what we just processed</em>. By step 500 we’ve pushed the first prompt token through the network 500 times and gotten the exact same intermediate results 500 times.</p>
<p>How expensive? For hidden size <img src="https://latex.codecogs.com/png.latex?d"> and sequence length <img src="https://latex.codecogs.com/png.latex?t">, one forward pass costs roughly <img src="https://latex.codecogs.com/png.latex?O(t%20%5Ccdot%20d%5E2)"> in the linear layers (QKV projections + MLP) and <img src="https://latex.codecogs.com/png.latex?O(t%5E2%20%5Ccdot%20d)"> in attention. A single full-sequence forward is <em>quadratic</em> in length - the cubic term below appears only because the naive loop repeats that forward for prefixes of length <img src="https://latex.codecogs.com/png.latex?1,%202,%20%5Cdots,%20n">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cunderbrace%7B%5Csum_%7Bt=1%7D%5E%7Bn%7D%20O(t%20d%5E2)%7D_%7B%5Ctext%7Blinear%20layers%7D%7D%20+%20%5Cunderbrace%7B%5Csum_%7Bt=1%7D%5E%7Bn%7D%20O(t%5E2%20d)%7D_%7B%5Ctext%7Battention%7D%7D%20%5C;=%5C;%20O(n%5E2%20d%5E2)%20+%20O(n%5E3%20d)."></p>
<p>Two things to notice, because most explanations get the emphasis wrong:</p>
<ul>
<li>The attention term is <em>cubic</em> in generated length. That’s the headline.</li>
<li>But for realistic sizes (<img src="https://latex.codecogs.com/png.latex?n"> in the hundreds–thousands, <img src="https://latex.codecogs.com/png.latex?d"> in the thousands, so <img src="https://latex.codecogs.com/png.latex?n%20%5Clesssim%20d">), the <strong>linear-layer term <img src="https://latex.codecogs.com/png.latex?O(n%5E2d%5E2)"> dominates the actual FLOP count</strong>. The biggest practical waste isn’t recomputing attention - it’s re-running every MLP over the whole prefix, every step.</li>
</ul>
<p>The KV cache eliminates the repeated <em>prefix</em> work in both terms. Note carefully what it doesn’t do: the new token still attends over the full history, so cached decoding still costs <img src="https://latex.codecogs.com/png.latex?O(td)"> attention per step, <img src="https://latex.codecogs.com/png.latex?O(n%5E2%20d)"> total - and generation still stays sequential, because token <img src="https://latex.codecogs.com/png.latex?t%7B+%7D1"> needs token <img src="https://latex.codecogs.com/png.latex?t">. The cache only removes redundancy. Totals:</p>
<p><img src="https://latex.codecogs.com/png.latex?O(n%5E2%20d%5E2)%20+%20O(n%5E3%20d)%20%5C;%5Clongrightarrow%5C;%20O(n%20d%5E2)%20+%20O(n%5E2%20d)."></p>
</section>
<section id="what-decode-actually-needs-the-math" class="level2">
<h2 class="anchored" data-anchor-id="what-decode-actually-needs-the-math">2. What decode actually needs: the math</h2>
<p>Causal self-attention for token <img src="https://latex.codecogs.com/png.latex?t"> (single head, scale <img src="https://latex.codecogs.com/png.latex?1/%5Csqrt%7Bd_h%7D">):</p>
<p><img src="https://latex.codecogs.com/png.latex?o_t%20=%20%5Csum_%7Bj%20%5Cle%20t%7D%20%5Cmathrm%7Bsoftmax%7D_j%5C!%5Cleft(%5Cfrac%7Bq_t%5E%5Ctop%20k_j%7D%7B%5Csqrt%7Bd_h%7D%7D%5Cright)%20v_j%0A=%20%5Cmathrm%7Bsoftmax%7D%5C!%5Cleft(%5Cfrac%7Bq_t%20K_%7B1:t%7D%5E%5Ctop%7D%7B%5Csqrt%7Bd_h%7D%7D%5Cright)%20V_%7B1:t%7D."></p>
<ol type="1">
<li><img src="https://latex.codecogs.com/png.latex?o_t"> needs <strong><img src="https://latex.codecogs.com/png.latex?q_t"></strong> - the query of the <em>current</em> token only.</li>
<li><img src="https://latex.codecogs.com/png.latex?o_t"> needs <strong><img src="https://latex.codecogs.com/png.latex?K_%7B1:t%7D,%20V_%7B1:t%7D"></strong> - keys and values of <em>all</em> tokens so far.</li>
<li>Because the mask is causal, <img src="https://latex.codecogs.com/png.latex?k_j"> and <img src="https://latex.codecogs.com/png.latex?v_j"> for <img src="https://latex.codecogs.com/png.latex?j%20%3C%20t"> were computed from a prefix that hasn’t changed. <strong>They are constants.</strong> Recomputing them is pure waste.</li>
</ol>
<p>So the algorithm rewrites itself: keep <img src="https://latex.codecogs.com/png.latex?K_%7B1:t-1%7D,%20V_%7B1:t-1%7D"> in memory; each step, run the network on <strong>one token</strong> - compute <img src="https://latex.codecogs.com/png.latex?q_t,%20k_t,%20v_t">, append <img src="https://latex.codecogs.com/png.latex?k_t,%20v_t"> to the cache, attend <img src="https://latex.codecogs.com/png.latex?q_t"> against the full cache. Per-step cost drops from <img src="https://latex.codecogs.com/png.latex?O(td%5E2%20+%20t%5E2d)"> to <img src="https://latex.codecogs.com/png.latex?O(d%5E2%20+%20td)">.</p>
<p><strong>Why not cache Q too?</strong> Remember that past queries <img src="https://latex.codecogs.com/png.latex?q_%7Bj%3Ct%7D"> appear <em>nowhere</em> in any future step. A query is a search request - once token <img src="https://latex.codecogs.com/png.latex?t">’s attention row is computed, <img src="https://latex.codecogs.com/png.latex?q_t"> is dead. Causality means new tokens query the past; the past never queries the future. (In an encoder, where everyone attends to everyone and the sequence doesn’t grow, none of this machinery applies. Caching is a <em>decoding</em> trick.)</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    subgraph step["decode step t"]
        x["x_t (1 token)"] --&gt; qkv["W_qkv"]
        qkv --&gt; q["q_t"]
        qkv --&gt; k["k_t"]
        qkv --&gt; v["v_t"]
    end
    style step fill:#e3f2fd,stroke:#90caf9,stroke-width:1px
    k --&gt;|append| K[("K cache 1..t")]
    v --&gt;|append| V[("V cache 1..t")]
    q --&gt; att["attention: softmax(q_t·Kᵀ)·V"]
    K --&gt; att
    V --&gt; att
    att --&gt; out["o_t → MLP → logits"]
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><strong>Figure 1. Cached decoding in one transformer layer.</strong> The current token produces a fresh query, key, and value. The key and value are appended to the cache; the query attends over the entire cached prefix. Past queries are never stored - nothing ever asks for them again.</p>
</section>
<section id="build-it-a-minimal-gpt-2-with-a-cache" class="level2">
<h2 class="anchored" data-anchor-id="build-it-a-minimal-gpt-2-with-a-cache">3. Build it: a minimal GPT-2 with a cache</h2>
<p>We’ll write a ~120-line GPT-2 that loads real pretrained weights from Hugging Face, with a <code>forward</code> that optionally consumes and returns per-layer caches. (Architecture and weight-porting follow the well-known nanoGPT pattern.) First, here are the symbols we’ll use in the code and math:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Symbol</th>
<th>Meaning</th>
<th>GPT-2 small</th>
<th>Where you’ll see it</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?B"></td>
<td>batch size</td>
<td>1 here</td>
<td>first dim of everything</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?T"></td>
<td>tokens in <em>this</em> forward call</td>
<td>prompt length, or <strong>1</strong> during decode</td>
<td><code>x: (B, T, C)</code></td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?C"></td>
<td>embedding width</td>
<td>768</td>
<td>residual stream</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?H"></td>
<td>attention heads</td>
<td>12</td>
<td><code>q,k,v: (B, H, T, D_h)</code></td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?D_h"></td>
<td>head dim <img src="https://latex.codecogs.com/png.latex?=%20C/H"></td>
<td>64</td>
<td>last dim of q/k/v</td>
</tr>
<tr class="even">
<td><code>past</code></td>
<td>tokens already cached</td>
<td>grows by <img src="https://latex.codecogs.com/png.latex?T"> each call</td>
<td><code>cache: (B, H, past, D_h)</code> per layer, ×2 for K and V</td>
</tr>
</tbody>
</table>
<p><strong>Cell 1 - setup</strong></p>
<div id="d63ddd4b" class="cell" data-execution_count="2">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># GPU: Runtime → Change runtime type → T4</span></span>
<span id="cb2-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>pip <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>q install tiktoken transformers</span>
<span id="cb2-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> math, time, torch, torch.nn <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> nn, torch.nn.functional <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> F</span>
<span id="cb2-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dataclasses <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> dataclass</span>
<span id="cb2-5">torch.manual_seed(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb2-6">device <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cuda"</span></span>
<span id="cb2-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(torch.__version__, torch.version.cuda, torch.cuda.get_device_name())</span></code></pre></div></div>
</div>
<p><strong>Cell 2 - the model</strong></p>
<div id="5f7c6234" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span></span>
<span id="cb3-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> GPTConfig:</span>
<span id="cb3-3">    block_size: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># GPT-2 max context</span></span>
<span id="cb3-4">    vocab_size: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50257</span></span>
<span id="cb3-5">    n_layer: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span></span>
<span id="cb3-6">    n_head: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span></span>
<span id="cb3-7">    n_embd: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">768</span></span>
<span id="cb3-8"></span>
<span id="cb3-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> CausalSelfAttention(nn.Module):</span>
<span id="cb3-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, cfg):</span>
<span id="cb3-11">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb3-12">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_head <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cfg.n_head</span>
<span id="cb3-13">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_attn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(cfg.n_embd, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> cfg.n_embd)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># fused QKV</span></span>
<span id="cb3-14">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_proj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(cfg.n_embd, cfg.n_embd)</span>
<span id="cb3-15"></span>
<span id="cb3-16">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, cache<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb3-17">        B, T, C <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x.shape</span>
<span id="cb3-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># One linear layer produces Q, K, V together:</span></span>
<span id="cb3-19">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   x: (B, T, C) → qkv: (B, T, 3C) → split → q, k, v: each (B, T, C)</span></span>
<span id="cb3-20">        q, k, v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_attn(x).split(C, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-21">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Reshape for multi-head attention: (B, T, C) → (B, H, T, D_h).</span></span>
<span id="cb3-22">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Heads become a batch-like dim so each head attends independently.</span></span>
<span id="cb3-23">        q, k, v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (t.view(B, T, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_head, C <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_head).transpose(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-24">                   <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (q, k, v))</span>
<span id="cb3-25">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> cache <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb3-26">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Decode: this call's k,v cover only the NEW T tokens.</span></span>
<span id="cb3-27">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Prepend history along the sequence dim (dim=2 in (B,H,T,D_h)).</span></span>
<span id="cb3-28">            k_past, v_past <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cache          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># each (B, H, past, D_h)</span></span>
<span id="cb3-29">            k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cat([k_past, k], dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (B, H, past+T, D_h)</span></span>
<span id="cb3-30">            v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cat([v_past, v], dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-31">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ^ Simple, correct, and secretly expensive - see §8.</span></span>
<span id="cb3-32">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Masking (the full story is §5, bug 2):</span></span>
<span id="cb3-33">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   prefill  → square T×T attention → causal mask ON</span></span>
<span id="cb3-34">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   decode   → 1 query vs past+1 keys → everything IS the past; no mask needed.</span></span>
<span id="cb3-35">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># is_causal=True during decode is not just unnecessary - it's WRONG:</span></span>
<span id="cb3-36">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># SDPA aligns non-square causal masks top-left, hiding nearly the whole cache.</span></span>
<span id="cb3-37">        y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> F.scaled_dot_product_attention(q, k, v, is_causal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(cache <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>))</span>
<span id="cb3-38">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (B, H, T, D_h) → (B, T, H, D_h) → (B, T, C).</span></span>
<span id="cb3-39">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># contiguous() because transpose changed strides and view() needs flat memory.</span></span>
<span id="cb3-40">        y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> y.transpose(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).contiguous().view(B, T, C)</span>
<span id="cb3-41">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_proj(y), (k, v)</span>
<span id="cb3-42"></span>
<span id="cb3-43"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> MLP(nn.Module):</span>
<span id="cb3-44">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, cfg):</span>
<span id="cb3-45">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb3-46">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_fc   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(cfg.n_embd, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> cfg.n_embd)</span>
<span id="cb3-47">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_proj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> cfg.n_embd, cfg.n_embd)</span>
<span id="cb3-48">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.act    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.GELU(approximate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tanh"</span>)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># GPT-2 uses tanh-GELU</span></span>
<span id="cb3-49">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb3-50">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_proj(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.act(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_fc(x)))</span>
<span id="cb3-51"></span>
<span id="cb3-52"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Block(nn.Module):</span>
<span id="cb3-53">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, cfg):</span>
<span id="cb3-54">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb3-55">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ln_1, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.attn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(cfg.n_embd), CausalSelfAttention(cfg)</span>
<span id="cb3-56">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ln_2, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.mlp  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(cfg.n_embd), MLP(cfg)</span>
<span id="cb3-57">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, cache<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb3-58">        a, cache <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.attn(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ln_1(x), cache)</span>
<span id="cb3-59">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> a</span>
<span id="cb3-60">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.mlp(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ln_2(x))</span>
<span id="cb3-61">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x, cache</span>
<span id="cb3-62"></span>
<span id="cb3-63"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> GPT(nn.Module):</span>
<span id="cb3-64">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, cfg):</span>
<span id="cb3-65">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb3-66">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.config <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cfg</span>
<span id="cb3-67">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.ModuleDict(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(</span>
<span id="cb3-68">            wte <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Embedding(cfg.vocab_size, cfg.n_embd),</span>
<span id="cb3-69">            wpe <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Embedding(cfg.block_size, cfg.n_embd),</span>
<span id="cb3-70">            h   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.ModuleList(Block(cfg) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(cfg.n_layer)),</span>
<span id="cb3-71">            ln_f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(cfg.n_embd),</span>
<span id="cb3-72">        ))</span>
<span id="cb3-73">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lm_head <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(cfg.n_embd, cfg.vocab_size, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb3-74">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lm_head.weight <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.wte.weight   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># weight tying</span></span>
<span id="cb3-75"></span>
<span id="cb3-76">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, idx, caches<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb3-77">        B, T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> idx.shape</span>
<span id="cb3-78">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Every layer has cached the same number of tokens;</span></span>
<span id="cb3-79">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># caches[0][0] is layer 0's key cache: (B, H, past, D_h) → read `past` off dim 2.</span></span>
<span id="cb3-80">        past <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> caches <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> caches[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].size(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-81">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> past <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.config.block_size</span>
<span id="cb3-82">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># POSITION OFFSET - the #1 cache bug (§5): token t must get position</span></span>
<span id="cb3-83">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># embedding t, not 0. During decode T=1, so without the offset every</span></span>
<span id="cb3-84">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># generated token would sit at position 0.</span></span>
<span id="cb3-85">        pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.arange(past, past <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> T, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>idx.device)</span>
<span id="cb3-86">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.wte(idx) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.wpe(pos)</span>
<span id="cb3-87">        new_caches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb3-88">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.h):</span>
<span id="cb3-89">            x, c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> block(x, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> caches <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> caches[i])</span>
<span id="cb3-90">            new_caches.append(c)</span>
<span id="cb3-91">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.ln_f(x)</span>
<span id="cb3-92">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lm_head(x), new_caches</span></code></pre></div></div>
</div>
<p><strong>Cell 3 - load the real GPT-2 weights</strong> (weight surgery; fold open if curious)</p>
<div id="0998c2bf" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@classmethod</span></span>
<span id="cb4-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> from_pretrained(cls):</span>
<span id="cb4-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Load HF GPT-2 (124M) weights into this implementation."""</span></span>
<span id="cb4-4">    <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> transformers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> GPT2LMHeadModel</span>
<span id="cb4-5">    model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cls(GPTConfig())</span>
<span id="cb4-6">    sd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.state_dict()</span>
<span id="cb4-7">    hf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> GPT2LMHeadModel.from_pretrained(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt2"</span>).state_dict()</span>
<span id="cb4-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># HF stores these linear layers as Conv1D → weights are transposed</span></span>
<span id="cb4-9">    transposed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"attn.c_attn.weight"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"attn.c_proj.weight"</span>,</span>
<span id="cb4-10">                  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mlp.c_fc.weight"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mlp.c_proj.weight"</span>)</span>
<span id="cb4-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> torch.no_grad():</span>
<span id="cb4-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> hf:</span>
<span id="cb4-13">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> k.endswith(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".attn.masked_bias"</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> k.endswith(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".attn.bias"</span>):</span>
<span id="cb4-14">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span>                      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># causal-mask buffers, not params</span></span>
<span id="cb4-15">            w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hf[k].t() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(k.endswith(t) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> transposed) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> hf[k]</span>
<span id="cb4-16">            sd[k].copy_(w)</span>
<span id="cb4-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> model</span>
<span id="cb4-18"></span>
<span id="cb4-19">GPT.from_pretrained <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> from_pretrained</span>
<span id="cb4-20">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> GPT.from_pretrained().to(device).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span>()</span>
<span id="cb4-21"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(p.numel() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> model.parameters())<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e6</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"M params"</span>)</span></code></pre></div></div>
</details>
</div>
<p><strong>Cell 4 - two generation loops</strong></p>
<div id="51f93a0c" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> timed(fn):</span>
<span id="cb5-2">    torch.cuda.synchronize()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> t0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.perf_counter()</span>
<span id="cb5-3">    out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fn()</span>
<span id="cb5-4">    torch.cuda.synchronize()</span>
<span id="cb5-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> out, time.perf_counter() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> t0</span>
<span id="cb5-6"></span>
<span id="cb5-7"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@torch.inference_mode</span>()</span>
<span id="cb5-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_no_cache(model, idx, max_new_tokens):</span>
<span id="cb5-9">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""The naive loop: full forward over the whole sequence, every step."""</span></span>
<span id="cb5-10">    per_step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb5-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_new_tokens):</span>
<span id="cb5-12">        (logits, _), dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> timed(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span>: model(idx[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>model.config.block_size:]))</span>
<span id="cb5-13">        idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cat([idx, logits[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].argmax(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, keepdim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)], dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-14">        per_step.append(dt)</span>
<span id="cb5-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> idx, per_step</span>
<span id="cb5-16"></span>
<span id="cb5-17"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@torch.inference_mode</span>()</span>
<span id="cb5-18"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_with_cache(model, idx, max_new_tokens):</span>
<span id="cb5-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Prefill once over the prompt, then feed ONE token per step."""</span></span>
<span id="cb5-20">    per_step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb5-21">    (logits, caches), t_prefill <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> timed(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span>: model(idx))       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># PREFILL</span></span>
<span id="cb5-22">    nxt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> logits[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].argmax(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, keepdim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb5-23">    out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [idx, nxt]</span>
<span id="cb5-24">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_new_tokens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):                            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># DECODE</span></span>
<span id="cb5-25">        (logits, caches), dt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> timed(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span>: model(nxt, caches))</span>
<span id="cb5-26">        nxt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> logits[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].argmax(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, keepdim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb5-27">        out.append(nxt)</span>
<span id="cb5-28">        per_step.append(dt)</span>
<span id="cb5-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.cat(out, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>), per_step, t_prefill</span></code></pre></div></div>
</div>
<p>(<code>inference_mode</code> rather than <code>no_grad</code>: it additionally disables autograd’s version tracking on tensors - marginally faster, and nothing here will ever backprop.)</p>
</section>
<section id="correctness-first-the-cache-must-be-exact" class="level2">
<h2 class="anchored" data-anchor-id="correctness-first-the-cache-must-be-exact">4. Correctness first: the cache must be <em>exact</em></h2>
<p>The KV cache is not an approximation, and we can say precisely why.</p>
<p><strong>Proof sketch.</strong> Assume eval mode, no dropout. In a decoder-only transformer, the causal mask guarantees that the layer-<img src="https://latex.codecogs.com/png.latex?%5Cell"> representation of position <img src="https://latex.codecogs.com/png.latex?j"> depends only on tokens <img src="https://latex.codecogs.com/png.latex?1..j"> - so appending token <img src="https://latex.codecogs.com/png.latex?t"> cannot change any representation at positions <img src="https://latex.codecogs.com/png.latex?%3C%20t">. Now compare full-prefix execution (forward over <img src="https://latex.codecogs.com/png.latex?1..t">) with cached execution (forward over <img src="https://latex.codecogs.com/png.latex?1..t%7B-%7D1">, store every layer’s <img src="https://latex.codecogs.com/png.latex?K,V">, then forward token <img src="https://latex.codecogs.com/png.latex?t"> alone). At layer <img src="https://latex.codecogs.com/png.latex?%5Cell">: the current token’s query <img src="https://latex.codecogs.com/png.latex?q_t%5E%5Cell"> is computed from the same hidden state in both executions; the past keys and values are identical because the past hidden states are; the new <img src="https://latex.codecogs.com/png.latex?k_t%5E%5Cell,%20v_t%5E%5Cell"> come from the same current hidden state. So token <img src="https://latex.codecogs.com/png.latex?t">’s attention row is identical, and since LayerNorm and the MLP act position-wise, its layer output is too. Induct over layers, then over decode steps: <strong>cached greedy decoding produces the same logits as full recomputation</strong> - up to floating-point kernel differences, which is the one honest caveat: the two paths hit different kernels with different summation orders, so in fp16 the logits can differ in the last bits, and after a near-tie in argmax that can flip a token. Same math, different rounding. Run correctness checks in fp32.</p>
<p>Two tests, in increasing strictness of what they catch:</p>
<p><strong>Cell 5a - logit-level check</strong> (catches numerical drift that token equality can hide behind argmax):</p>
<div id="e1cfedfe" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@torch.inference_mode</span>()</span>
<span id="cb6-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> assert_cached_logits_match(model, prompt):</span>
<span id="cb6-3">    logits_full, caches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model(prompt)                     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># prefill</span></span>
<span id="cb6-4">    nxt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> logits_full[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].argmax(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, keepdim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb6-5">    logits_ref, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model(torch.cat([prompt, nxt], dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># recompute from scratch</span></span>
<span id="cb6-6">    logits_inc, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model(nxt, caches)                      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># one cached step</span></span>
<span id="cb6-7">    torch.testing.assert_close(logits_inc[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], logits_ref[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>],</span>
<span id="cb6-8">                               rtol<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-5</span>, atol<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-5</span>)</span>
<span id="cb6-9">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"logit exactness ✓"</span>)</span>
<span id="cb6-10"></span>
<span id="cb6-11"></span>
<span id="cb6-12">model_fp32 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>()</span>
<span id="cb6-13">assert_cached_logits_match(model_fp32, prompt)</span>
<span id="cb6-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># logit exactness ✓</span></span></code></pre></div></div>
</div>
<p><strong>Cell 5b - 100-token integration check</strong> (catches everything else):</p>
<div id="886aa0eb" class="cell" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">a, _     <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_no_cache(model_fp32, prompt.clone(), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>)</span>
<span id="cb7-2">b, _, _  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_with_cache(model_fp32, prompt.clone(), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>)</span>
<span id="cb7-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> torch.equal(a, b), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cache changed the output!"</span></span>
<span id="cb7-4"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"greedy exactness ✓ :"</span>, enc.decode(a[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].tolist())[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">120</span>], <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"..."</span>)</span>
<span id="cb7-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># greedy exactness ✓ : The key to fast inference is to know what the data is about.</span></span>
<span id="cb7-6"></span>
<span id="cb7-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The first thing to know is that the data is not a single p ...</span></span></code></pre></div></div>
</div>
</section>
<section id="break-it-the-three-common-bugs" class="level2">
<h2 class="anchored" data-anchor-id="break-it-the-three-common-bugs">5. Break it: the three common bugs</h2>
<p>Let’s write the common bugs deliberately - each one below is a real mistake I’ve made or reviewed, each has a distinctive failure signature, and each is a one-line fix once you can name it.</p>
<section id="bug-1---positions-the-fluent-garbage-generator" class="level3">
<h3 class="anchored" data-anchor-id="bug-1---positions-the-fluent-garbage-generator">Bug 1 - positions: the fluent-garbage generator</h3>
<p>With a cache, step <img src="https://latex.codecogs.com/png.latex?t"> feeds the model a tensor of shape <code>(B, 1)</code>. Compute positions as <code>arange(T)</code> and every generated token gets embedded at position 0. GPT-2’s learned absolute embeddings make this catastrophic <em>and</em> silent - the model produces grammatical text that slowly stops making sense, which is the worst kind of bug: nothing crashes.</p>
<div id="2ef7b1c7" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> PositionBuggyGPT(GPT):</span>
<span id="cb8-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""The bug, isolated: positions restart at 0 every forward call."""</span></span>
<span id="cb8-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, idx, caches<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb8-4">        B, T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> idx.shape</span>
<span id="cb8-5">        past <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> caches <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> caches[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].size(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb8-6">        pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, T, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>idx.device)          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ← </span><span class="al" style="color: #AD0000;
background-color: null;
font-style: inherit;">BUG</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">: no offset</span></span>
<span id="cb8-7">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.wte(idx) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.wpe(pos)</span>
<span id="cb8-8">        new_caches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb8-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.h):</span>
<span id="cb8-10">            x, c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> block(x, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> caches <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> caches[i])</span>
<span id="cb8-11">            new_caches.append(c)</span>
<span id="cb8-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lm_head(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.ln_f(x)), new_caches</span>
<span id="cb8-13"></span>
<span id="cb8-14">buggy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> PositionBuggyGPT(GPTConfig()).to(device).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>().<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span>()</span>
<span id="cb8-15">buggy.load_state_dict(model_fp32.state_dict())</span>
<span id="cb8-16"></span>
<span id="cb8-17">a, _    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_no_cache(buggy, prompt.clone(), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>)       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># no cache → bug dormant</span></span>
<span id="cb8-18">b, _, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_with_cache(buggy, prompt.clone(), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>)     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># cached  → bug live</span></span>
<span id="cb8-19"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"outputs equal?"</span>, torch.equal(a, b))                   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># False</span></span>
<span id="cb8-20"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"no cache:"</span>, enc.decode(a[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].tolist()))</span>
<span id="cb8-21"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cached  :"</span>, enc.decode(b[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].tolist()))                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># fluent, then unmoored</span></span>
<span id="cb8-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># outputs equal? False</span></span>
<span id="cb8-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># no cache: The key to fast inference is to know what the data is about.</span></span>
<span id="cb8-24"></span>
<span id="cb8-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The first thing to know is that the data is not a single point. It is a collection of points.</span></span>
<span id="cb8-26"></span>
<span id="cb8-27"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The second thing to know is</span></span>
<span id="cb8-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># cached  : The key to fast inference is to the the,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,</span></span></code></pre></div></div>
</div>
<p>Note <em>when</em> the bug fires: the no-cache path is immune (its <code>arange(T)</code> is accidentally correct because <img src="https://latex.codecogs.com/png.latex?T"> is always the full length). The bug only exists on the cached path - which is why the §4 equality test is the right detector, and why “it works without the cache” proves nothing. RoPE models have the same bug in rotary-phase clothing: rotate <img src="https://latex.codecogs.com/png.latex?q_t,%20k_t"> by angle 0 instead of angle <img src="https://latex.codecogs.com/png.latex?t">.</p>
</section>
<section id="bug-2---masking-the-model-that-can-only-see-its-first-token" class="level3">
<h3 class="anchored" data-anchor-id="bug-2---masking-the-model-that-can-only-see-its-first-token">Bug 2 - masking: the model that can only see its first token</h3>
<p>During prefill you attend over a square <img src="https://latex.codecogs.com/png.latex?T%20%5Ctimes%20T"> block and need the causal mask. During cached decode the query attends over <code>past+1</code> keys, <em>all of which are legitimately in its past</em> - the “mask” is the cache boundary itself. Keeping <code>is_causal=True</code> feels harmlessly conservative. It isn’t:</p>
<div id="a8f42ee1" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">B, H, past, Dh <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span></span>
<span id="cb9-2">q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.randn(B, H, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, Dh, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>device)          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># one decode query</span></span>
<span id="cb9-3">k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.randn(B, H, past <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, Dh, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>device)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># full cache</span></span>
<span id="cb9-4">v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.randn(B, H, past <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, Dh, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>device)</span>
<span id="cb9-5"></span>
<span id="cb9-6">right <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> F.scaled_dot_product_attention(q, k, v, is_causal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb9-7">wrong <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> F.scaled_dot_product_attention(q, k, v, is_causal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb9-8"></span>
<span id="cb9-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># What did the wrong one compute? Attention to ONLY the first key:</span></span>
<span id="cb9-10">only_first <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> v[:, :, :<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, :]</span>
<span id="cb9-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>((wrong <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> only_first).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>().<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>().item())   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ~0  ← it ignored 64 of 65 keys</span></span>
<span id="cb9-12"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>((right <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> wrong).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">abs</span>().<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>().item())        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># large</span></span>
<span id="cb9-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 0.0</span></span>
<span id="cb9-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3.039318561553955</span></span></code></pre></div></div>
</div>
<p>The mechanism: for non-square shapes, SDPA aligns the causal mask to the <strong>top-left</strong> - query row 0 may attend to key column 0, full stop. Your one query gets matched against the <em>first</em> cache entry and the other 64 are masked away. The model can only see the first token it ever read. Hence the line in our implementation: <code>is_causal=(cache is None)</code>.</p>
</section>
<section id="bug-3---batching-padding-deferred-deliberately" class="level3">
<h3 class="anchored" data-anchor-id="bug-3---batching-padding-deferred-deliberately">Bug 3 - batching + padding: deferred, deliberately</h3>
<p>With batched prompts of different lengths you must left-pad (so every sequence’s <em>last</em> token lands in the same column - that’s the position whose logits you sample) and carry an explicit padding mask, because cached decode has no causal mask to hide behind and will happily attend to padding K/V as if they were content. Right-padding breaks in two places at once: the logits you gather at <code>[:, -1]</code> belong to padding, and the position offsets go wrong per-sequence. We dodge all of this with batch = 1 here.</p>
</section>
</section>
<section id="measure-it-benchmarks-on-a-free-t4" class="level2">
<h2 class="anchored" data-anchor-id="measure-it-benchmarks-on-a-free-t4">6. Measure it: benchmarks on a free T4</h2>
<p><strong>Cell 6 - the race</strong></p>
<div id="d947d928" class="cell" data-execution_count="10">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.half().<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span>()</span>
<span id="cb10-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(torch.__version__, torch.version.cuda, torch.cuda.get_device_name())</span>
<span id="cb10-3"></span>
<span id="cb10-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> reset_mem():  torch.cuda.empty_cache()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> torch.cuda.reset_peak_memory_stats()</span>
<span id="cb10-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> peak_mb():    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> torch.cuda.max_memory_allocated() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span></span>
<span id="cb10-6"></span>
<span id="cb10-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>):                                   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># warmup: CUDA context, kernels, allocator</span></span>
<span id="cb10-8">    generate_with_cache(model, torch.randint(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50257</span>, (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span>), device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>device), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>)</span>
<span id="cb10-9"></span>
<span id="cb10-10">rows <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb10-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> plen <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span>):</span>
<span id="cb10-12">    prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.randint(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50257</span>, (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, plen), device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>device)</span>
<span id="cb10-13">    n_new <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">400</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> plen <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">400</span>              <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># keep plen+n_new &lt; 1024</span></span>
<span id="cb10-14">    reset_mem()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> _, steps_nc      <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_no_cache(model, prompt.clone(), n_new)</span>
<span id="cb10-15">    reset_mem()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> _, steps_c, tp   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_with_cache(model, prompt.clone(), n_new)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> mem_c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> peak_mb()</span>
<span id="cb10-16">    rows.append((plen, n_new, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(steps_nc), tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(steps_c),</span>
<span id="cb10-17">                 <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(steps_nc)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>(tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(steps_c)), <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e3</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(steps_c)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(steps_c), mem_c))</span>
<span id="cb10-18"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> r <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> rows: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(r)</span></code></pre></div></div>
</div>
<table class="caption-top table">
<colgroup>
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
</colgroup>
<thead>
<tr class="header">
<th>Prompt</th>
<th>New tokens</th>
<th>No-cache total</th>
<th>Cache total (incl.&nbsp;prefill)</th>
<th>Speedup</th>
<th>Cached TPOT</th>
<th>Peak mem</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>16</td>
<td>400</td>
<td>2.52 s</td>
<td>1.84 s</td>
<td>1.37×</td>
<td>4.60 ms</td>
<td>769.63 MiB</td>
</tr>
<tr class="even">
<td>128</td>
<td>400</td>
<td>2.98 s</td>
<td>2.14 s</td>
<td>1.39×</td>
<td>5.35 ms</td>
<td>777.50 MiB</td>
</tr>
<tr class="odd">
<td>512</td>
<td>400</td>
<td>5.76 s</td>
<td>1.78 s</td>
<td>3.22×</td>
<td>4.45 ms</td>
<td>835.24 MiB</td>
</tr>
</tbody>
</table>
<p>Expect the speedup to <em>grow</em> with prompt length - exactly what the <img src="https://latex.codecogs.com/png.latex?O(n%5E2d%5E2)%20%5Cto%20O(nd%5E2)"> analysis predicts.</p>
<p><strong>Cell 7 - the money plot</strong> (note the cached cumulative curve includes the prefill, so both curves are honest end-to-end time):</p>
<div id="20741d53" class="cell" data-execution_count="11">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np, matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb11-2">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb11-3">ax[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].plot([t<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e3</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> steps_nc], label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"no cache"</span>)</span>
<span id="cb11-4">ax[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].plot([t<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e3</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> steps_c],  label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"KV cache"</span>)</span>
<span id="cb11-5">ax[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(xlabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"decode step"</span>, ylabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"latency (ms)"</span>, title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Per-token latency"</span>)</span>
<span id="cb11-6">ax[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].legend()</span>
<span id="cb11-7">ax[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].plot(np.cumsum(steps_nc),              label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"no cache"</span>)</span>
<span id="cb11-8">ax[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].plot(np.cumsum([tp] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> steps_c),        label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"KV cache (incl. prefill)"</span>)</span>
<span id="cb11-9">ax[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(xlabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"decode step"</span>, ylabel<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cumulative time (s)"</span>, title<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Total generation time"</span>)</span>
<span id="cb11-10">ax[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].legend()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> plt.tight_layout()<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> plt.savefig(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"latency.png"</span>, dpi<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>)</span></code></pre></div></div>
</div>
<p><img src="https://maitreyasuin.github.io/blog/posts/kv-cache/latency.png" class="img-fluid"></p>
<p><strong>Why is the cached line flat - and would it stay flat?</strong> Honest answer: it’s flat <em>at this scale</em>, not in principle. A cached step costs <img src="https://latex.codecogs.com/png.latex?O(d%5E2)"> for weights plus <img src="https://latex.codecogs.com/png.latex?O(t%20%5Ccdot%20d)"> for attending over the cache. At GPT-2 size (<img src="https://latex.codecogs.com/png.latex?d%7B=%7D768">, <img src="https://latex.codecogs.com/png.latex?t%20%5Cle%201024">) the weight term towers over the cache term, so growth is invisible. Push to a 7B model at 128k context and the ratio inverts: reading the cache <em>is</em> the step, per-token latency climbs with context, and we will realize why decode-specialized kernels (Flash-Decoding) and cache-compression schemes exist.</p>
</section>
<section id="the-bill-the-cache-is-memory-you-now-have-to-plan-for" class="level2">
<h2 class="anchored" data-anchor-id="the-bill-the-cache-is-memory-you-now-have-to-plan-for">7. The bill: the cache is memory you now have to plan for</h2>
<p>Per token, per sequence, the cache stores one K and one V vector per layer per KV-head:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bbytes/token%7D%20=%202%20%5Ctimes%20n_%7B%5Ctext%7Blayers%7D%7D%20%5Ctimes%20n_%7B%5Ctext%7Bkv-heads%7D%7D%20%5Ctimes%20d_%7B%5Ctext%7Bhead%7D%7D%20%5Ctimes%20%5Ctext%7Bbytes%7D_%7B%5Ctext%7Bdtype%7D%7D"></p>
<table class="caption-top table">
<colgroup>
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
</colgroup>
<thead>
<tr class="header">
<th>Model</th>
<th>Layers</th>
<th>KV heads × d_head</th>
<th>KiB / token (fp16)</th>
<th>8k context</th>
<th>128k context</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>GPT-2 124M</td>
<td>12</td>
<td>12×64 (MHA)</td>
<td>36</td>
<td>0.29 GiB</td>
<td>- (ctx 1k)</td>
</tr>
<tr class="even">
<td>Llama-2-7B</td>
<td>32</td>
<td>32×128 (MHA)</td>
<td>512</td>
<td>4.0 GiB</td>
<td>-</td>
</tr>
<tr class="odd">
<td>Llama-3-8B</td>
<td>32</td>
<td>8×128 (GQA)</td>
<td>128</td>
<td>1.0 GiB</td>
<td><strong>16 GiB</strong></td>
</tr>
<tr class="even">
<td>Llama-3-70B</td>
<td>80</td>
<td>8×128 (GQA)</td>
<td>320</td>
<td>2.5 GiB</td>
<td><strong>40 GiB</strong></td>
</tr>
</tbody>
</table>
<p><em>The cache is not a model property; it’s a <strong>model × sequence length × batch size</strong> property. At batch 32, the Llama-3-8B 128k row becomes <img src="https://latex.codecogs.com/png.latex?32%20%5Ctimes%2016%20=%20512"> GiB, before weights, activations, or allocator overhead.</em></p>
<p>There are a few ways to address this, such as <strong>MQA / GQA</strong> (1 or few KV heads shared across query heads), <strong>MLA</strong> (DeepSeek, don’t store K,V at all; store a small latent), <strong>quantized / windowed caches</strong>, and <strong>PagedAttention</strong> (vLLM). We will cover some of these in future parts of this series.</p>
</section>
<section id="the-uncomfortable-truth-our-cache-is-correct-and-still-bad" class="level2">
<h2 class="anchored" data-anchor-id="the-uncomfortable-truth-our-cache-is-correct-and-still-bad">8. The uncomfortable truth: our cache is correct, and still bad</h2>
<p>We proved exactness and measured a big speedup. Time to disappoint ourselves. This line -</p>
<div id="0560073d" class="cell" data-execution_count="12">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1">k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cat([k_past, k], dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span></code></pre></div></div>
</div>
<p>allocates a brand-new tensor and copies the <em>entire old cache</em> into it, every layer, every step. The step-<img src="https://latex.codecogs.com/png.latex?t"> copy moves <img src="https://latex.codecogs.com/png.latex?%5Csim%20t%20%5Ctimes"> 36 KiB; summed over a generation from <img src="https://latex.codecogs.com/png.latex?n_0"> to <img src="https://latex.codecogs.com/png.latex?n_1"> tokens the total memcpy is roughly</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bbytes%20copied%7D%20%5C;%5Capprox%5C;%20%5Ctext%7Bbytes/token%7D%20%5Ctimes%20%5Cfrac%7Bn_1%5E2%20-%20n_0%5E2%7D%7B2%7D,"></p>
<p>which for our 128-prompt, 400-token benchmark is <img src="https://latex.codecogs.com/png.latex?36%7B,%7D864%20%5Ctimes%20(528%5E2%20-%20128%5E2)/2%20%5Capprox"> <strong>4.5 GiB of data the GPU already had</strong>.</p>
<p>It’s <em>fine here</em> - GPT-2 is small and the T4 shrugs. An improved version preallocates once and writes each new token into its slot:</p>
<div id="00d1e138" class="cell" data-execution_count="13">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> StaticLayerKVCache:</span>
<span id="cb13-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Preallocated KV cache for one layer - no torch.cat, no reallocation.</span></span>
<span id="cb13-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    This is Hugging Face's StaticCache in miniature.</span></span>
<span id="cb13-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    k/v storage: (B, H, max_len, D_h); `pos` is the write pointer."""</span></span>
<span id="cb13-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, B, H, max_len, Dh, dtype, device):</span>
<span id="cb13-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.empty(B, H, max_len, Dh, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dtype, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>device)</span>
<span id="cb13-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.empty_like(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.k)</span>
<span id="cb13-8">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb13-9"></span>
<span id="cb13-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> append(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, k_new, v_new):</span>
<span id="cb13-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""k_new, v_new: (B, H, T_new, Dh). T_new=1 in decode, =prompt_len in prefill.</span></span>
<span id="cb13-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Returns views of the valid prefix - zero copy of history."""</span></span>
<span id="cb13-13">        t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> k_new.size(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> end <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> t</span>
<span id="cb13-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> end <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.k.size(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>):</span>
<span id="cb13-15">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">RuntimeError</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"KV cache overflow: need </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>end<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, capacity </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>k<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>size(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-16">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.k[:, :, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.pos:end].copy_(k_new)</span>
<span id="cb13-17">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.v[:, :, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.pos:end].copy_(v_new)</span>
<span id="cb13-18">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> end</span>
<span id="cb13-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.k[:, :, :end], <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.v[:, :, :end]</span></code></pre></div></div>
</div>
<p>The win is bigger than avoided copies. A static cache means <strong>static shapes</strong>, and static shapes are what let production stacks capture the whole decode step as a <strong>CUDA graph</strong> (<code>torch.compile(mode="reduce-overhead")</code> does this for you): the CPU stops launching hundreds of kernels per token and replays one recorded graph, which at small batch sizes is often worth more than any single kernel optimization. Dynamic <code>torch.cat</code> shapes forbid all of that. Hugging Face’s <code>model.generate(..., use_cache=True)</code> uses something very similar to the loop we wrote.</p>
<p>We now have a model that generates efficiently for <em>one</em> sequence, a proof that the optimization is free, three common bugs, and a cache whose size we can predict to the byte. In future articles, we’ll explore why materializing the <img src="https://latex.codecogs.com/png.latex?T%5Ctimes%20T"> score matrix is the real crime of prefill, and how FlashAttention computes exact attention without ever writing it down.</p>
<p><em>Feel free to reach out to me for any comments or thoughts: maitreyasuin21 [at] gmail [dot] com</em></p>
</section>
<section id="references-further-reading" class="level2">
<h2 class="anchored" data-anchor-id="references-further-reading">References &amp; further reading</h2>
<p><strong>Papers</strong></p>
<ul>
<li>Vaswani et al., <a href="https://arxiv.org/abs/1706.03762"><em>Attention Is All You Need</em></a>, 2017 - the <img src="https://latex.codecogs.com/png.latex?O(n%5E2)"> we’re taming.</li>
<li>Shazeer, <a href="https://arxiv.org/abs/1911.02150"><em>Fast Transformer Decoding: One Write-Head Is All You Need</em></a> (MQA), 2019.</li>
<li>Ainslie et al., <a href="https://arxiv.org/abs/2305.13245"><em>GQA: Training Generalized Multi-Query Transformer Models</em></a>, 2023.</li>
<li>DeepSeek-AI, <a href="https://arxiv.org/abs/2405.04434"><em>DeepSeek-V2</em></a> (introduces MLA), 2024.</li>
<li>Kwon et al., <a href="https://arxiv.org/abs/2309.06180"><em>Efficient Memory Management for LLM Serving with PagedAttention</em></a>.</li>
</ul>
<p><strong>Docs &amp; code</strong></p>
<ul>
<li><a href="https://huggingface.co/docs/transformers/en/cache_explanation">Hugging Face: KV cache strategies</a> (<code>DynamicCache</code>, <code>StaticCache</code>, <code>past_key_values</code>).</li>
<li><a href="https://pytorch.org/docs/stable/generated/torch.nn.functional.scaled_dot_product_attention.html">PyTorch: <code>scaled_dot_product_attention</code></a> - the fine print behind bug 2’s mask alignment.</li>
<li><a href="https://kipply.com/blog/transformer-inference-arithmetic/">kipply, <em>Transformer Inference Arithmetic</em></a>, 2022.</li>
<li><a href="https://github.com/karpathy/nanoGPT">Karpathy, <em>nanoGPT</em></a> - the architecture and weight-loading pattern used here.</li>
<li><a href="https://docs.vllm.ai/">vLLM documentation</a> - PagedAttention, continuous batching, prefix caching: Part 3’s syllabus.</li>
</ul>


</section>

 ]]></description>
  <category>llm-inference</category>
  <category>kv-cache</category>
  <category>pytorch</category>
  <category>gpt-2</category>
  <guid>https://maitreyasuin.github.io/blog/posts/kv-cache/</guid>
  <pubDate>Mon, 13 Jul 2026 00:00:00 GMT</pubDate>
</item>
</channel>
</rss>
