<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Code-Series on Jaehun's Blog</title><link>https://jaehun.me/en/categories/code-series/</link><description>Recent content in Code-Series on Jaehun's Blog</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Thu, 17 Sep 2026 20:08:00 +0900</lastBuildDate><atom:link href="https://jaehun.me/en/categories/code-series/index.xml" rel="self" type="application/rss+xml"/><item><title>1. An Inference Engine in 1,500 Lines of CUDA: The Map</title><link>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-01/</link><pubDate>Thu, 17 Sep 2026 00:00:00 +0900</pubDate><guid>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-01/</guid><description>&lt;p&gt;&lt;a&#10; href="https://github.com/jmaczan/tiny-vllm"target="_blank"&#10; class="inline-flex items-center gap-1"&#10; &gt;jmaczan/tiny-vllm&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;&#10; &lt;/a&gt; is an inference engine for Llama 3.2 1B Instruct, written from scratch in C++ and CUDA. No PyTorch, no Hugging Face, not even a tokenizer. It opens the safetensors file itself, pushes the weights onto the GPU, and computes attention, RMSNorm, and softmax in its own kernels.&lt;/p&gt;</description></item><item><title>2. Weight Loading and GPU Buffer Design</title><link>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-02/</link><pubDate>Thu, 17 Sep 2026 00:00:00 +0900</pubDate><guid>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-02/</guid><description>&lt;p&gt;In this engine, right after creating the cuBLAS handle, &lt;code&gt;main()&lt;/code&gt; immediately does two things (&lt;a&#10; href="https://github.com/jmaczan/tiny-vllm/blob/e25bf1994efa90bc98b721ba7c527402f86fbeaf/src/main.cpp#L557-L569"target="_blank"&#10; class="inline-flex items-center gap-1"&#10; &gt;&lt;code&gt;src/main.cpp:557-569&lt;/code&gt;&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;&#10; &lt;/a&gt;): it takes the weights out of &lt;code&gt;model.safetensors&lt;/code&gt; and puts them on the GPU, and it preallocates the buffers that every computation from here on will use. This chapter reads both.&lt;/p&gt;</description></item><item><title>3. The prefill path: flowing the entire prompt at once</title><link>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-03/</link><pubDate>Thu, 17 Sep 2026 00:00:00 +0900</pubDate><guid>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-03/</guid><description>&lt;p&gt;In Part 1 I said &lt;code&gt;prefill()&lt;/code&gt; is a function that processes the entire prompt in one pass. All 17 tokens pass through everything from embedding to next-token selection within a single call. This part looks inside that function. We follow which kernels the tokens pass through on their way through one layer, in what order, and why that order has the shape it does, by reading each kernel&amp;rsquo;s code.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;prefill&lt;/code&gt; is simple in code. It pops a prompt from the queue, turns it into embeddings, and runs one loop over the 16 layers.&lt;/p&gt;</description></item><item><title>4. The cuBLAS Transposition Trick — Why the Matrices Are Passed Reversed</title><link>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-04/</link><pubDate>Thu, 17 Sep 2026 00:00:00 +0900</pubDate><guid>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-04/</guid><description>&lt;p&gt;In part 3, when looking at the Q/K/V projections, I wrote this sentence and moved on: &amp;ldquo;Activations and weights are row-major, but cuBLAS assumes column-major, so the call is made using only the &lt;code&gt;CUBLAS_OP_T&lt;/code&gt; transpose flag and the lda/ldb/ldc arguments. Why it&amp;rsquo;s called this way is covered in part 4.&amp;rdquo; This part is that &amp;ldquo;why.&amp;rdquo; While part 3 looked at the order in which the kernels flow, this part pulls out the same cuBLAS call again and reads each argument one by one.&lt;/p&gt;</description></item><item><title>5. The decode path that produces one token — why separate kernels were made</title><link>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-05/</link><pubDate>Thu, 17 Sep 2026 00:00:00 +0900</pubDate><guid>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-05/</guid><description>&lt;p&gt;Once prefill has streamed the whole prompt through in one pass, everything after that has to produce tokens &lt;strong&gt;one at a time&lt;/strong&gt; based on the tokens produced so far. That step is decode. As we saw in part 1, prefill and decode have different operation shapes — the former is matrix × matrix, the latter vector × matrix — so this repo makes separate kernels. They&amp;rsquo;re the variants with &lt;code&gt;Decode&lt;/code&gt; in the name, like &lt;code&gt;embeddingGatherKernelDecode&lt;/code&gt;, &lt;code&gt;ropeKernelDecode&lt;/code&gt;, &lt;code&gt;softmaxKernelDecode&lt;/code&gt;, and one more &lt;code&gt;pagedAttentionKernel&lt;/code&gt; with no prefill counterpart. This part lays those kernels next to their prefill counterparts and reads &lt;strong&gt;what differs and why the shape is different&lt;/strong&gt;, and how a new token&amp;rsquo;s K/V gets appended to the cache.&lt;/p&gt;</description></item><item><title>6. Splitting the KV cache into 16-token blocks — reading non-contiguous blocks via the block table</title><link>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-06/</link><pubDate>Thu, 17 Sep 2026 00:00:00 +0900</pubDate><guid>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-06/</guid><description>&lt;p&gt;Every time decode produces one token, attention re-reads &lt;strong&gt;all&lt;/strong&gt; the K/V that the sequence has accumulated so far. If that K/V is held in one large contiguous buffer per sequence, sequences that finish at different times leave unused gaps and waste memory. PagedAttention, the idea that made vLLM famous, answers by splitting the KV cache into &lt;strong&gt;small blocks&lt;/strong&gt; — like operating system pages — allocating them only when needed, and letting an array called the block table track &amp;ldquo;which chunk of which sequence lives in which block&amp;rdquo; (&lt;a&#10; href="https://arxiv.org/pdf/2309.06180"target="_blank"&#10; class="inline-flex items-center gap-1"&#10; &gt;Kwon et al., SOSP 2023&lt;svg class="h-3 w-3 flex-shrink-0" id="external-link" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 3h6v6m-11 5L21 3m-3 10v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/&gt;&lt;/svg&gt;&#10; &lt;/a&gt;). This repository implements that idea with &lt;code&gt;BLOCK_SIZE = 16&lt;/code&gt;-token blocks, a block table, and a single kernel, &lt;code&gt;pagedAttentionKernel&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>7. Overlapping multiple requests with slots and a queue — implementing continuous batching</title><link>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-07/</link><pubDate>Thu, 17 Sep 2026 00:00:00 +0900</pubDate><guid>https://jaehun.me/en/posts/code-series-jmaczan--tiny-vllm-07/</guid><description>&lt;p&gt;If the GPU processes only one sequence at a time, it sits idle while that sequence finishes prefill and moves on to decode. Continuous batching is the approach of feeding the next request waiting in the queue into the spot of a request that finishes, so the GPU is never idle. This repository&amp;rsquo;s README also summarizes the principle in one paragraph — &amp;ldquo;fill slots with prompts, and when any slot finishes generating, return its result and then fill the just-emptied slot with a prompt waiting in the queue.&amp;rdquo;&lt;/p&gt;</description></item></channel></rss>