<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://junhe.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://junhe.github.io/" rel="alternate" type="text/html" /><updated>2026-10-20T06:01:53+00:00</updated><id>https://junhe.github.io/feed.xml</id><title type="html">Jun He’s hand-written notes</title><subtitle></subtitle><entry><title type="html">Accelerated Human Learning with AI, for Engineers</title><link href="https://junhe.github.io/2026/09/22/accelerated-human-learning-with-ai.html" rel="alternate" type="text/html" title="Accelerated Human Learning with AI, for Engineers" /><published>2026-09-22T00:00:00+00:00</published><updated>2026-09-22T00:00:00+00:00</updated><id>https://junhe.github.io/2026/09/22/accelerated-human-learning-with-ai</id><content type="html" xml:base="https://junhe.github.io/2026/09/22/accelerated-human-learning-with-ai.html"><![CDATA[<p>At Google, the idea of the Elephant and the Goldfish is popular. It recommends having an agent with a long-term memory of the overall architecture and plan, and many goldfish that start fresh and focus on small-scope tasks. This is not surprising. Human society has been doing this for a long time; for example, shop owners distribute tasks to apprentices, who focus on smaller tasks.</p>

<p>Using the same analogy, I think great tech leads are like elephants: they have long-term memory of the overall architecture and plans for one or multiple projects. They guide and distribute tasks to other team members who focus on smaller scopes.</p>

<p>Becoming a great tech lead is hard. There are a lot of docs and code to read, and a lot of experience to gain. Fortunately, we now have AI, which can retrieve information much faster than traditional search engines. What was not possible before AI is that AI can test your knowledge, which is a critical part of learning.</p>

<p>This article describes a structured way of learning in the era of AI. It should be applicable to learning any topic. The idea is simple: <strong>manually write a paper by recursively reading and testing via AI</strong>.</p>

<h2 id="why-write-a-paper-manually">Why write a paper manually?</h2>

<p>A handwritten paper reflects your mind. If you can organize the paper well, it means that your mind is well-organized. In the meantime, by forcing yourself to organize the paper, you force yourself to understand the structure of the project.</p>

<p>It is easy to understand why we have to write the paper manually, instead of using AI. A lot of times, we ask AI something and read the answer. We think we understand it. But if you close the tab and ask the question again, you are often still clueless.</p>

<p>Start your paper with the following sections and gradually fill in the content manually:</p>

<ol>
  <li>Abstract</li>
  <li>Introduction</li>
  <li>Background</li>
  <li>Related Work</li>
  <li>Design</li>
  <li>Evaluation</li>
  <li>Conclusion</li>
  <li>References</li>
</ol>

<p>Always keep the paper organized. If you have some messy notes, use a scratch doc to host them temporarily. Additionally, if you have content but don’t know exactly where to put it, do your best to place it somewhere. Often, it becomes obvious where it should go after a while.</p>

<p>Make the paper flow well; this forces you to connect the knowledge you have acquired.</p>

<h2 id="read-and-test-with-ai">Read and Test with AI</h2>

<p>Essentially, we want to learn like Richard Feynman, who strongly believed that you should close your book and test your understanding after reading. If you fail, you should learn it again. This process forces knowledge into your brain and back out. The following diagram illustrates the process:</p>

<p><img src="/assets/images/accelerated-human-learning-with-ai/accelerated-learning.png" alt="image.png" width="500px" /></p>

<p>Nowadays, you can almost get information directly from AI. You can ask clarification questions. If you think the AI is hallucinating, you can press further, and it will usually produce the correct answer.</p>

<p>The testing part is interesting. There are many ways to validate whether you have the correct knowledge. You can “close the book” and write down notes; you can apply the knowledge by writing code; you can add logging to the system and run the code; you can try explaining the concept to others (including AI); or you can ask an AI to quiz you.</p>

<h2 id="update-the-paper">Update the Paper</h2>

<p>After validating your knowledge, update the paper. If you have already written notes during the testing step, you can simply move those notes into the paper. (You can also directly test your knowledge by writing or updating the paper.)</p>

<p>When updating the paper, think about where the content fits best and how the overall flow should be adjusted.</p>

<h2 id="example">Example</h2>

<p>For example, if you just moved to a new team and need to understand a new project, this is what you might do:</p>

<ol>
  <li>Collect a bunch of design docs and code pointers, and add them to the References section.</li>
  <li>Start understanding why the project was started and fill out the Background section.</li>
  <li>When you encounter a similar project (e.g., from another team or company), update the Related Work section.</li>
  <li>Each time you read about a specific component of the system, add a new subsection to Design.</li>
  <li>…</li>
</ol>

<p>Now, learning becomes “filling in the paper,” which is very concrete. In the end, you will have structured artifacts that you can easily refer back to.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[At Google, the idea of the Elephant and the Goldfish is popular. It recommends having an agent with a long-term memory of the overall architecture and plan, and many goldfish that start fresh and focus on small-scope tasks. This is not surprising. Human society has been doing this for a long time; for example, shop owners distribute tasks to apprentices, who focus on smaller tasks.]]></summary></entry><entry><title type="html">[Hands-On] Building a Real LLM From Scratch for Systems People</title><link href="https://junhe.github.io/2026/09/11/llm-from-scratch.html" rel="alternate" type="text/html" title="[Hands-On] Building a Real LLM From Scratch for Systems People" /><published>2026-09-11T00:00:00+00:00</published><updated>2026-09-11T00:00:00+00:00</updated><id>https://junhe.github.io/2026/09/11/llm-from-scratch</id><content type="html" xml:base="https://junhe.github.io/2026/09/11/llm-from-scratch.html"><![CDATA[<p><img src="/assets/images/llm-from-scratch/image-5.png" alt="image.png" /></p>

<h2 class="no_toc" id="table-of-contents">Table of contents</h2>

<ul id="markdown-toc">
  <li><a href="#1-introduction" id="markdown-toc-1-introduction">1. Introduction</a></li>
  <li><a href="#2-what-will-we-do-in-this-article" id="markdown-toc-2-what-will-we-do-in-this-article">2. What will we do in this article?</a></li>
  <li><a href="#3-what-will-our-llm-do" id="markdown-toc-3-what-will-our-llm-do">3. What will our LLM do?</a></li>
  <li><a href="#4-how-to-train-a-simple-non-language-model-in-pytorch" id="markdown-toc-4-how-to-train-a-simple-non-language-model-in-pytorch">4. How to train a simple non-language model in PyTorch?</a>    <ul>
      <li><a href="#what-is-a-tensor-what-is-an-embedding" id="markdown-toc-what-is-a-tensor-what-is-an-embedding">What is a tensor? What is an embedding?</a></li>
      <li><a href="#how-to-determine-the-similarity-between-two-embeddings" id="markdown-toc-how-to-determine-the-similarity-between-two-embeddings">How to determine the similarity between two embeddings?</a></li>
      <li><a href="#what-is-a-neural-network" id="markdown-toc-what-is-a-neural-network">What is a neural network?</a></li>
    </ul>
  </li>
  <li><a href="#5-making-the-llm" id="markdown-toc-5-making-the-llm">5. Making the LLM</a>    <ul>
      <li><a href="#what-do-llms-do" id="markdown-toc-what-do-llms-do">What do LLMs do?</a></li>
      <li><a href="#what-are-tokens" id="markdown-toc-what-are-tokens">What are tokens?</a></li>
      <li><a href="#whats-the-input-and-output-for-the-training-what-is-the-training-data" id="markdown-toc-whats-the-input-and-output-for-the-training-what-is-the-training-data">What’s the input and output for the training? What is the training data?</a></li>
      <li><a href="#batching" id="markdown-toc-batching">Batching</a></li>
      <li><a href="#we-need-both-semantics-and-position-to-understand-a-token" id="markdown-toc-we-need-both-semantics-and-position-to-understand-a-token">We need both semantics and position to understand a token</a></li>
      <li><a href="#define-embedding-tables-for-semantic-and-position" id="markdown-toc-define-embedding-tables-for-semantic-and-position">Define embedding tables for semantic and position</a></li>
      <li><a href="#convert-token-ids-and-positions-to-embeddings" id="markdown-toc-convert-token-ids-and-positions-to-embeddings">Convert token IDs and positions to embeddings</a></li>
      <li><a href="#merge-token-meanings-and-position-info" id="markdown-toc-merge-token-meanings-and-position-info">Merge token meanings and position info</a></li>
      <li><a href="#understanding-the-context" id="markdown-toc-understanding-the-context">Understanding the context</a></li>
      <li><a href="#what-is-self-attention" id="markdown-toc-what-is-self-attention">What is self-attention?</a></li>
      <li><a href="#implement-self-attention" id="markdown-toc-implement-self-attention">Implement self-attention</a></li>
      <li><a href="#whats-the-output-of-the-model" id="markdown-toc-whats-the-output-of-the-model">What’s the output of the model?</a></li>
      <li><a href="#calculate-the-loss" id="markdown-toc-calculate-the-loss">Calculate the Loss</a></li>
      <li><a href="#how-to-generate-new-text" id="markdown-toc-how-to-generate-new-text">How to generate new text?</a></li>
    </ul>
  </li>
  <li><a href="#summary" id="markdown-toc-summary">Summary</a></li>
</ul>

<h2 id="1-introduction">1. Introduction</h2>

<p>By now, we’re probably all familiar with large language models (LLMs) such as ChatGPT, Gemini, and Claude. Since 2022, LLMs have begun to revolutionize the world and have had a significant impact on our lives.</p>

<p>We have been told that LLMs are trained on enormous datasets and generate text by predicting the next token. But this understanding seems superficial. How does it work exactly? As Richard Feynman noted, knowing the name of something is not the same as knowing something.</p>

<p><strong>Systems people should understand LLM internals.</strong> As a systems person, I believe systems people should understand LLM internals well. Systems people have deep knowledge of how the infrastructure works. They know how concurrency works. They know sharding. They know OS page management. But they usually know less about machine learning or LLM internals. For example, I didn’t know what “logits” were while working with someone at DeepMind; such gaps create communication friction in collaborations. Systems people should learn the fundamentals of LLMs for the following reasons:</p>

<ol>
  <li><strong>Better identify system opportunities for AI</strong></li>
  <li><strong>Better identify AI opportunities for systems</strong></li>
  <li><strong>Better collaborate with AI/ML teams (so we speak the same language)</strong></li>
  <li><strong>Get a sense of the limitations of LLMs.</strong></li>
</ol>

<p>The above are the overall goals of this article; we achieve these goals by building an LLM from scratch.</p>

<h2 id="2-what-will-we-do-in-this-article">2. What will we do in this article?</h2>

<p>You will build and run a real GPT model that is trained on Shakespeare’s books and speaks like Shakespeare. Along the way, you will learn the necessary fundamental ML/AI concepts and tools.</p>

<p>This article is from a systems person, for systems people. It is helpful in this way because systems people understand systems people.</p>

<p>At the end, you will be able to:</p>

<ul>
  <li>understand, build, and run a real GPT model</li>
  <li>get hands-on experience with LLMs</li>
  <li>understand core concepts from embeddings and neural networks to self-attention</li>
</ul>

<p>We will use a <a href="https://github.com/karpathy/ng-video-lecture">GPT model</a> written by Andrej Karpathy in this article.</p>

<p>The code for this article is available at <a href="https://github.com/junhe/llm-from-scratch">https://github.com/junhe/llm-from-scratch</a>. We expect you to clone the repo and follow along with the article.</p>

<h2 id="3-what-will-our-llm-do">3. What will our LLM do?</h2>

<p>To get you excited, let’s see what LLM you will get at the end of this article. You will get an LLM that will write as if it is Shakespeare.</p>

<p>This LLM can do so because it learned from the text of Shakespeare. It is similar to ChatGPT, which is trained on almost all the text in the world.</p>

<p>The following is the text written by the LLM. As you can see, the output has the style of Shakespeare.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(.venv) (base) junhe@Juns-MacBook-Pro-2 llm-0-to-1 % python gpt_complete.py 

step 0: train loss 4.2221, val loss 4.2306
step 500: train loss 1.7434, val loss 1.8896
step 1000: train loss 1.4037, val loss 1.6222
step 1500: train loss 1.2688, val loss 1.5321
step 2000: train loss 1.1918, val loss 1.5063
step 2500: train loss 1.1316, val loss 1.4906
step 3000: train loss 1.0727, val loss 1.4827
step 3500: train loss 1.0164, val loss 1.5064
step 4000: train loss 0.9637, val loss 1.5192
step 4500: train loss 0.9156, val loss 1.5392
step 4999: train loss 0.8574, val loss 1.5730

O BARATHUMBERLANCE:
But never a word than I scape to King Henry,
Before I a high forehereing traops
And hollow the hath or this ceremal.

KING RICHARD IIUS CHARD II:
Touch'd it yon towards,
But the greatest tedious day what the gates:
'Though Clarence is he sworn were breathed
My clie that searloath to this fap, that thou beheld
And heaven shalt us alt thou furnish,
That marges with strongeth age thy oath fearful fame,
With be busher'd with thy birth-swaining blood and tear
Whisp'd stratal arch-
</code></pre></div></div>

<h2 id="4-how-to-train-a-simple-non-language-model-in-pytorch">4. How to train a simple non-language model in PyTorch?</h2>

<p>We will use PyTorch to build our Shakespeare LLM. Let’s learn how to use PyTorch first, without getting into details of LLMs.</p>

<h3 id="what-is-a-tensor-what-is-an-embedding">What is a tensor? What is an embedding?</h3>

<p>Let’s start with tensors and embeddings. A <strong>tensor</strong> is simply multidimensional data: a list is a 1-D tensor, and a matrix is a 2-D tensor. In C++ terms (for systems people):</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">is</span> <span class="n">a</span> <span class="mi">1</span><span class="o">-</span><span class="n">D</span> <span class="n">tensor</span><span class="p">.</span>

<span class="c1">// Create a 3x3 tensor initialized with 0.0</span>
<span class="kt">int</span> <span class="n">rows</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">cols</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;&gt;</span> <span class="n">tensor</span><span class="p">(</span><span class="n">rows</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">cols</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">));</span>
</code></pre></div></div>

<p>Then what is <strong>embedding</strong>? An embedding is a 1-D tensor that contains numbers of some meanings. For example, let’s imagine that we want to teach computers the meaning of different animals. We can create a vector with two numbers, the first number represents how domesticated it is (-1=Wild, 1=Pet), the second number represents how big it is (-1=Tiny, 1=Huge). By scoring these two features, we create embeddings for them.</p>

<table>
  <thead>
    <tr>
      <th><strong>Word</strong></th>
      <th><strong>Vector (Embedding)</strong></th>
      <th><strong>Why?</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Dog</strong></td>
      <td><code class="language-plaintext highlighter-rouge">[0.7, 0.2]</code></td>
      <td>Very domesticated, medium size.</td>
    </tr>
    <tr>
      <td><strong>Cat</strong></td>
      <td><code class="language-plaintext highlighter-rouge">[0.8, -0.4]</code></td>
      <td>Very domesticated, small size.</td>
    </tr>
    <tr>
      <td><strong>Lion</strong></td>
      <td><code class="language-plaintext highlighter-rouge">[-0.9, 0.7]</code></td>
      <td>Very wild, large size.</td>
    </tr>
    <tr>
      <td><strong>Tiger</strong></td>
      <td><code class="language-plaintext highlighter-rouge">[-0.8, 0.6]</code></td>
      <td>Very wild, large size.</td>
    </tr>
    <tr>
      <td><strong>Mouse</strong></td>
      <td><code class="language-plaintext highlighter-rouge">[0.1, -0.9]</code></td>
      <td>Mostly wild, very tiny.</td>
    </tr>
  </tbody>
</table>

<p>Embeddings quantify the features so computers can understand them.</p>

<h3 id="how-to-determine-the-similarity-between-two-embeddings">How to determine the similarity between two embeddings?</h3>

<p>One magical property of embeddings is that, by turning the animal features into numbers, computers can now determine how similar two animals are. One way is to use the dot product, which we will use later. Let’s use the dot product to answer the question: is a dog more similar to a cat or a lion?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Dog           Cat
[0.7, 0.2] x [0.8, -0.4] = 0.7*0.8 + 0.2*(-0.4) = 0.48

Dog           Lion
[0.7, 0.2] x [-0.9, 0.7] = 0.7*(-0.9) + 0.2*0.7 = -0.49
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">0.48</code> is larger than <code class="language-plaintext highlighter-rouge">-0.49</code>, so a dog is more similar to a cat than to a lion.</p>

<h3 id="what-is-a-neural-network">What is a neural network?</h3>

<p>I view a neural network as a computer algorithm inspired by the human brain.</p>

<ul>
  <li>First, it takes some input, and produces some output.</li>
  <li>Second, it learns from the output and adjusts itself.</li>
  <li>Third, it has a neuron-like data structure.</li>
</ul>

<p><img src="/assets/images/llm-from-scratch/image.png" alt="image.png" /></p>

<p>The following is the anatomy of a single artificial neuron.</p>

<p><img src="/assets/images/llm-from-scratch/image-1.png" alt="image.png" width="80%" /></p>

<p>(<em>Anatomy of a single artificial neuron. Source: Тюжина Ирина / Getty Images</em>)</p>

<p>Let’s use a very simple neural network to explain it. Let’s say we want to determine if today is a good day to play outdoor pickleball.</p>

<p><strong>The inputs:</strong></p>

<ol>
  <li>Temperature (in Fahrenheit)</li>
  <li>Wind Speed (in mph)</li>
</ol>

<p><strong>The weights (the importance)</strong></p>

<ol>
  <li>Temperature weight (+0.5): warmer weather generally makes it better for playing</li>
  <li>Wind speed weight (-2.0): high wind speed ruins the game, so it gets a heavier negative weight.</li>
</ol>

<p><strong>The bias (the baseline)</strong></p>

<p>Some people like to play more than other people, before any outside factors are considered. Let’s say our bias is 10.</p>

<p><strong>The Summation</strong></p>

<p>Let’s look at a calm, 75°F afternoon in Sunnyvale:</p>

<ul>
  <li><strong>Temperature contribution:</strong> 75°F × 0.5 = 37.5</li>
  <li><strong>Wind contribution:</strong> 2 mph × -2.0 = -4.0</li>
  <li><strong>Total Score:</strong> 37.5 - 4.0 + 10 (bias) = <strong>43.5</strong></li>
</ul>

<p>Now let’s look at a cold, blustery day:</p>

<ul>
  <li><strong>Temperature contribution:</strong> 50°F × 0.5 = 25</li>
  <li><strong>Wind contribution:</strong> 20 mph × -2.0 = -40</li>
  <li><strong>Total Score:</strong> 25 - 40 + 10 (bias) = -<strong>5</strong></li>
</ul>

<p><strong><em>What are logits?</em></strong></p>

<p><em>In this example, the scores 43.5 and -5 are logits, which are raw scores. These raw scores are what computers need, but they are confusing for humans: (1) they have no limits, and (2) they are not probabilities. We usually need to convert the logits to a more unified space so computers and humans can better interpret them.</em></p>

<p><strong>The Activation Function (The Final Call)</strong></p>

<p>Raw scores of <code class="language-plaintext highlighter-rouge">43.5</code> and <code class="language-plaintext highlighter-rouge">-5</code> are not very useful predictions. We just want a “yes” or “no”. The activation function is what produces this “yes” or “no” output.</p>

<p><img src="/assets/images/llm-from-scratch/image-2.png" alt="image.png" /></p>

<p><strong>Wait—where do all the weights, bias, and activation come from?</strong> In the example above, we manually picked the numbers; but in reality, we don’t know in the beginning. In the beginning of the neural network, these parameters are just random, much like newborn babies have no clue on how wind and temperatures would affect pickleball playing, and babies don’t know how much they like pickleball (i.e., the bias) either. Humans learn by playing. For example, Bob played 5 games and recorded his experience as follows.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Temperature (°F)    Wind Speed (mph)    Play
75.0                3.0                 1.0
80.0                5.0                 1.0
68.0                2.0                 1.0
72.0                15.0                0.0
50.0                20.0                0.0
45.0                8.0                 0.0
85.0                22.0                0.0
65.0                25.0                0.0
</code></pre></div></div>

<p>Bob learned from the experience above. The next time that he sees temperature 102 and wind 20, he will know that he should not go, because Bob’s brain has been programmed.</p>

<p>The following code is a key part of the training process.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">epoch</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">epochs</span><span class="p">):</span>
    <span class="c1"># Initialize the optimizer, which is an algorithm that will improve the accuracy
</span>    <span class="c1"># of the model.
</span>    <span class="n">optimizer</span><span class="p">.</span><span class="n">zero_grad</span><span class="p">()</span>
    
    <span class="c1"># Make predictions based on the training input.
</span>    <span class="n">logits</span> <span class="o">=</span> <span class="n">model</span><span class="p">(</span><span class="n">X_train</span><span class="p">)</span>
    <span class="c1"># Find out how wrong the predictions are based on the training targets.
</span>    <span class="n">loss</span> <span class="o">=</span> <span class="n">criterion</span><span class="p">(</span><span class="n">logits</span><span class="p">,</span> <span class="n">y_train</span><span class="p">)</span>
    
    <span class="c1"># Note down how to change the parameters based on the loss
</span>    <span class="n">loss</span><span class="p">.</span><span class="n">backward</span><span class="p">()</span>
    <span class="c1"># Actually update the parameters.
</span>    <span class="n">optimizer</span><span class="p">.</span><span class="n">step</span><span class="p">()</span>
</code></pre></div></div>

<p>The following diagram demonstrates one iteration of the process.</p>

<ol>
  <li>Feed the model with training inputs</li>
  <li>Get training outputs (i.e., for the training inputs). The predictions can be very wrong initially.</li>
  <li>Calculate the loss</li>
  <li>Use the loss to update the parameters in the model</li>
</ol>

<p><img src="/assets/images/llm-from-scratch/image-3.png" alt="image.png" /></p>

<p>Note that the process above is repeated many times. Each time, the parameters of the model are updated a little bit. The reason we can’t find the right parameters in one shot is that it’s like searching for the bottom of a valley on a foggy hillside. We can’t see anything beyond a few feet around us. So we have to search the hill gradually.</p>

<p><strong>Hands-On (</strong><code class="language-plaintext highlighter-rouge">hands_on_001_pickleball.py</code>): Add more training examples to <code class="language-plaintext highlighter-rouge">hands_on_001_pickleball.py</code> . Use your common sense to construct 8 examples. Run the program to see if the model makes good predictions.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ---------------------------------------------------------
# 2. Create Training Data
# ---------------------------------------------------------
# FIXME: Add more training data
# Features: [Temperature (°F), Wind Speed (mph)]
</span><span class="n">X_raw</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span>
    <span class="p">[</span><span class="mf">75.0</span><span class="p">,</span>  <span class="mf">3.0</span><span class="p">],</span>  <span class="c1"># Warm, calm -&gt; YES
</span><span class="p">],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">)</span>

<span class="c1"># Target Labels: 1.0 (YES - Play), 0.0 (NO - Don't Play)
</span><span class="n">y_train</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span>
    <span class="p">[</span><span class="mf">1.0</span><span class="p">],</span>
<span class="p">],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="5-making-the-llm">5. Making the LLM</h2>

<p>We have covered the basics of neural networks. Let’s start building the LLM.</p>

<h3 id="what-do-llms-do">What do LLMs do?</h3>

<p>LLMs generate text by predicting the next token based on the current text. For example, if the text is “the cat sat on the ___”, the LLM may predict the probability of the possible words like the following:</p>

<ul>
  <li><strong>“mat”</strong> - 72%</li>
  <li><strong>“couch”</strong> - 10%</li>
  <li><strong>“floor”</strong> - 5%</li>
  <li><strong>“dog”</strong> - 2%</li>
  <li><strong>“refrigerator”</strong> - 0.001%</li>
  <li>…</li>
</ul>

<p>If “mat” is selected, then the text for the next round of prediction will become “the cat sat on the mat ___”. This process is repeated to produce long text.</p>

<p>Fundamentally, LLMs predict the next token based on the current context of tokens.</p>

<h3 id="what-are-tokens">What are tokens?</h3>

<p>Humans read text word by word, but that may not be the optimal approach for computers. Tokens are chunks of characters that computers use for processing. There are many ways to tokenize text, which are different ways of splitting words into smaller units. Here are a few examples of tokenizing “Unbelievably, it rained!”</p>

<ul>
  <li>Word-level: [“Unbelievably”, “,”, “it”, “rained”, “!”]</li>
  <li>Character-level: [“U”, “n”, “b”, “e”, “l”, “i”, “e”, “v”, “a”, “b”, “l”, “y”, …]</li>
  <li>Subword: [“Un”, “believ”, “ably”, “,”, “it”, …]</li>
</ul>

<p>Modern LLMs use subword tokenization. But we will use character-level tokenization for simplicity in this article.</p>

<p>We also want to convert tokens to token IDs (integers) so that it is easier for computers to work with them. For example, we can put the information of tokens into a matrix, where each row is one token; as a result, we can quickly find information about a token by accessing its row.</p>

<p>In our example, we sort the characters and use the index IDs as token IDs. For example, “hii there” becomes <code class="language-plaintext highlighter-rouge">[46, 47, 47, 1, 58, 46, 43, 56, 43]</code>.</p>

<p><strong>Hands-On (</strong><code class="language-plaintext highlighter-rouge">hands_on_002_tokenization.py</code><strong>)</strong>: create a decoding lambda.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'input.txt'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">'utf-8'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">text</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>

<span class="c1"># here are all the unique characters that occur in this text
</span><span class="n">chars</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">text</span><span class="p">)))</span>
<span class="n">vocab_size</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">chars</span><span class="p">)</span>
<span class="c1"># create a mapping from characters to integers
</span><span class="n">stoi</span> <span class="o">=</span> <span class="p">{</span> <span class="n">ch</span><span class="p">:</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span><span class="n">ch</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">chars</span><span class="p">)</span> <span class="p">}</span>
<span class="n">itos</span> <span class="o">=</span> <span class="p">{</span> <span class="n">i</span><span class="p">:</span><span class="n">ch</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span><span class="n">ch</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">chars</span><span class="p">)</span> <span class="p">}</span>
<span class="n">encode</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">s</span><span class="p">:</span> <span class="p">[</span><span class="n">stoi</span><span class="p">[</span><span class="n">c</span><span class="p">]</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">s</span><span class="p">]</span> <span class="c1"># encoder: take a string, output a list of integers
# FIXME: implement decode function
# decode = 
</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"chars: </span><span class="si">{</span><span class="n">chars</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"stoi: </span><span class="si">{</span><span class="n">stoi</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"vocab_size: </span><span class="si">{</span><span class="n">vocab_size</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"encode('hii there'): </span><span class="si">{</span><span class="n">encode</span><span class="p">(</span><span class="s">'hii there'</span><span class="p">)</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="c1"># print(f"decode(encode('hii there')): {decode(encode('hii there'))}")
</span><span class="k">print</span><span class="p">(</span><span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">chars</span><span class="p">))</span>
</code></pre></div></div>

<p><strong>Answer</strong>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">decode</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">l</span><span class="p">:</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="n">itos</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">l</span><span class="p">])</span>
</code></pre></div></div>

<p><strong>Hands-On</strong> (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>): create the decoding function.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">encode</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">s</span><span class="p">:</span> <span class="p">[</span><span class="n">stoi</span><span class="p">[</span><span class="n">c</span><span class="p">]</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">s</span><span class="p">]</span> <span class="c1"># encoder: take a string, output a list of integers
# FIXME-01: implement decode function
</span><span class="n">decode</span> <span class="o">=</span> <span class="p">...</span>
</code></pre></div></div>

<p><strong>Answer:</strong></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">decode</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">l</span><span class="p">:</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="n">itos</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">l</span><span class="p">])</span>
</code></pre></div></div>

<h3 id="whats-the-input-and-output-for-the-training-what-is-the-training-data">What’s the input and output for the training? What is the training data?</h3>

<p>Recall that, in the earlier pickleball example, we had the following training data. We feed the model <code class="language-plaintext highlighter-rouge">X_raw</code> and get some output, then we compare the output with the target output. We see how wrong the model is and then adjust the model.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Features: [Temperature (°F), Wind Speed (mph)]
</span><span class="n">X_raw</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span>
    <span class="p">[</span><span class="mf">75.0</span><span class="p">,</span>  <span class="mf">3.0</span><span class="p">],</span>  <span class="c1"># Warm, calm -&gt; YES
</span>    <span class="p">[</span><span class="mf">80.0</span><span class="p">,</span>  <span class="mf">5.0</span><span class="p">],</span>  <span class="c1"># Warm, light breeze -&gt; YES
</span>    <span class="p">[</span><span class="mf">68.0</span><span class="p">,</span>  <span class="mf">2.0</span><span class="p">],</span>  <span class="c1"># Mild, calm -&gt; YES
</span>    <span class="p">[</span><span class="mf">72.0</span><span class="p">,</span> <span class="mf">15.0</span><span class="p">],</span>  <span class="c1"># Mild, gusty -&gt; NO
</span>    <span class="p">[</span><span class="mf">50.0</span><span class="p">,</span> <span class="mf">20.0</span><span class="p">],</span>  <span class="c1"># Cold, windy -&gt; NO
</span>    <span class="p">[</span><span class="mf">45.0</span><span class="p">,</span>  <span class="mf">8.0</span><span class="p">],</span>  <span class="c1"># Cold, light wind -&gt; NO
</span>    <span class="p">[</span><span class="mf">85.0</span><span class="p">,</span> <span class="mf">22.0</span><span class="p">],</span>  <span class="c1"># Hot, high wind -&gt; NO
</span>    <span class="p">[</span><span class="mf">65.0</span><span class="p">,</span> <span class="mf">25.0</span><span class="p">],</span>  <span class="c1"># Mild, very windy -&gt; NO
</span><span class="p">],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">)</span>

<span class="c1"># Target Labels: 1.0 (YES - Play), 0.0 (NO - Don't Play)
</span><span class="n">y_train</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span>
    <span class="p">[</span><span class="mf">1.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">1.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">1.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">0.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">0.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">0.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">0.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">0.0</span><span class="p">]</span>
<span class="p">],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">)</span>
</code></pre></div></div>

<p>Now that we want to build a model that can speak like Shakespeare, what will the examples look like? Given the text from Shakespeare, how can we structure the examples to train the model? I think we have all heard that an LLM predicts the next token given the tokens in the context. Let’s take a look at a small example.</p>

<p>The following is a snippet from Shakespeare. We will generate some examples from it for training.</p>

<p><img src="/assets/images/llm-from-scratch/image-4.png" alt="image.png" /></p>

<p>We can use a sliding window of a certain size to create the examples for training, as illustrated below.</p>

<p><img src="/assets/images/llm-from-scratch/image-5.png" alt="image.png" /></p>

<p>We will get the following training examples:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x</span> <span class="o">=</span> <span class="p">[</span>
 <span class="p">[</span><span class="s">'a'</span><span class="p">,</span> <span class="s">'c'</span><span class="p">,</span> <span class="s">'c'</span><span class="p">,</span> <span class="s">'o'</span><span class="p">,</span> <span class="s">'u'</span><span class="p">,</span> <span class="s">'n'</span><span class="p">,</span> <span class="s">'t'</span><span class="p">,</span> <span class="s">'e'</span><span class="p">],</span>
 <span class="p">[</span><span class="s">'s'</span><span class="p">,</span> <span class="s">'u'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">,</span> <span class="s">'f'</span><span class="p">,</span> <span class="s">'e'</span><span class="p">,</span> <span class="s">'i'</span><span class="p">,</span> <span class="s">'t'</span><span class="p">,</span> <span class="s">'s'</span><span class="p">],</span>
 <span class="p">[</span><span class="s">'m'</span><span class="p">,</span> <span class="s">'i'</span><span class="p">,</span> <span class="s">'g'</span><span class="p">,</span> <span class="s">'h'</span><span class="p">,</span> <span class="s">'t'</span><span class="p">,</span> <span class="s">' '</span><span class="p">,</span> <span class="s">'g'</span><span class="p">,</span> <span class="s">'u'</span><span class="p">],</span>
<span class="p">]</span>

<span class="n">y</span> <span class="o">=</span> <span class="p">[</span>
 <span class="p">[</span><span class="s">'c'</span><span class="p">,</span> <span class="s">'c'</span><span class="p">,</span> <span class="s">'o'</span><span class="p">,</span> <span class="s">'u'</span><span class="p">,</span> <span class="s">'n'</span><span class="p">,</span> <span class="s">'t'</span><span class="p">,</span> <span class="s">'e'</span><span class="p">,</span> <span class="s">'d'</span><span class="p">],</span>
 <span class="p">[</span><span class="s">'u'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">,</span> <span class="s">'f'</span><span class="p">,</span> <span class="s">'e'</span><span class="p">,</span> <span class="s">'i'</span><span class="p">,</span> <span class="s">'t'</span><span class="p">,</span> <span class="s">'s'</span><span class="p">,</span> <span class="s">' '</span><span class="p">],</span>
 <span class="p">[</span><span class="s">'i'</span><span class="p">,</span> <span class="s">'g'</span><span class="p">,</span> <span class="s">'h'</span><span class="p">,</span> <span class="s">'t'</span><span class="p">,</span> <span class="s">' '</span><span class="p">,</span> <span class="s">'g'</span><span class="p">,</span> <span class="s">'u'</span><span class="p">,</span> <span class="s">'e'</span><span class="p">],</span>
<span class="p">]</span>
</code></pre></div></div>

<p>(Note that, in the article we use the actual characters for better illustration. But what actually goes into the model are token IDs, which are integers.)</p>

<p>We actually get 8 examples from the first block.</p>

<p><img src="/assets/images/llm-from-scratch/image-6.png" alt="image.png" width="200px" /></p>

<p>The hope is that, when the model has seen enough examples like these, it will be able to predict the next token. The following diagram shows how the training data is used. The diagram is the same as the pickleball example, except that the temperature and wind speed become a block of text from Shakespeare; the Yes/No becomes the next tokens.</p>

<p><img src="/assets/images/llm-from-scratch/image-7.png" alt="image.png" /></p>

<h3 id="batching">Batching</h3>

<p>Earlier we have generated the following training examples.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x</span> <span class="o">=</span> <span class="p">[</span>
 <span class="p">[</span><span class="s">'a'</span><span class="p">,</span> <span class="s">'c'</span><span class="p">,</span> <span class="s">'c'</span><span class="p">,</span> <span class="s">'o'</span><span class="p">,</span> <span class="s">'u'</span><span class="p">,</span> <span class="s">'n'</span><span class="p">,</span> <span class="s">'t'</span><span class="p">,</span> <span class="s">'e'</span><span class="p">],</span>
 <span class="p">[</span><span class="s">'s'</span><span class="p">,</span> <span class="s">'u'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">,</span> <span class="s">'f'</span><span class="p">,</span> <span class="s">'e'</span><span class="p">,</span> <span class="s">'i'</span><span class="p">,</span> <span class="s">'t'</span><span class="p">,</span> <span class="s">'s'</span><span class="p">],</span>
 <span class="p">[</span><span class="s">'m'</span><span class="p">,</span> <span class="s">'i'</span><span class="p">,</span> <span class="s">'g'</span><span class="p">,</span> <span class="s">'h'</span><span class="p">,</span> <span class="s">'t'</span><span class="p">,</span> <span class="s">' '</span><span class="p">,</span> <span class="s">'g'</span><span class="p">,</span> <span class="s">'u'</span><span class="p">],</span>
<span class="p">]</span>
</code></pre></div></div>

<p>There are 3 rows here. To maximize the efficiency of training (higher parallelism if on GPUs), we want to train on multiple rows at the same time. We call the list of characters (e.g., <code class="language-plaintext highlighter-rouge">['a', 'c', 'c', 'o', 'u', 'n', 't', 'e']</code>) a <strong>block</strong>. Here the block size is 8, which indicates that there are 8 characters in the list. (In the complete example, the block size is 256.) We call the list of blocks a batch. Here the batch size is 3, which indicates that there are 3 blocks. (In the complete example, the batch size is 64.) The following diagram illustrates the setup.</p>

<p><img src="/assets/images/llm-from-scratch/image-8.png" alt="image.png" /></p>

<p><strong>HandsOn (<code class="language-plaintext highlighter-rouge">hands_on_003_training_examples.py</code>)</strong>: the following code from our LLM is missing the part that fills <code class="language-plaintext highlighter-rouge">y</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch</span>

<span class="n">text</span> <span class="o">=</span> <span class="s">"""First Citizen:
We are accounted poor citizens, the patricians good.
What authority surfeits on would relieve us: if they
would yield us but the superfluity, while it were
wholesome, we might guess they relieved us humanely;
but they think we are too dear: the leanness that
afflicts us, the object of our misery, is as an
inventory to particularise their abundance; our
sufferance is a gain to them Let us revenge this with
our pikes, ere we become rakes: for the gods know I
speak this in hunger for bread, not in thirst for revenge."""</span>

<span class="n">block_size</span> <span class="o">=</span> <span class="mi">8</span>
<span class="n">batch_size</span> <span class="o">=</span> <span class="mi">4</span>

<span class="c1"># here are all the unique characters that occur in this text
</span><span class="n">chars</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">text</span><span class="p">)))</span>
<span class="n">vocab_size</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">chars</span><span class="p">)</span>
<span class="c1"># create a mapping from characters to integers
</span><span class="n">stoi</span> <span class="o">=</span> <span class="p">{</span> <span class="n">ch</span><span class="p">:</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span><span class="n">ch</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">chars</span><span class="p">)</span> <span class="p">}</span>
<span class="n">itos</span> <span class="o">=</span> <span class="p">{</span> <span class="n">i</span><span class="p">:</span><span class="n">ch</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span><span class="n">ch</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">chars</span><span class="p">)</span> <span class="p">}</span>
<span class="n">encode</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">s</span><span class="p">:</span> <span class="p">[</span><span class="n">stoi</span><span class="p">[</span><span class="n">c</span><span class="p">]</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">s</span><span class="p">]</span> <span class="c1"># encoder: take a string, output a list of integers
</span><span class="n">decode</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">l</span><span class="p">:</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="n">itos</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">l</span><span class="p">])</span> <span class="c1"># decoder: take a list of integers, output a string
</span>
<span class="c1"># Train and test splits
</span><span class="n">data</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">(</span><span class="n">encode</span><span class="p">(</span><span class="n">text</span><span class="p">),</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="nb">long</span><span class="p">)</span>

<span class="n">ix</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">randint</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="o">-</span> <span class="n">block_size</span><span class="p">,</span> <span class="p">(</span><span class="n">batch_size</span><span class="p">,))</span>
<span class="k">print</span><span class="p">(</span><span class="s">"ix:"</span><span class="p">,</span> <span class="n">ix</span><span class="p">)</span>
<span class="n">x</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">stack</span><span class="p">([</span><span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span><span class="o">+</span><span class="n">block_size</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">ix</span><span class="p">])</span>
<span class="c1"># FIXME: implement y
</span><span class="n">y</span> <span class="o">=</span> <span class="p">...</span>
<span class="k">print</span><span class="p">(</span><span class="s">'x:</span><span class="se">\n</span><span class="s">'</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">'y:</span><span class="se">\n</span><span class="s">'</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">'decoded x:</span><span class="se">\n</span><span class="s">'</span><span class="p">,</span> <span class="p">[</span><span class="n">decode</span><span class="p">(</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">tolist</span><span class="p">())</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">batch_size</span><span class="p">)])</span>
<span class="k">print</span><span class="p">(</span><span class="s">'decoded y:</span><span class="se">\n</span><span class="s">'</span><span class="p">,</span> <span class="p">[</span><span class="n">decode</span><span class="p">(</span><span class="n">y</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">tolist</span><span class="p">())</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">batch_size</span><span class="p">)])</span>
</code></pre></div></div>

<p>Quiz (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>): What is the value of B and T during training?</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">GPTLanguageModel</span><span class="p">(</span><span class="n">nn</span><span class="p">.</span><span class="n">Module</span><span class="p">):</span>
    <span class="p">...</span>
    <span class="k">def</span> <span class="nf">forward</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">idx</span><span class="p">,</span> <span class="n">targets</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="n">B</span><span class="p">,</span> <span class="n">T</span> <span class="o">=</span> <span class="n">idx</span><span class="p">.</span><span class="n">shape</span>
        <span class="p">...</span>
</code></pre></div></div>

<p>During training, <code class="language-plaintext highlighter-rouge">idx</code> is one input batch of token IDs. It is called <code class="language-plaintext highlighter-rouge">idx</code> because token IDs are essentially the indexes of tokens in the vocabulary. Here, <code class="language-plaintext highlighter-rouge">B</code> is the batch size, which is equal to <code class="language-plaintext highlighter-rouge">batch_size</code> , which is <code class="language-plaintext highlighter-rouge">64</code>. <code class="language-plaintext highlighter-rouge">T</code> is the <code class="language-plaintext highlighter-rouge">block_size</code>, which is <code class="language-plaintext highlighter-rouge">256</code>.</p>

<h3 id="we-need-both-semantics-and-position-to-understand-a-token">We need both semantics and position to understand a token</h3>

<p>(We will use word-level tokenization and an example not from Shakespeare to make it more intuitive to understand.)</p>

<p>Let’s consider the word “bat”: it could mean a piece of baseball gear or a flying animal. The exact meaning can only be determined by the context. The following are two scenarios:</p>

<ol>
  <li>Scenario A (Sports): “The top teen player swung the bat.”</li>
  <li>Scenario B (Nature): “Out of the cave flew a bat.”</li>
</ol>

<p>Intuitively, to understand a word, the meaning of the word matters and its position in the text also matters. For example, the two sentences above could appear in the same article; the meaning of ‘bat’ depends on its position. So, we need the following parameters to cover semantics and positions.</p>

<p><strong>Token Embedding</strong>. For simplicity, let’s assume that the embedding is a vector of size three: <code class="language-plaintext highlighter-rouge">[sports score, nature score, emotion score]</code>.</p>

<p><strong>Position Embedding</strong>. Position also matters. For example, ‘bat’ could appear in an article two times for different meanings. It really depends on where the token is in the document. For example, the embedding could be <code class="language-plaintext highlighter-rouge">[document progress, paragraph progress, sentence progress]</code>.</p>

<h3 id="define-embedding-tables-for-semantic-and-position">Define embedding tables for semantic and position</h3>

<p>We need a lookup table to contain embeddings for tokens and positions. For the Shakespeare example, the tables can be visualized as follows.</p>

<p>The token embedding has 65 rows, because there are 65 characters in the text. Each row contains the embedding for a token (i.e., a character). For example, the second row has the embedding for ‘!’ (token id is <code class="language-plaintext highlighter-rouge">2</code>).</p>

<p><img src="/assets/images/llm-from-scratch/image-9.png" alt="image.png" width="300px" /></p>

<p>The position embedding table has 256 rows. Each row contains the embedding for a particular position.</p>

<p><img src="/assets/images/llm-from-scratch/image-10.png" alt="image.png" width="300px" /></p>

<p>In PyTorch, use <code class="language-plaintext highlighter-rouge">nn.Embedding</code> to define learnable lookup tables. It is learnable because PyTorch can automatically update the embeddings when optimizing.</p>

<p><strong>HandsOn</strong>(<code class="language-plaintext highlighter-rouge">hands_on_004_embedding_table.py</code>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch.nn</span> <span class="k">as</span> <span class="n">nn</span>

<span class="c1"># Create an embedding table. Each token index maps to a trainable vector.
# FIXME: define the embedding table that stores 5 embeddings, the dimension of
# each embedding is 8.
</span><span class="n">embedding</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Embedding</span><span class="p">(...)</span>

<span class="c1"># Look up the embedding for a single token index
</span><span class="n">token_id</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span><span class="mi">2</span><span class="p">])</span>
<span class="n">single_embedding</span> <span class="o">=</span> <span class="n">embedding</span><span class="p">(</span><span class="n">token_id</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Single token embedding shape:"</span><span class="p">,</span> <span class="n">single_embedding</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Single token embedding:</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">single_embedding</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>Answer of HandsOn</strong>(<code class="language-plaintext highlighter-rouge">hands_on_004_embedding_table.py</code>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">embedding</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Embedding</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">8</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>): define the token and position embedding tables.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">GPTLanguageModel</span><span class="p">(</span><span class="n">nn</span><span class="p">.</span><span class="n">Module</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">super</span><span class="p">().</span><span class="n">__init__</span><span class="p">()</span>
        <span class="c1"># each token directly reads off the logits for the next token from a lookup table
</span>        <span class="c1"># FIXME-02: define the token embedding table and position embedding table
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">token_embedding_table</span> <span class="o">=</span> <span class="p">...</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">position_embedding_table</span> <span class="o">=</span> <span class="p">...</span>

        <span class="bp">self</span><span class="p">.</span><span class="n">blocks</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Sequential</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">Block</span><span class="p">(</span><span class="n">n_embd</span><span class="p">,</span> <span class="n">n_head</span><span class="o">=</span><span class="n">n_head</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n_layer</span><span class="p">)])</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ln_f</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">LayerNorm</span><span class="p">(</span><span class="n">n_embd</span><span class="p">)</span> <span class="c1"># final layer norm
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">lm_head</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Linear</span><span class="p">(</span><span class="n">n_embd</span><span class="p">,</span> <span class="n">vocab_size</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>Answer of HandsOn</strong> (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>): FIXME-01:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">self</span><span class="p">.</span><span class="n">token_embedding_table</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Embedding</span><span class="p">(</span><span class="n">vocab_size</span><span class="p">,</span> <span class="n">n_embd</span><span class="p">)</span>
<span class="bp">self</span><span class="p">.</span><span class="n">position_embedding_table</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Embedding</span><span class="p">(</span><span class="n">block_size</span><span class="p">,</span> <span class="n">n_embd</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="convert-token-ids-and-positions-to-embeddings">Convert token IDs and positions to embeddings</h3>

<p>We need to turn the token IDs and their positions into embeddings, which will be optimized during training. Recall that earlier we defined two embedding tables:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">self</span><span class="p">.</span><span class="n">token_embedding_table</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Embedding</span><span class="p">(</span><span class="n">vocab_size</span><span class="p">,</span> <span class="n">n_embd</span><span class="p">)</span>
<span class="bp">self</span><span class="p">.</span><span class="n">position_embedding_table</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Embedding</span><span class="p">(</span><span class="n">block_size</span><span class="p">,</span> <span class="n">n_embd</span><span class="p">)</span>
</code></pre></div></div>

<p>Now we just need to look up the tables. The following code does the lookup for the token meanings.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tok_emb</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">token_embedding_table</span><span class="p">(</span><span class="n">idx</span><span class="p">)</span>
</code></pre></div></div>

<p>What are the dimensions of <code class="language-plaintext highlighter-rouge">tok_emb</code>?</p>

<p>Let’s take the small example we have used. Given the following input</p>

<p><img src="/assets/images/llm-from-scratch/image-11.png" alt="image.png" width="300px" /></p>

<p>Given the following input, the output will look like the following.</p>

<p><img src="/assets/images/llm-from-scratch/image-12.png" alt="image.png" width="300px" /></p>

<p>Essentially, for each element in <code class="language-plaintext highlighter-rouge">idx</code>, we get an embedding and store it in an additional dimension. For example, <code class="language-plaintext highlighter-rouge">tok_emb[0, 3, 2]</code> stores a single number of the embedding for ‘o’. The dimensions of <code class="language-plaintext highlighter-rouge">tok_emb</code> are 64x256x384.</p>

<p>Now, let us get the position embeddings. In each block, we have positions 0, 1, …, block_size-1. We just need to look up the position embedding table with this sequence, as follows.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># torch.arange(T) produces [0, 1, 2, ..., T-1]
</span><span class="n">pos_emb</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">position_embedding_table</span><span class="p">(</span><span class="n">torch</span><span class="p">.</span><span class="n">arange</span><span class="p">(</span><span class="n">T</span><span class="p">,</span> <span class="n">device</span><span class="o">=</span><span class="n">device</span><span class="p">))</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">pos_emb</code> can be illustrated as follows.</p>

<p><img src="/assets/images/llm-from-scratch/image-13.png" alt="image.png" width="400px" /></p>

<h3 id="merge-token-meanings-and-position-info">Merge token meanings and position info</h3>

<p>A token can’t be understood without position, so we want to merge them together. We can do this simply by adding the token embedding of a token with its corresponding position embedding.  For example, if token embedding is <code class="language-plaintext highlighter-rouge">[1, 2, 3]</code> and position embedding is <code class="language-plaintext highlighter-rouge">[4, 5, 6]</code>, then <code class="language-plaintext highlighter-rouge">[1+4, 2+5, 3+6] = [5, 7, 9]</code> is an embedding that contains both meaning and position info.</p>

<p><img src="/assets/images/llm-from-scratch/image-14.png" alt="image.png" /></p>

<p>The resulting embedding will then contain values from the token itself and its position.</p>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">hands_on_005_merge_token_and_pos.py</code>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch</span>

<span class="c1"># 3x8x4 concrete token embeddings
</span><span class="n">tok_emb</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span>
    <span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">],</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">],</span> <span class="p">[</span><span class="mi">9</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">12</span><span class="p">],</span> <span class="p">[</span><span class="mi">13</span><span class="p">,</span> <span class="mi">14</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="mi">16</span><span class="p">],</span> <span class="p">[</span><span class="mi">17</span><span class="p">,</span> <span class="mi">18</span><span class="p">,</span> <span class="mi">19</span><span class="p">,</span> <span class="mi">20</span><span class="p">],</span> <span class="p">[</span><span class="mi">21</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">23</span><span class="p">,</span> <span class="mi">24</span><span class="p">],</span> <span class="p">[</span><span class="mi">25</span><span class="p">,</span> <span class="mi">26</span><span class="p">,</span> <span class="mi">27</span><span class="p">,</span> <span class="mi">28</span><span class="p">],</span> <span class="p">[</span><span class="mi">29</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">31</span><span class="p">,</span> <span class="mi">32</span><span class="p">]],</span>
    <span class="p">[[</span><span class="mi">41</span><span class="p">,</span> <span class="mi">42</span><span class="p">,</span> <span class="mi">43</span><span class="p">,</span> <span class="mi">44</span><span class="p">],</span> <span class="p">[</span><span class="mi">45</span><span class="p">,</span> <span class="mi">46</span><span class="p">,</span> <span class="mi">47</span><span class="p">,</span> <span class="mi">48</span><span class="p">],</span> <span class="p">[</span><span class="mi">49</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">51</span><span class="p">,</span> <span class="mi">52</span><span class="p">],</span> <span class="p">[</span><span class="mi">53</span><span class="p">,</span> <span class="mi">54</span><span class="p">,</span> <span class="mi">55</span><span class="p">,</span> <span class="mi">56</span><span class="p">],</span> <span class="p">[</span><span class="mi">57</span><span class="p">,</span> <span class="mi">58</span><span class="p">,</span> <span class="mi">59</span><span class="p">,</span> <span class="mi">60</span><span class="p">],</span> <span class="p">[</span><span class="mi">61</span><span class="p">,</span> <span class="mi">62</span><span class="p">,</span> <span class="mi">63</span><span class="p">,</span> <span class="mi">64</span><span class="p">],</span> <span class="p">[</span><span class="mi">65</span><span class="p">,</span> <span class="mi">66</span><span class="p">,</span> <span class="mi">67</span><span class="p">,</span> <span class="mi">68</span><span class="p">],</span> <span class="p">[</span><span class="mi">69</span><span class="p">,</span> <span class="mi">70</span><span class="p">,</span> <span class="mi">71</span><span class="p">,</span> <span class="mi">72</span><span class="p">]],</span>
    <span class="p">[[</span><span class="mi">81</span><span class="p">,</span> <span class="mi">82</span><span class="p">,</span> <span class="mi">83</span><span class="p">,</span> <span class="mi">84</span><span class="p">],</span> <span class="p">[</span><span class="mi">85</span><span class="p">,</span> <span class="mi">86</span><span class="p">,</span> <span class="mi">87</span><span class="p">,</span> <span class="mi">88</span><span class="p">],</span> <span class="p">[</span><span class="mi">89</span><span class="p">,</span> <span class="mi">90</span><span class="p">,</span> <span class="mi">91</span><span class="p">,</span> <span class="mi">92</span><span class="p">],</span> <span class="p">[</span><span class="mi">93</span><span class="p">,</span> <span class="mi">94</span><span class="p">,</span> <span class="mi">95</span><span class="p">,</span> <span class="mi">96</span><span class="p">],</span> <span class="p">[</span><span class="mi">97</span><span class="p">,</span> <span class="mi">98</span><span class="p">,</span> <span class="mi">99</span><span class="p">,</span> <span class="mi">100</span><span class="p">],</span> <span class="p">[</span><span class="mi">101</span><span class="p">,</span> <span class="mi">102</span><span class="p">,</span> <span class="mi">103</span><span class="p">,</span> <span class="mi">104</span><span class="p">],</span> <span class="p">[</span><span class="mi">105</span><span class="p">,</span> <span class="mi">106</span><span class="p">,</span> <span class="mi">107</span><span class="p">,</span> <span class="mi">108</span><span class="p">],</span> <span class="p">[</span><span class="mi">109</span><span class="p">,</span> <span class="mi">110</span><span class="p">,</span> <span class="mi">111</span><span class="p">,</span> <span class="mi">112</span><span class="p">]]</span>
<span class="p">],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">)</span>

<span class="c1"># 8x4 concrete position embeddings
</span><span class="n">pos_emb</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span>
    <span class="p">[</span><span class="mf">0.1</span><span class="p">,</span> <span class="mf">0.2</span><span class="p">,</span> <span class="mf">0.3</span><span class="p">,</span> <span class="mf">0.4</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">0.5</span><span class="p">,</span> <span class="mf">0.6</span><span class="p">,</span> <span class="mf">0.7</span><span class="p">,</span> <span class="mf">0.8</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">0.9</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.1</span><span class="p">,</span> <span class="mf">1.2</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">1.3</span><span class="p">,</span> <span class="mf">1.4</span><span class="p">,</span> <span class="mf">1.5</span><span class="p">,</span> <span class="mf">1.6</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">1.7</span><span class="p">,</span> <span class="mf">1.8</span><span class="p">,</span> <span class="mf">1.9</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">2.1</span><span class="p">,</span> <span class="mf">2.2</span><span class="p">,</span> <span class="mf">2.3</span><span class="p">,</span> <span class="mf">2.4</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">2.5</span><span class="p">,</span> <span class="mf">2.6</span><span class="p">,</span> <span class="mf">2.7</span><span class="p">,</span> <span class="mf">2.8</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">2.9</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">,</span> <span class="mf">3.1</span><span class="p">,</span> <span class="mf">3.2</span><span class="p">]</span>
<span class="p">],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">)</span>

<span class="c1"># FIXME:
# Add the two tensors. PyTorch broadcasts pos_emb from (8, 4) to (3, 8, 4).
</span><span class="n">x</span> <span class="o">=</span> <span class="p">...</span>

<span class="k">print</span><span class="p">(</span><span class="s">"tok_emb shape:"</span><span class="p">,</span> <span class="n">tok_emb</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"pos_emb shape:"</span><span class="p">,</span> <span class="n">pos_emb</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"x = tok_emb + pos_emb shape:"</span><span class="p">,</span> <span class="n">x</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
 
</code></pre></div></div>

<p><strong>Answer of HandsOn</strong> (<code class="language-plaintext highlighter-rouge">hands_on_005_merge_token_and_pos.py</code>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x</span> <span class="o">=</span> <span class="n">tok_emb</span> <span class="o">+</span> <span class="n">pos_emb</span>
</code></pre></div></div>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>): merge the <code class="language-plaintext highlighter-rouge">tok_emb</code> and <code class="language-plaintext highlighter-rouge">pos_emb</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tok_emb</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">token_embedding_table</span><span class="p">(</span><span class="n">idx</span><span class="p">)</span>
<span class="n">pos_emb</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">position_embedding_table</span><span class="p">(</span><span class="n">torch</span><span class="p">.</span><span class="n">arange</span><span class="p">(</span><span class="n">T</span><span class="p">,</span> <span class="n">device</span><span class="o">=</span><span class="n">device</span><span class="p">))</span>
<span class="n">x</span> <span class="o">=</span> <span class="p">...</span>
</code></pre></div></div>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x</span> <span class="o">=</span> <span class="n">tok_emb</span> <span class="o">+</span> <span class="n">pos_emb</span>
</code></pre></div></div>

<p>As a reminder, these embeddings are random initially. The goal is to learn these parameters, just like how we learned the parameters in the pickleball example.</p>

<h3 id="understanding-the-context">Understanding the context</h3>

<p>We have combined the token meaning and position into one tensor, but the tokens are still isolated: the combined embedding only contains the info of one token. We must understand the context, which is the tokens at and before a particular token in a block, in order to really understand a token and predict the next token. The following diagram illustrates the goal; for example, the embedding of ‘o’ should contain the information of ‘a’, ‘c’, ‘c’ and ‘o’, which is the context.</p>

<p><img src="/assets/images/llm-from-scratch/image-15.png" alt="image.png" width="400px" /></p>

<p>GPT understands the context by a mechanism called self-attention, which is the key part of modern LLMs. Let’s step back and look at a more intuitive example to understand it, then we will come back to the Shakespeare example, which uses the less intuitive character-level tokenization.</p>

<h3 id="what-is-self-attention">What is self-attention?</h3>

<p>Let’s look at the ‘bat’ example again.</p>

<ol>
  <li>Scenario A (Sports): “The top teen player swung the bat.”</li>
  <li>Scenario B (Nature): “Out of the cave flew a bat.”</li>
</ol>

<p>The goal of LLMs is to predict the next token based on the tokens in the context. So, when we try to understand the token in a block (i.e., learn the token embedding and position embedding), we will need to collect information of tokens at and before the particular token. Let’s look at an example.</p>

<p><img src="/assets/images/llm-from-scratch/image-16.png" alt="image.png" /></p>

<p>Let’s say we have some scores for a feature for the different tokens. Now we want to collect scores for understanding ‘bat’. One way is to sum the scores. The result is <code class="language-plaintext highlighter-rouge">0.3+0.5+2+1.3+3+0.3+2.4=9.8</code>.</p>

<p>This approach can collect info, but it also makes the collected score depend on the number of tokens before it. We don’t want this because it will generally make the token that appears later in the context have a larger score. We can solve the problem by averaging the score. It then becomes <code class="language-plaintext highlighter-rouge">9.8/7=1.4</code>. This is better.</p>

<p>But some tokens are clearly more important to ‘bat’ than others; ‘bat’ should pay more attention to them. So, we should add a <strong>weight</strong> for each token to indicate how much attention it should receive. The new score of <code class="language-plaintext highlighter-rouge">bat</code> becomes <code class="language-plaintext highlighter-rouge">w_1*0.3+w_2*0.5+w_3*2+w_4*1.3+w_5*3+w_6*0.3+w_7*2.4</code> .</p>

<p>How can we determine the weight? The self-attention mechanism uses two matrices to do it. They are ‘query’ and ‘key’.</p>

<ul>
  <li>Q (query): what information should I look for at my position? For example, a query of “bat” at a certain position could have a query “I look for information about sports, nature”. Then the over-simplified Q may be <code class="language-plaintext highlighter-rouge">[1, 1, 0]</code> .</li>
  <li>K (key): what <strong>quick</strong> information does the token provide at its position? For example, the token ‘player’ could be learned and become <code class="language-plaintext highlighter-rouge">[0.9, 0.2, 0.1]</code>, which indicates that “I am related to sports, not so much about nature or emotion”. The token ‘the’ could have key <code class="language-plaintext highlighter-rouge">[0.1, 0.1, 0.1]</code>, indicating that it is not so much about sports, nature, or emotion.</li>
</ul>

<p>For example, let’s get the weights of ‘The’ and ‘player’; in other words, let’s check which of ‘The’ and ‘player’ is more related to ‘bat’. This is done by getting the product of the query of ‘bat’ and the keys of ‘the’ and ‘player’.</p>

<p><img src="/assets/images/llm-from-scratch/image-17.png" alt="image.png" width="500px" /></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Query(’bat’) @ Key(’the’) = [1, 1, 0] @ [0.1, 0.1, 0.1] = 1*0.1+1*0.1+0*0.1 = 0.2</code></li>
  <li>…</li>
  <li><code class="language-plaintext highlighter-rouge">Query('bat') @ Key('player') = [1, 1, 0] @ [0.9, 0.2, 0.1] = 1*0.9+1*0.2+0*0.1 = 1.1</code></li>
  <li>…</li>
</ul>

<p>In the end, we may get the following weight.</p>

<p><img src="/assets/images/llm-from-scratch/image-18.png" alt="image.png" /></p>

<p>Q and K are for calculating the weight. We also need V (value):</p>

<ul>
  <li>V (value): what actual meaning does the token provide at its position if someone pays attention to it? For example, the value of the token ‘player’ could be <code class="language-plaintext highlighter-rouge">[1, -1, -1]</code> , indicating that the word is sporty, not so much about nature or emotion.</li>
</ul>

<p>As you can see, ‘player’ relates to ‘bat’ more than ‘the’, because 1.1 &gt; 0.2. So bat will pay more attention to ‘player’ than ‘the’, which is done by scaling the values. For example,</p>

<p><img src="/assets/images/llm-from-scratch/image-19.png" alt="image.png" /></p>

<p>As we can see, the value of ‘player’ becomes more important than ‘the’; in other words, when trying to understand ‘bat’, we will pay more attention to ‘player’ than ‘the’. This makes sense because ‘player’ is about sports, which helps in understanding ‘bat’. In the end, for any position <code class="language-plaintext highlighter-rouge">t</code>, we will have <code class="language-plaintext highlighter-rouge">block[t]</code> containing information at and before <code class="language-plaintext highlighter-rouge">t</code>, as illustrated below.</p>

<p><img src="/assets/images/llm-from-scratch/image-20.png" alt="image.png" width="500px" /></p>

<p>Note that we have used the query of ‘bat’ to match the tokens before and including it, to understand ‘bat’. In fact, we have to do the same for all tokens, to understand all tokens.</p>

<p><strong>Why do we need Query and Key?</strong> Query controls what we look for. For example, even if ‘bat’ has meanings about sports and nature in itself, it may not want to look for nature-related tokens, because we have found that this particular ‘bat’ is the baseball bat. Key can control what is exposed to the query. There could be cases where the token has meanings about nature but does not want to let others know about it because the particular token is not about nature.</p>

<p><strong>Why do we need both K and V?</strong> V contains the actual meaning, and K controls what parts of V, and how much of them, are exposed, depending on the query. An analogy would be: Q is like a customer at a bookstore, looking for a book; K is like the author. Q and K will talk, then K will bring the book (V) that the customer needs. Or even more accurately, K will actually read the book to Q, skipping information that isn’t relevant to Q, according to Q’s needs.</p>

<p><strong>Where do Q, K, and V come from?</strong> Note that Q, K, V are matrices produced by their corresponding linear layers. The following is an illustration of how the data flows. The input is a tensor with token meaning and position info. The input is fed into three models: one model learns how to build Query, one model learns how to build K, and one model learns how to build V. Then Q and K are used to determine a weight matrix, which is used to determine what features we extract from V.</p>

<p><img src="/assets/images/llm-from-scratch/image-21.png" alt="image.png" /></p>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>): the definition of <code class="language-plaintext highlighter-rouge">self.query</code> is missing. Please define it.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Head</span><span class="p">(</span><span class="n">nn</span><span class="p">.</span><span class="n">Module</span><span class="p">):</span>
    <span class="s">""" one head of self-attention """</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">head_size</span><span class="p">):</span>
        <span class="nb">super</span><span class="p">().</span><span class="n">__init__</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Linear</span><span class="p">(</span><span class="n">n_embd</span><span class="p">,</span> <span class="n">head_size</span><span class="p">,</span> <span class="n">bias</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
        <span class="c1"># FIXME-03: define the query layer
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">query</span> <span class="o">=</span> <span class="p">...</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Linear</span><span class="p">(</span><span class="n">n_embd</span><span class="p">,</span> <span class="n">head_size</span><span class="p">,</span> <span class="n">bias</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>Answer of Hands-On</strong> (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">self</span><span class="p">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Linear</span><span class="p">(</span><span class="n">n_embd</span><span class="p">,</span> <span class="n">head_size</span><span class="p">,</span> <span class="n">bias</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="implement-self-attention">Implement self-attention</h3>

<p>The input to the attention layer is the ‘sum of semantics and position’. If we feed it to <code class="language-plaintext highlighter-rouge">self.key</code>, it will become the following (note that the output size of <code class="language-plaintext highlighter-rouge">self.key</code> is <code class="language-plaintext highlighter-rouge">head_size</code>).</p>

<p><img src="/assets/images/llm-from-scratch/image-22.png" alt="image.png" /></p>

<p>The query can be obtained similarly.</p>

<p><img src="/assets/images/llm-from-scratch/image-23.png" alt="image.png" /></p>

<p>Here is where we should explain what “multi-head attention” is. Multi-head attention is just multiple self-attention pipelines running independently, whose results are then simply concatenated together. Because we need to concatenate the results, each head should only produce an embedding of size <code class="language-plaintext highlighter-rouge">n_emb//n_head</code>. The following diagram demonstrates how two results are concatenated.</p>

<p><img src="/assets/images/llm-from-scratch/image-24.png" alt="image.png" width="500px" /></p>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">hands_on_006_multi_head_contatenation.py</code>): concatenate two tensors. The results of multi-head attentions are concatenated in a similar way.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch</span>

<span class="c1"># Two 3x8x2 attention-head outputs.
# Shape: (batch_size=3, seq_len=8, head_dim=2)
</span><span class="n">head_1</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span>
    <span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">],</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span> <span class="p">[</span><span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">],</span> <span class="p">[</span><span class="mi">9</span><span class="p">,</span> <span class="mi">10</span><span class="p">],</span> <span class="p">[</span><span class="mi">11</span><span class="p">,</span> <span class="mi">12</span><span class="p">],</span> <span class="p">[</span><span class="mi">13</span><span class="p">,</span> <span class="mi">14</span><span class="p">],</span> <span class="p">[</span><span class="mi">15</span><span class="p">,</span> <span class="mi">16</span><span class="p">]],</span>
    <span class="p">[[</span><span class="mi">17</span><span class="p">,</span> <span class="mi">18</span><span class="p">],</span> <span class="p">[</span><span class="mi">19</span><span class="p">,</span> <span class="mi">20</span><span class="p">],</span> <span class="p">[</span><span class="mi">21</span><span class="p">,</span> <span class="mi">22</span><span class="p">],</span> <span class="p">[</span><span class="mi">23</span><span class="p">,</span> <span class="mi">24</span><span class="p">],</span> <span class="p">[</span><span class="mi">25</span><span class="p">,</span> <span class="mi">26</span><span class="p">],</span> <span class="p">[</span><span class="mi">27</span><span class="p">,</span> <span class="mi">28</span><span class="p">],</span> <span class="p">[</span><span class="mi">29</span><span class="p">,</span> <span class="mi">30</span><span class="p">],</span> <span class="p">[</span><span class="mi">31</span><span class="p">,</span> <span class="mi">32</span><span class="p">]],</span>
    <span class="p">[[</span><span class="mi">33</span><span class="p">,</span> <span class="mi">34</span><span class="p">],</span> <span class="p">[</span><span class="mi">35</span><span class="p">,</span> <span class="mi">36</span><span class="p">],</span> <span class="p">[</span><span class="mi">37</span><span class="p">,</span> <span class="mi">38</span><span class="p">],</span> <span class="p">[</span><span class="mi">39</span><span class="p">,</span> <span class="mi">40</span><span class="p">],</span> <span class="p">[</span><span class="mi">41</span><span class="p">,</span> <span class="mi">42</span><span class="p">],</span> <span class="p">[</span><span class="mi">43</span><span class="p">,</span> <span class="mi">44</span><span class="p">],</span> <span class="p">[</span><span class="mi">45</span><span class="p">,</span> <span class="mi">46</span><span class="p">],</span> <span class="p">[</span><span class="mi">47</span><span class="p">,</span> <span class="mi">48</span><span class="p">]]</span>
<span class="p">],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">)</span>

<span class="n">head_2</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span>
    <span class="p">[[</span><span class="mf">0.1</span><span class="p">,</span> <span class="mf">0.2</span><span class="p">],</span> <span class="p">[</span><span class="mf">0.3</span><span class="p">,</span> <span class="mf">0.4</span><span class="p">],</span> <span class="p">[</span><span class="mf">0.5</span><span class="p">,</span> <span class="mf">0.6</span><span class="p">],</span> <span class="p">[</span><span class="mf">0.7</span><span class="p">,</span> <span class="mf">0.8</span><span class="p">],</span> <span class="p">[</span><span class="mf">0.9</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">],</span> <span class="p">[</span><span class="mf">1.1</span><span class="p">,</span> <span class="mf">1.2</span><span class="p">],</span> <span class="p">[</span><span class="mf">1.3</span><span class="p">,</span> <span class="mf">1.4</span><span class="p">],</span> <span class="p">[</span><span class="mf">1.5</span><span class="p">,</span> <span class="mf">1.6</span><span class="p">]],</span>
    <span class="p">[[</span><span class="mf">1.7</span><span class="p">,</span> <span class="mf">1.8</span><span class="p">],</span> <span class="p">[</span><span class="mf">1.9</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">],</span> <span class="p">[</span><span class="mf">2.1</span><span class="p">,</span> <span class="mf">2.2</span><span class="p">],</span> <span class="p">[</span><span class="mf">2.3</span><span class="p">,</span> <span class="mf">2.4</span><span class="p">],</span> <span class="p">[</span><span class="mf">2.5</span><span class="p">,</span> <span class="mf">2.6</span><span class="p">],</span> <span class="p">[</span><span class="mf">2.7</span><span class="p">,</span> <span class="mf">2.8</span><span class="p">],</span> <span class="p">[</span><span class="mf">2.9</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">],</span> <span class="p">[</span><span class="mf">3.1</span><span class="p">,</span> <span class="mf">3.2</span><span class="p">]],</span>
    <span class="p">[[</span><span class="mf">3.3</span><span class="p">,</span> <span class="mf">3.4</span><span class="p">],</span> <span class="p">[</span><span class="mf">3.5</span><span class="p">,</span> <span class="mf">3.6</span><span class="p">],</span> <span class="p">[</span><span class="mf">3.7</span><span class="p">,</span> <span class="mf">3.8</span><span class="p">],</span> <span class="p">[</span><span class="mf">3.9</span><span class="p">,</span> <span class="mf">4.0</span><span class="p">],</span> <span class="p">[</span><span class="mf">4.1</span><span class="p">,</span> <span class="mf">4.2</span><span class="p">],</span> <span class="p">[</span><span class="mf">4.3</span><span class="p">,</span> <span class="mf">4.4</span><span class="p">],</span> <span class="p">[</span><span class="mf">4.5</span><span class="p">,</span> <span class="mf">4.6</span><span class="p">],</span> <span class="p">[</span><span class="mf">4.7</span><span class="p">,</span> <span class="mf">4.8</span><span class="p">]]</span>
<span class="p">],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">)</span>

<span class="c1"># FIXME:
# Concatenate the two heads along the last dimension (head_dim).
# The resulting shape is (3, 8, 4), combining the two head dimensions.
</span><span class="n">out</span> <span class="o">=</span> <span class="p">...</span>

<span class="k">print</span><span class="p">(</span><span class="s">"head_1 shape:"</span><span class="p">,</span> <span class="n">head_1</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"head_2 shape:"</span><span class="p">,</span> <span class="n">head_2</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"concatenated output shape:"</span><span class="p">,</span> <span class="n">out</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">hands_on_006_multi_head_contatenation.py</code>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">out</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">cat</span><span class="p">([</span><span class="n">head_1</span><span class="p">,</span> <span class="n">head_2</span><span class="p">],</span> <span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<p>Now, as we discussed in the ‘bat’ example, we need to get the dot product of query and key. This is done by <code class="language-plaintext highlighter-rouge">q @ k.transpose(-2,-1)</code> in PyTorch.</p>

<p><img src="/assets/images/llm-from-scratch/image-25.png" alt="image.png" /></p>

<p>The trick to quickly understand the manipulation is to look at only one block. For example, looking at the row of ‘t’ in <code class="language-plaintext highlighter-rouge">weight</code>, it contains how much attention ‘t’ should pay to other tokens in the same block.</p>

<p><img src="/assets/images/llm-from-scratch/image-26.png" alt="image.png" /></p>

<p>Recall that, for any token, we should only look at the token itself and the tokens before it. But the weight matrix right now contains weights we don’t want. For example, the cell pointed to by an arrow below tells how much ‘t’ should pay attention to ‘e’, which is after ‘t’. It is easy to see that the top-right half of the matrix contains such unwanted values. We don’t want to include them in the results.</p>

<p><img src="/assets/images/llm-from-scratch/image-27.png" alt="image.png" width="500px" /></p>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">hands_on_007_masking.py</code>): set the red part (upper triangle) to <code class="language-plaintext highlighter-rouge">-inf</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch</span>
<span class="kn">import</span> <span class="nn">torch.nn.functional</span> <span class="k">as</span> <span class="n">F</span>

<span class="n">T</span> <span class="o">=</span> <span class="mi">8</span>

<span class="n">self_tril</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tril</span><span class="p">(</span><span class="n">torch</span><span class="p">.</span><span class="n">ones</span><span class="p">(</span><span class="n">T</span><span class="p">,</span> <span class="n">T</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s">"self.tril[:T, :T]:"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">self_tril</span><span class="p">)</span>
<span class="k">print</span><span class="p">()</span>

<span class="n">wei</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span>
    <span class="p">[</span> <span class="mf">1.0</span><span class="p">,</span>  <span class="mf">2.0</span><span class="p">,</span>  <span class="mf">3.0</span><span class="p">,</span>  <span class="mf">4.0</span><span class="p">,</span>  <span class="mf">5.0</span><span class="p">,</span>  <span class="mf">6.0</span><span class="p">,</span>  <span class="mf">7.0</span><span class="p">,</span>  <span class="mf">8.0</span><span class="p">],</span>
    <span class="p">[</span> <span class="mf">9.0</span><span class="p">,</span> <span class="mf">10.0</span><span class="p">,</span> <span class="mf">11.0</span><span class="p">,</span> <span class="mf">12.0</span><span class="p">,</span> <span class="mf">13.0</span><span class="p">,</span> <span class="mf">14.0</span><span class="p">,</span> <span class="mf">15.0</span><span class="p">,</span> <span class="mf">16.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">17.0</span><span class="p">,</span> <span class="mf">18.0</span><span class="p">,</span> <span class="mf">19.0</span><span class="p">,</span> <span class="mf">20.0</span><span class="p">,</span> <span class="mf">21.0</span><span class="p">,</span> <span class="mf">22.0</span><span class="p">,</span> <span class="mf">23.0</span><span class="p">,</span> <span class="mf">24.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">25.0</span><span class="p">,</span> <span class="mf">26.0</span><span class="p">,</span> <span class="mf">27.0</span><span class="p">,</span> <span class="mf">28.0</span><span class="p">,</span> <span class="mf">29.0</span><span class="p">,</span> <span class="mf">30.0</span><span class="p">,</span> <span class="mf">31.0</span><span class="p">,</span> <span class="mf">32.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">33.0</span><span class="p">,</span> <span class="mf">34.0</span><span class="p">,</span> <span class="mf">35.0</span><span class="p">,</span> <span class="mf">36.0</span><span class="p">,</span> <span class="mf">37.0</span><span class="p">,</span> <span class="mf">38.0</span><span class="p">,</span> <span class="mf">39.0</span><span class="p">,</span> <span class="mf">40.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">41.0</span><span class="p">,</span> <span class="mf">42.0</span><span class="p">,</span> <span class="mf">43.0</span><span class="p">,</span> <span class="mf">44.0</span><span class="p">,</span> <span class="mf">45.0</span><span class="p">,</span> <span class="mf">46.0</span><span class="p">,</span> <span class="mf">47.0</span><span class="p">,</span> <span class="mf">48.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">49.0</span><span class="p">,</span> <span class="mf">50.0</span><span class="p">,</span> <span class="mf">51.0</span><span class="p">,</span> <span class="mf">52.0</span><span class="p">,</span> <span class="mf">53.0</span><span class="p">,</span> <span class="mf">54.0</span><span class="p">,</span> <span class="mf">55.0</span><span class="p">,</span> <span class="mf">56.0</span><span class="p">],</span>
    <span class="p">[</span><span class="mf">57.0</span><span class="p">,</span> <span class="mf">58.0</span><span class="p">,</span> <span class="mf">59.0</span><span class="p">,</span> <span class="mf">60.0</span><span class="p">,</span> <span class="mf">61.0</span><span class="p">,</span> <span class="mf">62.0</span><span class="p">,</span> <span class="mf">63.0</span><span class="p">,</span> <span class="mf">64.0</span><span class="p">],</span>
<span class="p">],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"wei before masked_fill:"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">wei</span><span class="p">)</span>
<span class="k">print</span><span class="p">()</span>

<span class="c1"># FIXME
# Set the upper triangle to -inf.
</span><span class="n">wei</span> <span class="o">=</span> <span class="n">wei</span><span class="p">.</span><span class="n">masked_fill</span><span class="p">(...,</span> <span class="nb">float</span><span class="p">(</span><span class="s">'-inf'</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s">"wei after masked_fill (future positions set to -inf):"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">wei</span><span class="p">)</span>
<span class="k">print</span><span class="p">()</span>

</code></pre></div></div>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>): set the upper triangle to be <code class="language-plaintext highlighter-rouge">-inf</code>.</p>

<p>The answer for the two HandsOns above is as follows. We will soon see why we set it to <code class="language-plaintext highlighter-rouge">-inf</code> instead of 0.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">wei</span> <span class="o">=</span> <span class="n">wei</span><span class="p">.</span><span class="n">masked_fill</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">tril</span><span class="p">[:</span><span class="n">T</span><span class="p">,</span> <span class="p">:</span><span class="n">T</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">float</span><span class="p">(</span><span class="s">'-inf'</span><span class="p">))</span>
</code></pre></div></div>

<p>Let’s assume the weight for the ‘t’ row is as follows. We want to turn the numbers into probabilities with sum <code class="language-plaintext highlighter-rouge">1</code>, because we want ‘weighted average’, which keeps the Value at the same scale. In other words, we say “I have one unit of attention to spend, distribute it across the sources”. If the sum is a random number, different rows in the matrix may be scaled differently and not comparable.</p>

<p><img src="/assets/images/llm-from-scratch/image-28.png" alt="image.png" width="500px" /></p>

<p>The code uses Softmax to turn the numbers into probabilities.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">wei</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="n">softmax</span><span class="p">(</span><span class="n">wei</span><span class="p">,</span> <span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># do softmax at the last dimension.
</span></code></pre></div></div>

<p>Softmax is essentially the following.</p>

\[p_i = \frac{e^{z_i}}{\sum_j e^{z_j}}\]

<p>So the original numbers become</p>

\[\text{Softmax}(2) = \frac{e^2}{e^2 + e^1 + e^4 + e^1 + e^3 + e^8 + e^5 + e^{-\infty}} \approx 0.0023\]

\[\text{Softmax}(1) = \frac{e^1}{e^2 + e^1 + e^4 + e^1 + e^3 + e^8 + e^5 + e^{-\infty}} \approx 0.0008\]

\[\text{Softmax}(4) = \frac{e^4}{e^2 + e^1 + e^4 + e^1 + e^3 + e^8 + e^5 + e^{-\infty}} \approx 0.0170\]

\[\text{Softmax}(1) = \frac{e^1}{e^2 + e^1 + e^4 + e^1 + e^3 + e^8 + e^5 + e^{-\infty}} \approx 0.0008\]

<p>…</p>

\[\text{Softmax}(-\infty) = \frac{e^{-\infty}}{e^2 + e^1 + e^4 + e^1 + e^3 + e^8 + e^5 + e^{-\infty}} = 0.0000\]

<p>The result is as follows.</p>

<p><img src="/assets/images/llm-from-scratch/image-29.png" alt="image.png" width="500px" /></p>

<p>Note that the ‘t-e’ cell becomes 0 because</p>

\[e^{-\infty} = 0\]

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">hands_on_008_softmax.py</code>): add 3 numbers to <code class="language-plaintext highlighter-rouge">weight</code> , with at least one <code class="language-plaintext highlighter-rouge">-inf</code>, and then observe the results. What’s the sum? What does <code class="language-plaintext highlighter-rouge">-inf</code> become?</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch</span>
<span class="kn">import</span> <span class="nn">torch.nn.functional</span> <span class="k">as</span> <span class="n">F</span>

<span class="c1"># FIXME
# Add 3 numbers, with at least one -info.
</span><span class="n">weight</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([...])</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Input:"</span><span class="p">,</span> <span class="n">weight</span><span class="p">)</span>

<span class="n">probs</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="n">softmax</span><span class="p">(</span><span class="n">weight</span><span class="p">,</span> <span class="n">dim</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Softmax probabilities:"</span><span class="p">,</span> <span class="n">probs</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Sum of probabilities:"</span><span class="p">,</span> <span class="n">probs</span><span class="p">.</span><span class="nb">sum</span><span class="p">().</span><span class="n">item</span><span class="p">())</span>
</code></pre></div></div>

<p>Now we come to the last step: getting the weighted Value. Similar to how we get <code class="language-plaintext highlighter-rouge">k</code> and <code class="language-plaintext highlighter-rouge">q</code>, we use <code class="language-plaintext highlighter-rouge">self.value</code> to get the matrix <code class="language-plaintext highlighter-rouge">v</code>. Recall that <code class="language-plaintext highlighter-rouge">v</code> is the actual info each token wants to expose.</p>

<p><img src="/assets/images/llm-from-scratch/image-30.png" alt="image.png" /></p>

<p>The following diagram demonstrates the process to get weighted value.</p>

<p><img src="/assets/images/llm-from-scratch/image-31.png" alt="image.png" /></p>

<p>To more intuitively understand it, let’s again look at one single block (i.e., a flat horizontal layer in the diagram above).</p>

<p><img src="/assets/images/llm-from-scratch/image-32.png" alt="image.png" /></p>

<p>For example, the highlighted cell in the weighted value is the dot product of the weight of the ‘t’ row and the first column of the value. In the end, the highlighted cell in weighted value has aggregated info from all tokens at and before its position.</p>

<h3 id="whats-the-output-of-the-model">What’s the output of the model?</h3>

<p>So far, we have obtained the weighted value. But that’s not what we want in the end. We want to predict the next token. In other words, we want to know the probabilities of all tokens in the vocabulary for being the next token. For example, given the context ‘accoun’, the probability of ‘t’ being the next may be <code class="language-plaintext highlighter-rouge">0.4</code> and the probability of ‘&amp;’ may be <code class="language-plaintext highlighter-rouge">0.003</code>.</p>

<p><img src="/assets/images/llm-from-scratch/image-33.png" alt="image.png" /></p>

<p>How can we turn the weighted value into probabilities of next tokens? Well, just run the data through a model. The training examples will teach the model to produce the probabilities.</p>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>): define the model that turns the weighted value to probabilities of next tokens.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># FIXME-04: define the model that maps the embedding to the vocabulary
# probabilities
</span><span class="bp">self</span><span class="p">.</span><span class="n">lm_head</span> <span class="o">=</span> <span class="p">...</span>
</code></pre></div></div>

<p><strong>FIXME-04 Answer</strong>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">self</span><span class="p">.</span><span class="n">lm_head</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Linear</span><span class="p">(</span><span class="n">n_embd</span><span class="p">,</span> <span class="n">vocab_size</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="calculate-the-loss">Calculate the Loss</h3>

<p>The model outputs the probabilities of the next token, and we know the correct next token. How can we calculate how wrong the outputs are (i.e., the loss)? The loss is the following.</p>

\[\text{Loss} = -\log(p_{\text{correct}})\]

<p><img src="/assets/images/llm-from-scratch/code-generated-image.png" alt="Code_Generated_Image.png" width="500px" /></p>

<p>Looking at the plot, you can see that the lower the probability of the correct token is, the higher the loss.</p>

<p>Let’s take a look at an example to intuitively understand it. Let’s assume that, the model predicts the probabilities of the next tokens for ‘accounte’ are:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span>
<span class="n">p</span><span class="p">(</span><span class="s">'a'</span><span class="p">)</span> <span class="o">=</span> <span class="mf">0.03</span>
<span class="n">p</span><span class="p">(</span><span class="s">'b'</span><span class="p">)</span> <span class="o">=</span> <span class="mf">0.01</span>
<span class="n">p</span><span class="p">(</span><span class="s">'c'</span><span class="p">)</span> <span class="o">=</span> <span class="mf">0.09</span>
<span class="n">p</span><span class="p">(</span><span class="s">'d'</span><span class="p">)</span> <span class="o">=</span> <span class="mf">0.72</span>
<span class="p">...</span>
</code></pre></div></div>

<p>We know the target token in the example is ‘d’. So the loss is</p>

\[\text{Loss} = -\log(0.72)= 0.14\]

<p>Note that, the larger the probability is, the smaller the loss. For example, let’s say that the model’s prediction is off and <code class="language-plaintext highlighter-rouge">p(’d’) = 0.01</code>. Then the loss becomes:</p>

\[\text{Loss} = -\log(0.01)= 2\]

<p>So, if the prediction is more wrong, the loss will become bigger. The particular loss function above is called cross-entropy.</p>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">hands_on_009_cross_entropy.py</code>): Specify target class index to be 2. Observe the results.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch</span>
<span class="kn">import</span> <span class="nn">torch.nn.functional</span> <span class="k">as</span> <span class="n">F</span>

<span class="c1"># 4 classes; the correct class is index 2.
# class logit
# 0     2
# 1     1
# 2     5   &lt;- correct class with high logit
# 3     3
</span><span class="n">logits_good</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([[</span><span class="mf">2.</span><span class="p">,</span> <span class="mf">1.</span><span class="p">,</span> <span class="mf">5.</span><span class="p">,</span> <span class="mf">3.</span><span class="p">]])</span>
<span class="c1"># class logit
# 0     2
# 1     1
# 2     0.5 &lt;- correct class with low logit
# 3     3
</span><span class="n">logits_bad</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([[</span><span class="mf">2.</span><span class="p">,</span> <span class="mf">1.</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">,</span> <span class="mf">3.</span><span class="p">]])</span>
<span class="c1"># FIXME
# Specify target class index to be 2
</span><span class="n">target</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">(...)</span>

<span class="n">loss_good</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="n">cross_entropy</span><span class="p">(</span><span class="n">logits_good</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
<span class="n">loss_bad</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="n">cross_entropy</span><span class="p">(</span><span class="n">logits_bad</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="s">"Good prediction loss:"</span><span class="p">,</span> <span class="n">loss_good</span><span class="p">.</span><span class="n">item</span><span class="p">())</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Bad prediction loss:"</span><span class="p">,</span> <span class="n">loss_bad</span><span class="p">.</span><span class="n">item</span><span class="p">())</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Probs (good):"</span><span class="p">,</span> <span class="n">F</span><span class="p">.</span><span class="n">softmax</span><span class="p">(</span><span class="n">logits_good</span><span class="p">,</span> <span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Probs (bad):"</span><span class="p">,</span> <span class="n">F</span><span class="p">.</span><span class="n">softmax</span><span class="p">(</span><span class="n">logits_bad</span><span class="p">,</span> <span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">))</span>
</code></pre></div></div>

<p><strong>Answer</strong> (<code class="language-plaintext highlighter-rouge">hands_on_009_cross_entropy.py</code>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">target</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span><span class="mi">2</span><span class="p">])</span>
</code></pre></div></div>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>): replace the function name below with the right loss function name.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># FIXME-05: fill the function name
</span><span class="n">loss</span> <span class="o">=</span> <span class="p">...(</span><span class="n">logits</span><span class="p">,</span> <span class="n">targets</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>FIXME-05</strong> <strong>Answer</strong>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">loss</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="n">cross_entropy</span><span class="p">(</span><span class="n">logits</span><span class="p">,</span> <span class="n">targets</span><span class="p">)</span>
</code></pre></div></div>

<p>Now you have it. The complete code for training an LLM.</p>

<h3 id="how-to-generate-new-text">How to generate new text?</h3>

<p>Now we have the model; how can we use it to generate new text? Well, as we have seen, the model can predict the next token. So, all we have to do is:</p>

<ol>
  <li>give a context (starting with <code class="language-plaintext highlighter-rouge">\n</code> in the Shakespeare example) to the model</li>
  <li>predict the next token</li>
  <li>concatenate the context with the predicted token</li>
  <li>repeat until the context length reaches a predefined max.</li>
</ol>

<p>The following code initializes the context and starts generating.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># [[0]]
</span><span class="n">context</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">zeros</span><span class="p">((</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="nb">long</span><span class="p">,</span> <span class="n">device</span><span class="o">=</span><span class="n">device</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">decode</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="n">generate</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">max_new_tokens</span><span class="o">=</span><span class="mi">500</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="n">tolist</span><span class="p">()))</span>
</code></pre></div></div>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">hands_on_010_predict_next_token.py</code>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch</span>

<span class="c1"># A tiny single-character vocabulary
</span><span class="n">chars</span> <span class="o">=</span> <span class="p">[</span><span class="s">'a'</span><span class="p">,</span> <span class="s">'b'</span><span class="p">,</span> <span class="s">'c'</span><span class="p">,</span> <span class="s">'d'</span><span class="p">]</span>
<span class="n">probs</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">tensor</span><span class="p">([</span><span class="mf">0.10</span><span class="p">,</span> <span class="mf">0.20</span><span class="p">,</span> <span class="mf">0.30</span><span class="p">,</span> <span class="mf">0.40</span><span class="p">])</span>

<span class="c1"># Sample the next token index according to the probabilities
</span><span class="n">idx_next</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">multinomial</span><span class="p">(</span><span class="n">probs</span><span class="p">,</span> <span class="n">num_samples</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">'---'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Sampled token index: </span><span class="si">{</span><span class="n">idx_next</span><span class="p">.</span><span class="n">item</span><span class="p">()</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Sampled character  : '</span><span class="si">{</span><span class="n">chars</span><span class="p">[</span><span class="n">idx_next</span><span class="p">.</span><span class="n">item</span><span class="p">()]</span><span class="si">}</span><span class="s">'"</span><span class="p">)</span>
</code></pre></div></div>

<p><strong>HandsOn</strong> (<code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>): determine the number of samples.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">generate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">idx</span><span class="p">,</span> <span class="n">max_new_tokens</span><span class="p">):</span>
        <span class="c1"># idx is (B, T) array of indices in the current context
</span>        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">max_new_tokens</span><span class="p">):</span>
            <span class="c1"># crop idx to the last block_size tokens
</span>            <span class="n">idx_cond</span> <span class="o">=</span> <span class="n">idx</span><span class="p">[:,</span> <span class="o">-</span><span class="n">block_size</span><span class="p">:]</span>
            <span class="c1"># get the predictions
</span>            <span class="n">logits</span><span class="p">,</span> <span class="n">loss</span> <span class="o">=</span> <span class="bp">self</span><span class="p">(</span><span class="n">idx_cond</span><span class="p">)</span>
            <span class="c1"># focus only on the last time step
</span>            <span class="n">logits</span> <span class="o">=</span> <span class="n">logits</span><span class="p">[:,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="p">:]</span> <span class="c1"># becomes (B, C)
</span>            <span class="c1"># apply softmax to get probabilities
</span>            <span class="n">probs</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="n">softmax</span><span class="p">(</span><span class="n">logits</span><span class="p">,</span> <span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># (B, C)
</span>            <span class="c1"># sample from the distribution
</span>            <span class="c1"># FIXME-06: fill the number of samples
</span>            <span class="n">idx_next</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">multinomial</span><span class="p">(</span><span class="n">probs</span><span class="p">,</span> <span class="n">num_samples</span><span class="o">=</span><span class="err">?</span><span class="p">)</span>
            <span class="c1"># append sampled index to the running sequence
</span>            <span class="n">idx</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">cat</span><span class="p">((</span><span class="n">idx</span><span class="p">,</span> <span class="n">idx_next</span><span class="p">),</span> <span class="n">dim</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># (B, T+1)
</span>        <span class="k">return</span> <span class="n">idx</span>
</code></pre></div></div>

<p><strong>FIXME-06</strong>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">idx_next</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">multinomial</span><span class="p">(</span><span class="n">probs</span><span class="p">,</span> <span class="n">num_samples</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<p>Now, you have a complete LLM in <code class="language-plaintext highlighter-rouge">gpt_DIY.py</code>. Run it by <code class="language-plaintext highlighter-rouge">python gpt_DIY.py</code> to train and generate Shakespeare-style text.</p>

<h2 id="summary">Summary</h2>

<p>In this article, we covered what it takes to build a real LLM—from basic concepts such as neural networks and tensors to more advanced topics such as self-attention. Hopefully, the hands-on sessions help you better understand how LLMs work, and the visualizations clarify how data flows.</p>

<p>This article should set systems people up for getting deeper into AI, whether it is AI for systems or systems for AI.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry></feed>