<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohammad Nazibul Islam</title>
    <description>The latest articles on DEV Community by Mohammad Nazibul Islam (@mdnazib963).</description>
    <link>https://dev.to/mdnazib963</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4173349%2Fb6b4e8dd-4c98-4bc1-863f-4589f10e367f.jpg</url>
      <title>DEV Community: Mohammad Nazibul Islam</title>
      <link>https://dev.to/mdnazib963</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mdnazib963"/>
    <language>en</language>
    <item>
      <title>I made a language model that lives in your CPU's L3 cache</title>
      <dc:creator>Mohammad Nazibul Islam</dc:creator>
      <pubDate>Mon, 12 Oct 2026 13:01:09 +0000</pubDate>
      <link>https://dev.to/mdnazib963/i-made-a-language-model-that-lives-in-your-cpus-l3-cache-17ja</link>
      <guid>https://dev.to/mdnazib963/i-made-a-language-model-that-lives-in-your-cpus-l3-cache-17ja</guid>
      <description>&lt;p&gt;In January I wrote a spec for myself: &lt;em&gt;"&amp;gt;50 tokens/second on one CPU core."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's a deliberately boring number — llama.cpp does that on a laptop. So I kept pulling the thread, and nine months later the number is &lt;strong&gt;3,405 tok/s on a single core of a $60 CPU&lt;/strong&gt;, with a 1.9 MB model I trained from scratch.&lt;/p&gt;

&lt;p&gt;The interesting part isn't the number. It's the one-line explanation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I stopped treating the CPU cache as an implementation detail and made it the design target.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The arithmetic that decides everything
&lt;/h3&gt;

&lt;p&gt;Every forward pass of a language model reads its weights once. That read is the cost.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My model: 1.91M parameters, int8 → 1.97 MB of weights&lt;/li&gt;
&lt;li&gt;A typical desktop L3 cache: 4 MB (mine: 4 MB on the Ryzen 5 3400G)&lt;/li&gt;
&lt;li&gt;1.97 / 4 = &lt;strong&gt;49.8% of L3&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the weights fit in cache, decode is a cache-streaming problem: ~6.7 GB/s of sequential reads served by L3. If they don't, every pass goes to DRAM, and you fight bandwidth, latency, and contention.&lt;/p&gt;

&lt;p&gt;Most inference work optimizes compute. &lt;strong&gt;Cache-fit makes compute the non-bottleneck by removing the memory problem entirely.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model isn't an accident of size — I sized it to the cache on purpose: 4 gated short-conv blocks + 2 grouped-query attention blocks (kv=1, so the KV cache is one vector per layer, not seq_len×d), vocab 4096 so the output head doesn't dominate, int8 export so bytes = params.&lt;/p&gt;

&lt;h3&gt;
  
  
  The measurement that proves it
&lt;/h3&gt;

&lt;p&gt;A number without a control isn't a measurement. So the benchmark has an &lt;code&gt;evict_read&lt;/code&gt; mode: a second process hammers the L3 with random reads &lt;em&gt;while the model decodes&lt;/em&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;tok/s&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Warm (weights resident in L3)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3,405&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evicted (L3 polluted mid-run)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,681&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Penalty&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.03×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same binary, same model, same positions. That 2.03× is what cache residency is worth — measured, not argued.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;stream&lt;/code&gt; command runs the opposite probe (128 MB sequential scan) to show your box's raw DRAM bandwidth, so you can compare the two numbers yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quality didn't get sacrificed for speed
&lt;/h3&gt;

&lt;p&gt;Speed claims are cheap; parity claims aren't. The C runtime must match the PyTorch reference it was exported from, enforced as hard gates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Perplexity:&lt;/strong&gt; 26.8407 (C) vs 26.8188 (PyTorch) → &lt;strong&gt;0.082% delta&lt;/strong&gt;, gate ≤1%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Greedy token match:&lt;/strong&gt; &lt;strong&gt;99.41%&lt;/strong&gt;, gate ≤1% mismatch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Determinism + AddressSanitizer suites:&lt;/strong&gt; clean&lt;/li&gt;
&lt;li&gt;All gates pass from JSON records checked into the repo — &lt;code&gt;phase0/phase5_results.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The int8 story: per-input activation quantization amortized across all rows, sign-extended dot products (&lt;code&gt;vpmovsxbw&lt;/code&gt; + &lt;code&gt;vpmaddwd&lt;/code&gt;, 4 accumulators), AVX2 attention, degree-7 polynomial exp. The float→int kernel push alone was 1,494 → 3,696 tok/s.&lt;/p&gt;

&lt;h3&gt;
  
  
  The whole pipeline, reproducible
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;python&lt;/span&gt; &lt;span class="kd"&gt;data&lt;/span&gt;&lt;span class="na"&gt;/clean&lt;/span&gt;.py &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kd"&gt;python&lt;/span&gt; &lt;span class="kd"&gt;data&lt;/span&gt;&lt;span class="na"&gt;/generate&lt;/span&gt;.py &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kd"&gt;python&lt;/span&gt; &lt;span class="kd"&gt;data&lt;/span&gt;&lt;span class="na"&gt;/pack&lt;/span&gt;.py
&lt;span class="kd"&gt;python&lt;/span&gt; &lt;span class="kd"&gt;train&lt;/span&gt;.py &lt;span class="na"&gt;--train &lt;/span&gt;&lt;span class="kd"&gt;data&lt;/span&gt;&lt;span class="na"&gt;/train&lt;/span&gt;.bin &lt;span class="na"&gt;--val &lt;/span&gt;&lt;span class="kd"&gt;data&lt;/span&gt;&lt;span class="na"&gt;/val&lt;/span&gt;.bin &lt;span class="na"&gt;--out &lt;/span&gt;&lt;span class="nb"&gt;run&lt;/span&gt; &lt;span class="na"&gt;--d &lt;/span&gt;&lt;span class="m"&gt;144&lt;/span&gt; ...
&lt;span class="kd"&gt;python&lt;/span&gt; &lt;span class="kd"&gt;phase0_export&lt;/span&gt;.py &lt;span class="na"&gt;--ckpt &lt;/span&gt;&lt;span class="nb"&gt;run&lt;/span&gt;&lt;span class="na"&gt;/best&lt;/span&gt;.pt &lt;span class="na"&gt;--out &lt;/span&gt;&lt;span class="kd"&gt;phase0&lt;/span&gt;&lt;span class="na"&gt;/model&lt;/span&gt;.i8
&lt;span class="kd"&gt;phase0&lt;/span&gt;\build_p0.bat
&lt;span class="kd"&gt;phase0&lt;/span&gt;\phase0.exe &lt;span class="kd"&gt;phase0&lt;/span&gt;\model_p3.i8 &lt;span class="kd"&gt;bench&lt;/span&gt; &lt;span class="kd"&gt;data&lt;/span&gt;\val.bin &lt;span class="m"&gt;15&lt;/span&gt;.0 &lt;span class="m"&gt;65&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gutenberg → cleaning → synthetic task streams → BPE → train → QAT-lite int8 export → C runtime. Corpus build ~80 s. The benchmark prints one JSON line and you either reproduce my number or you don't.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it can't do (read this part)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The outputs are weak.&lt;/strong&gt; 1.9M parameters is a design study, not a chatbot. It's a benchmark vehicle for the cache-fit thesis, and I'm not going to pretend otherwise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows x64 only today.&lt;/strong&gt; MSVC build works; a Makefile for Linux/macOS is issue #2. (If this post gets any traction, that becomes priority #1.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One model, one machine class.&lt;/strong&gt; The numbers are from a Ryzen 5 3400G (4 MB L3). Bigger L3 = bigger headroom; the repo's &lt;code&gt;info&lt;/code&gt; command tells you &lt;code&gt;fit_half_l3&lt;/code&gt; for your CPU.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why publish this
&lt;/h3&gt;

&lt;p&gt;The interesting claims in ML inference are usually about GPUs. But the memory-hierarchy argument generalizes: &lt;strong&gt;if your weights fit where the compute is, the whole problem changes shape.&lt;/strong&gt; Small models sized to cache are the cheapest possible place to demonstrate that.&lt;/p&gt;

&lt;p&gt;Everything is MIT: training code, kernels, measurements.&lt;br&gt;
&lt;strong&gt;&lt;a href="https://github.com/mdnazib963/CoreFlow" rel="noopener noreferrer"&gt;https://github.com/mdnazib963/CoreFlow&lt;/a&gt;&lt;/strong&gt; — weights on Hugging Face at &lt;code&gt;nazib61/coreflow-1.9m&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you reproduce (or fail to reproduce) the numbers on your hardware, open an issue with your JSON output. Hardware diversity is the point.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>performance</category>
      <category>cpp</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
