<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Imad Dabbura</title>
<link>https://imaddabbura.github.io/posts.html</link>
<atom:link href="https://imaddabbura.github.io/posts.xml" rel="self" type="application/rss+xml"/>
<description>Deep science. Built from scratch. Shared openly.</description>
<image>
<url>https://imaddabbura.github.io/images/profile-pic.png</url>
<title>Imad Dabbura</title>
<link>https://imaddabbura.github.io/posts.html</link>
<height>152</height>
<width>144</width>
</image>
<generator>quarto-1.9.37</generator>
<lastBuildDate>Mon, 27 Apr 2026 05:00:00 GMT</lastBuildDate>
<item>
  <title>Why Cross-Entropy Never Reaches Zero</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/dl/why-cross-entropy-never-zero.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>Cross-entropy is bounded below by zero, but on linearly separable data it never gets there. The bound is only reached in the limit of infinite certainty, and a finite-weight model can approach that limit without ever attaining it.</p>
<p>A sigmoid can get arbitrarily close to 0 or 1, but with finite weights it never reaches either endpoint. One-hot labels demand those endpoints exactly. The target lives on the boundary; the model’s predictions live inside it.</p>
<p><strong>1. The loss has no finite minimum.</strong> Cross-entropy is bounded below by zero, but the bound is unattainable.</p>
<p><strong>2. The optimizer never settles.</strong> The gradient <img src="https://latex.codecogs.com/png.latex?(p%20-%201)x"> is non-zero for any <img src="https://latex.codecogs.com/png.latex?p%20%3C%201">, so on separable data the weights keep growing.</p>
<p><strong>3. The predictor collapses to a step.</strong> As <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Ctheta%5C%7C"> grows, the smooth sigmoid sharpens into a near-step function, and the model becomes confidently wrong on any point that strays across the boundary.</p>
</section>
<section id="the-loss-has-no-finite-minimum" class="level2">
<h2 class="anchored" data-anchor-id="the-loss-has-no-finite-minimum">The Loss Has No Finite Minimum</h2>
<p>The sigmoid <img src="https://latex.codecogs.com/png.latex?%5Csigma(z)%20=%201/(1%20+%20e%5E%7B-z%7D)"> maps <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BR%7D"> to the <em>open</em> interval <img src="https://latex.codecogs.com/png.latex?(0,%201)">. The endpoints <img src="https://latex.codecogs.com/png.latex?0"> and <img src="https://latex.codecogs.com/png.latex?1"> are limits, never attained:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Clim_%7Bz%20%5Cto%20+%5Cinfty%7D%20%5Csigma(z)%20=%201,%20%5Cqquad%20%5Clim_%7Bz%20%5Cto%20-%5Cinfty%7D%20%5Csigma(z)%20=%200"></p>
<p>Equivalently, the logit blows up at the endpoints:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Blogit%7D(p)%20=%20%5Clog%5Cfrac%7Bp%7D%7B1-p%7D%20%5Cto%20%5Cpm%20%5Cinfty%20%5Cquad%20%5Ctext%7Bas%20%7D%20p%20%5Cto%201,%200"></p>
<p>This generalizes to softmax: outputs live strictly in the <em>open</em> probability simplex, every coordinate is positive, none equal to <img src="https://latex.codecogs.com/png.latex?1">.</p>
<p>A one-hot label, by contrast, is a <strong>Dirac measure</strong>: probability <img src="https://latex.codecogs.com/png.latex?1"> on the true class, <img src="https://latex.codecogs.com/png.latex?0"> everywhere else. It sits on a <em>vertex</em> of the simplex. The vertices are exactly the points the softmax can never produce.</p>
<p>For a one-hot target <img src="https://latex.codecogs.com/png.latex?p%5E*">, <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BCE%7D(p%5E*,%20q)%20=%20%5Ctext%7BKL%7D(p%5E*%20%5C,%5C%7C%5C,%20q)">, which vanishes only at <img src="https://latex.codecogs.com/png.latex?q%20=%20p%5E*">, a Dirac the softmax cannot produce. The infimum is approached, never attained.</p>
</section>
<section id="why-gradient-descent-never-settles" class="level2">
<h2 class="anchored" data-anchor-id="why-gradient-descent-never-settles">Why Gradient Descent Never Settles</h2>
<p>Why doesn’t the optimizer just stop somewhere reasonable? Because the gradient of cross-entropy doesn’t have a stationary point at any finite weight.</p>
<p>For binary CE with logit <img src="https://latex.codecogs.com/png.latex?z%20=%20%5Ctheta%5E%5Ctop%20x"> and sigmoid output <img src="https://latex.codecogs.com/png.latex?p%20=%20%5Csigma(z)">:</p>
<p><img src="https://latex.codecogs.com/png.latex?L%20=%20-y%20%5Clog%20p%20-%20(1-y)%20%5Clog(1-p)"></p>
<p>The gradient w.r.t. the logit collapses to a clean expression:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20z%7D%20=%20p%20-%20y%20%5Cquad%20%5CRightarrow%20%5Cquad%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20%5Ctheta%7D%20=%20(p%20-%20y)%5C,%20x"></p>
<p>For a correctly classified positive example (<img src="https://latex.codecogs.com/png.latex?y=1">, <img src="https://latex.codecogs.com/png.latex?p"> close to <img src="https://latex.codecogs.com/png.latex?1">), the gradient is <img src="https://latex.codecogs.com/png.latex?(p%20-%201)%5C,x">, <em>non-zero for any <img src="https://latex.codecogs.com/png.latex?p%20%3C%201"></em>. The gradient <em>shrinks</em> as <img src="https://latex.codecogs.com/png.latex?p%20%5Cto%201">, but it never <em>vanishes</em>. The optimizer keeps inflating <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> in the direction of <img src="https://latex.codecogs.com/png.latex?x">.</p>
<p>On linearly separable data, this means <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Ctheta%5C%7C%20%5Cto%20%5Cinfty">. Soudry et al.&nbsp;(2018) showed that <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Ctheta_t%5C%7C"> grows logarithmically while the direction converges to the max-margin hyperplane. The same effect applies to any strictly decreasing margin-based loss. I cover the mechanics of SGD, momentum, and Adam in <a href="../../posts/optimization/gradient-descent.html">Gradient Descent and Its Variants</a>.</p>
<p>We can verify the growth empirically in twenty lines:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb1-2"></span>
<span id="cb1-3">rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb1-4">N <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span></span>
<span id="cb1-5">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(N, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb1-6">y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>).astype(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># linearly separable</span></span>
<span id="cb1-7"></span>
<span id="cb1-8">theta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb1-9">lr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span></span>
<span id="cb1-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100_001</span>):</span>
<span id="cb1-11">    p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> np.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> theta))</span>
<span id="cb1-12">    grad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> (p <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> y) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> N</span>
<span id="cb1-13">    theta <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-=</span> lr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> grad</span></code></pre></div></div>
<p>Sampling <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Ctheta%5C%7C"> at <img src="https://latex.codecogs.com/png.latex?t%20=%201,%2010,%2010%5E2,%2010%5E3,%2010%5E4,%2010%5E5"> gives <img src="https://latex.codecogs.com/png.latex?0.07,%200.55,%202.1,%204.5,%206.9,%209.3">: logarithmic growth, exactly the rate Soudry et al.&nbsp;predict. The loss is shrinking the entire time; the weights are growing the entire time; neither ever stops.</p>
</section>
<section id="how-diverging-weights-break-the-predictor" class="level2">
<h2 class="anchored" data-anchor-id="how-diverging-weights-break-the-predictor">How Diverging Weights Break the Predictor</h2>
<p>The diverging weights aren’t an abstract concern about the loss landscape: they have a direct, visible effect on the predictor’s geometry. Walk this chain one link at a time:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    A["∂L/∂θ = (p−y)x&lt;br/&gt;never zero"] --&gt; B["‖θ‖ grows&lt;br/&gt;like log t"]
    B --&gt; C["σ(θᵀx) → step&lt;br/&gt;function"]
    C --&gt; D["small Δx ⟶&lt;br/&gt;large Δp"]
    D --&gt; E["confidently wrong&lt;br/&gt;on borderline points"]
</pre>
</div>
<p></p><figcaption> The causal chain from the non-vanishing gradient to the confidently-wrong predictor. Each link is a derivation, a number, or a chart in the rest of this section.</figcaption> </figure><p></p>
</div>
</div>
</div>
<section id="from-smooth-curve-to-step-function" class="level3">
<h3 class="anchored" data-anchor-id="from-smooth-curve-to-step-function">From smooth curve to step function</h3>
<p>The sensitivity of the sigmoid to its input has a clean closed form:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%20p%7D%7B%5Cpartial%20x%7D%20=%20p(1-p)%5C,%5Ctheta,%20%5Cqquad%20%5Cmax_x%20%5Cleft%5C%7C%20%5Cfrac%7B%5Cpartial%20p%7D%7B%5Cpartial%20x%7D%20%5Cright%5C%7C%20=%20%5Cfrac%7B%5C%7C%5Ctheta%5C%7C%7D%7B4%7D"></p>
<p>The maximum is achieved at <img src="https://latex.codecogs.com/png.latex?p%20=%200.5">, where <img src="https://latex.codecogs.com/png.latex?p(1-p)%20=%201/4">. So the Lipschitz constant of the predictor scales <em>linearly</em> with <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Ctheta%5C%7C">. The “active region”, the band of <img src="https://latex.codecogs.com/png.latex?x"> where the sigmoid isn’t saturated near <img src="https://latex.codecogs.com/png.latex?0"> or <img src="https://latex.codecogs.com/png.latex?1">, has width <img src="https://latex.codecogs.com/png.latex?O(1/%5C%7C%5Ctheta%5C%7C)">.</p>
<p>This isn’t a thought experiment: it’s what actually happens to the sigmoid as you keep training. Below is the same logistic-regression run from earlier, with the predicted probability surface plotted at four checkpoints in training time. As iterations accumulate and <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Ctheta%5C%7C"> grows logarithmically, the smooth <img src="https://latex.codecogs.com/png.latex?S">-curve sharpens into a near-vertical step:</p>
<div id="cell-fig-sigmoid-evolution" class="cell" data-execution_count="1">
<div id="fig-sigmoid-evolution" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="1">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-sigmoid-evolution-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">

<script src="https://cdn.bokeh.org/bokeh/release/bokeh-3.9.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.9.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.9.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.9.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-3.9.0.min.js"></script>
<script>
Bokeh.set_log_level("info");
</script>
<script>
(function() {
  const fn = function() {
    Bokeh.safely(function() {
      (function(root) {
        function embed_document(root) {
        const docs_json = '{"02ffcba6-5632-4545-b408-b03c372a5302":{"version":"3.9.0","title":"Bokeh Application","config":{"type":"object","name":"DocumentConfig","id":"p1102","attributes":{"notifications":{"type":"object","name":"Notifications","id":"p1103"}}},"roots":[{"type":"object","name":"Figure","id":"p1006","attributes":{"width":720,"height":380,"x_range":{"type":"object","name":"Range1d","id":"p1016","attributes":{"start":-2.0,"end":2.0}},"y_range":{"type":"object","name":"Range1d","id":"p1017","attributes":{"start":-0.03,"end":1.03}},"x_scale":{"type":"object","name":"LinearScale","id":"p1018"},"y_scale":{"type":"object","name":"LinearScale","id":"p1019"},"title":{"type":"object","name":"Title","id":"p1009","attributes":{"text":"Sigmoid sharpens as the weight norm grows during training","text_font_size":"13pt"}},"outline_line_color":"#e5e7eb","renderers":[{"type":"object","name":"GlyphRenderer","id":"p1049","attributes":{"data_source":{"type":"object","name":"ColumnDataSource","id":"p1043","attributes":{"selected":{"type":"object","name":"Selection","id":"p1044","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1045"},"data":{"type":"map","entries":[["x",[-2.0,2.0]],["y",[0.5,0.5]]]}}},"view":{"type":"object","name":"CDSView","id":"p1050","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1051"}}},"glyph":{"type":"object","name":"Line","id":"p1046","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#bbbbbb","line_dash":[2,4]}},"nonselection_glyph":{"type":"object","name":"Line","id":"p1047","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#bbbbbb","line_alpha":0.1,"line_dash":[2,4]}},"muted_glyph":{"type":"object","name":"Line","id":"p1048","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#bbbbbb","line_alpha":0.2,"line_dash":[2,4]}}}},{"type":"object","name":"GlyphRenderer","id":"p1058","attributes":{"data_source":{"type":"object","name":"ColumnDataSource","id":"p1052","attributes":{"selected":{"type":"object","name":"Selection","id":"p1053","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1054"},"data":{"type":"map","entries":[["x",[0.0,0.0]],["y",[-0.03,1.03]]]}}},"view":{"type":"object","name":"CDSView","id":"p1059","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1060"}}},"glyph":{"type":"object","name":"Line","id":"p1055","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#888888","line_dash":[6]}},"nonselection_glyph":{"type":"object","name":"Line","id":"p1056","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#888888","line_alpha":0.1,"line_dash":[6]}},"muted_glyph":{"type":"object","name":"Line","id":"p1057","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#888888","line_alpha":0.2,"line_dash":[6]}}}},{"type":"object","name":"GlyphRenderer","id":"p1067","attributes":{"data_source":{"type":"object","name":"ColumnDataSource","id":"p1061","attributes":{"selected":{"type":"object","name":"Selection","id":"p1062","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1063"},"data":{"type":"map","entries":[["x",{"type":"ndarray","array":{"type":"bytes","data":"H4sIAAEAAAAA/zVYe2DOZRt+Px9akpCQkHOSc86HriUJSUvSkrQkUUIicgilKKoVoYglsUQtxCpci9EwejFjDK+dzDbz7nzevud57vvzh8O87+/3PPd93dfh9njcr6jiLbvO/JhSRf+w13588lgV07Mbzc3ZWUXfquPD135VxfN9FjYbOKeK/17u4k96sYr/fJB0aHlgFfnA1193blvFfbFPTI67vYq/vl3ab152Jbc13Hlni7OV3PTXy74j+yq5JqT+7jc3VPLzGkc+qrekkh9vnxO877VKvv90h4deGl7Jd/MTK6p1qeS0b77wht9dyUmPDPphZHEFxyfnzzb/xTHLtw399u8KPtVp7H2BWyv4+Jna2amfVnDgHEatmF7Bnk1nruo2uoKd/m4z6XyfCrY1vy9sVsFmd3x6R+tqFWwQMeBKTFo5az93K2LaiXJWL/3+wwYR5SzfOHrMn6vLmf/YbQ+GvFfOrPQ/ymqML2fKZ1NP/TyonInd7//+mQfKGXf+9DtFd5QzdsHSId/5y3i4Ze97HztXxr+O3sg0X6G53MHPN5bx57pPf9njwzL+8Pt/Jl58vYzrx+7ptXhEGb+qmnR7u25l/HTLvYkn7injB8Nif3m7tJTzst9f0uhqKe3pDxwu5Rt9Utq9Gl7KCZfXlAR8Vsqx8kGOesCetJTD7df6lfLRt18ZHHZ/Kfs2bNBoSPVSdnMHKaG5xP4vT5awZY2OX/TeVcJ7t195xTyK7ljzSxjgLlpCzzeFCacGl7B44E87Zj1YQtPURU3qlDB9WZ1RUbnF9HW0hSzmBXvt/cX0vtvu+G9hxYy5L2HD8x8VM8pWfUox973mGsVfa+U0GPZwMbf9+sP17EbF3OTKWsS15hb9rhXx840WCEX8+LFp3T/eXsT301uYExbx3c/O2srS/nROcBFd2wYWcfyCzKcPtyzimJYbW02pWcSRR4MK6mQWcsib/43Z828hH6m799uxewrZ6/fJb5m70MFiYSHbVp2sP2JCIZttWZyaM6SQ9wx7OHLtQ4W0aBlYt5DVV617KTm/gOW9n+z6SUIBHewOFjBrScS5uM0FtNWft6yApknzW0wtYNyMmKfMqxlr/9mzgNF/dsqr16SA+x2s87lHHsgdPz1uTpZPUwtT0Xyuz9tuSp5P97rZ+Vwx0B44n25skM95y2a/mNY6nzM7tu+8MiCfb5y+6Ol+M48TXEHy6K6zN4+mGXNbr8/jcDuWi/I4qFZw8+kT89jPFTyP3Q1qzJHYoWS6GbE8SrlyacDZf8elXNaThvJ224UtuTQv2f3dJ7ksjt9kWpFL//xRL9wYlUvXjt65tLfq0TSXF96wJ8ql9y5XSZqiGwTkMOoFC8gcRppPzVyVwwjb7rk5DDfTeWBcDsNujlj96qM5NJxhAJ9DA8K+5qhcljjRjJ6fiyzaz/rp4BTp57QTbqA4aUaX57OW+Dn+nqQHv5rkp0VP7yf9HPnyE5ZyaDBuBtbPBe64tzjVjttftzgurXWPjKW3ONx0s+fIWzTkYwbqFjtEP9vmpC+bTer8cazx9my6sr2TTTf2A7KZYae2RjYv9rRvuMnji3abTt5kpPn0lVduMvxuy0w3Ke3LoqOfA1k0pIg7l2Vxcv+7U4ODshj80Vwz0lkc6ogxk71c4TPpYDQ7k44GH8lkzaKZR5oHZLIw0FY2g2kGjXu/zWB83GbDFBk82jzgxac6ZXCPg/MNOjrmDdoqdP3kBm2R5o+6wVmh1db8c98NGnIxJ0jnaHPL8b+kc7AboHR2t2gPTGerahXeR2ul0wyDQdR12m8lbLjO3Kt2sK/TVnlGl+s848Y7jYcOjPvttkNp3HXboTHPrkhjmCG9jaPTGLp+5fcZzdJop6nX9VS6NkWk0tHMe6kMckSYysA7bYdT2dX+97kUNresuTGFdTL3GmVIoYNLtxQK3SXTkPH+DtHJPOUOmMyD9vVjkvnLVluxZG5wwEziSgvbXUl0KJmfRMvKAx5P4rjGFtlJdNc7f40DdlwyTH2NHV2Br7GJHZ+Hr7GWo38fS8/aQfYxo1n8W9984aMrX7CPFoVdW/k4y5Yh/ipHr4ivGTrlKns42r/Cuo7Yr9Bvbv9cyyv02jHdfZlGRMyRLtO1IyGRM5zAJTLIydkldj1YsO1a6CXWdgN8iVmWfvZeZOx7zxkoXeQOC4tLCbTVjX8rgVN7WiJL4IhjFogX2NE8ZX3bCwywtBp5nulLrxolPs8YC8/L8Qy3rD09noZMDCTiacTXSPE5DnV/Ocf2TtjiWD10yqabI+KYYsfk6llG2/GeeZZbDJvsrn6W7rFrz3DiO3byznCwlcH9p9nGEdhpVjfkuO2alz5LW7O8jDLoiK/pZaCll+7/0mNhFnyKUVZ2F56k0bqpr2+OZaAD6gl6NrRdF511nJGWpeod51xL672PsY8dn3ExLHYD+w8jJ9y1dOfWo7TTlHjiCPs42ohmsVGpvg2jGWHltP9hzjC3XhNyiF2fsU7ib/otO26PYogTRNLnAHuAIY7I9tNn7cqiPxlk1O2PY5H02sc12McgSwfjf6fXoMEbvptBjkZ/o9cJRQQtKrx37WTY83bAf2KYlcu+W7nYup2Tm+kxojTzyEZ6nOCtpcdep/GX9DiiWkqPHeexb/7/T+jPoZ+Dfg/6HOhzESXvQZS8F1FyDvjkXAiRc8In50aI3AM+uRdC5J7Qe0PrAK0LtE7QukHrCK0rtM7wXHR1R6D0AYulL4iSPsHT3/UNgdJHLJa+Ikr6DO07FAdQXEBxAsUNFEdQXEFxhgDBHdoLDjFUcInJglMsF9wiXHCMGME10gXnCBDcQ+cAOhfQOYHODXSOoHMFnTPo3KGrzCGCZC4xQ+YUoTK3iJA5hlfmGn6Zc9SVuYfyAJQXoDwB5Q0oj0B5BcozUN5BH+EhBAsvYa7wFGKFt5AoPIYs4TWUC8+htvAemgoPQnkRypNQ3oTyKJRXoTwL5V0oDyNCeBlRwtPwCm/DJzwOv7OxyfA4I56CusL7aCE6ANUFqE5AdQOqI1BdgeoMVHegOoQ9okuIFp1CnOgWUkTHkC+6huqic2gguoc2ooPoIboI1UmobkJ11JKo1VWozkJ1F6rDUF1GjOg0LohuI110HMWi6wgQnUdj0X20Fx+APuILoD4B6hugPgLqK6A+A+o7oD4E6ksQKz4FieJbkCU+BuXia1BbfA6aiu9BR/FBGCC+CCPEJ0F9E9RHQX0V1GdBfRfUh0F9GdSnYYb4NswVH4fF4uuwXHweQsX3YZ34QKgvhPpEqG+E+kior4T6TKjvhPpQqC+F+lTkim9FqfhYVBNfi1ric1FffC+aiA9GKxcjcqE+GeqboT4a6quhPhvqu6E+HOrLoT4d6tuxQHw8loqvx0rx+VgtDcQGyQHYIrkAmhOguQGaI6C5ApozoLkDmkOguQSaU6C5BZWSY1BTcg3qSM5BQ8k9aC45CO0kF6Gz5CRoboLmKGiuguYsaO6C5jBoLoPmNGhug+Y4LJdch1DJeVgnuQ9hkgMRLrkQEZITESm5EZojobkSmjOhuROaQ6G5FJpTobkVmmNRS3It6kvORRPJvWglORgdJBeju+Rk9JPcjEGSo6G5GpqzobkbmsOhuRya06G5HZrjobkeqyXnY4PkfmyRPQB2yF4Ae2RPgP2yN0C07BGgewXongG6d4DuIaB7CeieArq3gO4xoHsNNJQ9B5rL3gPtZA+CzrIXQS/Zk+AR2ZtgiOxRMFL2KtA9C3TvAt3DQPcy0D0NdG8D3eNA9zrQPQ/CZO+DcNkDIUL2QoiUPRGiZG+EGNkjwSt7JVyQPRN07wTdQ0H3UtA9FWRt5Qn8H3eWIf/AEgAA"},"shape":[600],"dtype":"float64","order":"little"}],["y",{"type":"ndarray","array":{"type":"bytes","data":"H4sIAAEAAAAA/xXWdzzX3xcH8JBCRMoqhUhFKNnrtEmEklERfW0h2UQyI5KsSlmlkoqikpWRnXyRknjfK6OS8KVhJL/z++v5+PDmfe+5r3Pu57KCZOLKJwxwzvI/3FrEwLOw9ibDYga4RKw/uzxlIJS7hP3CMwYuPGCkcp8zcF2vfU9NCQPiWxPsyQsGLj0Riv5TysABMZc8kXIGausiX6tUMGD703nicCUDMfMCAp4vGciRi9G4VMUAd1eNTX41A5t310Q31jBg1hBVMFTLwFQhbzdrHQOWRsdZxOsZoP+5ymo3MDDwWdvsaCMDj0Pbz/s1MXB7mfSj5GZcX7fGx8ctDGxX5uVse82A+5kc1dFWBpaQKQfONgb2NrOmSf/LgFf4m4bd7Qy8szSbsenA/d24JhvSycCh9DTr9LcMjN02ulLSxUADR3V91zsGRAS/zU2+Z0BKtHUb3wcG1ro4OMn1MLDT6Fmm/kcGalaXv3fsZaBrjR9fZB+uK3t4fw7DQPgcZ8RLwkCAP63spQy8cnSenelnIEH4torQAANsPQleSoMM6M3JPjYZYiCixW/cfZiB/mtn5OM+MxD0dI173hcG/N3OPKr/ysBvPt/xgREGhMY3bGMZZeA/s/Ne677j/tOin2mOMaC7SG3WYpyBSZKo4zvBQHlkcnjSf7i/HdBUOMnAzxOJvG+mGLioGGv27Qe+b+nmDI5f+H/k3IY3/Gag98cRhd3TDJjkf/K3mWHAIou/NniWgen1QzzpcwyohVlalvxhYDWPR27XPAO5/BsmJ/9iHfsDgW8RgQdNHvFyLARkRNh69VkJ6IkpyTqxEWCVZQ2KXEzg0MVTLTnsBBQSzohWLSGQGLnSvW8pAZeKg1WzHAQKIyVXCnMRMJVLdFBeRkCXLa30EDcBfzMl3tM8BDp9nOzilxPYVahYep+XwDPTK3yNfAT4n0Q5Dq0gsEOI9yXrSgIbO2WExFfhepQ+eGgLECiIFWk6KkhAQOrben8hAi+NDINThAnEHIfuJyIEDl+r2v7vagLTpv8mfF9DwJxxG+Vai++JuLp/0zoCV1OM7u4VIyBve4X9H3EC75Vt7EIlCDifLKm9uR7XoXxdskySwJW/iyO6pQg0Lpse/LmBwJYHZ/at3EggVt773tZNBB71z3Ed3EzAZhWnu6sMAVeB9PYLsgQytj9WvrOFQHGF/vVaOQLBrI4LVJ5AiTG7/V8FApt/S7Ws2Yb12lO3TV2RwNPooatm2wkY80Yu8lYiICFzyylRmcBXaWh/pELAytRC47UqAW2WsVtf1QjonF3gWapBQHRVnL+UJoE4oaSBnVoE9natNDqhTeB3Ok/ZWR0Cx0vPbbwOBNafc05+voPArFozS9dOAmkq2R6Tuwjk1n/v491DoEO1wEBuL4Hm7sEy/X0ENKbjZZ10CbQM5qVH6hHY9F2V+9Z+gnlTDa7SJ/Dx4t2xvgMEzr+MtpkzwHrsf98hfJDAdf+UvSpGeN7p1SWHjQkcXXRsi6cJgQO/bLIuHcI6tnSsenCYgFtbQUyTKYEPRr//Dh8hkJd+z3uxOQEfltoRCQsC3W17bcGSgMeRrd3HjxJopeeNAo8RuFSl3pB2HPe52xyeWhG48PzD8w5rrJNr1daJEwRYCrju89jivtueS8qexOfXNNzU+wfr/UlV2MEO6+/JmRRuT+CNsObybAcCvza8jql0JFD5uYy91wlz9YAzbMaZgFlt8V9BVwJageVBSqcIjIqJz5i4EfjGSnw93AnUeE7/iPPAfJW6et0/TYBXTm2ywRPfJ2jlOXSGwEzdhwlWb8x3zMPT4j4Ebha+n9D2JfDugrnnMT8C7LZbJv39CdyKPuKVGkBg64GOH0WBmK/ZbN/2IMz/QO302FnMlalqEHcIgcs35uY3n8Pz5uI7rxtKIOyb12L78wSUEqQvhIUReLx7I3dWONbL3OdyRQSBQWE+gY+RBCLe/bo2HUWggdkkJngBcxCae3t7DAEOzlMyJrEERhqCCt0vEoBlb1Xi4gj4SnhX5MVjHS2P7Wm4RMBwUUzLYAKBNvs/h1gTCUy9L+4Ru4L1zH18UjuJQO+ayZGjyQTOxvp4+acQ8ATNPympBORidkUWpREQeha7vP0qgWurVl4du0ZAfKpHgjsd95fVn7/5Bp6zs7SK7k0CTMa9KrsMAlHxjgfCMjF/ESffZWYRsG6/aluRTcCylvN7Tw6B5HvF/tO3cL60XV0smEtgf9TTy9vv/P+cudaa3MW+60vJc79HoG+1hUpcHoH7Esa1efcJOO0INm7Ix3zVMX2DD7BuC16urI/wHLS0ZsQKcC41q0RrF+K5T58UOPaYQMqqqlv+TwiccDNUTC0isMKEq7qomMDS5b+M2p8SyJrkJWPPCKgamrtzl2B/nGqZ3/wC63TTNV63FOu+VXWtfRk+d0buYVg59skTQ+2sCgKhOmmtFZUEfthxWn98SUDF+9bYdBWBzFK7c4I1uP8oAz6lWgJ7pI9mm7zCufolTtGjjsCN1YO1cfUEhpadOHK/AdfFNT/c0Ij5sKvwH2oioO6UycXWgn9nl3ND/DWBrjt18jqtOD9DOKqPvcE+U3M5HNBGYFJqZCj1XwKfLkf7F7djfpt3LuvowP6VEM4Y78Scf+LcxtNF4JSV4CuZdwRsiZa53nsCws/Pjth3ExhW7AoO/0BgTfbeFdk9BJYZt96u/Ij3wSVXtd5erF+e2OuZPuz/iS8nhAgBg8f1U0oU86tREn2on8DpnvI1pz8R0CSdBfEDmLu0P7vzB7EfjVW6G4dwH0dDTw0P45xg6Vm0+AvOpZRdKRJfsY52LzbDCOa+XLvy+DcCTd/eHAocxbm/49TntO84rzkFzj4dIzCf3MjXOY552hqVOzFB4J6socbySQL5naJtslMEtvv9+Gf/D1yPXee0w08CPCMl8RG/sA5Hbq/P+Y19+iv5+ctpAgdXxxr0zeA+eMP7Z2exH6TO+Qr/wXzdCV6mMk8gYCg46/BfAlT+nLLnAv4/px32uoso7OGpVniHPtmyc9aOhcIi96pXU+gRf52EMFYKHyZLLVewUXgcoSyVhbJ3PRyTX0xhNlbyRQVaYpgabsBOweEV+8GPqEyqp7DLEgpbXD58mkYTP2s9jF5KIexpup8gB4UNQT935qJZZD+3EicF0dir72rQlxL9WSZcFAoNJV0pKvTUWtljGf5+4crCPDrY8bIpjpvCtfeDSWt4KNgUs1jfR//hE9ykvpxC4xWJyQa05JNkuRkvhaN3RaOG0PfRy4y9+XA/shMibCsoLKg1DySic+bXHorzUzijYOVXgMY7COzUWUnB+3Y1VytqfNn27bFVFPTbf9wcQWOXBzkGCFDQGJ/ayiFIIWL1idlU9LJqee0GIQrR37nii9HkAX2z3cIUaFWQWAeasuXmFxsRCn2tjx6PowMHCgNDVlP41zB7N88aNPA89w2UJ9C4S0YU6zzJk/EC/XLxhYPeWgrM6CGF92hF+Yff9uuwPjmGVT/Qob2PLoSLUYgynDPmF6dQZaMkko2+lLbsV5DA5/Y551WilY52nobrKYhs01fvRUf3r2ZxlaRgZ/m2cQYtXOJ/+YIUhT/Tiy2ENlDwfx8odgc13v9hWEmawrOfYo9q0bfhB30ObcR9XLfT6kfn+23ZTm+iYL9oX/NfdN9NvsT4zRRaT1Sbi8pQkB03X5ePXpnsHFSXpbBthXJ+I6o+GehpvoVCr1au6jCqf7Vo3luOwvP83Fo2eQqH1wXFXEFN0rYbSSjguVe8WVWI6qgb9OhsxTwV5me2oitbRu2Ob6MwrcAr8w09HcU/HqBIocjnVxHHdgo/b5f6p6EuZdba0koUdG0+sTxFazT21u9WpsA/GRXbgZ6Su3vQVoXCkuRc/glUpPX8uxBVzP05zes8ahRCD3dY3UD3ju+VkFXH3M2mDL5Al4nW3NXToHB2rsnlPToOD+UcNCncvOw68QOtTF5cFK5FIWHKz4dfm4KeaZ1qNrrRenRGQQfrVjJaVokCV32wIVD4b9YPetGSbawsrjswF0fsamZQv+UZ4Rd2UhhhLdgjtIuCU08q+x1UQetIvdJuCiFtX6Nq0WRNU91DezA/G5OX9qOCmnkNHnspsK1Niv6LvvA20Y3fRyHg68ASUV0Kkev16++jYg3hkep6FC6GXN7TiHJNeLCZ76fQXyNRM4QeycgN9dancG/znA7bAQoS3OvnE1H27yJl4gbYj16f/AtQKd0gFR1DCt+WD069RtXOiD4+dpDCIWEp9xE09N6sTIAR1q058/NSYwpaUitvp6LW1qY2G0xwfWL/iBaj13ihe9chCvu7Pie1o6uErYxsDmO+zmVwjaNbnuTXBZvizy0iQrmP4HPCmzTTUdHrqT83m1H4GNFWUIJ2nutw1jXH9StmSr5Di3Yr99lZUNA8lpw6he4QLzcKs8QcaDzgWHEUz9/YrjoTdeMcDJA/RiGdR1axAh1gUx85cBw/J/Hk9KBCdvcsna0oPBTlXDGNFtspNEZZUwgfWH1O4AQFjx0tyrfRjvU7RhVtKCxWDMipRk8I+FoY2+L88FZZTtDJny9q3U5inXcuCviDnlzgkL/4D4X2rs5PInYU4k7ZpN1Dx10eHVC1p2DhX7FQh0prxReZOmC/WK11HEBPRbitPuNI4bZ5cOsiJwrVIQahCah0do/iOmcKig4bhx6g+j6KaZoumCufOb1mVIY3bNbClULZl7r8z+hIXv1x31NYx9lwHnY3zGHofEUSumFI0X29O4XXbyTXPUYFhtpawQP7qk0l+A2qpGyxxeo0BVKx/eM3tIuvKSbQE+vRJaTGeYaCWZHYcBpaZkKTpL0wPxaWO5+iP91ix3Z7Y640vNI70Pv2K/VsfXA/qS4/xlEbb/+sEF8Kv+9oGfD4UcjtevY7HWXuDufI+OM51LUYvkDr3tpP6wZQsAopznmHmjk/N7APpLBJ2/3XFHoguzszLAjzqP1Lb8VZCs4l9f9loual+6/LB1M4NxKyqwK1mrAfORBCwdXrb2IPKrNGT935HPZRwi76G33k+z0qKpSCV4CenMB5nEs6hztvoRHuPAGKYTgfC3zXVaMW9y/VGoXj3GM/4sSgHM5N3G4ROL/OfiucQ8uGS01jI7F/jFWnhaNwXyds0u+i6yuUdVSiKSzle05fob0LA2GHL1CQVyqX+oRGm0L96Ri8nzjcHBfQiN97OC7F4v3dVH9P9CKFXRsm9fJRvqL6L+px+L7Vey40olasLhvN4/He41erH0JfTD20875EYZ1BCytbAoXa2oSsRBRY57TFL2PdCrg/PkIFzrzy006kkDkpseo1atMtVXj0Cs6p5tcHvqJjniKf/ZIoTHlzhS1JxrkXlCmagsbIdT+TTMH6KhYbP0Fzt20f2ZGK/dppGt6G6lYKi1qn4TleDykeRYP5ogyCrlJorpcf5LyG3yPs/YOuonNJJ/k3Xsf9z47ee4reBSHYk06Bl7v/bQfKt1TfxfYGBdNuk4VxNHrjdFLITfx8Q2sTTwbmiFlXno4GpmQYyWTivHQu+1SCarD4+OhmUTBib1n6Dl23tuKaXTZ+X5jQl51CS6W9y8/nUJhwVDPku4Vz41haXwZannfZTe423rMzkvNlaNCUcZx+LoU7OkJrPqBzYb55jnfwfnNyV/2F/g/EaYZkwBIAAA=="},"shape":[600],"dtype":"float64","order":"little"}]]}}},"view":{"type":"object","name":"CDSView","id":"p1068","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1069"}}},"glyph":{"type":"object","name":"Line","id":"p1064","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#453681","line_width":2.6}},"nonselection_glyph":{"type":"object","name":"Line","id":"p1065","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#453681","line_alpha":0.1,"line_width":2.6}},"muted_glyph":{"type":"object","name":"Line","id":"p1066","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#453681","line_alpha":0.2,"line_width":2.6}}}},{"type":"object","name":"GlyphRenderer","id":"p1078","attributes":{"data_source":{"type":"object","name":"ColumnDataSource","id":"p1072","attributes":{"selected":{"type":"object","name":"Selection","id":"p1073","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1074"},"data":{"type":"map","entries":[["x",{"type":"ndarray","array":{"type":"bytes","data":"H4sIAAEAAAAA/zVYe2DOZRt+Px9akpCQkHOSc86HriUJSUvSkrQkUUIicgilKKoVoYglsUQtxCpci9EwejFjDK+dzDbz7nzevud57vvzh8O87+/3PPd93dfh9njcr6jiLbvO/JhSRf+w13588lgV07Mbzc3ZWUXfquPD135VxfN9FjYbOKeK/17u4k96sYr/fJB0aHlgFfnA1193blvFfbFPTI67vYq/vl3ab152Jbc13Hlni7OV3PTXy74j+yq5JqT+7jc3VPLzGkc+qrekkh9vnxO877VKvv90h4deGl7Jd/MTK6p1qeS0b77wht9dyUmPDPphZHEFxyfnzzb/xTHLtw399u8KPtVp7H2BWyv4+Jna2amfVnDgHEatmF7Bnk1nruo2uoKd/m4z6XyfCrY1vy9sVsFmd3x6R+tqFWwQMeBKTFo5az93K2LaiXJWL/3+wwYR5SzfOHrMn6vLmf/YbQ+GvFfOrPQ/ymqML2fKZ1NP/TyonInd7//+mQfKGXf+9DtFd5QzdsHSId/5y3i4Ze97HztXxr+O3sg0X6G53MHPN5bx57pPf9njwzL+8Pt/Jl58vYzrx+7ptXhEGb+qmnR7u25l/HTLvYkn7injB8Nif3m7tJTzst9f0uhqKe3pDxwu5Rt9Utq9Gl7KCZfXlAR8Vsqx8kGOesCetJTD7df6lfLRt18ZHHZ/Kfs2bNBoSPVSdnMHKaG5xP4vT5awZY2OX/TeVcJ7t195xTyK7ljzSxjgLlpCzzeFCacGl7B44E87Zj1YQtPURU3qlDB9WZ1RUbnF9HW0hSzmBXvt/cX0vtvu+G9hxYy5L2HD8x8VM8pWfUox973mGsVfa+U0GPZwMbf9+sP17EbF3OTKWsS15hb9rhXx840WCEX8+LFp3T/eXsT301uYExbx3c/O2srS/nROcBFd2wYWcfyCzKcPtyzimJYbW02pWcSRR4MK6mQWcsib/43Z828hH6m799uxewrZ6/fJb5m70MFiYSHbVp2sP2JCIZttWZyaM6SQ9wx7OHLtQ4W0aBlYt5DVV617KTm/gOW9n+z6SUIBHewOFjBrScS5uM0FtNWft6yApknzW0wtYNyMmKfMqxlr/9mzgNF/dsqr16SA+x2s87lHHsgdPz1uTpZPUwtT0Xyuz9tuSp5P97rZ+Vwx0B44n25skM95y2a/mNY6nzM7tu+8MiCfb5y+6Ol+M48TXEHy6K6zN4+mGXNbr8/jcDuWi/I4qFZw8+kT89jPFTyP3Q1qzJHYoWS6GbE8SrlyacDZf8elXNaThvJ224UtuTQv2f3dJ7ksjt9kWpFL//xRL9wYlUvXjt65tLfq0TSXF96wJ8ql9y5XSZqiGwTkMOoFC8gcRppPzVyVwwjb7rk5DDfTeWBcDsNujlj96qM5NJxhAJ9DA8K+5qhcljjRjJ6fiyzaz/rp4BTp57QTbqA4aUaX57OW+Dn+nqQHv5rkp0VP7yf9HPnyE5ZyaDBuBtbPBe64tzjVjttftzgurXWPjKW3ONx0s+fIWzTkYwbqFjtEP9vmpC+bTer8cazx9my6sr2TTTf2A7KZYae2RjYv9rRvuMnji3abTt5kpPn0lVduMvxuy0w3Ke3LoqOfA1k0pIg7l2Vxcv+7U4ODshj80Vwz0lkc6ogxk71c4TPpYDQ7k44GH8lkzaKZR5oHZLIw0FY2g2kGjXu/zWB83GbDFBk82jzgxac6ZXCPg/MNOjrmDdoqdP3kBm2R5o+6wVmh1db8c98NGnIxJ0jnaHPL8b+kc7AboHR2t2gPTGerahXeR2ul0wyDQdR12m8lbLjO3Kt2sK/TVnlGl+s848Y7jYcOjPvttkNp3HXboTHPrkhjmCG9jaPTGLp+5fcZzdJop6nX9VS6NkWk0tHMe6kMckSYysA7bYdT2dX+97kUNresuTGFdTL3GmVIoYNLtxQK3SXTkPH+DtHJPOUOmMyD9vVjkvnLVluxZG5wwEziSgvbXUl0KJmfRMvKAx5P4rjGFtlJdNc7f40DdlwyTH2NHV2Br7GJHZ+Hr7GWo38fS8/aQfYxo1n8W9984aMrX7CPFoVdW/k4y5Yh/ipHr4ivGTrlKns42r/Cuo7Yr9Bvbv9cyyv02jHdfZlGRMyRLtO1IyGRM5zAJTLIydkldj1YsO1a6CXWdgN8iVmWfvZeZOx7zxkoXeQOC4tLCbTVjX8rgVN7WiJL4IhjFogX2NE8ZX3bCwywtBp5nulLrxolPs8YC8/L8Qy3rD09noZMDCTiacTXSPE5DnV/Ocf2TtjiWD10yqabI+KYYsfk6llG2/GeeZZbDJvsrn6W7rFrz3DiO3byznCwlcH9p9nGEdhpVjfkuO2alz5LW7O8jDLoiK/pZaCll+7/0mNhFnyKUVZ2F56k0bqpr2+OZaAD6gl6NrRdF511nJGWpeod51xL672PsY8dn3ExLHYD+w8jJ9y1dOfWo7TTlHjiCPs42ohmsVGpvg2jGWHltP9hzjC3XhNyiF2fsU7ib/otO26PYogTRNLnAHuAIY7I9tNn7cqiPxlk1O2PY5H02sc12McgSwfjf6fXoMEbvptBjkZ/o9cJRQQtKrx37WTY83bAf2KYlcu+W7nYup2Tm+kxojTzyEZ6nOCtpcdep/GX9DiiWkqPHeexb/7/T+jPoZ+Dfg/6HOhzESXvQZS8F1FyDvjkXAiRc8In50aI3AM+uRdC5J7Qe0PrAK0LtE7QukHrCK0rtM7wXHR1R6D0AYulL4iSPsHT3/UNgdJHLJa+Ikr6DO07FAdQXEBxAsUNFEdQXEFxhgDBHdoLDjFUcInJglMsF9wiXHCMGME10gXnCBDcQ+cAOhfQOYHODXSOoHMFnTPo3KGrzCGCZC4xQ+YUoTK3iJA5hlfmGn6Zc9SVuYfyAJQXoDwB5Q0oj0B5BcozUN5BH+EhBAsvYa7wFGKFt5AoPIYs4TWUC8+htvAemgoPQnkRypNQ3oTyKJRXoTwL5V0oDyNCeBlRwtPwCm/DJzwOv7OxyfA4I56CusL7aCE6ANUFqE5AdQOqI1BdgeoMVHegOoQ9okuIFp1CnOgWUkTHkC+6huqic2gguoc2ooPoIboI1UmobkJ11JKo1VWozkJ1F6rDUF1GjOg0LohuI110HMWi6wgQnUdj0X20Fx+APuILoD4B6hugPgLqK6A+A+o7oD4E6ksQKz4FieJbkCU+BuXia1BbfA6aiu9BR/FBGCC+CCPEJ0F9E9RHQX0V1GdBfRfUh0F9GdSnYYb4NswVH4fF4uuwXHweQsX3YZ34QKgvhPpEqG+E+kior4T6TKjvhPpQqC+F+lTkim9FqfhYVBNfi1ric1FffC+aiA9GKxcjcqE+GeqboT4a6quhPhvqu6E+HOrLoT4d6tuxQHw8loqvx0rx+VgtDcQGyQHYIrkAmhOguQGaI6C5ApozoLkDmkOguQSaU6C5BZWSY1BTcg3qSM5BQ8k9aC45CO0kF6Gz5CRoboLmKGiuguYsaO6C5jBoLoPmNGhug+Y4LJdch1DJeVgnuQ9hkgMRLrkQEZITESm5EZojobkSmjOhuROaQ6G5FJpTobkVmmNRS3It6kvORRPJvWglORgdJBeju+Rk9JPcjEGSo6G5GpqzobkbmsOhuRya06G5HZrjobkeqyXnY4PkfmyRPQB2yF4Ae2RPgP2yN0C07BGgewXongG6d4DuIaB7CeieArq3gO4xoHsNNJQ9B5rL3gPtZA+CzrIXQS/Zk+AR2ZtgiOxRMFL2KtA9C3TvAt3DQPcy0D0NdG8D3eNA9zrQPQ/CZO+DcNkDIUL2QoiUPRGiZG+EGNkjwSt7JVyQPRN07wTdQ0H3UtA9FWRt5Qn8H3eWIf/AEgAA"},"shape":[600],"dtype":"float64","order":"little"}],["y",{"type":"ndarray","array":{"type":"bytes","data":"H4sIAAEAAAAA/w2UdzTWDRTHIxmRVaSSJCVvMqJQ8hW9KpVQSpJIFBFaSFNFCC/JzN6yUjaRke2x93o8xuMZv6ehIuv11z33nPv5fs8933tutveesJOlbyFE+uJpxR6Cx/ldriYnQiAom8O5NzAESoffkCh9IWh8Uev2ZEcoCvTTctlvhWJVf568U1EoXruFpTWsCUNHsurkhrNhEGKvzDKMC4OrZknn829hGJjlVsrSCsdul+WEjsBw8J25JfKHstL/yLi7RTUCayRSCnV8IzC9d3rk9mgE5k5lfHt/IBKvPcdmv/tFYihXm//4ZCSkNc/pf8A7CNQ9GFaMeAf7cDZG/a93UCF2ZTwxisJlIRkz45woJB4/dkRfIBppfqOpDo7RuCRsR81vjcYW/+eX9yrHQNZw2aEzJAZ84gyv7PkY8JuRBMmKsbBLGRr0uhYLb80YenNYLDoEqjjymmMhT5eb0VwdB7OoOW8H9TjEUjwitJ3icPWA1VJ5Shz4bd2CpkbiMLWVoVoiFo9l3r2dGkbxIIksGNx6HY8Oebt3hrXxcOIfTKWyJ+BuFp/DfiTA/o3AuPrDBGwcMlo3W7RSt8YxnGcTIPZDyidNNRGPrC1+xLokooJH+4BpYSIe9gjd7JlLxMFa9/TNGkn4sdNfeNeTJCC+qGa+Mgkygm20KK5k3CBzZ/KfTsZGg9+HDN4k47q6f+O1/mQ4JO8I1pdKgecqm3aBmykAy7w2/VMKdIYcC7cvp0BU6gzrvl4q9rZwFGeEpOK36JRdDSUV1hYhJlVKaXhDnh1KfZqGFvJ+U9fWNBT65iiqbE/H57Ps9cO302HMmvF/UJOO5/3uP3g2vcenG5nXAxze4415ic26qvdoFKLf9NyUATk+vz8LThl4qjDjd6c+AzPn7QsYUpnwuqO46uajTIRIWsx+681EWOaM8rP9WdgZkacn8SYL7s/kehu+Z+He7TB3b8NszOWM9pjlZoPPsap+x9ocdB85JZd8KAfaqwxo+Q45kLXVM7sQm4O/76wHX3XkIDfS+esprg8QcBhwSTz0Aa++J5wJdPqAe6bnszYkfwDl2NIvhcGVC/PZ6jMunIv+V3EkZb1cCB7tldvmkQvrpWs8qcW50Hys0kL6mYu7hNNE6N6PSLh4r5jtxkfsKK8o4k/4CPkKqkHF8EeUU3yJjVs+Id9Rh32jySeQuoIXPr/9BNGPY/pCnZ9w2uLyTYH1eRAT/lldbJSH614gbXiTh+6j7ksSnXlov2RX2y+SD7FSvZgTJvkw5a+ct4/MB1W00Uh3JB/rY+I39+wogNr4x3pJ2wIY+L7ols4uwCqx2ILxXwVYM+lBsTxciEOJL1qjXhZC2KByJrylEHU5XF8viBXhlGFhUe/VItydLTgqkVWEC9FGA7J/i/B0eMf3Bd1ibJ9cnA0LLsafIdK15bFiqJF8k5T2leDiar49Sh4lkBn+Y7XcXoJ9+1LLoqRLsUWAN3GdSynY1no9M2goRcvy0OfrEmUINRSoOnenDMaR6j/F6ssgNOGem7ft88of6LaUc/kMVfW1Fo9Jn9E36caTubscKdFchUUe5XhwfKYyaagcGdXmKBKvgGv2o2jBkxWoi5JI7nOrwKzhz/kNaRXYlrrmaFVvBUZuJElOcH+Bw4jxPnf1L0hQLtnoafcFYt5eDpzvvuDg+PWWheYv8On+1m67qhIKP32E9JUr8ZvvikS6TSUAbd/HEZUQr4qbamyphGjQrYqg1VU43Msd26FWhcUDe/gDblXBu78roCaxCv5W6ikuA1WQTInrSBKuRrauRaCBXjUOSRVcdPWoRnxyZfzWkmr4/rUa0ZqpBpl3z5OxvTW4+1SLYL9Rg42vz7yNj69Bd5Zj35ehGtRc5he33PQVuuG9xU+Nv+LqnUSB7UFfoeVt6qRN+orXt0fkJ/hqMSe7GMZzshZk51Oz2d61uKImEdRbVwuOpWspz7nr8Ek7+u6H43X4O1+idNW7DoFi/MIhDXVw3yB39DRfPZLihr976dcj9QaPlm5gPaImgvVfddaDec1ETV+sAWLSHOvDzBrwMoKXZR3XgIdEQE/BZAMCIvTa/eQaEXnMdWTqdiOqR6rmaooaUTSsJLqbrQndFF5p/hNNcPVp4nMNbIKYH+8n6/4mVKna/ene0YwA+adZjQ7NePe6Ll2nsBnPLQIKNFe3INJnJLxUvwWb2J0FyyJakPYytANTLThSqvT2qAoJfxrCVjU+I6Ht2n/F7SQSXDMic/YKtGKP0cK/uQda0fPy88R981bwxrWcc/dsRaFB+rnyrFaEa5gmaPe0gnvh5jzHqjYUmjLWc8q2wZdlGfuvURsaueoMqtzbwDvt3fY4qQ2Um48bXUhtyP9OoWTOtSGpwKxUUrodLa/oP0j67Rho+7a9xK0dfzbuHiUntkOgK6lCp7UdJ0q6jEbn2/HO0k2qQKYDhy6lFNSe7UBOSUOI0NMOyPAkaAVndCAr+ui+M30d6H2yaeIIZycOxIy13FTuxIbwIssmi04UU4T5rPw74Xi/Qk2htBPnqDP6irRO7OM+lmst1gVBIq+yRbcLAV6Zg3b3upAXw3tRPbEL9Vd3+ql3dGHCvLnBjr0bqWymFk1K3dB/kxF22bIbntlqJVsDuyGowcfH96UbekM9jTLfu2FA/aPqtL0Hje2PfMYNezCw7eDf5x49aKr/Wnz6Yw/mZYIWtMZ7oGlAY5iJ9IL9J3d+rG4vFD9se8Lr2otB2jX7d2m98FqzPcZooBfvd5fu37uuD6qOTSf3oA83X0v/Pench7tjY+f9E/rAyfPs3veuPqyppNjc5e6HrelNddFD/WC/xzHX5dAPkcPq2bmx/bBf0rZ639EPC0nxzeWcAzjzLKiboT6AcQXTcGWHAVx4ImkZFDsA8p2Yf7g7B6Dz4sO3N1yDeJ5pnaFyaBABXeuNp28NItjNeTQnfhCfE5O1fboHMbzH6tbdtUMI/Gfm3C3NIRz5fHHy/u0h2M+bCfklD8FpU3JVdv8QONrap4f4h1E2/dpRVGcYz37EwsRlGDzbRY7Gvx9G7Dktm58jwyhUJfvrbRhB4cP9ycnHR+CnJBTM+WgEnLt+w/bDCIYt9gQ2T4wg8Iyd6qZ1o1hkcFiclBnFwet/8h4eGUXikYmMjEujkL4WUNh/bxQ5XSOnOP8bxUPHX0Py6aP44qkwerZ6FFK+6nV3h0fxJFSUN3B2FCd+ykmkCpNx0sPQpUiODLJkx+tqXTKKaC+76izIELJrqat5QEZcZsZwcTAZB3RqA1KyyFATuW/sW0cGZJvTrMfIGD6cwnVggQxB3RzavMgYzlU4kvIVxsBPNVK2OTEG0/L8YB6rMbSxvt+OfTiGxqfm+v+EjMHF0io1OXsMT2serRapH0OzkR5lJWMsSfg31cyP4bE7/uUSoeCrv3+nmjwFDUVMdtNjFFyr22RqZ0FBOn27ta0bBRqPHHPOB1EQ5NNcr/SeghsyTlp/qyh47DmfmD1IAbnHx9vwFwWrGfnPRtaNY3PkO6mLu8ax/tsY+2fNcbwT/VK87sI4+ng5xo85jsNMaMNme69xzJHn5txixoFUKynngnFczE9TNiSNQ265nbRpahx+BuERDUsrvL6DupXoBHYqPjhL2TsBdY20QL1/JzDHnRQdZjaBKffBXc13JtDt+7J3ymcC9dUxVtS4CUj7/3ZrKZyAi/IOUjhpAjY5/mJ6kxMQ0bqzbmRhAuk5QzYm6yfh4J41kic7Cdn4CJU/mMRVIzlp8fOT2Hem6pa0/SQuSb3tFvCYhKtI5rbh0EmsmaELBGROYn0ol6NU1STCD/lwhvVOQj6xLY7FnMQuLn5+2dVTEGjQ2nxUbAruvIdCtfZOQXwh8oKE9hQOcZMUhs5PIamd5/fDm1NYZNPzWHwyhU9koyzz4Ckk9qQbx6aucNlhxpWlUxApk/Srbp0CvZttKHl8CtWyuTK2s1NomrxkyMtHhXZzH/y2UcH672M/sY+KUInNvIq6VLiXU0oMLlJhuvVVn4E9FaMy4kaKT6joHzwtwgqkwnzmyga/RCoEb+/UXltARbH4YpBNPRVXh5oW4gao4OVavF/KpMLo1Z2/ectUCHAHPPMXmsYmYju37o5pmF108B1UmYYlh/haI91pXNl0+EXyhWn8/a0533djGjWT084Mt2mEfzVlDPlMoydp0TErchqhF16tNs+YxtjfmSxG6TSG3B66mzZPg50t8E7K0DTOOvbGdzCn8bRil+jI4jSozq4D9eto8KIw2IK30uCRVxF+eC8Nl3cc//RVg4acy9NWCqdo2E0RLXW5RENW34m2GDsaAm4sFKa40XBqQ6J3wCsa/opnmF4IpYEy4nh0OYkG7sP7Lbw+reiwydSxKmkwOX8mVK2NBmfVoGnLERqMiZSBm0wa1EI2+p6fp0E8RUlUiocOc++171pE6Tiy756GmTQd3+oXNzYq0eF+afzMFtBxMGAj5+lTdCg5KVy2uEhHlcYNV2MbOvbrRLnL3VnhG3R8J57Q8YPTpunpazpKtkZfWwqjQ0xZ6IF5Eh07X9kpx32goz6pu+BrGR2zb5fkSfV0eOxZ/aW4iw4/E9cQbzIdUsEWvYeYdHTp785pm6Xj+/24Yyc5GPiHb7kuVYCBnVWt5rTNDPQXLikL7mJg24GaO5uVGLjyPOcEjwYDIU1h5EFdBlpEoq+/NWSAU0uaW9mMgQoJEivPhoGt0Y9PSjgzUDynd9zenYFDHM/Wx75kIPhJdH9RAAObPPUbi8IZOMJhsz42gQENndFR+0wGFF8MWEoWMKCS8ba6uIKBH6tsVTUaGOj5VUdN7GCAygOuX4MMLOVd/yI3yYDbTda/p1gMaCUEVJ2dZYBtkddWh42Jt7cUrLesZcLtef3kgDATEhnvxV9uYUI6Knn/RmkmVD57WQTJMfEwVbTptwoTnp+EU44eZsImVFHy4b9MbBAQs4k6zcTnKMuUVGMm/pwNFo6+zER02o7uR9ZM6Fo7SB53YGJtUMG2xbtMeD+XZ496yMT81NgvmRdMbKHW7o72ZaLp3Ie65SAmlMYMV5+OYELNSZ3TM44JZYuOpfRUJh7/uba5NJuJ8RZez6J8JjL1S28llDHBoarF9qCaiQ8q++0ONzLR+oy3m9nGxJUgDmffXiZ8m5Sui44wYXnqzrj/BBMDufc3/KIzMbucJ3XiBxM1LvcP+s6u7B1U61myxMS3mWf7+jkI9Lnq3phaS4Cq+siIIkjAPPnclhZRAjJ5FYwUcQK/F92+OUkRqJtdOCO7m0DgQW+Vtr0EuMoka28oE9BU8FL8pkZgXP9I+HVNAhv1gmVJOgTuyb7kkTlBwES718JRn8DaD4IXUs8SqO53XN9uQuDon5lSxuUVLs7swexVAlPOkrd/XScwF36/gWK/4uNvmFblTGDrxysqb+8TGFnHG2jiTkCO3W183VMC64R8zua9WJmP6WM/400goqZMdMCPgK34sY8XgwgsR2gtNIQQoDkriChEElgijLZ7xhAYtE7UaU0gELe1J3pdKgFfHRNjzQwCXmP63ldzCFiPRJo9+EQgda7v28vCFd/Wy9dflhJQIPaTXSsIyLf/88SimkADZ7mlRh0Bsf0KaXxNBDbj9r02EgHhy+qTPh0E4jW/bVbvIaClULZvsJ+AlOazU3eHCTjZ9nqyjRGIbNq06sUEgZq2C93zVAJ7bCxkbBkE9JXKBJtYBNhJH7Olf67o8+luv/ObgM/OfR4FcwSw9ifxfYHAtpiK51KrWHBZ85/ZidUs/EdZ/9aGkwWH7NLjD3hYCJ4vDHrBx0JJzS+/lwIs3K+VMH4kzIJ/XTS7vQgL+3/G5BiKsVAuy+cmv4UFfnO/e+wSLNjS/MqbJVlooqs5/beDhbCoB7F6u1gIzF9lu7CbBcHag2NJe1i4FWYqfUyehVd+546NKrJwjGP0qrMyC+qFLv5/97PAt8uU7K7GAovlffvXQRYatFtMrh9mQUXjY3obWFA09Hmsos2CT54dLeAoC/1VShzjuixstHlOVjzBQsHjhtD7J1k4ZaSukXd6xed7PYVxhoXcBM2ErUYs/A8Whn5dwBIAAA=="},"shape":[600],"dtype":"float64","order":"little"}]]}}},"view":{"type":"object","name":"CDSView","id":"p1079","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1080"}}},"glyph":{"type":"object","name":"Line","id":"p1075","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#2B748E","line_width":2.6}},"nonselection_glyph":{"type":"object","name":"Line","id":"p1076","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#2B748E","line_alpha":0.1,"line_width":2.6}},"muted_glyph":{"type":"object","name":"Line","id":"p1077","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#2B748E","line_alpha":0.2,"line_width":2.6}}}},{"type":"object","name":"GlyphRenderer","id":"p1088","attributes":{"data_source":{"type":"object","name":"ColumnDataSource","id":"p1082","attributes":{"selected":{"type":"object","name":"Selection","id":"p1083","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1084"},"data":{"type":"map","entries":[["x",{"type":"ndarray","array":{"type":"bytes","data":"H4sIAAEAAAAA/zVYe2DOZRt+Px9akpCQkHOSc86HriUJSUvSkrQkUUIicgilKKoVoYglsUQtxCpci9EwejFjDK+dzDbz7nzevud57vvzh8O87+/3PPd93dfh9njcr6jiLbvO/JhSRf+w13588lgV07Mbzc3ZWUXfquPD135VxfN9FjYbOKeK/17u4k96sYr/fJB0aHlgFfnA1193blvFfbFPTI67vYq/vl3ab152Jbc13Hlni7OV3PTXy74j+yq5JqT+7jc3VPLzGkc+qrekkh9vnxO877VKvv90h4deGl7Jd/MTK6p1qeS0b77wht9dyUmPDPphZHEFxyfnzzb/xTHLtw399u8KPtVp7H2BWyv4+Jna2amfVnDgHEatmF7Bnk1nruo2uoKd/m4z6XyfCrY1vy9sVsFmd3x6R+tqFWwQMeBKTFo5az93K2LaiXJWL/3+wwYR5SzfOHrMn6vLmf/YbQ+GvFfOrPQ/ymqML2fKZ1NP/TyonInd7//+mQfKGXf+9DtFd5QzdsHSId/5y3i4Ze97HztXxr+O3sg0X6G53MHPN5bx57pPf9njwzL+8Pt/Jl58vYzrx+7ptXhEGb+qmnR7u25l/HTLvYkn7injB8Nif3m7tJTzst9f0uhqKe3pDxwu5Rt9Utq9Gl7KCZfXlAR8Vsqx8kGOesCetJTD7df6lfLRt18ZHHZ/Kfs2bNBoSPVSdnMHKaG5xP4vT5awZY2OX/TeVcJ7t195xTyK7ljzSxjgLlpCzzeFCacGl7B44E87Zj1YQtPURU3qlDB9WZ1RUbnF9HW0hSzmBXvt/cX0vtvu+G9hxYy5L2HD8x8VM8pWfUox973mGsVfa+U0GPZwMbf9+sP17EbF3OTKWsS15hb9rhXx840WCEX8+LFp3T/eXsT301uYExbx3c/O2srS/nROcBFd2wYWcfyCzKcPtyzimJYbW02pWcSRR4MK6mQWcsib/43Z828hH6m799uxewrZ6/fJb5m70MFiYSHbVp2sP2JCIZttWZyaM6SQ9wx7OHLtQ4W0aBlYt5DVV617KTm/gOW9n+z6SUIBHewOFjBrScS5uM0FtNWft6yApknzW0wtYNyMmKfMqxlr/9mzgNF/dsqr16SA+x2s87lHHsgdPz1uTpZPUwtT0Xyuz9tuSp5P97rZ+Vwx0B44n25skM95y2a/mNY6nzM7tu+8MiCfb5y+6Ol+M48TXEHy6K6zN4+mGXNbr8/jcDuWi/I4qFZw8+kT89jPFTyP3Q1qzJHYoWS6GbE8SrlyacDZf8elXNaThvJ224UtuTQv2f3dJ7ksjt9kWpFL//xRL9wYlUvXjt65tLfq0TSXF96wJ8ql9y5XSZqiGwTkMOoFC8gcRppPzVyVwwjb7rk5DDfTeWBcDsNujlj96qM5NJxhAJ9DA8K+5qhcljjRjJ6fiyzaz/rp4BTp57QTbqA4aUaX57OW+Dn+nqQHv5rkp0VP7yf9HPnyE5ZyaDBuBtbPBe64tzjVjttftzgurXWPjKW3ONx0s+fIWzTkYwbqFjtEP9vmpC+bTer8cazx9my6sr2TTTf2A7KZYae2RjYv9rRvuMnji3abTt5kpPn0lVduMvxuy0w3Ke3LoqOfA1k0pIg7l2Vxcv+7U4ODshj80Vwz0lkc6ogxk71c4TPpYDQ7k44GH8lkzaKZR5oHZLIw0FY2g2kGjXu/zWB83GbDFBk82jzgxac6ZXCPg/MNOjrmDdoqdP3kBm2R5o+6wVmh1db8c98NGnIxJ0jnaHPL8b+kc7AboHR2t2gPTGerahXeR2ul0wyDQdR12m8lbLjO3Kt2sK/TVnlGl+s848Y7jYcOjPvttkNp3HXboTHPrkhjmCG9jaPTGLp+5fcZzdJop6nX9VS6NkWk0tHMe6kMckSYysA7bYdT2dX+97kUNresuTGFdTL3GmVIoYNLtxQK3SXTkPH+DtHJPOUOmMyD9vVjkvnLVluxZG5wwEziSgvbXUl0KJmfRMvKAx5P4rjGFtlJdNc7f40DdlwyTH2NHV2Br7GJHZ+Hr7GWo38fS8/aQfYxo1n8W9984aMrX7CPFoVdW/k4y5Yh/ipHr4ivGTrlKns42r/Cuo7Yr9Bvbv9cyyv02jHdfZlGRMyRLtO1IyGRM5zAJTLIydkldj1YsO1a6CXWdgN8iVmWfvZeZOx7zxkoXeQOC4tLCbTVjX8rgVN7WiJL4IhjFogX2NE8ZX3bCwywtBp5nulLrxolPs8YC8/L8Qy3rD09noZMDCTiacTXSPE5DnV/Ocf2TtjiWD10yqabI+KYYsfk6llG2/GeeZZbDJvsrn6W7rFrz3DiO3byznCwlcH9p9nGEdhpVjfkuO2alz5LW7O8jDLoiK/pZaCll+7/0mNhFnyKUVZ2F56k0bqpr2+OZaAD6gl6NrRdF511nJGWpeod51xL672PsY8dn3ExLHYD+w8jJ9y1dOfWo7TTlHjiCPs42ohmsVGpvg2jGWHltP9hzjC3XhNyiF2fsU7ib/otO26PYogTRNLnAHuAIY7I9tNn7cqiPxlk1O2PY5H02sc12McgSwfjf6fXoMEbvptBjkZ/o9cJRQQtKrx37WTY83bAf2KYlcu+W7nYup2Tm+kxojTzyEZ6nOCtpcdep/GX9DiiWkqPHeexb/7/T+jPoZ+Dfg/6HOhzESXvQZS8F1FyDvjkXAiRc8In50aI3AM+uRdC5J7Qe0PrAK0LtE7QukHrCK0rtM7wXHR1R6D0AYulL4iSPsHT3/UNgdJHLJa+Ikr6DO07FAdQXEBxAsUNFEdQXEFxhgDBHdoLDjFUcInJglMsF9wiXHCMGME10gXnCBDcQ+cAOhfQOYHODXSOoHMFnTPo3KGrzCGCZC4xQ+YUoTK3iJA5hlfmGn6Zc9SVuYfyAJQXoDwB5Q0oj0B5BcozUN5BH+EhBAsvYa7wFGKFt5AoPIYs4TWUC8+htvAemgoPQnkRypNQ3oTyKJRXoTwL5V0oDyNCeBlRwtPwCm/DJzwOv7OxyfA4I56CusL7aCE6ANUFqE5AdQOqI1BdgeoMVHegOoQ9okuIFp1CnOgWUkTHkC+6huqic2gguoc2ooPoIboI1UmobkJ11JKo1VWozkJ1F6rDUF1GjOg0LohuI110HMWi6wgQnUdj0X20Fx+APuILoD4B6hugPgLqK6A+A+o7oD4E6ksQKz4FieJbkCU+BuXia1BbfA6aiu9BR/FBGCC+CCPEJ0F9E9RHQX0V1GdBfRfUh0F9GdSnYYb4NswVH4fF4uuwXHweQsX3YZ34QKgvhPpEqG+E+kior4T6TKjvhPpQqC+F+lTkim9FqfhYVBNfi1ric1FffC+aiA9GKxcjcqE+GeqboT4a6quhPhvqu6E+HOrLoT4d6tuxQHw8loqvx0rx+VgtDcQGyQHYIrkAmhOguQGaI6C5ApozoLkDmkOguQSaU6C5BZWSY1BTcg3qSM5BQ8k9aC45CO0kF6Gz5CRoboLmKGiuguYsaO6C5jBoLoPmNGhug+Y4LJdch1DJeVgnuQ9hkgMRLrkQEZITESm5EZojobkSmjOhuROaQ6G5FJpTobkVmmNRS3It6kvORRPJvWglORgdJBeju+Rk9JPcjEGSo6G5GpqzobkbmsOhuRya06G5HZrjobkeqyXnY4PkfmyRPQB2yF4Ae2RPgP2yN0C07BGgewXongG6d4DuIaB7CeieArq3gO4xoHsNNJQ9B5rL3gPtZA+CzrIXQS/Zk+AR2ZtgiOxRMFL2KtA9C3TvAt3DQPcy0D0NdG8D3eNA9zrQPQ/CZO+DcNkDIUL2QoiUPRGiZG+EGNkjwSt7JVyQPRN07wTdQ0H3UtA9FWRt5Qn8H3eWIf/AEgAA"},"shape":[600],"dtype":"float64","order":"little"}],["y",{"type":"ndarray","array":{"type":"bytes","data":"H4sIAAEAAAAA/yXSdzxWbRzHcRUlO6tQ2dJQlBn39bMqDUTKyEhmkUIU0lCSaJkZDUJIZqXs9UgisjJSNhn3QYSs5zqnv96v71nXH+eTESe42Ychl+Y8kZpoNvueFt3F22cslE/bGvfihPi5Qlp6htWRooliWtx51aGSE6W0FJV1/KENZbQro9dv97tU0LrUl3Ze31lJG2+aHXfgraKlF9jV94pX06o3+MT6naqhMSMbXtEvX2iRNzQOXn5fT1OOGJEolmqgvQifemjxppG27rmn8ErbZlpeymmxCOVvNMkD7tmaCm20IWaQ8LTooInmtUz9etNJ6xfli20S7qIp+2+pZr3RTdsy3n72CkMvrflkTljB8z7aNc98phjbAZqgUOiCuP4QbStrpk+SzTBNX3Vw+UPiKO0x58Y32gIEzemaHb9G9zjtdnPASaZjk7QzD3tenOv7TWt/o8K26/E0LbD1IsNe1xlaB2OzXrHbHC2//ESqwvN52lhyeaLV4iJtl3/vhq0HGFDD8JOmwlcr0KobRH7zzlWIxVxPnr2REeW0DDapxKxGnTv9Dp+/y4xKnUPTGBNYEB9HnlL0CBta0xscLWzHiSradCzPsaxD7HdZH+Ze40bW7saTk7y86Bf7A7WOGj6k9aSHyTF1PbrBNdj3NVMAvVZ0XI7sFUIaX7h4b+/fjKJStt3qdBFBZY5zK1tbRNFbD0nB5xbiqCXL614rkyTqaJzrtGiQQomLOXsPV0ojr421nct929DHYZafErtl0Cb9X/45WbtQ7y1unqU9cqjJ/NCnlrTdaMA2Ioq+Tx41HeQeWLNSEQlcyPYT7VNCHXOm3T6ECurqpmU83aKGTgodGLN8iBAz02LlZIQ6itggbXhypSZCz6cjvwdrIfkdbNXCKvsQ20sRW2aWA4h/ukn6w8qDqP9EW1CU1GH0cjkl5KSXLvq+kVNx5bI+Mh/P2jJlYYB8US2HbbshWvK9kmt9yQgdSGlE75ROoG/9Mh8dNpugALbuzKKdZsg+xnDfYWdzNJuo9GOk3RKJdtd9OlN8Cn1u3lqks/M0qrtxe39wgQ1ScuHk7nO2Qxdp0sl7NR3QI99LrnlwBiVlqe1qdHRC+2pthqH0HHr54FD2bakLKCNYNujoHVcU4K9rc4rZHVXILdvoplxER6LSPAbPeSKGrd8E1UwvI76Vd9/GuHijM4801J9lXUEaXTtu7ZS6hnZDqZPW0HXESa9UijPzQ7m+wVszR28iqTgPX6k4f5RnH/dt2jsA3ZxVMT3qG4he2W+WTH8VhM5mnbNoXHsfmdyQNBzRfYiKPt/l4815hFzOiKXXKIYin7lHEdkdYajcqj8kNiECiUZ4RD4Le4wcS2WFeDOiUeJ79x8xM7Go3M1FVtrtGZJL/G15fV0c8hd64G0XEI8u6XxiOrgpAU0VlLUNNieixwqiVXvfvERx9iUFPvkpyOzWZ/3txCs01dD5lf9YOkqM0XXJ8MhEqipBKvAjCzH7brVKsctBS2NPPzzieIvOt+rNNne8Q38enwwJqX+PXtv/kJQez0MGg9VHNFAhGp9/MNFaVIz02uQiRVRLUVJzkHX4mzJ0+s72yBN6FWhrmkTBFtZKpBTe+4OZ/hFVRutWqv79hOY/mDw6uKcGGQbXfWV+8gVdjbXjb3hSj/72ba2sX9uAWrt0ZqbCGlFrG9P4kEYzuv6jyvgRzzf0OOi7Ajd7G1JqD7FokutAvok7DIL8O9Ex5ZaGTau7kFGy7pyIXTeKFPy+Q6anB2n8/GjadK0PTYYE5XapD6ADqTVRptJDaHhTdeodlWFEMOo8Xr40iljky9SeDtKR/tryl0cqxxHPpqeNJQqTyPloDntSxW802y2/9rXHNBIWu2BXengGxVlses9xaA5dTGRavfriPNo375RjU7uIgl/unReUYQBJnuMjrg9WgH5E3WlBrlWQ6zHqkpbFCGIbOUZmPVeDpeJBcw4bZvAQVlNCXizgJ0KLEitkA8WAd1cTdnMC+0C6+gqCC5ZfjPGOWnODM/3DeMgUD4jcflWVlsIH4at3Lm+/tR4yMmIvawUIwCph/c3O74TgmcSa9rN8m2EH51LgdiMRyPlP72f9B1FIF2S9yoHEweKCnoJfrwRMw3fuVa+lIJN7cF/SE2ngWX/EueL9NmCV23dseYUMmKw1UtDx3QWRXgvgvl4OYpuNIorv7QazJ7nmHBLyEHD7yKBQpwL4ea452pWvBEuNS8Fh5SogxjM5lTujClWKCqr85ghWfPT+Y++tDhIsuoXbezXAs/S8c+VZLRASF+5t5dkH2ly81qh7P8QmWZ7matGBX3usel9NH4JX3BZ8uVq64OnXE7ylQh+aQjq8EmgGIOB0R6LnnSHki6d3HjtqBDWGw6G5bCegPPxG0mrCGDwK+H96/zWFghumjaG7zeH9xYx3G2Mswfm9rKFA7CnQV8t+K7nmNKyJznbb/MgGjhonu8ar2YH7JG/nHTYHCNOqKuxfdQY8p2YmOaScQOfEueA4z3OwOUjvXsDceXiKxDivmbpCoP+c4JdGN/gZeeHnDpeL8GEzY9zh7Z7QEv+Cg4fjMhhXuyUlC3iDtv0DB3eDK9BG7GwS+XAVzDxDxI+nXofMrHJ9GV4/kOowEjsbexMyT64oC9rnD/rlXQna3AHwaWRAwJcpEMQ1mpdOiAVBgs26ODnne6At2Ff5euABTAiU0lsPPYJr5y6K5xaFwIjb9S5ZgzBQSaunpzNGQILXhZL/OiKh9T+jNKW2KFhp0bh5ZjkGPr9RfCt97ClsOesfy+30HNayf39Hq4yDw2rW9bNqL0DWS8dD62sC5LVfZdrrnwTV+9b1851Mhg+2HtYyx1Nhz8Ki2m+PNHCoPFw/XZwOSUM8rfToTPAV9ulRGMuC0vwZK1WTHAjf1Tjq3vkG0tyjT5+8+g70PMas3dTeQ1MBq42qQB44xuRrK/EXwNMrTYbd8kVw9ADjqeOqJfCnuqbK26UU3lYWXwt4UwZHywWTeTkqoL7p/NNUn/9gUdXycuFiJWjHtlmmhFXBuMiDxnT1ahB/mjlNX1EDZSVrfzi01kJPRaDMRHkd5NPZPTf21ANPWaHTqdGvsGqDnKgLQyN4eX83+LapCRplIr34tJvhRtCBP4FuLeDwvCTpavI3qC0h2Lb0t0KOK+903JZ2qD3f/HTepQN05dqtZPO+w02ac/Yu5h9gfrv1yU/jn9BQwCPHZdQFqPuT1NB8F5gOctcPxneDgE64rJ9OD0h99GDaNdoDUcJ3vJzv9UJDxrO0ku19sG/vd8b8yj7YeEZea9qiHz6VrTPmnegHxosORNW1AVjYLFpdt3YQXjLPVUzcH4Sa+0/erOIcgqYHvswDgUNwqTVU89byEFQobTzcdP4X0B8fTG5u/wUMrkNxD9WHoYbF1oQvbhh26M81ey8MwzEOLpEfhiOgVVuja50wArvoSy+3TYyAy4XEZhuVUQgPdlyv4jsKjneehrYWjIKJQVmO6+wo3MtKC5WXHQPOaDs/A9sxuMu8Imw2bAy4YjO1TMvGYNRvUSJ+dAyitebjGXjpoCYiW/NcmQ5vU4a2vjajw4uR9Dg9bzoYcSdbpUXS4dzlV1Uj2XSgjc65ydbQQRZCJGJ76WCZotSiN0cHs0Mn8uzZCRCwW3uAQYSABtMnmopyBMhfWfgqqkHAM+/AO+36BJxdmL/saUFAmHnffpazBMjOBCqneBAQeb5Px+46Aa55Pir6dwlYSGVM8AwlwMD2MetwDAG/rziPvXtBwKO/M/W/UvH7aZ7G97MI2DtRcigjl4At71xfnSgkQCTeuOheGT63W1bA4iMBToI56TWfCch739PWW0dA+UyhY0ojAfWbNZLEvxEwzVZtY9JOwM387hW6nQR4ZrldXt1FQEqIu2VgDwGszOwTbX0EFChpCi4MEMAxInbp9xABycb3LcqHCbDXvdftMoq/q3uofXaMAJ261wM2BAH7kxNcs8cJgGP87wYmCHhx8WHZyt8EKBSnXWGeIqCFRTFhDktIr2v5Nk1Adf7Y/YQ/BChxl8tYzxBw6uPGPxyzBOQn7+XOwMpLrwnXnCPA2HHQpxo7n+eQeeAvAcrKH9fmY7n6bPUk5wkoSuNSCMDqWWvad2PNopR95RfwdesVa29gJU+53PuI5V6evsm8SIAL/01zbSzbGfmrPlg7BXaddOwFhsfrOrHFqVe2My8R8PozS9cuLFL94G6EfRgXI+mJVWv4siscu+HV949ZWD3hDcI1WB7WydN9WJFTBwr/Yit3pRhxLhMQwP7siBhWSLXvvz3Yh6uf/9DCJuRFZRtiZb0dT57Chpo8G3fGBv6dDriMlWJyUbiJZYvxYL2H5RRfIxSB7bhf6f4UW3Vso0QSVl9bT+U1tuxiRHEO9pWhctkHLD9P2OFi7NYoZbsK7PTv3+s/YSsf9DjVYkUUfB2/YiPHEnmbsVF9OZ6t2AqTq2EdWJ9Me/cfWFRuvaEb+96B4U4vVuH4pcp+7B77pG+DWA8d4eJfWOWk7ddHsBKNDZvGsNmrf0bTsVtleuYI7FnjvbQJrNJXPodJLHe/p9dvrJCRkucUdimA1Woae/KWv8If7HDs+nnSL1P8WTPY4M+FJrPY1BiZ36TOHK435rA6Vb6Mf7GzXIq+pApmZ0dJY1+2G85j5duVskjruiWYF7AOLUompFrzf+NI374e6yedUfeXWMTWVslYkgo2HQolrdugXEGqyH1ogrQ35IvgEnbVYXYN0j3aOrakvscKbpHa+sXFk744v72I1Dg58Bvpur+DdNIrRS6My1g2Ua0NpFJWAdtItVedVCU9I9RxiPSIk5gp6f3og/akx60c3EgvJD70JQ3Kbg0gdTpg8Ig0poglmjRemjeelMXeJ4XUDHQzSUV8wt+RGkxaFpDOxOeUkl4rja0kDa8S+0xaHaBbR8onJ9FIqmT3uoVUBYg20mFL4jtpzGT2T1L+EtRD+nVHZB/p/X0lA6TG/xUOkdKFQodJ89y1Rkk1c7+OkSpFqBOk6+6GjJNatn+aIH35fGiSdFPN+G/SLom+KVJx/Ypp0rnakD+ko0wGM6RuwDBLujEljvKJqeIc6VuDYspBX9pfUvb3WZQdVRvnSbefuUapr9VOKTYls0C6jc2H8hZXGWW9z6pFUsZioNyheYlSyi6VEl1vo6zqY1wize7cQbky04DySIw7pVh7COXl+AzKJclqyushPZTS3HOU9Z/Zl0nVe0UoL3jtpixJ06SUijKgdDSxotRe6UwZkXaJ0s/Jj/KxQTAl/Xw4pWbLE8oNoYmUNTlplKnaOZTMBh8ozXuLKNWZKijDiqso/QRrKYM3faWMr2uilFBqpfxj2kF5lfaDsmCgi3K/WS+lWWw/5ffMQUrbmF+U2lYjlLeWcR7Yqat0yh1dOA9ssCTOgnzOAGeBtbTDWWDf2OAssEu6OAtsgyTOAvt89J8VL3AeeF8+grPAdg3+k/kSzgLvgb//PHURZ4H3/r5/mh/GOeB9NPWfVStwBnibG/4zIvafU13/HBfGvx/f/x/3s3EOwBIAAA=="},"shape":[600],"dtype":"float64","order":"little"}]]}}},"view":{"type":"object","name":"CDSView","id":"p1089","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1090"}}},"glyph":{"type":"object","name":"Line","id":"p1085","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#24AA82","line_width":2.6}},"nonselection_glyph":{"type":"object","name":"Line","id":"p1086","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#24AA82","line_alpha":0.1,"line_width":2.6}},"muted_glyph":{"type":"object","name":"Line","id":"p1087","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#24AA82","line_alpha":0.2,"line_width":2.6}}}},{"type":"object","name":"GlyphRenderer","id":"p1098","attributes":{"data_source":{"type":"object","name":"ColumnDataSource","id":"p1092","attributes":{"selected":{"type":"object","name":"Selection","id":"p1093","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1094"},"data":{"type":"map","entries":[["x",{"type":"ndarray","array":{"type":"bytes","data":"H4sIAAEAAAAA/zVYe2DOZRt+Px9akpCQkHOSc86HriUJSUvSkrQkUUIicgilKKoVoYglsUQtxCpci9EwejFjDK+dzDbz7nzevud57vvzh8O87+/3PPd93dfh9njcr6jiLbvO/JhSRf+w13588lgV07Mbzc3ZWUXfquPD135VxfN9FjYbOKeK/17u4k96sYr/fJB0aHlgFfnA1193blvFfbFPTI67vYq/vl3ab152Jbc13Hlni7OV3PTXy74j+yq5JqT+7jc3VPLzGkc+qrekkh9vnxO877VKvv90h4deGl7Jd/MTK6p1qeS0b77wht9dyUmPDPphZHEFxyfnzzb/xTHLtw399u8KPtVp7H2BWyv4+Jna2amfVnDgHEatmF7Bnk1nruo2uoKd/m4z6XyfCrY1vy9sVsFmd3x6R+tqFWwQMeBKTFo5az93K2LaiXJWL/3+wwYR5SzfOHrMn6vLmf/YbQ+GvFfOrPQ/ymqML2fKZ1NP/TyonInd7//+mQfKGXf+9DtFd5QzdsHSId/5y3i4Ze97HztXxr+O3sg0X6G53MHPN5bx57pPf9njwzL+8Pt/Jl58vYzrx+7ptXhEGb+qmnR7u25l/HTLvYkn7injB8Nif3m7tJTzst9f0uhqKe3pDxwu5Rt9Utq9Gl7KCZfXlAR8Vsqx8kGOesCetJTD7df6lfLRt18ZHHZ/Kfs2bNBoSPVSdnMHKaG5xP4vT5awZY2OX/TeVcJ7t195xTyK7ljzSxjgLlpCzzeFCacGl7B44E87Zj1YQtPURU3qlDB9WZ1RUbnF9HW0hSzmBXvt/cX0vtvu+G9hxYy5L2HD8x8VM8pWfUox973mGsVfa+U0GPZwMbf9+sP17EbF3OTKWsS15hb9rhXx840WCEX8+LFp3T/eXsT301uYExbx3c/O2srS/nROcBFd2wYWcfyCzKcPtyzimJYbW02pWcSRR4MK6mQWcsib/43Z828hH6m799uxewrZ6/fJb5m70MFiYSHbVp2sP2JCIZttWZyaM6SQ9wx7OHLtQ4W0aBlYt5DVV617KTm/gOW9n+z6SUIBHewOFjBrScS5uM0FtNWft6yApknzW0wtYNyMmKfMqxlr/9mzgNF/dsqr16SA+x2s87lHHsgdPz1uTpZPUwtT0Xyuz9tuSp5P97rZ+Vwx0B44n25skM95y2a/mNY6nzM7tu+8MiCfb5y+6Ol+M48TXEHy6K6zN4+mGXNbr8/jcDuWi/I4qFZw8+kT89jPFTyP3Q1qzJHYoWS6GbE8SrlyacDZf8elXNaThvJ224UtuTQv2f3dJ7ksjt9kWpFL//xRL9wYlUvXjt65tLfq0TSXF96wJ8ql9y5XSZqiGwTkMOoFC8gcRppPzVyVwwjb7rk5DDfTeWBcDsNujlj96qM5NJxhAJ9DA8K+5qhcljjRjJ6fiyzaz/rp4BTp57QTbqA4aUaX57OW+Dn+nqQHv5rkp0VP7yf9HPnyE5ZyaDBuBtbPBe64tzjVjttftzgurXWPjKW3ONx0s+fIWzTkYwbqFjtEP9vmpC+bTer8cazx9my6sr2TTTf2A7KZYae2RjYv9rRvuMnji3abTt5kpPn0lVduMvxuy0w3Ke3LoqOfA1k0pIg7l2Vxcv+7U4ODshj80Vwz0lkc6ogxk71c4TPpYDQ7k44GH8lkzaKZR5oHZLIw0FY2g2kGjXu/zWB83GbDFBk82jzgxac6ZXCPg/MNOjrmDdoqdP3kBm2R5o+6wVmh1db8c98NGnIxJ0jnaHPL8b+kc7AboHR2t2gPTGerahXeR2ul0wyDQdR12m8lbLjO3Kt2sK/TVnlGl+s848Y7jYcOjPvttkNp3HXboTHPrkhjmCG9jaPTGLp+5fcZzdJop6nX9VS6NkWk0tHMe6kMckSYysA7bYdT2dX+97kUNresuTGFdTL3GmVIoYNLtxQK3SXTkPH+DtHJPOUOmMyD9vVjkvnLVluxZG5wwEziSgvbXUl0KJmfRMvKAx5P4rjGFtlJdNc7f40DdlwyTH2NHV2Br7GJHZ+Hr7GWo38fS8/aQfYxo1n8W9984aMrX7CPFoVdW/k4y5Yh/ipHr4ivGTrlKns42r/Cuo7Yr9Bvbv9cyyv02jHdfZlGRMyRLtO1IyGRM5zAJTLIydkldj1YsO1a6CXWdgN8iVmWfvZeZOx7zxkoXeQOC4tLCbTVjX8rgVN7WiJL4IhjFogX2NE8ZX3bCwywtBp5nulLrxolPs8YC8/L8Qy3rD09noZMDCTiacTXSPE5DnV/Ocf2TtjiWD10yqabI+KYYsfk6llG2/GeeZZbDJvsrn6W7rFrz3DiO3byznCwlcH9p9nGEdhpVjfkuO2alz5LW7O8jDLoiK/pZaCll+7/0mNhFnyKUVZ2F56k0bqpr2+OZaAD6gl6NrRdF511nJGWpeod51xL672PsY8dn3ExLHYD+w8jJ9y1dOfWo7TTlHjiCPs42ohmsVGpvg2jGWHltP9hzjC3XhNyiF2fsU7ib/otO26PYogTRNLnAHuAIY7I9tNn7cqiPxlk1O2PY5H02sc12McgSwfjf6fXoMEbvptBjkZ/o9cJRQQtKrx37WTY83bAf2KYlcu+W7nYup2Tm+kxojTzyEZ6nOCtpcdep/GX9DiiWkqPHeexb/7/T+jPoZ+Dfg/6HOhzESXvQZS8F1FyDvjkXAiRc8In50aI3AM+uRdC5J7Qe0PrAK0LtE7QukHrCK0rtM7wXHR1R6D0AYulL4iSPsHT3/UNgdJHLJa+Ikr6DO07FAdQXEBxAsUNFEdQXEFxhgDBHdoLDjFUcInJglMsF9wiXHCMGME10gXnCBDcQ+cAOhfQOYHODXSOoHMFnTPo3KGrzCGCZC4xQ+YUoTK3iJA5hlfmGn6Zc9SVuYfyAJQXoDwB5Q0oj0B5BcozUN5BH+EhBAsvYa7wFGKFt5AoPIYs4TWUC8+htvAemgoPQnkRypNQ3oTyKJRXoTwL5V0oDyNCeBlRwtPwCm/DJzwOv7OxyfA4I56CusL7aCE6ANUFqE5AdQOqI1BdgeoMVHegOoQ9okuIFp1CnOgWUkTHkC+6huqic2gguoc2ooPoIboI1UmobkJ11JKo1VWozkJ1F6rDUF1GjOg0LohuI110HMWi6wgQnUdj0X20Fx+APuILoD4B6hugPgLqK6A+A+o7oD4E6ksQKz4FieJbkCU+BuXia1BbfA6aiu9BR/FBGCC+CCPEJ0F9E9RHQX0V1GdBfRfUh0F9GdSnYYb4NswVH4fF4uuwXHweQsX3YZ34QKgvhPpEqG+E+kior4T6TKjvhPpQqC+F+lTkim9FqfhYVBNfi1ric1FffC+aiA9GKxcjcqE+GeqboT4a6quhPhvqu6E+HOrLoT4d6tuxQHw8loqvx0rx+VgtDcQGyQHYIrkAmhOguQGaI6C5ApozoLkDmkOguQSaU6C5BZWSY1BTcg3qSM5BQ8k9aC45CO0kF6Gz5CRoboLmKGiuguYsaO6C5jBoLoPmNGhug+Y4LJdch1DJeVgnuQ9hkgMRLrkQEZITESm5EZojobkSmjOhuROaQ6G5FJpTobkVmmNRS3It6kvORRPJvWglORgdJBeju+Rk9JPcjEGSo6G5GpqzobkbmsOhuRya06G5HZrjobkeqyXnY4PkfmyRPQB2yF4Ae2RPgP2yN0C07BGgewXongG6d4DuIaB7CeieArq3gO4xoHsNNJQ9B5rL3gPtZA+CzrIXQS/Zk+AR2ZtgiOxRMFL2KtA9C3TvAt3DQPcy0D0NdG8D3eNA9zrQPQ/CZO+DcNkDIUL2QoiUPRGiZG+EGNkjwSt7JVyQPRN07wTdQ0H3UtA9FWRt5Qn8H3eWIf/AEgAA"},"shape":[600],"dtype":"float64","order":"little"}],["y",{"type":"ndarray","array":{"type":"bytes","data":"H4sIAAEAAAAA/+3TaThV69/AcYRUh0MhUwglkUbEGm5DkSEzmTJlyJESJUOEJPNQisw0KYRyUKmM6ZhyjAk7c2y2rZ1xK/732rx6rufF8+55899vPtd932uta/2+114st0s/8/i6Io7//JhurXJH0q96cQ6e90SM0oPjK1y9kZj6Y00xNB9E/GGvNVkiAOm9aXsjbFswwiuqr2W8HIqouXY6rV0KRwyiOrZHREQhQbYxUr728YiYvTevfewdJOv60huL2XvITnlH4aQfDxBy5o/Z+cJM5PKuPOZOyVzE5ga3XoT3Y6S9OnWWfvYZctmuV999rRB5L87pll1ZglykeM8FLpcijn2Z15+1VCBvtoQWVJu+Q5QCz9PKz1Qj6Vv0pmVC65DLhVL8saqfEJ6qhKXhwmakqs09kXO+DSnwi6y/ztmJHNbhDLpb1oO8ISlmvd7aj/BYsIYlJQ0itNAxN9e0EcQql2Pg+cHvyN9lrrcPuU8hhamlhqqKs4hc5oxAn+xPRGZWs7o6fwGRkxXlPvqZjvAuzkRuKV1DBKXFXf55z4ImqymJNOxnR5nbSzgLFbeiLGSL7/lrXOjyCKuSlOl2dKTHWPV5AR/6Vg1piMoRRJMzBV0NDUTRrbJO1QNTu1El3nmS1eE9KF1h81zFnzKo33ETZDD3AKrs6hc5ceswapIH2P+pOIbyHBd4LRVyHE2PH915ko6imTUK9koSaqhmw1axMfUTaNiVsidv57VQP77bB93M9NDaN9EO76oMUZM/h8hZn03QEE/OMnePM2iX1O9DSqXWaLdbHc3DwR71pLCHkm3PoWyGPZqSEy5o5xnDE6n87qitNqkKZfVE7ZVijcpmvVBmO75d/zr7oHIJYhxB1/3RKD9yIGJ8A3WuvCg4HxaKnk7osnUav4VeN/oV1zoWiY6auWpvzY5Dh2IryCnCd9B9Bax3mt3uobIiPTa+xg9QI0pKb+uPDLSsvnJPUmkOKjOil7pMfYR2XfJ3Ol+Th2qrrzzx0ipE90Yb+PYalKBsIUzlfb6lqLYUL1uMQgU60KL4/Ft2JXpnF1e/NaUKfejLHXKOtQ49Vd+8+d/8BnSHrpYl02oT2pFflMQV24Y2hHNNhtzpQLNyL2Ts2dODTpa9fPTBtg9lrbaTH5MfRMWfqyjskxxB5bW4bHxzxtFGmbY5jloy+krsxNKTx1S0qChtTauMhjJvJaEV4guojLyo9Kf9dDS1KUgu7ccqyn5X4liHLgt2xmP8QWcuG3YBz4uevLcFc9p/rSRejQu7c+0LV+UID9bNNin7TpoPq+AOGLZkFcSm8Lxaj3u7sDe/+IXmA3djidkaHI5FUtgxe2lLdp99WMkQ124xihzG/rJR7rLQYeyg41DmK+VjmNU344h+shLGsniKPe4Uir3z+nBe8rUqdj1dqSL3owaWz3zmcqOjFnZ2U73s46e62OTFheNrloaYNJ++t7i5CSZtSAl4P2COXa5lrjXYao1pWG7Lnl22w3YGt2uITjhi+vQJ76fWLhif+4C3n+dfWEoS82m2E5ewfLLibOx1L6zd4nf4lf6rGOnInCXXVz+sNbWLJnUnCKvs/RrvsCMUM+BPVOezv4UlfeVkWdGMxCLuOiQeH43FjEl4ZHRBIpZYnpHoN56EHW954CpXnoIVnbXKl1HJwBrOqSesauVgZSppbxIvPcJUE5Rp8fvzsFFuV43huwWY0HX3R4VjxVhT+LWz+iuvsPD9lt47csqxg8zavXo/3mKSHsf/jLlVhWU7PricHVmL5e/vknws1ICpDexwNTVuwtJr/B1jpduwouczGn5CHVj1YmHO0eRuzDToqu1o2Vfsp+Y1ik7yN4zTh+1qY+Ewxrr5E6WQfxxTmP7jyYQoGfNCjDsSRmawtcaDi7c0aFhiaIXbpgfzmCFH2/3TUctY1C4B5a6jq9hmz5I/MvuY8YTGjC5JUTY8ivfG8toSB37tVIwzcwQnvlNu8SSbDw++OM2iv/aYF2+iSrFS/hLAf9u9EF/7JoLHX+i+8WL7bhwraNYOPySF6+zMPcMyKI0P0NuKKxA5/NTNjqGYkkP4jYwDKc/eHcUleMDvAxZKuOaL8h6BNAR30Xbvv2usitfMpNtandbAKTyqTrYdmrjW91eft67q4MPOpfoXpwzwV3EN/FHfjPEdjhftQo3M8dm7xaf8naxwB+v8G10Kdrio7IWY/Vcccc6mEInIdmc88DSXs0mrG+5y6mTLybCLuGAQa9+BbV54X5cqcDS/iv/y4i8LRfxwc1I1ktcdiI+XbXIPfxSCG+ptyf3dH4Y7OCUrzedH4HsnA/XG5WJxgwp8Zq9qIm7PIRur65KE31f/op0iloJ3DLItTIan4/mGJVYspGw84+FCIkp7iEcUmORZ3n+KP655mJwxmo/7DAkMbwsqxu9E29yqDn6Fs9ARK2nuclysUXl06uRb/Oh23xFF8Sr83RLbhSaeWlzrNk8yNeYjXvnUvfLD80a8Lqo3hxLxGe/j2Gmu+7gd5zlzNin7j25803LLxKYdX3GhCaPVwC4S/mw5T/gIMowX6uZL74sfw9+ljt7PDZrEhyTVk1j3zuBmey4MXOr4gX9caef15p3Hn6P8DmGUJVy5r+Gzk/9vXOPpl+0/PZjBzS6lSvd0VlCc0+MTZ8cB7IzoWGD7HwB1f/DuyVYeAK5KJS/u5QVHM6QEszp3gg+n/34oIS8C5A/P0y8+EwcPPevdaa8kwfERrvFwPWngXBWQyR8rC9QcDl6T0DkEli6279mjcRSEXi3n/PVREQRMZNbOz6gAjc9e31a+AKAW4O14rFMdXGDhnw3R0ARNlLYEPhMdMHJ+vlNA3AB0B79RueFiDDpuFFSrVJuBusGOtxoVlkBmet/BOVdbwHlbwnpwyQFIf9s8FI87gxE0WlhPxA0cLF58G1ToAd7btIwrRV4G9ZTiptiKK4BpwUuEL8AXVLe+S2Cfug7cxE5nJe4IASrDr1LLZcJA/VSxJkvLbbAlOJe8xhcDVKXZPXISEgDK1klfvH0XVERdtZFlTQY+3sZ+WsJp4KOq64+o8SzgNbloZFGdCxJt/33Jz/EEpOnf1C7ofQben3mfpKzzAhQorF38Vl4C/PHAeFpxKchNOPKzWaQC+L6wOa3MUQlGxdnYfoIq0CBLifneUAOeifjJqebUA89DVSTBtk+gQZ3Jm2zeDHbSzYZWhdqAvbJHVqBCO+CUChdW9+0E/myc7qukbnBigi7/6VwvqCft/4mt9YE2Why9LY8EUsIrQpPzB0FGwl/sDkzDgBQdOiGsPwI0Av0agxNGAZ2C0ki1Y8DwnPYhtbFxsMpiFh9M+w7MqeDcSfIEmP5pELHcNAkGKobmipLJgO/vOhF2/SnQUv4e3zQ7BRT+Mu1eCpoGPwofOLj8mgbHLLhfDLlRgK6g+HbWfyjgllCpi43wDOBvckWcHWfATnJ7UlDWDHALGfQnd8wAev2VvCYmKnC+oFC3R5oK8k1EHpO0qOCLnYxq1Tkq+NiaPOcfQAXMPmZMefFUYISOLTRlU8G1Q/d2WxZRQVhT6OuSt/Blw0g0mY9UwCvU0LXlMxVIZZ4fjeumAk3nIwmUfngfx4kQ42EqqLsy+mpknAoklkQ9q8lU4DfwSGzzDBVEuk+wNc9SATe/BEnyJxV8kMlyFJungqpcx6rmBSp4XNO4rLQE729KU3FfpoJg+k2xS3Qq2H3uO01nBZ7zhA2y/oLPjdHanQvl0Q4Sl/5NBS9PWqclQ3mFSt4uQE8K3JfXWoXnWYlK0dBnGr1VddAXhtxu81CzYXKc6BoV9CtutlYj1NT4ZAs9tqPx+TUoP8e2shhortTHkEyo+J9GcYXQzNKjp95APe0Vt9VDO7BFqVaoVbdgTxd030lxy37oB/eV3iGosNvZkHGo2ZceTzLUYrSljgLtYVK7Nwt9sC1ogQZt/7toeQ46b2pVuACNSq7btQQd4zY0W4aiQnU2dOiaVCyyApXQoi0S+t5UT/4Ffc27sus31PTz93jCypv4LGH43jK1VWiAc2o44ZBifxWh6VcxGqFh8SfhNehLbBMgvP+wxYYw9tPqVcJWF71oQglZwwxC3y0xBYSm4x6vCSOLjOsIk+gRLYQhiEkXoe6jyT7Co28ChgjTCg6PEw74HCATendFUwhV+ZxnCftqWmiEbXjPHOG8SsIC4QUptiVC3UDVZULXfbp0QrdnciuEW6ynGI6mx/wiVKXz/iZs1olg6NQ6yfDKCXSV8OtaCMP0xUqGAl0zDNmZBNcIU/JQhuxtVgy7Da4wpH+LZMgkks7Q83Y+w4jECobmKbUMnaaa169v6WQ45wszENeLwAxQgaExhsaTkwwVjWEGqNUxmAEa9whmgLqUwgzQOi+YAaoyvcjwL1mYAWquDDNAU3bB8aHppHW7Q2AGuCZtg2ND7wevqzOybo0iHBfuWwSuy/563eCpdeX54TE811RZt9li3XSvdT0j1h1NXTf4+bovy9ftr1lXj8gAn3OKyABlJzJAKwfXTSAywPUjIgNUgMgAZSUyQP2JDNA7RAaoKZEB2klkgPITGaD7iAxQLmJ8aPuGPkQGuGYhxob6bti/4RFiXGJ/w7INJzbkJY7h+fENzTf03DB8Q0YGeB0jA7RsQ0YGuGZkgDIyQL9uyMgA1/CjYAg/CobTGzIywDUjA5SRAcrIAIUfBUPG+P+LjPHh/v+UMR7c/7/KxPjNgv/63w7/n/+D/wCtsZE2wBIAAA=="},"shape":[600],"dtype":"float64","order":"little"}]]}}},"view":{"type":"object","name":"CDSView","id":"p1099","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1100"}}},"glyph":{"type":"object","name":"Line","id":"p1095","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#95D73F","line_width":2.6}},"nonselection_glyph":{"type":"object","name":"Line","id":"p1096","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#95D73F","line_alpha":0.1,"line_width":2.6}},"muted_glyph":{"type":"object","name":"Line","id":"p1097","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#95D73F","line_alpha":0.2,"line_width":2.6}}}}],"toolbar":{"type":"object","name":"Toolbar","id":"p1015","attributes":{"tools":[{"type":"object","name":"PanTool","id":"p1030"},{"type":"object","name":"WheelZoomTool","id":"p1031","attributes":{"renderers":"auto"}},{"type":"object","name":"BoxZoomTool","id":"p1032","attributes":{"overlay":{"type":"object","name":"BoxAnnotation","id":"p1033","attributes":{"syncable":false,"line_color":"black","line_alpha":1.0,"line_width":2,"line_dash":[4,4],"fill_color":"lightgrey","fill_alpha":0.5,"level":"overlay","visible":false,"left":{"type":"number","value":"nan"},"right":{"type":"number","value":"nan"},"top":{"type":"number","value":"nan"},"bottom":{"type":"number","value":"nan"},"left_units":"canvas","right_units":"canvas","top_units":"canvas","bottom_units":"canvas","handles":{"type":"object","name":"BoxInteractionHandles","id":"p1039","attributes":{"all":{"type":"object","name":"AreaVisuals","id":"p1038","attributes":{"fill_color":"white","hover_fill_color":"lightgray"}}}}}}}},{"type":"object","name":"SaveTool","id":"p1040"},{"type":"object","name":"ResetTool","id":"p1041"},{"type":"object","name":"HelpTool","id":"p1042"}]}},"toolbar_location":null,"left":[{"type":"object","name":"LinearAxis","id":"p1025","attributes":{"ticker":{"type":"object","name":"BasicTicker","id":"p1026","attributes":{"mantissas":[1,2,5]}},"formatter":{"type":"object","name":"BasicTickFormatter","id":"p1027"},"axis_label":"p(y=1 | x)","major_label_policy":{"type":"object","name":"AllLabels","id":"p1028"}}}],"below":[{"type":"object","name":"LinearAxis","id":"p1020","attributes":{"ticker":{"type":"object","name":"BasicTicker","id":"p1021","attributes":{"mantissas":[1,2,5]}},"formatter":{"type":"object","name":"BasicTickFormatter","id":"p1022"},"axis_label":"input coordinate along the unit vector of theta","major_label_policy":{"type":"object","name":"AllLabels","id":"p1023"}}}],"center":[{"type":"object","name":"Grid","id":"p1024","attributes":{"axis":{"id":"p1020"},"grid_line_alpha":0.3}},{"type":"object","name":"Grid","id":"p1029","attributes":{"dimension":1,"axis":{"id":"p1025"},"grid_line_alpha":0.3}},{"type":"object","name":"Legend","id":"p1070","attributes":{"location":"bottom_right","border_line_alpha":0.0,"background_fill_alpha":0.85,"label_text_font_size":"10pt","items":[{"type":"object","name":"LegendItem","id":"p1071","attributes":{"label":{"type":"value","value":"t =       1    ||theta|| =  0.04"},"renderers":[{"id":"p1067"}]}},{"type":"object","name":"LegendItem","id":"p1081","attributes":{"label":{"type":"value","value":"t =     100    ||theta|| =  1.92"},"renderers":[{"id":"p1078"}]}},{"type":"object","name":"LegendItem","id":"p1091","attributes":{"label":{"type":"value","value":"t =  10,000    ||theta|| = 12.30"},"renderers":[{"id":"p1088"}]}},{"type":"object","name":"LegendItem","id":"p1101","attributes":{"label":{"type":"value","value":"t = 100,000    ||theta|| = 25.11"},"renderers":[{"id":"p1098"}]}}]}}],"background_fill_color":"#fafafa"}}]}}';
        const render_items = [{"docid":"02ffcba6-5632-4545-b408-b03c372a5302","roots":{"p1006":"aa75ad85-2b76-496d-a0ed-75ab4fac6412"},"root_ids":["p1006"]}];
        root.Bokeh.embed.embed_items(docs_json, render_items);
        }
        if (root.Bokeh !== undefined) {
          embed_document(root);
        } else {
          let attempts = 0;
          const timer = setInterval(function(root) {
            if (root.Bokeh !== undefined) {
              clearInterval(timer);
              embed_document(root);
            } else {
              attempts++;
              if (attempts > 100) {
                clearInterval(timer);
                console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing");
              }
            }
          }, 10, root)
        }
      })(window);
    });
  };
  if (document.readyState != "loading") fn();
  else document.addEventListener("DOMContentLoaded", fn);
})();
</script>
<div id="aa75ad85-2b76-496d-a0ed-75ab4fac6412" data-root-id="p1006" style="display: contents;"></div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-sigmoid-evolution-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: The same model, four points in training, projected onto the unit vector of the <em>terminal</em> <img src="https://latex.codecogs.com/png.latex?%5Ctheta"> (direction stabilizes early; the curves here are dominated by magnitude growth). At <img src="https://latex.codecogs.com/png.latex?t=1"> the sigmoid is barely tilted, and the predictor is essentially uniform. By <img src="https://latex.codecogs.com/png.latex?t=100%7B,%7D000"> the curve has collapsed to a near-step function around the decision boundary, and any <img src="https://latex.codecogs.com/png.latex?x"> outside a tiny band is assigned probability indistinguishable from <img src="https://latex.codecogs.com/png.latex?0"> or <img src="https://latex.codecogs.com/png.latex?1">. This is the high-norm regime: same model, same data, different norm of <img src="https://latex.codecogs.com/png.latex?%5Ctheta">.
</figcaption>
</figure>
</div>
</div>
<p>The curve at <img src="https://latex.codecogs.com/png.latex?t=1"> is gently sloped: it answers borderline points with a hedge. The curve at <img src="https://latex.codecogs.com/png.latex?t=100%7B,%7D000"> has collapsed to a step, and any input that strays even slightly across the boundary is met with near-total confidence. The model didn’t get more right; it got more emphatic.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 6%">
<col style="width: 23%">
<col style="width: 26%">
<col style="width: 43%">
</colgroup>
<thead>
<tr class="header">
<th><img src="https://latex.codecogs.com/png.latex?w"></th>
<th>slope at <img src="https://latex.codecogs.com/png.latex?p=0.5"></th>
<th>active region width</th>
<th><img src="https://latex.codecogs.com/png.latex?%5CDelta%20p"> for <img src="https://latex.codecogs.com/png.latex?%5CDelta%20x%20=%200.01"></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?1"></td>
<td><img src="https://latex.codecogs.com/png.latex?0.25"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Csim%204"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Csim%200.0025"></td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?4"></td>
<td><img src="https://latex.codecogs.com/png.latex?1.0"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Csim%201"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Csim%200.01"></td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?1000"></td>
<td><img src="https://latex.codecogs.com/png.latex?250"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Csim%200.004"></td>
<td>saturates to <img src="https://latex.codecogs.com/png.latex?%5Csim%201"></td>
</tr>
</tbody>
</table>
<p>The same <img src="https://latex.codecogs.com/png.latex?%5CDelta%20x"> that produces a <img src="https://latex.codecogs.com/png.latex?0.25%5C%25"> probability shift at <img src="https://latex.codecogs.com/png.latex?w%20=%201"> produces <em>full saturation</em> at <img src="https://latex.codecogs.com/png.latex?w%20=%201000">. The sigmoid has stopped behaving like a smooth probability and started behaving like an indicator function, a step at the decision boundary.</p>
</section>
<section id="confidently-wrong-a-worked-case" class="level3">
<h3 class="anchored" data-anchor-id="confidently-wrong-a-worked-case">Confidently wrong: a worked case</h3>
<p>Take a noisy positive example whose features land at <img src="https://latex.codecogs.com/png.latex?x%20=%20-0.01">, just past the boundary on the wrong side. The true label is <img src="https://latex.codecogs.com/png.latex?y%20=%201">, but a small measurement error has placed it where <img src="https://latex.codecogs.com/png.latex?%5Ctheta%5E%5Ctop%20x"> is slightly negative.</p>
<ul>
<li><strong>With <img src="https://latex.codecogs.com/png.latex?w%20=%204">:</strong> <img src="https://latex.codecogs.com/png.latex?%5Ctheta%5E%5Ctop%20x%20=%20-0.04">, so <img src="https://latex.codecogs.com/png.latex?p%20=%20%5Csigma(-0.04)%20%5Capprox%200.49">. The model <em>hedges</em>: it says roughly <img src="https://latex.codecogs.com/png.latex?50/50">, which is well-calibrated for a borderline point.</li>
<li><strong>With <img src="https://latex.codecogs.com/png.latex?w%20=%201000">:</strong> <img src="https://latex.codecogs.com/png.latex?%5Ctheta%5E%5Ctop%20x%20=%20-10">, so <img src="https://latex.codecogs.com/png.latex?p%20=%20%5Csigma(-10)%20%5Capprox%205%20%5Ctimes%2010%5E%7B-5%7D">. The model is <em>catastrophically wrong</em>: it says “definitely class <img src="https://latex.codecogs.com/png.latex?0">” with <img src="https://latex.codecogs.com/png.latex?99.995%5C%25"> confidence on a point whose true label is class <img src="https://latex.codecogs.com/png.latex?1">.</li>
</ul>
<p>This is the cost of unbounded weights. High <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Ctheta%5C%7C"> does not make correct answers more correct; it makes wrong answers more <em>confidently wrong</em>. The well-calibrated “hedge” disappears, replaced by a response so steep that any deviation from the training distribution flips the prediction.</p>
<p>For reference: <img src="https://latex.codecogs.com/png.latex?%5Csigma(30)%20%5Capprox%201%20-%2010%5E%7B-13%7D">, the edge of float64 precision. Trained classifiers routinely produce logits at this scale, which is exactly the high-<img src="https://latex.codecogs.com/png.latex?%5C%7C%5Ctheta%5C%7C"> regime where the sigmoid has collapsed to a step. Working in raw logits rather than materializing these saturated probabilities is also the numerically stable choice, which I make the case for in <a href="../../posts/dl/why-not-softmax.html">Why Your Final Layer Shouldn’t Have Softmax</a>.</p>
</section>
</section>
<section id="what-to-do-about-it" class="level2">
<h2 class="anchored" data-anchor-id="what-to-do-about-it">What to Do About It</h2>
<p>If the gradient never vanishes on its own, the cure is to remove the thing that’s pushing the weights to infinity. Three practical options:</p>
<p><strong>Weight decay.</strong> Add <img src="https://latex.codecogs.com/png.latex?%5Clambda%20%5C%7C%5Ctheta%5C%7C%5E2"> to the loss. The gradient on a correctly classified example becomes <img src="https://latex.codecogs.com/png.latex?(p%20-%201)%5C,x%20+%202%5Clambda%5C,%5Ctheta">. The data term shrinks exponentially in margin; the penalty grows linearly in <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Ctheta%5C%7C">. Linear growth meets exponential decay at exactly one finite point, and that’s where training stops. I implement L2 and weight decay from scratch in <a href="../../posts/coding-nn/regularization/Coding-Neural-Network-Regularization.html">Coding Neural Network: Regularization</a>.</p>
<p><strong>Label smoothing.</strong> Replace the one-hot target with <img src="https://latex.codecogs.com/png.latex?%5Ctilde%20y"> that places mass <img src="https://latex.codecogs.com/png.latex?1%20-%20%5Cvarepsilon"> on the true class and <img src="https://latex.codecogs.com/png.latex?%5Cvarepsilon/(K-1)"> on each other. The target now lives <em>inside</em> the simplex, not on a vertex, so the softmax can actually reach it. The optimum has a closed form: the logit gap between the true class and the rest equals <img src="https://latex.codecogs.com/png.latex?%5Clog%5Cfrac%7B1-%5Cvarepsilon%7D%7B%5Cvarepsilon/(K-1)%7D">, which is finite. Fix the target the model is allowed to reach, and the weights stop chasing infinity.</p>
<p><strong>Early stopping.</strong> Halt while <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Ctheta%5C%7C"> is still bounded. Logarithmic growth is slow, but it is monotone - every extra epoch buys you a sharper sigmoid and worse calibration on borderline points. There’s no setting of the learning rate that fixes this; only stopping does.</p>
<p>The three attack the problem from different sides (penalize the weights, soften the target, or cut the trajectory short), but they’re all preventing the same divergence. Pick whichever is easiest to tune in your setup.</p>
</section>
<section id="key-takeaways" class="level2">
<h2 class="anchored" data-anchor-id="key-takeaways">Key Takeaways</h2>
<ol type="1">
<li><p><strong>Cross-entropy’s infimum is at infinity, not zero.</strong> Softmax/sigmoid outputs live in the open simplex; one-hot targets live on its vertices. The attainable set never reaches the target set.</p></li>
<li><p><strong>The optimizer never stops because the gradient never vanishes.</strong> <img src="https://latex.codecogs.com/png.latex?%5Cpartial%20L/%5Cpartial%20%5Ctheta%20=%20(p%20-%20y)%5C,x"> is non-zero for any <img src="https://latex.codecogs.com/png.latex?p%20%3C%201">, so on separable data <img src="https://latex.codecogs.com/png.latex?%5C%7C%5Ctheta%5C%7C"> grows logarithmically without bound.</p></li>
<li><p><strong>Diverging weights produce a steep, near-step sigmoid.</strong> A small input change flips the prediction from near <img src="https://latex.codecogs.com/png.latex?0"> to near <img src="https://latex.codecogs.com/png.latex?1">, so the model becomes confidently wrong on borderline points.</p></li>
</ol>
</section>
<section id="resources" class="level2">
<h2 class="anchored" data-anchor-id="resources">Resources</h2>
<ul>
<li><strong>Soudry, D., Hoffer, E., Nacson, M. S., Gunasekar, S., &amp; Srebro, N.</strong> (2018). <a href="https://arxiv.org/abs/1710.10345">The Implicit Bias of Gradient Descent on Separable Data</a>. <em>JMLR</em>, 19.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Machine Learning</category>
  <category>Deep Learning</category>
  <category>Optimization</category>
  <guid>https://imaddabbura.github.io/posts/dl/why-cross-entropy-never-zero.html</guid>
  <pubDate>Mon, 27 Apr 2026 05:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/dl/images/ce-never-zero-img.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>Why Autograd Needs Floats, Not Ints</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/dl/why-autograd-needs-floats.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>Set <code>requires_grad=True</code> on an integer tensor in PyTorch and you get a runtime error: only floating point and complex tensors can require gradients. The fix is one keyword argument. The rule itself isn’t a framework choice: it’s a structural constraint on gradient descent, and the same constraint explains a handful of related failures that aren’t loud enough to throw errors of their own.</p>
<p>Gradient descent works by reading how the loss responds to a tiny change in a parameter and stepping in the direction that lowers it. Two conditions have to hold for the nudge to carry information: the parameter has to be able to move by arbitrarily small amounts, and the loss has to respond smoothly to that move. Integers break both: once in the calculus that produces the gradient, once in the arithmetic that applies it. The rest of this post works through both failures, the cases where integer tensors remain safe, and why quantized training is a workaround rather than a counterexample. (Chain-rule mechanics this post relies on are covered in <a href="../../posts/mlsys/automatic-differentiation.html">Automatic Differentiation Demystified</a>.)</p>
</section>
<section id="the-derivative-on-floats-and-integers" class="level2">
<h2 class="anchored" data-anchor-id="the-derivative-on-floats-and-integers">The Derivative on Floats and Integers</h2>
<p>Plotting the same function over floats and over integers makes the contrast immediate. Below is <img src="https://latex.codecogs.com/png.latex?f(x)%20=%20x%5E2"> as a continuous curve, and the same function under integer rounding, <img src="https://latex.codecogs.com/png.latex?%5Clfloor%20x%5E2%20%5Crfloor">. The slider sets <img src="https://latex.codecogs.com/png.latex?x">; the toggle switches between the two regimes.</p>
<div id="cell-fig-autograd-widget" class="cell" data-execution_count="1">
<div id="fig-autograd-widget" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored" data-execution_count="1">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-autograd-widget-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">

<script src="https://cdn.bokeh.org/bokeh/release/bokeh-3.9.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.9.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.9.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.9.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-3.9.0.min.js"></script>
<script>
Bokeh.set_log_level("info");
</script>
<div id="bc604083-e2b8-4c67-90c5-2fc988204369" data-root-id="p1084" style="display: contents;"></div><script>
(function() {
  const fn = function() {
    Bokeh.safely(function() {
      (function(root) {
        function embed_document(root) {
        const docs_json = '{"980253a2-952e-427c-9d26-411cd76a51f4":{"version":"3.9.0","title":"Bokeh Application","config":{"type":"object","name":"DocumentConfig","id":"p1085","attributes":{"notifications":{"type":"object","name":"Notifications","id":"p1086"}}},"roots":[{"type":"object","name":"Column","id":"p1084","attributes":{"children":[{"type":"object","name":"Row","id":"p1082","attributes":{"children":[{"type":"object","name":"Div","id":"p1072","attributes":{"text":"&lt;div style=&#x27;display:inline-block; margin-right:28px; font-family:ui-monospace,monospace;&#x27;&gt;&lt;div style=&#x27;font-size:10px; color:#57606a; letter-spacing:0.7px;&#x27;&gt;INPUT X&lt;/div&gt;&lt;div style=&#x27;font-size:15px; font-weight:700; color:#24292f;&#x27;&gt;5.00&lt;/div&gt;&lt;/div&gt;"}},{"type":"object","name":"Div","id":"p1073","attributes":{"text":"&lt;div style=&#x27;display:inline-block; margin-right:28px; font-family:ui-monospace,monospace;&#x27;&gt;&lt;div style=&#x27;font-size:10px; color:#57606a; letter-spacing:0.7px;&#x27;&gt;OUTPUT Y&lt;/div&gt;&lt;div style=&#x27;font-size:15px; font-weight:700; color:#24292f;&#x27;&gt;25.00&lt;/div&gt;&lt;/div&gt;"}},{"type":"object","name":"Div","id":"p1074","attributes":{"text":"&lt;div style=&#x27;display:inline-block; margin-right:28px; font-family:ui-monospace,monospace;&#x27;&gt;&lt;div style=&#x27;font-size:10px; color:#57606a; letter-spacing:0.7px;&#x27;&gt;GRADIENT dy/dx&lt;/div&gt;&lt;div style=&#x27;font-size:15px; font-weight:700; color:#24292f;&#x27;&gt;10.00&lt;/div&gt;&lt;/div&gt;"}},{"type":"object","name":"Div","id":"p1075","attributes":{"text":"&lt;div style=&#x27;display:inline-block; margin-right:28px; font-family:ui-monospace,monospace;&#x27;&gt;&lt;div style=&#x27;font-size:10px; color:#57606a; letter-spacing:0.7px;&#x27;&gt;STATUS&lt;/div&gt;&lt;div style=&#x27;font-size:15px; font-weight:700; color:#1a7f37;&#x27;&gt;OPTIMIZING&lt;/div&gt;&lt;/div&gt;"}}]}},{"type":"object","name":"Div","id":"p1071","attributes":{"text":"&lt;div style=&#x27;font-size:13px; color:#57606a; padding:4px 0;&#x27;&gt;Continuous Gradient: the slope changes smoothly. Gradient descent &#x27;feels&#x27; the curve and knows which direction to move.&lt;/div&gt;"}},{"type":"object","name":"Div","id":"p1070","attributes":{"text":"&lt;span style=&#x27;color:#57606a; font-size:12px;&#x27;&gt;\\u2197 Continuous Flow&lt;/span&gt;"}},{"type":"object","name":"Figure","id":"p1015","attributes":{"width":680,"height":320,"x_range":{"type":"object","name":"Range1d","id":"p1024","attributes":{"start":-6,"end":6}},"y_range":{"type":"object","name":"Range1d","id":"p1025","attributes":{"start":-3,"end":30}},"x_scale":{"type":"object","name":"LinearScale","id":"p1026"},"y_scale":{"type":"object","name":"LinearScale","id":"p1027"},"title":{"type":"object","name":"Title","id":"p1022"},"outline_line_color":"#e5e7eb","renderers":[{"type":"object","name":"GlyphRenderer","id":"p1054","attributes":{"data_source":{"type":"object","name":"ColumnDataSource","id":"p1006","attributes":{"selected":{"type":"object","name":"Selection","id":"p1007","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1008"},"data":{"type":"map","entries":[["x",{"type":"ndarray","array":{"type":"bytes","data":"H4sIAAEAAAAA/zVZe3zP9ff/7P7Z/XMZli7mXsilJETnEFKpluuS3JNyTRf3WmLmVosRcpm7NNekxXJOvi4jtBghl7nPdTOz++X3upzf+58ejx61nZ3L8/ZyOPQXxcsOvf1Ys4debv3dyuf63/ByVu/cN+ad9fLYp+CD9KNeDrvx7Zd3yMsbN1/8oeYvXu70edPtr633cna7L49MWOLlKf7Hr26Y6+Xoo09WnP7KyzsXjKoe+KmXY9/7o9kLw7x8t074a0Pf9XLi7X6DF3Tzcv0dqZP3gZf/nFie/OA5L7/foduWmAZeLnHqSry8MPNO9pdhXm6x+KXSzVUePjZgjvdCvoc/avhfk7AbHg7IbdTlpbMeXrVr0oCPj3q4vS6DPHy2c835GTs8/Hn4xz8XrfOw+9Tv+xss8fDmZcEXe831sCqiaPpXHr7e+CfXznEe/vph8TNXP/Dwk3u6vuJ518O/T1vcr0M3D/d6PefzseDhB27dEQ/POztz4/H6Hn5m1b9/VkR7+MBwXYiHBzUfX/BelZsriw6Gz8l381Kq3nD3dTe3mjkMb51x84m3dr0bfdTNo1U3XiU3h6gqvtjh5vXr9OfmjqMe7c1a7OaLLTuf8Zvr5km6HV+5ufr+ayGDx7l5x5yW9b7/wM1v9ZjenuPcfLumnoybE67UVaNxc51Nn8568zk37/3kf6un1Hdz3zbe9J+j3VzoGHLqXKibVTPuB1e5uGmSr7NNvouP9Olee/h1Fw+rtbrtD2dc7HvzQY+Df7l4xZYOqhIXt/3i+4R6O1x8uv3llT3WuXhcQAvVEhdHHIs/sX2Oizcl68G4uEu/mADXOBebMj5w8Vd36MXRcS5+/JfId5a/4eLf9FhednGPjlu/KWvh4tzgqmWN6rt4zj+6IS5uuGTF34mhLk748/w/YyoieW+s3qBILrz0+ItNjkRy0zF7Ppq9LZKHVfZdlrMoklfMKz3eZWokn35iqc+6IZEckdqmpd/rkayWYNig5pFsxl89knW3n6yI4Ps5v1VOvhrBDSb0aXHucAT3Dyoa0npbBC8yXwQfr9/qcMGUCA769VRZ9yERDGahI3j8yahBkc0jeOtgvcERbNpTHs61zKKEcx/XgsaJh8PZnMnWcM5oeiKp06JwdpgBhHPrN92q9eE89vy2pwe8Fs4bR+gLCOfs0tx5j1cP5+jZ3/HE8jCOVdf275UwTlRb1epwGLPqXvLWMC7Wa7QwjJv33vxH7JQwHn69W96WwWGc8pm+oDBWS9JrRLMwNmVUC+Oudf7a3bA8lOO3f3xvxpVQTsOQmGsZoZz390/dO24NZVXEjJSFoTzINCSUl0zVFxjK/4Q9rVY+lIPN4Ydyh8YfThtfLZQn7g5UHQlhddw3n78SwrfP6IMK4TqmkBDuq67nrYUhPF9tSerkED5STS9qCPuuG1Ljo64h3FZN5VDTEB6n17FaCG9SVXxTFsxXLusvmGt+8nUUZgRzd0ftV1dsCWbTjuRg3vfUwNS+k4NZHfvFtEHB/Fz7le4aXYNZLZGaTDCvfu/iFyejgvnc7ak/tShzsmfSk+e/u+zk14P/iLh/yMnf6LPd4uQ9Dcs/3ZTs5Ie7flzvnOzkxmZBnKyv4cCrTv5x6KSX6zZ18smHj6lKnBz6ze9rLpUGsTp+tfJBPHlVsUKgIP6luQaeIL6r1zo5iOu9/W/KrklB3E+XMSiIkzW8vRrER8t/bf3Ps0Hsr8cSFcTtHn+0fF5pIH9mDiWQzZoeCuRrGSdf2Lg5kJ+I+3R4YHIg97zpUaUEsgKDY/sGBvL+AN2RQK7Q5/9sIL+grv6CN5BHmUICeF3HzCNLsgP4grqWooMBXE1tae/NAfym2o6dCwJ4hpqKZ1IA626MHRjAuorjXQL4WfMFsAJxhaABvPyNkfNvlfizaUe2P0domDzoz11KXm/kv9mfvzQA7c+7aujJ+PP99c+o0fhzA30uXfy5vwE+f17U06mgxp816v9Q4sca3R5d8uOXDaD4sYGRVD9WoK5Oxo9vbD0wa/REPzZ0MsCP+xz3Vwjux9+9v7b2rCZ+fOjuK6olflw1+WpC5xJfftEMxpfHLK1z1+egL2/QZaT68qW0Qe/sne/LNbr6TH9ioi+bsQzwZQO3nX1ZFaEAxJfNmnp8uVmUvlwfHr5m7y/vXPLhFH22B3z4jELxiFQfdim0Gjnfh7saIvPh+DH6cH04TaFHQmcfzlNXe72xD+trecXjw3pLVxU72NDSRQfrqbx/wMG6G+k/O1hXUXO+gydO0J+DtwdF/3y6v4NvLfrtQsvODq6t29HYwe/+WtjxgdvBdWY1ubMir4rmb9BAUUW+Bzf9WmN2FY27dkshfRVd8X0mPqluFWnU7pZXSftgg1r5SjJ/xqxK0lc2rXclRZnFrqTpep1zK6jg3yudfkuvoKGFtRVDVFCWhrXeFdTpeY0kFbTznUtnN+aWUz1D8OWU/K0eRDn5G6Isp8/++m/00jrlZMrILSMz7vQyUmSqgLuMDF31KqP1+uzrlFH1ab3VREspIWXh83/uKaVCxWpTE0tp2AVvZZtepXRaoWxh7VLqoun5fgntUlczZk8JmXYkltCiz98uvtmzhAJNISVkzuJ+Md3I1EBVTEaOzCymDA2PPYupdRPNZMW00QBAEUWrKv7aXUSJM/VXRMXrDqqNKqLh5rCK6Ixux71CMmuzu5DSav3v6xdmFtLTZmELycBHTCE5J2skfUSaTRrufkS3NY0kPKK+hhAf0RF1Bf1iHlFbTbP3CmhTizLXqd8LqGasZs4Cmj164n9v9iig0rkaiQvInNfdh3TucCu10Q/JyIqEh2RgtvtDalS/oG1arYe09BU9mHwKG6wZN5+mGEDPp7srNJLnUz8to2rl01E9lrsPqJ0BygeUGn1v6I8zHtATijX7dH9ABoZqPSCNgpl38khf+9y0PDJ0NCOPuv39oepIHqXf04edR3FGsOVSayN4cinaCIZcKlZs++Ga+3RGw0S7+5Sm5dHpe7R4hibsezTBLOI9Glj1hyK+u4Tt9HeXYgyB3CGHGfAdys7XQHyHWJe/+jaljNSAdpviFYq7Tt2igQYYbhHW1QrlFsWYA8shx3LdmRzKVls6MusmsZZVo25SSo910/sH3SR1FOr0b9DAY6sei217gwztnbxOMYpVOoy6Tg495sDrlM26s9eI9bm3uUYpSvXUO3mV4sdXW7N85FUaaITqVUIj9K5QjBFKV8gxQiuey5Sj4W3EZcrQcizgMm1UaHVnRTYlGkGRTZl6rGsuUdI2fegXKVahqLogcq11dPy23QXK1HJ4zHlKUj/lk9X/UayWB6fOkUuvsfMcZWoYfOksJSl1/eLoMxSrrkBpTHIZADhNRgYHnaYkxQaVbU5RrD7bkVnk0nS18iRlnjAfJZnBnqBYpVYUI5ELM77fMCKTUrQ8bP83xWh6X3OMUjR8OY9SzFv6wo8Qa9mXlUGox9P2EFnBcIDQENV+Yi3nRu4j1LR+gileqYy6Y/dSfNb4Vrdf2kPxWqYFpVG8IeidFK9/3crt5Lisy04lhxFS68kxVSvGFHIY4lhAjoyxanQT//+fIP8e5L8D+f9Afg7IzwX5PSC/F6QOkLpA6gSpG+TvgBQjhA5AjP07IcX+3SB9AOkLSJ9A+gbSR5C+gvQZpO8gcwCZC8icQOYGMkeQuYLMGWTuIHsAshcgewKyNyB7BLJXIHsGsncgewgpdi+B7Z5Ctt1bkD0G2WuQPQfZe5A7ALkLkDsBuRuQOwK5K5A7A7k7kDsEuUuQOwW5W5A7BrlrkDsHuXsQHADBBRCcAMENEBwBwRVobXEG4izugOAQCC6B4BQIboHgGAiugeAcCO6B4CAILoLgJAhuguAoCK6C4CwI7oLgMAgug+A0CG6D4DgIrsMyi/PQxOI+pFsegG6WF+CM5QkYbnkDii2PQKLlFYi2PAMbLe9Aa8tDkGF5CeIsT4GxPwmPQHgMhNdAeA6E90B4EIQXQXgShDdBeBSEV0F4FoR3QXgYhJchzvI05FjehgmWx8FpeR0WW56Hepb3YafVAdDJ6gLIsjoBhlrdAAVWR8B0qysgyuoMWGt1B4gOAdElIDoFRLeA6BgQXQOic0B0D4gOAtFFIDoJRDeB6CgQXQWis2Ct1V3Q0uow2G91GfS0Og2yrW6DsVbHgcPqOkiyOg9irO6DOKsD0cjkxg7MsToRDZ30d6CRkRMcKLoSRWei6E4UHYqiS1F0KopuRdGxKLoWReei6F4UHYyii1F0Mja3uhmLrY5GtroaE63Oxliru7G61eGoTJvW5WjsQqovapWidDtaevRFI6dLfFGrMqXz0cjNJn6oVaPyASi+AMUnoPgGFB+B4itQfAaK70DxISi+BMWnoPgWFB+D4mtQfA6K78HT1gfhCuuLcJj1SdjU+iYssD4K062vQtOOSQGoXbnyXWhk9eYA1CmG8mW41vo0NDKnNAB1eqN8HJpY59lAFJ+H4vtQfKClqeRAFJ+I4htRfCSKr0TxmSi+E8WHovhSFJ+K4ltxp/WxOMX6WuxkfS6GWd+LWdYH4zLri3Go9cnYxPpmLLA+GtOtr8bp1mdjN+u7Mcr6cDxvfTmutT4dR1rfji2tj8dy6+txv/X5mGh9P8baHACjbS6A2TYnwI02N8CxNkfA1jZXQIfNGTDD5g6YZHMIjLO5BMbYnAJzbG6B22yOgRNsroFocw40MWOzUDQw1TUUlVnRuQgOtDkJSm6CkqOg5CooOQtK7oKSw6DkMig5DUpug5LjoOQ6KDkPSu6DkgOh5EIoORFKboSSI6HkSig5E0ruhEk2h8I4m0thjM2pMMfmVrjN5lg4weZaiDbnQqfNvTDT5mC42OZiONDmZPi0zc0wz+ZomGZzNYy3ORt2tbkbhtkcDrNsLofLbE6HQ21uh01sjocFNtfDdJvz4XSb+6Gxk6EunG1zQVRhpc4J0Yy/hQtNvPuyC2vaXBGNTI5zoeSOKDkkSi6JklOi5JYoOSZKromSc6Lknig5KEouipKTouSmKDkqSq6KkrOi5K4oOSxKLouS06Lktig5Lkqui5LzouS+KDkwSi6MkhOj5MYoOTJKroySM+NgmzvjQZtDYyObS+O3NqfGfJtbY2+bY+Num2vjUzbnxmk298ZrNgfHrjYXx1Sbk6MytTo3x89sjo7mnHZ40MT95EGduqjcHa198aAp44YHdcqkcno0sFPlQRMXh3lRp2oq10djsxt4Uad+KvdHnSKqdwCUdwGUdwKUdwOUdwSUdwWUdwaUdwe7zuu9KO8SKO8UKO8WKO8YKO8aKO8caJ49HFH4f6x7bQEAGQAA"},"shape":[800],"dtype":"float64","order":"little"}],["y",{"type":"ndarray","array":{"type":"bytes","data":"H4sIAAEAAAAA/z2ZeTzVWxfGTRlCOObZMTumg+MYwl6mKDQqNJKikKlBUahbqZS5VLeUcMuVixIqdd3SJJK4RCqSEkWEytTb+7bX6x8fPofzO3uv9azn+S4env9+2YDDuDs/T5c1DI5kpcjUWIP4eE+u/XlryPlhaR+XaA3nJZL9G/ytgaHbI8pxtIYxJwdSwrSG+UFnJhz4fr4+bdq+540VONUEiJy5awV94w9Xb863gh8WFtYeB6wgKeb8745BVpDwj2SSp5sVDEjtnwjVt4LaTRNj50SsQOL+1l19/Vx4bPB5v2s9F75mRihUFnMhS2DEwj6VC7lxMW1tkVzQ+P6DL2kpFyT2JFcs4HAhQkhpRFOOC3Cy6JrEN0vYawq8Uh2WwGloa9GttgSfqGj2khxL6FeWlElLtISh+tKEnvWWsGG/91YPV0vwcJocfKBnCbmCBSO+sy1hc9OS+JmPHLhwgTejqpEDHrHlxklXOLDRN2RJyHEODNlpTQfs5MCA3mt2+CoO+CjmfExx4ABXyp9dy+TAfgmdKfFZHHCW+bQorM8CotWrDF8/tgCG2cHUoBIL0JrvE8eXaQEFwYZDV3ZYQPZRvoGYlRYwUdkZvsTBAp58uB5LNC1AWvP0HCdBC3jov4fl128OgwWBT/Y/MYcDw54TtVfM4ZizzV+K2eYgeEa/f1+cOQxNKP3F428O8/0lJzJczEGmXviJjYE5LHIQYI2Jm8NUBd+ceyNmoMgViC1sM4OLN4XDL1SbQYGbZH9JrhnIdigNNh00g7Fo/VjhMDOYx7BJWbbEDISrPFlXuWZguyFwoZ6KGbyU2zNRwmsGw42nTLzes2FH6vX+mXo2hC/vNLl3hQ0tTL7JcyfZUDTKWpQaz4YvDSsMMzf8/Ln4QGrRAjb8m1kZ95zNhojEj0OK8mwYfef+/PWEKQy03+4qf2AKte22369kmUL8u2pmR4ApKPG4rmSZmkK2bvOFvCkTGPcLnnStMwGr07wbpE6agN/73M5ZQSawxtF9oxbHBJwujU5v5jMBYZWLf7Q/NYbSM+vWRZ8zBluWmj4n3BgKat/wqNsbw0ho8QdzMWPQ1Ih/E/HCCMy7vQf+/dMItEvMBDbGGsH4YRlj1QVGUBQ1vXFG0Qgcgz7+JdxvCFXB3UJ2NwxBakdn5MkjhrAw/eUH1VWGEHL9bXS9oSEEDw6L5k+xwI0961p+AwuEEtTCn+SwoLDD3ooZyQJj50DJs44syKxK+e4ozYJ225oRiV4D4Kv7OilYaQBiQZayeocN4OucXQ6RqwygtvZOzBtjA4hOYvyzh+fn63w2KTk168M2i9rfTP/QhwcqutOuu/RhipFy8ICnPjDkptQH1fVBRDfyUcIXPehx6t9n+0APzoWFeKr9rgdz8z7rGEbowfV3uyUCnPVAwUpCpEZeD/yyLjLcP+rC7hlXo+81upCwvX9583FdCP6eldoaogsmR1zb+UAXWvWmzH1ldcH/WdWZ1n4dqD8cJxdfowOKC13OLzyhA27q0rbuYTrgO/2+J8RJB+b33TlboaADql15PyteG5reHnF8dE8bNo3HGKad0YbXMqE6iVu1wQY2mJ5aoA3bdgbOf8HUhrRbm7bO+64FKRLbijueakF4+IGv2Ze0wLTjzJL4RC1oWnrj5jFfLVj+/KXlfbYWVIcI1eiJaIHAbKuVV7o1wagiTCDohiZwIy7ddsrUBE3L/kPuYZowJGQeGOOqCTnvE7wa1TTBtKXZbfE3JpxvMF460cSE0eZjYQ1FTDB4/+XEowNMcBTybxpcxwQbzjNle1smMMIX7CiXYUJD+cMu7yENCBH2WqVWpwF9m9p6JQs0wL01ONEoUQMOL55hRa3SgMttp3tfcTWgNNS+dDtDAwwV62VDO9XhxjaDAbssdRDrz+jy91QH5i7h8VYBdZhkHjE8d0sNTryV33t9hxqM1pSN67DVQLbS5+jbD6rw/b6I01ieKpz//EDed60qzOZkiIsrqoJ1+iYtmRYV0Bf28NuSqgIvTtqUKHiowFJnjo6ckAokC829ufGuMhzq9YoSSlQG95dhbpN2ytAwmG3vOKEE0kqNy9orlUBlHePI3e1K0HcroOuHhRLEcG+tSBlRhEcPtT+FlynCq20n83IjFaHCViFWl60IS5TyI6eHFKBE2i5Jt1QBmnS7buVGKsC1ZWkyEWYKsOa0Z3LKiDw8mJDR5CmXh7FtH57f3S4PfcJ1Je1W8nCmouIPxwk5YMSX3JqslgPPdeWjQolyMH/lPY8gZzkQiXhzR05IDo6eFV2p+FgWnvWCVHiqLHS67euT8ZaFS3cbX4sryoKVn8FX31cycEQ41WQ8TwbONfEc7N0sAzvL46d12TIgWyZ8/Ma4NGx7kON5/pY0nPgK2s/3S0Oc45Dyek9p0CwotHCQkYZjmpGhYZ0MuHnd6e7HfAZcDmHO/XsLA/wtxZp7uAxoU+RPXsnDAIaCYKBWnRRIsGVXOWRJQWMAe2vRWilYVuxTFGIgBb8zkvl3j0pCQXpd3Ou/JSGKJSdzPFkSeF9uqT/jIwnehU3541qS0Kz2m61rtwQcUIsLdYyTgFtLkju7ZSUgturqMd2yOfD30tF4Ca85cFjDq/TMB3FoZ95mPjkoDqUr3JrztMVB7O++WtW7YvBl5YXxuevFYDVr26YZXjHwMF6rtPGCKNQE+otFOovCzYZYB/Xe2WAXWlwanTQbXOy+rQ9hzYYmWLVc8IkI9O5sTZoXLQIJbzdPsORF4NxvssVXq4XBdUX7ua71whC1+tqzMhFh0D5R6KpXJgSr+Su+gZ8QqOV3DMzwCkHgTnn1gCJBMEkMywheLggJN9tdGTyCsIK1ziagaBZcrZ8K8/aZBdkXrnQN8M8CnsuJWXplAvC5L+iQ0DoBCPLbWJ0sLgDrp+PY5dX88Lblz7eJYfzw8fXw82EVfohVWyIu9IQPMtIe7q1J4AMjri9XzZwPvCR4DDV6eWFUuWbd/ZO8wFx9skXSixdaGg8d/sHLC+Ix6fEZlTxQv+TK1UdbeIDhP6BfoM0DN5x3Vc+v+kHWmnheTNf/QcQTFzE0T8+QR9a/TU+IzZAM3/ebVPZOk9DXcd4Hx6eIZwPUWG+ZIjaa3MsWvZPE/NUqhZi1k8Sa94oAf/sEmZ9sHdXmPUGCdw+tGW36TtJbWuv8Fn8nDzMHK4WefiOiN7g6M4u/kZVuJQz75q/k2lzfxNoVX4lKtll41otxkuY3t/NywDiR/W17nfiHMZIr3WVdGTVGbEXj9AonR8mrzfNO9BwcJammTgeCpEeJ1+rwUbPcL0Tuc12PK/sL+djvs+xCzQh54inr7LZ0hFTL8l/l9A6TSldmXsiuYXK7M0L6w5xh0tQxyF9a8Jl8Jjnb/rb/TKwUpupM/hoiF+NViiN6Bonp1bOpjsqD5Om1hOj85Z/I4YN3lp/M+EhW6QTZqD4bIK4ZIaoWMgPEvaGRt8O3n0S3Hn8ndf4DKSiuftzxoY8ML3Up43D7yIr7RtnqB96TZqEde35vfUc2y2tsuMR6R+THWB7z9vaSV+dPmG/veEtuKQUqWXLfkmvr0/gOZvWQezvVBoLH3pDB1eItHb5viKnEmluvbneTw6niF7fqdZOpl2rppzK6SNJEWtxini7iXfegnt39itTPNc17c/8l2T7fN7airJMs+uK09HLuC7LSZohVe6KDnFBeIzCT1k74Tx995Zf2nOSW77neltVGIkPZx2NyWknk7fwom+J/SW5xx0LVf1oIv80zI62OZnLJP1nU49szEqYtOJCl/IwE7Hd6zOPSRJIS4XJK1FNSbcajsse9kdSf5uvWqGogDnaHLgqb1xOGXHoEp6KOuOur2uS7PCLd0UYCvi8fkBdjt58uSrxPrMtacpKN75Hx/C3hor13iXzTYdL4xx1yytKA0Rb9D4l/5vFOc34NObKoJ2bjstukM8RSV9ygmoyMH2uzk75B6r98TB6VqiIhAf9V+ApST25PT3qWky8p7Jseh6+QlwsL47Q6SonCj8Xxggl/kRnH1aKt0UUkLGhbzrXUSyQw7JRVwb8FRNr7cWu+Sx6RV529u7zzPHFNLVp4ae5ZEn5xdZmQ5ClycZ+eRhL3OBnWEPtdtySdZAvIutR/OUq2eLmGJBsnkQ0qhZeOiO4j394kzmQqx5GQU3KdGZu2Eocq3Ratqc3E53JnrkvkOmKnKLzGafZiUtjpEMyvaEW0ayMF+Q8J/P87/h5fh3+H/wf/L74Pvi8+Bz4XPic+N36OoZRfn6tP5dfn7F7263PjOeC54DnhueE54rniOf//3Ok94L3gPeG94T3iveI9471jHWBdYJ1g3WAdYV1hnWHdYR0upnW5itZpNq1brGOsa6xzrHvsA+wL7BPsG+wj7CvsM+w77EPsS+xT7FvsY+xr7HPse9QB1AXUCdQN1BHUlWyqM4FUd1CHUJdQp1C3UMdQ11DnUPdQB1EXUSdRN1FHUVdRZ1F3UYdRl1GnUbdRx1HXj1OdZ1Ldv0HnwFo6FwTpnLhL58ZROkfW07niQueMOZ07xnQOcehccqNzKojOLZxjONdwzuHcwzmIcxHnJM5NnKM4V3HO4tzFOYxzeQ+d00N0br+nc3wjneub6Zz/ROf+dNEvH5BJfUEp9QnLqG/YTX2EIfUV/tRnKFPfgT4EfQn6FPQt6GPQ16DPQd+DPgh9Efok9E3oo9BXoc+SpL7rKvVhL6kvO0Z92k3q22Koj7tOfd1e6vMaqe9bSn3gj85fvjCC+sQ86htPUR+JvhJ9JvpO9KHoS9Gnom9FH4u+Fn0u+l70weiL0SfHUt+cS330UeqrbanP/pP67nbqw59SX36E+nQh6tvdqI9fQH29JPX5p6jvf0dzAOYCzAmYGzBHYK7AnIG5A3MI5hLMKZhbMMdgrsGcg7lnAc1ByTQXpdCctJzmpnaao3RorrKkOUuY5q4cmsPGaS5j0Jw2THNbJs1x32iuw5yHuQ9zIOZCzImYGzFHYq7EnIm5E3Mo5lLMqZhbWTTHjtFce4HmXDbmXpqDh2ku1qY52ZrmZhOao2fRXH2b5mxfmrtbaA43p7k8iub0dJrbM2mOj6G53o7m/E6a+zdQDvCEcgFFygnmUW7gTTmCC+UKcpQzPKTcYQ3lEM2US7Aopwik3CKecoxYyjV8KOeQp9yjknIQW8pFzlJOgtwEOQpyFeQsyF2QwyCXQU6D3AY5DnId5DzIfZADIRdCToTcCDkSciXkTMidpCmHukG5lDPlVMWUW32jHEuHci0Oci7KvcYpByukXMyBcrJyys3EKEebR7maP+Vsayh3s6AcboRyuUzK6eQot4ulHK+Gcr33lPN9ptxvC+WAzZQLFlJOOEy5YSHliM8oVwyjnBG5I3JI5JLIKZFbIsdEromcE7knclDkoshJkZsiR0WuipwVuStyWOSyyGmR2yLHRa6LnBe5L3Jg5MLIiZEbI0dGroyc2Y9y50+UQ49QLh1MObUX5db5lGOHUq6dTzm3F+Xe6ykH/0S5eB/l5N6Um5tRjp5Aubo95exbKHcXpxxelXL5c5TTp1NuP0o5/kPK9cUp579DuX8f3QPgXgD3BLg3wD0C7hVwz4B7B9xD4F4C9xS4t8A9Bu41cM/xv7XHz73HfwCFrmy9ABkAAA=="},"shape":[800],"dtype":"float64","order":"little"}]]}}},"view":{"type":"object","name":"CDSView","id":"p1055","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1056"}}},"glyph":{"type":"object","name":"Line","id":"p1051","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1f6feb","line_width":2.5}},"nonselection_glyph":{"type":"object","name":"Line","id":"p1052","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1f6feb","line_alpha":0.1,"line_width":2.5}},"muted_glyph":{"type":"object","name":"Line","id":"p1053","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1f6feb","line_alpha":0.2,"line_width":2.5}}}},{"type":"object","name":"GlyphRenderer","id":"p1060","attributes":{"data_source":{"type":"object","name":"ColumnDataSource","id":"p1012","attributes":{"selected":{"type":"object","name":"Selection","id":"p1013","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1014"},"data":{"type":"map","entries":[["x",[4.5,5.5]],["y",[20.0,30.0]]]}}},"view":{"type":"object","name":"CDSView","id":"p1061","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1062"}}},"glyph":{"type":"object","name":"Line","id":"p1057","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1a7f37","line_width":2.5,"line_dash":[6]}},"nonselection_glyph":{"type":"object","name":"Line","id":"p1058","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1a7f37","line_alpha":0.1,"line_width":2.5,"line_dash":[6]}},"muted_glyph":{"type":"object","name":"Line","id":"p1059","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1a7f37","line_alpha":0.2,"line_width":2.5,"line_dash":[6]}}}},{"type":"object","name":"GlyphRenderer","id":"p1066","attributes":{"data_source":{"type":"object","name":"ColumnDataSource","id":"p1009","attributes":{"selected":{"type":"object","name":"Selection","id":"p1010","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1011"},"data":{"type":"map","entries":[["x",[5.0]],["y",[25.0]]]}}},"view":{"type":"object","name":"CDSView","id":"p1067","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1068"}}},"glyph":{"type":"object","name":"Scatter","id":"p1063","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"size":{"type":"value","value":8},"line_color":{"type":"value","value":"#1a7f37"},"fill_color":{"type":"value","value":"#1a7f37"}}},"nonselection_glyph":{"type":"object","name":"Scatter","id":"p1064","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"size":{"type":"value","value":8},"line_color":{"type":"value","value":"#1a7f37"},"line_alpha":{"type":"value","value":0.1},"fill_color":{"type":"value","value":"#1a7f37"},"fill_alpha":{"type":"value","value":0.1},"hatch_alpha":{"type":"value","value":0.1}}},"muted_glyph":{"type":"object","name":"Scatter","id":"p1065","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"size":{"type":"value","value":8},"line_color":{"type":"value","value":"#1a7f37"},"line_alpha":{"type":"value","value":0.2},"fill_color":{"type":"value","value":"#1a7f37"},"fill_alpha":{"type":"value","value":0.2},"hatch_alpha":{"type":"value","value":0.2}}}}}],"toolbar":{"type":"object","name":"Toolbar","id":"p1023","attributes":{"tools":[{"type":"object","name":"PanTool","id":"p1038"},{"type":"object","name":"WheelZoomTool","id":"p1039","attributes":{"renderers":"auto"}},{"type":"object","name":"BoxZoomTool","id":"p1040","attributes":{"overlay":{"type":"object","name":"BoxAnnotation","id":"p1041","attributes":{"syncable":false,"line_color":"black","line_alpha":1.0,"line_width":2,"line_dash":[4,4],"fill_color":"lightgrey","fill_alpha":0.5,"level":"overlay","visible":false,"left":{"type":"number","value":"nan"},"right":{"type":"number","value":"nan"},"top":{"type":"number","value":"nan"},"bottom":{"type":"number","value":"nan"},"left_units":"canvas","right_units":"canvas","top_units":"canvas","bottom_units":"canvas","handles":{"type":"object","name":"BoxInteractionHandles","id":"p1047","attributes":{"all":{"type":"object","name":"AreaVisuals","id":"p1046","attributes":{"fill_color":"white","hover_fill_color":"lightgray"}}}}}}}},{"type":"object","name":"SaveTool","id":"p1048"},{"type":"object","name":"ResetTool","id":"p1049"},{"type":"object","name":"HelpTool","id":"p1050"}]}},"toolbar_location":null,"left":[{"type":"object","name":"LinearAxis","id":"p1033","attributes":{"visible":false,"ticker":{"type":"object","name":"BasicTicker","id":"p1034","attributes":{"mantissas":[1,2,5]}},"formatter":{"type":"object","name":"BasicTickFormatter","id":"p1035"},"major_label_policy":{"type":"object","name":"AllLabels","id":"p1036"}}}],"below":[{"type":"object","name":"LinearAxis","id":"p1028","attributes":{"visible":false,"ticker":{"type":"object","name":"BasicTicker","id":"p1029","attributes":{"mantissas":[1,2,5]}},"formatter":{"type":"object","name":"BasicTickFormatter","id":"p1030"},"major_label_policy":{"type":"object","name":"AllLabels","id":"p1031"}}}],"center":[{"type":"object","name":"Grid","id":"p1032","attributes":{"visible":false,"axis":{"id":"p1028"}}},{"type":"object","name":"Grid","id":"p1037","attributes":{"visible":false,"dimension":1,"axis":{"id":"p1033"}}},{"type":"object","name":"Label","id":"p1069","attributes":{"text":"POINT (X=5.0)","padding":6,"text_font_size":"10pt","text_font_style":"bold","text_align":"center","background_fill_color":"white","background_fill_alpha":0.95,"border_line_color":"#d0d7de","x":5.0,"y":22.0}}],"background_fill_color":"#fafafa"}},{"type":"object","name":"Row","id":"p1083","attributes":{"children":[{"type":"object","name":"Slider","id":"p1076","attributes":{"js_property_callbacks":{"type":"map","entries":[["change:value",[{"type":"object","name":"CustomJS","id":"p1080","attributes":{"args":{"type":"map","entries":[["curve",{"id":"p1006"}],["point",{"id":"p1009"}],["tangent",{"id":"p1012"}],["slider",{"id":"p1076"}],["mode",{"type":"object","name":"Switch","id":"p1078","attributes":{"js_property_callbacks":{"type":"map","entries":[["change:active",[{"id":"p1080"}]]]},"active":true}}],["stats_x",{"id":"p1072"}],["stats_y",{"id":"p1073"}],["stats_g",{"id":"p1074"}],["stats_s",{"id":"p1075"}],["caption",{"id":"p1071"}],["chart_label",{"id":"p1070"}],["point_label",{"id":"p1069"}],["x_input",{"type":"object","name":"NumericInput","id":"p1077","attributes":{"js_property_callbacks":{"type":"map","entries":[["change:value",[{"type":"object","name":"CustomJS","id":"p1081","attributes":{"args":{"type":"map","entries":[["slider",{"id":"p1076"}]]},"code":"if (Math.abs(slider.value - cb_obj.value) &gt; 1e-6) slider.value = cb_obj.value;"}}]]]},"width":70,"value":5.0,"low":-5,"high":5,"mode":"float"}}]]},"code":"\\n    const x = slider.value;\\n    const cont = mode.active;\\n    const N = 800;\\n    const xs = new Array(N);\\n    const ys = new Array(N);\\n    for (let i = 0; i &lt; N; i++) {\\n        const v = -5 + (10 * i) / (N - 1);\\n        xs[i] = v;\\n        ys[i] = cont ? (v*v) : Math.floor(v*v);\\n    }\\n    curve.data = { x: xs, y: ys };\\n\\n    const y_at_x = cont ? (x*x) : Math.floor(x*x);\\n    point.data = { x: [x], y: [y_at_x] };\\n\\n    const slope = cont ? (2*x) : 0;\\n    const L = 0.5;\\n    tangent.data = {\\n        x: [x - L, x + L],\\n        y: [y_at_x - slope*L, y_at_x + slope*L],\\n    };\\n\\n    point_label.x = x;\\n    point_label.y = y_at_x - 3.0;\\n    point_label.text = `POINT (X=${x.toFixed(1)})`;\\n\\n    const stat = (lbl, val, color) =&gt;\\n        `&lt;div style=&#x27;display:inline-block; margin-right:28px; font-family:ui-monospace,monospace;&#x27;&gt;\\n           &lt;div style=&#x27;font-size:10px; color:#57606a; letter-spacing:0.7px;&#x27;&gt;${lbl}&lt;/div&gt;\\n           &lt;div style=&#x27;font-size:15px; font-weight:700; color:${color || \\"#24292f\\"};&#x27;&gt;${val}&lt;/div&gt;\\n         &lt;/div&gt;`;\\n    stats_x.text = stat(\\"INPUT X\\", x.toFixed(2));\\n    stats_y.text = stat(\\"OUTPUT Y\\", y_at_x.toFixed(2));\\n    stats_g.text = stat(\\"GRADIENT dy/dx\\", cont ? slope.toFixed(2) : \\"0.00\\",\\n                        cont ? \\"#24292f\\" : \\"#cf222e\\");\\n    stats_s.text = stat(\\"STATUS\\", cont ? \\"OPTIMIZING\\" : \\"STUCK\\",\\n                        cont ? \\"#1a7f37\\" : \\"#cf222e\\");\\n\\n    caption.text = cont\\n        ? \\"&lt;div style=&#x27;font-size:13px; color:#57606a; padding:4px 0;&#x27;&gt;Continuous Gradient: the slope changes smoothly. Gradient descent &#x27;feels&#x27; the curve and knows which direction to move.&lt;/div&gt;\\"\\n        : \\"&lt;div style=&#x27;font-size:13px; color:#57606a; padding:4px 0;&#x27;&gt;Discrete Gradient: the slope is zero on every step. Gradient descent has no signal to follow, so learning stalls.&lt;/div&gt;\\";\\n\\n    chart_label.text = cont\\n        ? \\"&lt;span style=&#x27;color:#57606a; font-size:12px;&#x27;&gt;\\u2197 Continuous Flow&lt;/span&gt;\\"\\n        : \\"&lt;span style=&#x27;color:#57606a; font-size:12px;&#x27;&gt;\\u25a3 Discrete Steps&lt;/span&gt;\\";\\n\\n    if (Math.abs(x_input.value - x) &gt; 1e-6) x_input.value = x;\\n    "}}]]]},"width":280,"title":"Input (x)","start":-5,"end":5,"value":5.0,"step":0.01}},{"id":"p1077"},{"type":"object","name":"Div","id":"p1079","attributes":{"text":"&lt;span style=&#x27;font-size:13px; margin-right:8px;&#x27;&gt;Continuous Mode&lt;/span&gt;"}},{"id":"p1078"}]}}]}}]}}';
        const render_items = [{"docid":"980253a2-952e-427c-9d26-411cd76a51f4","roots":{"p1084":"bc604083-e2b8-4c67-90c5-2fc988204369"},"root_ids":["p1084"]}];
        root.Bokeh.embed.embed_items(docs_json, render_items);
        }
        if (root.Bokeh !== undefined) {
          embed_document(root);
        } else {
          let attempts = 0;
          const timer = setInterval(function(root) {
            if (root.Bokeh !== undefined) {
              clearInterval(timer);
              embed_document(root);
            } else {
              attempts++;
              if (attempts > 100) {
                clearInterval(timer);
                console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing");
              }
            }
          }, 10, root)
        }
      })(window);
    });
  };
  if (document.readyState != "loading") fn();
  else document.addEventListener("DOMContentLoaded", fn);
})();
</script>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-autograd-widget-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Drag the slider to move <strong>x</strong>. Flip <strong>Continuous Mode</strong> off to see the same function snapped to integer outputs: the tangent flattens, the gradient reads 0.00, and STATUS changes from OPTIMIZING to STUCK. That flat gradient is exactly what autograd would hand back to your optimizer.
</figcaption>
</figure>
</div>
</div>
<p>On the float curve the tangent tilts smoothly with slope <img src="https://latex.codecogs.com/png.latex?2x">. On the integer-rounded function, the slope is zero on every flat step and undefined at every jump; those are the only two values the derivative can take. The derivative at a point is the limit of the slope as the neighborhood around it shrinks, and on an integer-valued function every neighborhood resolves to either a flat step or a vertical jump. There is no third case to converge to, and a gradient computed at either of these points carries no information about which direction lowers the loss.</p>
</section>
<section id="integer-casts-and-the-chain-rule" class="level2">
<h2 class="anchored" data-anchor-id="integer-casts-and-the-chain-rule">Integer Casts and the Chain Rule</h2>
<p>A network is a long chain of operations. Reverse-mode autograd computes the gradient of the loss with respect to any parameter by multiplying local gradients along that chain (covered in <a href="../../posts/mlsys/automatic-differentiation.html">Automatic Differentiation Demystified</a>), so one zero anywhere in the product zeros the entire product. A single integer-rounding step in the forward pass, explicit (<code>.to(torch.int)</code>, <code>torch.round</code>) or implicit (<code>argmax</code>, a boolean mask from <code>&gt;</code> or <code>&lt;</code>), has local gradient zero almost everywhere, and zeros the gradient for every parameter upstream of it.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb1-2">x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.5</span>], requires_grad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb1-3">y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).to(torch.int64).to(torch.float32)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># round-trip through int</span></span>
<span id="cb1-4">loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">5.0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb1-5">loss.backward()</span>
<span id="cb1-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(x.grad)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># tensor([0.])</span></span></code></pre></div></div>
<p>The loss computes, the backward pass runs without error, and <code>x.grad</code> is zero. Training will proceed; the upstream parameters won’t move. The structure is the same as a dead ReLU, except a dead ReLU only kills the gradient for negative pre-activations; an integer cast represents a function with no nonzero gradient anywhere it’s defined.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Integer Operations in the Forward Pass Zero the Gradient
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>.to(torch.int)</code>, <code>torch.round</code>, <code>torch.floor</code>, <code>torch.ceil</code>, <code>argmax</code>, and boolean masks from <code>&gt;</code> / <code>&lt;</code> all have zero or undefined local gradient. Each one zeros the gradient for every parameter upstream of it, and the loss continues to compute as if nothing were wrong.</p>
</div>
</div>
</section>
<section id="the-update-granularity-problem" class="level2">
<h2 class="anchored" data-anchor-id="the-update-granularity-problem">The Update-Granularity Problem</h2>
<p>The second reason is independent of calculus. The SGD update is <img src="https://latex.codecogs.com/png.latex?w%20%5Cleftarrow%20w%20-%20%5Ceta%20%5Ccdot%20g">, and <img src="https://latex.codecogs.com/png.latex?%5Ceta%20%5Ccdot%20g"> is typically on the order of <img src="https://latex.codecogs.com/png.latex?10%5E%7B-3%7D"> to <img src="https://latex.codecogs.com/png.latex?10%5E%7B-5%7D"> per step (the update rule and its variants are the subject of <a href="../../posts/optimization/gradient-descent.html">Gradient Descent and Its Variants</a>). On a float weight that nudges it; on an integer weight <code>5 - 0.0015</code> rounds straight back to <code>5</code>. The weight doesn’t move until a gradient arrives that’s large enough to round to at least 1, which is orders of magnitude above any stable learning rate.</p>
<p>This is why low-precision training (bfloat16, fp8) still uses <em>floating</em> point. The issue isn’t the bit count, it’s continuity: a float grid is dense enough that the rounding error stays below the update magnitude; an integer grid is not.</p>
</section>
<section id="safe-uses-of-integer-tensors" class="level2">
<h2 class="anchored" data-anchor-id="safe-uses-of-integer-tensors">Safe Uses of Integer Tensors</h2>
<p>The rule is about the gradient path, not about integer tensors as such. Integer tensors appear in every real model and they’re fine, as long as nothing on the gradient path passes through them:</p>
<ul>
<li><strong>Embedding indices</strong> (<code>nn.Embedding</code>): the integer selects a row; the gradient flows back to the row’s float weights, not the index.</li>
<li><strong>Class labels</strong> (<code>nn.CrossEntropyLoss</code> targets): a lookup into the logits, not a parameter.</li>
<li><strong>Masks, <code>gather</code> indices, segment IDs</strong>: data, not values on the gradient path.</li>
</ul>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Integer Tensors Are Fine as Indices, Never as Values
</div>
</div>
<div class="callout-body-container callout-body">
<p>When a tensor’s dtype is integer, autograd treats it as an index and drops it from the graph. That is correct for lookups and labels, and silently wrong if it happens to a weight or an activation.</p>
</div>
</div>
</section>
<section id="quantization-and-the-straight-through-estimator" class="level2">
<h2 class="anchored" data-anchor-id="quantization-and-the-straight-through-estimator">Quantization and the Straight-Through Estimator</h2>
<p>The obvious counterexample is quantized networks, which use integer weights at inference. They train as floats. In quantization-aware training the weights are kept in <code>float32</code>; the forward pass simulates the quantization by rounding, but the backward pass replaces the round’s true local gradient (zero almost everywhere) with 1, the <strong>straight-through estimator</strong> (Bengio et al., 2013). Gradients flow through the rounding as if it weren’t there.</p>
<p>The STE is not calculus; it is a known approximation, used because the round’s true derivative (zero almost everywhere) is useless. It is the workaround for the rule, not an exception to it.</p>
</section>
<section id="key-takeaways" class="level2">
<h2 class="anchored" data-anchor-id="key-takeaways">Key Takeaways</h2>
<ol type="1">
<li><strong>Derivatives are defined on continuous spaces.</strong> On an integer-valued function, the derivative is zero on every flat segment and undefined at every jump, so autograd has nothing useful to compute and pass to the optimizer.</li>
<li><strong>One integer cast zeros the gradient for everything upstream of it.</strong> The chain rule multiplies local gradients along the network; a single zero in the product is enough to kill the whole thing. The loss continues to compute, so the failure is silent.</li>
<li><strong>Even setting calculus aside, the SGD update fails on integers.</strong> Step sizes of <img src="https://latex.codecogs.com/png.latex?10%5E%7B-3%7D"> to <img src="https://latex.codecogs.com/png.latex?10%5E%7B-5%7D"> round straight back to the previous integer; the weight never moves.</li>
<li><strong>Integer tensors are fine off the gradient path.</strong> Embedding indices, class labels, and masks are all valid uses. Autograd refuses <code>requires_grad=True</code> on integer dtypes precisely to keep them off the path.</li>
<li><strong>Quantization works by training in float and quantizing afterwards.</strong> The straight-through estimator substitutes a usable gradient for the round’s true (useless) one, a known approximation, not a counterexample to the rule.</li>
</ol>
</section>
<section id="resources" class="level2">
<h2 class="anchored" data-anchor-id="resources">Resources</h2>
<ul>
<li><a href="../../posts/mlsys/automatic-differentiation.html">Automatic Differentiation Demystified</a>.</li>
<li><a href="https://pytorch.org/docs/stable/notes/autograd.html">PyTorch Autograd Mechanics</a>.</li>
<li><strong>Bengio, Y., Léonard, N., &amp; Courville, A.</strong> (2013). <a href="https://arxiv.org/abs/1308.3432">Estimating or Propagating Gradients Through Stochastic Neurons for Conditional Computation</a>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Deep Learning</category>
  <category>Machine Learning</category>
  <guid>https://imaddabbura.github.io/posts/dl/why-autograd-needs-floats.html</guid>
  <pubDate>Tue, 21 Apr 2026 05:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/dl/images/autograd-floats-img.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>Improving ML Systems with the Theory of Constraints</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/mlsys/improving-mlsys-theory-of-constraint.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<p><a href="images/network-anomaly-detection-toc.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://imaddabbura.github.io/posts/mlsys/images/network-anomaly-detection-toc.jpg" class="img-fluid"></a></p>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>You burn six months “optimizing.” Swap in transformers. Squeeze another +<img src="https://latex.codecogs.com/png.latex?0.5%5C%25"> accuracy. Rewrite the feature pipeline. Add a shiny GPU cluster. And still: alert fatigue, missed incidents, and latency that kills real-time response.</p>
<p>This pattern shows up everywhere in production ML. Fraud teams add transaction features that never reduce false positives. Recommendation engines get fancier models that don’t move click-through rates. Forecasting pipelines gain complexity without improving planning accuracy. Parts get optimized with the hope that the full system would improve, which almost never happened.</p>
<p>This post gives you a systematic method to break out of the cycle. It’s based on the Theory of Constraints, originally developed for manufacturing, but a natural fit for ML systems. We’ll use a network anomaly detection system as our running example, but the playbook works for any ML system in production.</p>
</section>
<section id="the-theory-of-constraints-in-brief" class="level2">
<h2 class="anchored" data-anchor-id="the-theory-of-constraints-in-brief">The Theory of Constraints in Brief</h2>
<p>The traditional approach to improving ML systems is based on a seemingly logical but flawed assumption: if you improve each component, the whole system improves. It doesn’t. <em>The sum of all local improvements doesn’t give you a system improvement.</em></p>
<p>The breakthrough insight, from Eli Goldratt’s <em>The Goal</em> (1984) and made operational by Alan Barnard’s pairing method, is simple: every system has exactly one constraint at any given moment, the single resource or stage you don’t have enough of. That constraint sets the ceiling for the entire system. Improving anything else delivers diminishing-to-zero returns.</p>
<p>In a serial pipeline (which is what most ML systems are), throughput equals the throughput of the slowest stage. If your feature extraction handles 30K records/sec and everything else handles 100K, the system does 30K. Making inference 10x faster OR doubling ingest capacity would still give us 30K records/sec.&nbsp;Only improving the bottleneck stage moves the number.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/amdahl-law-optimization.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure 1: Amdahl’s Law applied to the pipeline. Making model inference (20% of runtime) 2x faster yields about 11% end-to-end improvement; making feature extraction (70%, the constraint) 2x faster yields about 35%. Only the constraint moves the system number."><img src="https://imaddabbura.github.io/posts/mlsys/images/amdahl-law-optimization.svg" class="img-fluid figure-img" alt="Figure 1: Amdahl’s Law applied to the pipeline. Making model inference (20% of runtime) 2x faster yields about 11% end-to-end improvement; making feature extraction (70%, the constraint) 2x faster yields about 35%. Only the constraint moves the system number."></a></p>
<figcaption><strong>Figure 1:</strong> Amdahl’s Law applied to the pipeline. Making model inference (20% of runtime) 2x faster yields about 11% end-to-end improvement; making feature extraction (70%, the constraint) 2x faster yields about 35%. Only the constraint moves the system number.</figcaption>
</figure>
</div>
<p>Barnard turns this into a strict chain of focused pairings. Each pairing links a WHAT (what you need) to a HOW (how to get it), maintaining a one-to-one relationship that keeps focus razor-sharp:</p>
<ol type="1">
<li><strong>Goal → Constraint</strong>: WHAT do I want? More of the Goal. HOW? By getting more of the Constraint, the single resource I don’t have enough of.</li>
<li><strong>Constraint → Problem</strong>: WHAT limits the Constraint? The one Problem causing at least 50% of the gap.</li>
<li><strong>Problem → Conflict</strong>: WHY hasn’t the Problem been solved? Because it’s an unresolved Conflict between two necessary-but-competing approaches.</li>
<li><strong>Conflict → Innovation</strong>: HOW do I resolve it? With an Innovation that captures the Pros of <em>both</em> the current approach and the new idea. The aim is all the Pros, but some tradeoffs may remain.</li>
<li><strong>Innovation → Experiment</strong>: HOW do I know it works? With a Minimally Viable Experiment, before building anything.</li>
</ol>
<p>The five how-to steps below translate these pairings into ML-systems language. Step 1 defines the Goal (SLOs) and finds the Constraint (bottleneck). Step 2 uncovers the Problem (root cause). Step 3 maps the Conflict (hidden tradeoff). Step 4 designs the Innovation. Step 5 runs the Experiment.</p>
<p>So why does this matter for ML specifically? Because ML pipelines are flow systems: ingest → features → inference → action. They have measurable stages with capacity limits. And they accumulate complexity over time: teams add features, models, and infrastructure without ever removing anything. This makes them natural candidates for constraint-based thinking. But ML teams rarely think this way, because they’re trained to optimize <em>models</em>, not <em>systems</em>.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/ml-pipeline-weak-constraint.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure 2: The pipeline as a chain. Throughput is capped by the weakest link, Feature-Tier2 at 30K/s, no matter how much faster the other stages run."><img src="https://imaddabbura.github.io/posts/mlsys/images/ml-pipeline-weak-constraint.svg" class="img-fluid figure-img" alt="Figure 2: The pipeline as a chain. Throughput is capped by the weakest link, Feature-Tier2 at 30K/s, no matter how much faster the other stages run."></a></p>
<figcaption><strong>Figure 2:</strong> The pipeline as a chain. Throughput is capped by the weakest link, Feature-Tier2 at 30K/s, no matter how much faster the other stages run.</figcaption>
</figure>
</div>
</section>
<section id="step-1-define-your-goal-and-find-the-bottleneck" class="level2">
<h2 class="anchored" data-anchor-id="step-1-define-your-goal-and-find-the-bottleneck">Step 1: Define Your Goal and Find the Bottleneck</h2>
<p>Before you can find the bottleneck, you need to define what success actually means in numbers (predefined metrics). And before you can fix the bottleneck, you need to know which stage is actually holding the system back. This step covers both.</p>
<section id="set-your-slos" class="level3">
<h3 class="anchored" data-anchor-id="set-your-slos">Set Your SLOs</h3>
<p>“Detect anomalies,” “reduce fraud,” and “improve recommendations” aren’t goals. Without measurable targets, every team member optimizes for a different thing, and you can’t tell whether you’re constrained by latency, precision, coverage, or something else entirely.</p>
<p>The fix is Service Level Objectives (specific, measurable thresholds tied to business outcomes):</p>
<table class="caption-top table">
<colgroup>
<col style="width: 34%">
<col style="width: 43%">
<col style="width: 21%">
</colgroup>
<thead>
<tr class="header">
<th>SLO Dimension</th>
<th>What It Measures</th>
<th>Fill In</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Time-to-Decision (TTD)</strong></td>
<td>How fast the system produces an actionable output</td>
<td>p95 ≤ ___</td>
</tr>
<tr class="even">
<td><strong>Decision Budget</strong></td>
<td>How many outputs a human can realistically handle</td>
<td>≤ ___ per day</td>
</tr>
<tr class="odd">
<td><strong>Outcome-Weighted Performance</strong></td>
<td>Accuracy weighted by business impact, not volume</td>
<td>≥ ___%</td>
</tr>
<tr class="even">
<td><strong>Coverage</strong></td>
<td>Fraction of relevant events actually processed</td>
<td>≥ ___%</td>
</tr>
<tr class="odd">
<td><strong>Data Loss</strong></td>
<td>Events dropped or degraded in transit</td>
<td>≤ ___%</td>
</tr>
</tbody>
</table>
<p>These five dimensions force hard conversations. A model with 99% accuracy but 30-minute detection latency fails the TTD target. A model with perfect precision but 500 daily alerts fails the decision budget. The SLOs define the feasible region, and crucially, reveal <em>what’s blocking you</em> from reaching it.</p>
<p>Here’s how our network anomaly detection system instantiated these:</p>
<ul>
<li><strong>TTD</strong>: p95 ≤ 5 minutes from event to alert</li>
<li><strong>Alert Budget</strong>: ≤ 10 analyst-actionable alerts/day</li>
<li><strong>Incident-Weighted Recall</strong>: ≥ 90%</li>
</ul>
<p>The same template applies to other domains. A fraud detection team might set TTD ≤ 200ms with ≤ 50 manual reviews/day. A recommendation system might target TTD ≤ 100ms with CTR-weighted precision ≥ X%.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/slo-hierarchy.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Figure 3: SLOs cascade from a business goal down to technical metrics. Each layer’s target (time, quality, coverage) constrains the ones below it."><img src="https://imaddabbura.github.io/posts/mlsys/images/slo-hierarchy.svg" class="img-fluid figure-img" alt="Figure 3: SLOs cascade from a business goal down to technical metrics. Each layer’s target (time, quality, coverage) constrains the ones below it."></a></p>
<figcaption><strong>Figure 3:</strong> SLOs cascade from a business goal down to technical metrics. Each layer’s target (time, quality, coverage) constrains the ones below it.</figcaption>
</figure>
</div>
<p>When defining SLOs, involve the people who <em>use</em> the system’s outputs, not just the team that builds it. Security analysts, operations teams, business stakeholders. When they disagree (and they will: security wants recall, ops wants fewer alerts), the SLOs make the tradeoff explicit rather than hiding it inside model thresholds.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Pitfall: Vanity Metrics Over Business Outcomes
</div>
</div>
<div class="callout-body-container callout-body">
<p>Teams optimize metrics that sound impressive but don’t connect to business value. “99.9% precision” means nothing if you’re missing 90% of incidents. “Processing 1M events/second” is irrelevant if decisions take 30 minutes. In our case, we celebrated achieving 99% detection rate on port scans (which the SOC ignored anyway) while missing lateral movement using legitimate credentials. Define SLOs tied to outcomes, not to model scorecards.</p>
</div>
</div>
<p>SLOs don’t just measure success; they <em>reveal</em> what’s blocking it. If you can’t meet your TTD target, the bottleneck is somewhere in your latency path. If you can’t meet your alert budget, the bottleneck is in precision or triage capacity. Now let’s find exactly where.</p>
</section>
<section id="find-the-bottleneck" class="level3">
<h3 class="anchored" data-anchor-id="find-the-bottleneck">Find the Bottleneck</h3>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Spotting the Constraint
</div>
</div>
<div class="callout-body-container callout-body">
<p>Your ML pipeline is a series of stages, each with a capacity ceiling. The stage with the lowest effective capacity is your constraint: it sets the ceiling for the entire system. Everything upstream queues up; everything downstream sits idle. Barnard’s memorable shortcut: <em>“Check what you’re waiting for. Where’s the backlog?”</em></p>
</div>
</div>
<p>With SLOs defined, you can systematically measure where the system breaks down. Build a <strong>constraint ledger</strong>, a table measuring capacity, utilization, latency, queue depth, and top failure mode at each pipeline stage:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 8%">
<col style="width: 22%">
<col style="width: 15%">
<col style="width: 15%">
<col style="width: 15%">
<col style="width: 21%">
</colgroup>
<thead>
<tr class="header">
<th>Stage</th>
<th style="text-align: right;">Capacity (rec/s)</th>
<th style="text-align: right;">Utilization</th>
<th style="text-align: right;">p95 Latency</th>
<th style="text-align: right;">Queue Depth</th>
<th>Top Failure Mode</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Ingest</td>
<td style="text-align: right;">100K</td>
<td style="text-align: right;">60%</td>
<td style="text-align: right;">2ms</td>
<td style="text-align: right;">0</td>
<td>burst loss</td>
</tr>
<tr class="even">
<td>Feature-Tier1</td>
<td style="text-align: right;">100K</td>
<td style="text-align: right;">65%</td>
<td style="text-align: right;">5ms</td>
<td style="text-align: right;">0</td>
<td>cache miss</td>
</tr>
<tr class="odd">
<td><strong>Feature-Tier2</strong></td>
<td style="text-align: right;"><strong>30K</strong></td>
<td style="text-align: right;"><strong>95%</strong></td>
<td style="text-align: right;"><strong>50ms</strong></td>
<td style="text-align: right;"><strong>1.2K</strong></td>
<td><strong>window skew</strong></td>
</tr>
<tr class="even">
<td>Feature-Tier3</td>
<td style="text-align: right;">10K</td>
<td style="text-align: right;">20%</td>
<td style="text-align: right;">200ms</td>
<td style="text-align: right;">0</td>
<td>cold start</td>
</tr>
<tr class="odd">
<td>Inference</td>
<td style="text-align: right;">50K</td>
<td style="text-align: right;">40%</td>
<td style="text-align: right;">10ms</td>
<td style="text-align: right;">0</td>
<td>batch sizing</td>
</tr>
<tr class="even">
<td>Alerting</td>
<td style="text-align: right;">1K</td>
<td style="text-align: right;">10%</td>
<td style="text-align: right;">100ms</td>
<td style="text-align: right;">0</td>
<td>dedup thrash</td>
</tr>
</tbody>
</table>
<p>The diagnostic pattern is simple: <strong>high utilization + growing queue = bottleneck</strong>. Feature-Tier2 jumps out: 95% utilization with a queue of 1.2K while other stages sit at 10–65%. During peak periods, the system is forced to either sample traffic (missing attacks), queue records (violating TTD), or drop features (hurting accuracy). The model never sees complete feature representations because feature extraction can’t keep pace.</p>
<p>Build this table for your own system. The constraint is almost always obvious once you measure.</p>
<blockquote class="blockquote">
<p><strong>Capacity conversion</strong>: 10 Gbps network traffic ≈ 100K flows/sec.&nbsp;1M daily e-commerce orders ≈ 12/sec average, 50/sec peak. 10K IoT sensors at 1Hz ≈ 10K records/sec.</p>
</blockquote>
</section>
<section id="validate-before-you-invest" class="level3">
<h3 class="anchored" data-anchor-id="validate-before-you-invest">Validate Before You Invest</h3>
<p>Before building anything, run a 24-hour experiment: temporarily throw 3x resources at your suspected bottleneck. If system-level metrics improve dramatically, you’ve found the right constraint. If not, look elsewhere. This experiment costs a day; building the wrong solution costs months.</p>
<p>We provisioned 3x compute for Feature-Tier2, enabling 90K records/sec.&nbsp;The results were dramatic: detection time dropped, false positives decreased (the model makes better decisions with complete feature sets), and we nearly met our SLOs. No other improvement (not model accuracy, not infrastructure, not threshold tuning) would have achieved this.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/before-after-dashboard.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Figure 4: The 24-hour validation test. Provisioning 3x compute for Feature-Tier2 alone lifted throughput from 30K to 90K rec/s and detection-within-5-minutes from 47% to 94%, confirming it was the true constraint."><img src="https://imaddabbura.github.io/posts/mlsys/images/before-after-dashboard.svg" class="img-fluid figure-img" alt="Figure 4: The 24-hour validation test. Provisioning 3x compute for Feature-Tier2 alone lifted throughput from 30K to 90K rec/s and detection-within-5-minutes from 47% to 94%, confirming it was the true constraint."></a></p>
<figcaption><strong>Figure 4:</strong> The 24-hour validation test. Provisioning 3x compute for Feature-Tier2 alone lifted throughput from 30K to 90K rec/s and detection-within-5-minutes from 47% to 94%, confirming it was the true constraint.</figcaption>
</figure>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Pitfall: Premature Model Optimization
</div>
</div>
<div class="callout-body-container callout-body">
<p>Teams spend months improving model accuracy while system-level metrics stagnate. The pipeline logic explains why: if the constraint isn’t in the model, then making the model infinitely better has zero impact on system throughput. We spent three months experimenting with transformer architectures for 2% accuracy improvement, while 70% of traffic was never analyzed due to feature extraction bottlenecks. The transformer detected sophisticated attacks brilliantly, on the 30% of traffic it actually saw. Always validate the constraint before optimizing.</p>
</div>
</div>
<p><strong>Key takeaway:</strong> The constraint is the only thing worth optimizing right now. Everything else is rearranging deck chairs.</p>
</section>
</section>
<section id="step-2-understand-why-its-stuck" class="level2">
<h2 class="anchored" data-anchor-id="step-2-understand-why-its-stuck">Step 2: Understand Why It’s Stuck</h2>
<p>You’ve found the bottleneck. Now resist the urge to fix the surface symptom. “Feature extraction is slow” is a temperature reading, not a diagnosis. You need the underlying cause, because the cause determines the cure.</p>
<section id="five-whys-with-evidence" class="level3">
<h3 class="anchored" data-anchor-id="five-whys-with-evidence">Five Whys: With Evidence</h3>
<p>The Five Whys technique is simple: ask “why” repeatedly until you reach a root cause, but <em>validate each answer with evidence</em> before proceeding to the next. Unvalidated whys lead to plausible-sounding but wrong root causes.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/five-whys.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="Figure 5: Five Whys on the bottleneck, each answer validated with evidence. The chain bottoms out at an organizational root cause, not a technical one."><img src="https://imaddabbura.github.io/posts/mlsys/images/five-whys.svg" class="img-fluid figure-img" alt="Figure 5: Five Whys on the bottleneck, each answer validated with evidence. The chain bottoms out at an organizational root cause, not a technical one."></a></p>
<figcaption><strong>Figure 5:</strong> Five Whys on the bottleneck, each answer validated with evidence. The chain bottoms out at an organizational root cause, not a technical one.</figcaption>
</figure>
</div>
<p>Here’s how this played out for our Feature-Tier2 bottleneck:</p>
<ol type="1">
<li><strong>Why</strong> is Feature-Tier2 at 95% utilization? → It computes 47 features per record. <em>(Validated: profiling shows 89% of computation in 12% of features)</em></li>
<li><strong>Why</strong> so many features? → Designed for offline research with unlimited compute. <em>(Validated: 31 features contribute &lt;0.1% to decisions)</em></li>
<li><strong>Why</strong> no production constraints in the design? → Development was disconnected from deployment. <em>(Validated: git history shows features added without removal)</em></li>
<li><strong>Why</strong> disconnected? → ML team and platform team operate in silos. <em>(Validated: team interviews confirm no shared requirements)</em></li>
<li><strong>Why</strong> silos? → No ownership of end-to-end system performance.</li>
</ol>
<p>Notice where we ended up: the root cause isn’t technical; it’s organizational.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/iceberg-visible-constraint.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="Figure 6: The visible constraint (Feature-Tier2 throughput) sits above technical, analytical, process, and cultural causes. Fixing the surface symptom without the causes beneath it lets the bottleneck return."><img src="https://imaddabbura.github.io/posts/mlsys/images/iceberg-visible-constraint.svg" class="img-fluid figure-img" alt="Figure 6: The visible constraint (Feature-Tier2 throughput) sits above technical, analytical, process, and cultural causes. Fixing the surface symptom without the causes beneath it lets the bottleneck return."></a></p>
<figcaption><strong>Figure 6:</strong> The visible constraint (Feature-Tier2 throughput) sits above technical, analytical, process, and cultural causes. Fixing the surface symptom without the causes beneath it lets the bottleneck return.</figcaption>
</figure>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Common Root Causes in ML Systems: Check Which Applies
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>Feature Explosion</strong>: Teams extract every conceivable signal because “it might help.” Features grow monotonically: each has an advocate, none has a removal date. Most provide redundant information.</li>
<li><strong>Multi-granularity Overhead</strong>: Computing signals at every timescale (seconds, minutes, hours, days) when most decisions only need one. Common in anomaly detection, fraud, and demand forecasting.</li>
<li><strong>Stale Reference Data</strong>: Maintaining expensive rolling statistics (baselines, embeddings, aggregates) for thousands of entities, even though most change negligibly between updates. The recomputation cost dwarfs the information gained.</li>
</ul>
</div>
</div>
<p>If your Five Whys keep ending at technical causes, go one more level. The technical problem often has an organizational parent: siloed teams, misaligned incentives, no end-to-end ownership. These patterns aren’t unique to our system. Fraud detection, recommendations, and forecasting all exhibit the same failure modes.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Pitfall: Feature Creep Without Cost Analysis
</div>
</div>
<div class="callout-body-container callout-body">
<p>Feature counts grow monotonically because each has an advocate who remembers when it caught something. Our system grew from 50 to 247 features over two years. Analysis showed 180 contributed &lt;0.1% to decisions but consumed 60% of computation. Track a <strong>feature value score</strong> (importance divided by computational cost) and require cost-benefit analysis for new features.</p>
</div>
</div>
<p><strong>Key takeaway:</strong> Root causes are usually organizational, not algorithmic. If you fix the technical symptom without fixing the organizational cause, the symptom will return.</p>
</section>
</section>
<section id="step-3-see-the-hidden-tradeoff" class="level2">
<h2 class="anchored" data-anchor-id="step-3-see-the-hidden-tradeoff">Step 3: See the Hidden Tradeoff</h2>
<p>You know the root cause. So why hasn’t anyone fixed it? Almost always, it’s because the problem is an unresolved conflict, and people are stuck choosing between two approaches that both seem necessary.</p>
<p>Barnard puts it precisely: <em>any problem can be defined as an unresolved conflict.</em> In our case, the Feature-Tier2 bottleneck persists because of a fundamental tension:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/feature-extraction-conflict.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-8" title="Figure 7: The conflict as two competing requirements. A rich feature set buys 94% recall but only 30K rec/s; a minimal set handles 100K rec/s at 61% recall."><img src="https://imaddabbura.github.io/posts/mlsys/images/feature-extraction-conflict.svg" class="img-fluid figure-img" alt="Figure 7: The conflict as two competing requirements. A rich feature set buys 94% recall but only 30K rec/s; a minimal set handles 100K rec/s at 61% recall."></a></p>
<figcaption><strong>Figure 7:</strong> The conflict as two competing requirements. A rich feature set buys 94% recall but only 30K rec/s; a minimal set handles 100K rec/s at 61% recall.</figcaption>
</figure>
</div>
<p>We need <strong>rich feature analysis</strong> for accurate detection of sophisticated attacks. We <em>also</em> need <strong>efficient processing</strong> for real-time response and cost control. These seem to contradict each other, so the team oscillates: add features after a missed attack, remove features after a performance degradation. Two years later, they’re exactly where they started.</p>
<section id="why-teams-get-stuck" class="level3">
<h3 class="anchored" data-anchor-id="why-teams-get-stuck">Why Teams Get Stuck</h3>
<p>Barnard identifies two failure modes that keep teams trapped in these oscillations:</p>
<ul>
<li><strong>Getting stuck / procrastinating</strong>: Exaggerated fears (fear of losing what the current approach does well, or fear of the effort and risk required to change). (“If we remove features, we’ll miss attacks.”)</li>
<li><strong>Overreacting / jumping to conclusions</strong>: Exaggerated frustration with the current approach’s downsides, or exaggerated expectations of a new solution. (“Let’s just throw out all the expensive features and rely on the model.”)</li>
</ul>
<p>Most ML teams alternate between these two modes without recognizing the pattern.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Pitfall: Alert Budget Myopia
</div>
</div>
<div class="callout-body-container callout-body">
<p>A textbook case of oscillation: facing missed incidents, teams lower thresholds (overreacting). This floods analysts with alerts, who start ignoring them, leading to <em>more</em> missed incidents, which triggers another round of threshold lowering. Little’s Law makes the math concrete: L = λW. If analysts can investigate 50 alerts/day and each takes 45 minutes, that’s the hard capacity ceiling. No threshold change can overcome it. This is the precision/coverage conflict manifesting as a vicious cycle.</p>
</div>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/alert-fatigue.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-9" title="Figure 8: The alert-fatigue cycle. Lowering thresholds to catch more attacks floods analysts, who begin ignoring alerts, which causes more misses and another round of threshold lowering."><img src="https://imaddabbura.github.io/posts/mlsys/images/alert-fatigue.svg" class="img-fluid figure-img" alt="Figure 8: The alert-fatigue cycle. Lowering thresholds to catch more attacks floods analysts, who begin ignoring alerts, which causes more misses and another round of threshold lowering."></a></p>
<figcaption><strong>Figure 8:</strong> The alert-fatigue cycle. Lowering thresholds to catch more attacks floods analysts, who begin ignoring alerts, which causes more misses and another round of threshold lowering.</figcaption>
</figure>
</div>
</section>
<section id="map-your-conflict" class="level3">
<h3 class="anchored" data-anchor-id="map-your-conflict">Map Your Conflict</h3>
<p>The breakthrough comes from asking: <em>what assumptions make this conflict seem unresolvable?</em> To find them, map the conflict explicitly:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode md code-with-copy"><code class="sourceCode markdown"><span id="cb1-1">We need <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">rich feature analysis</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> to achieve <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">accurate detection</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>.</span>
<span id="cb1-2">We need <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">efficient processing</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> to achieve <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">real-time response</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span>.</span>
<span id="cb1-3">These conflict because we assume:</span>
<span id="cb1-4"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  1. </span>All records need the same analysis depth</span>
<span id="cb1-5"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  2. </span>Features must be computed synchronously</span>
<span id="cb1-6"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  3. </span>One model handles all decisions</span>
<span id="cb1-7"></span>
<span id="cb1-8">Challenge each:</span>
<span id="cb1-9"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  - </span>Is assumption 1 always true? No, routine DNS queries</span>
<span id="cb1-10">    don't need the same scrutiny as connections to unknown IPs.</span>
<span id="cb1-11"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  - </span>Is assumption 2 always true? No, historical comparisons</span>
<span id="cb1-12">    could be asynchronous.</span>
<span id="cb1-13"><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">  - </span>Is assumption 3 always true? No, different attack types</span>
<span id="cb1-14">    could use specialized models.</span></code></pre></div></div>
<p>This template works for any ML conflict. A fraud detection team might write: “We need comprehensive transaction analysis AND sub-200ms decisions. Hidden assumption: every transaction needs the same analysis depth.” A recommendation team: “We need deep personalization AND instant page load. Hidden assumption: personalization must happen at request time.”</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Common ML Conflicts
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>Accuracy vs Latency</strong>: Complex models are more accurate but slower</li>
<li><strong>Precision vs Coverage</strong>: Tight thresholds reduce false positives but miss edge cases</li>
<li><strong>Real-time vs Historical Context</strong>: Immediate response vs rich contextual analysis</li>
<li><strong>Generic vs Specific Models</strong>: Broad coverage vs environment-specific accuracy</li>
</ul>
</div>
</div>
<p><strong>Key takeaway:</strong> The tradeoff that’s blocking you is almost never fundamental. It persists because of hidden assumptions. Find the assumption. Challenge it. The conflict evaporates.</p>
</section>
</section>
<section id="step-4-break-the-tradeoff" class="level2">
<h2 class="anchored" data-anchor-id="step-4-break-the-tradeoff">Step 4: Break the Tradeoff</h2>
<p>You’ve identified the assumptions propping up the conflict. Now comes the payoff: designing a solution that captures the Pros of both the current approach and the alternative.</p>
<p>The goal is to get as many Pros from both sides as possible. Sometimes you genuinely get all of them. More often, some tradeoffs remain: added complexity, operational overhead, calibration effort. The difference from compromise is that these residual cons are <em>deliberate and manageable</em>, not the paralyzing either/or that kept the team stuck. You’re not splitting the difference. You’re changing the game so the remaining tradeoffs feel trivial compared to where you started.</p>
<section id="the-thinking-process" class="level3">
<h3 class="anchored" data-anchor-id="the-thinking-process">The Thinking Process</h3>
<p>After mapping your conflict and challenging assumptions (Step 3), work through each challenged assumption systematically:</p>
<ol type="1">
<li><p><strong>Sketch the system without the assumption.</strong> If you challenged “all records need the same analysis depth,” draw the pipeline where they don’t. What would variable-depth processing look like? What decides the depth?</p></li>
<li><p><strong>Look for the four reusable patterns.</strong> Most ML system innovations are combinations of these:</p>
<ul>
<li><strong>Cascade filtering</strong>: cheap check first, expensive check only when needed. Applicable whenever most inputs are routine. (Fraud: score transactions with simple rules before running the full model. Recs: serve cached recommendations before running personalization.)</li>
<li><strong>Async enrichment</strong>: decide now, enrich later. Useful whenever decision speed and decision quality have different time horizons. (Generate an alert with basic info immediately; add forensic context over the next 30 seconds.)</li>
<li><strong>Confidence-based routing</strong>: let the model decide how much compute each input deserves. Turns a fixed-cost pipeline into an adaptive one. (High-confidence benign traffic exits at Tier-1; uncertain traffic escalates.)</li>
<li><strong>Feature caching</strong>: never compute the same thing twice across pipeline stages. Obvious but rarely implemented. (Features from early triage stages are reused in deep analysis; we achieved 84% cache hit rates.)</li>
</ul></li>
<li><p><strong>Check for async opportunities</strong>: what’s being computed <em>before</em> the decision that could move to <em>after</em>?</p></li>
<li><p><strong>Check for caching opportunities</strong>: what’s being computed repeatedly across stages, records, or time windows?</p></li>
</ol>
</section>
<section id="a-worked-example-progressive-analysis" class="level3">
<h3 class="anchored" data-anchor-id="a-worked-example-progressive-analysis">A Worked Example: Progressive Analysis</h3>
<p>In our case, the most load-bearing assumption was: <em>“All records need the same analysis depth.”</em> Once you challenge it, the architecture follows from the patterns above, cascade filtering with confidence-based routing between tiers:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 13%">
<col style="width: 22%">
<col style="width: 15%">
<col style="width: 28%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Tier</th>
<th>Features</th>
<th>Model</th>
<th>Traffic Seen</th>
<th>Latency</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Tier-1</strong>: Wire-speed Triage</td>
<td>5 cheap features</td>
<td>Logistic regression</td>
<td>100% (68% exits)</td>
<td>~3ms</td>
</tr>
<tr class="even">
<td><strong>Tier-2</strong>: Fast Analysis</td>
<td>25 features</td>
<td>Moderate</td>
<td>~32%</td>
<td>~15ms</td>
</tr>
<tr class="odd">
<td><strong>Tier-3</strong>: Deep Analysis</td>
<td>100 features</td>
<td>Complex</td>
<td>~4%</td>
<td>~100ms</td>
</tr>
<tr class="even">
<td><strong>Forensic</strong>: Full Analysis</td>
<td>All features</td>
<td>Exhaustive</td>
<td>&lt;1%</td>
<td>~500ms</td>
</tr>
</tbody>
</table>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/progressive-analysis-architecture.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-10" title="Figure 9: The progressive architecture. Cheap triage clears 70% of traffic at wire speed; only uncertain records escalate to the more expensive tiers."><img src="https://imaddabbura.github.io/posts/mlsys/images/progressive-analysis-architecture.svg" class="img-fluid figure-img" alt="Figure 9: The progressive architecture. Cheap triage clears 70% of traffic at wire speed; only uncertain records escalate to the more expensive tiers."></a></p>
<figcaption><strong>Figure 9:</strong> The progressive architecture. Cheap triage clears 70% of traffic at wire speed; only uncertain records escalate to the more expensive tiers.</figcaption>
</figure>
</div>
<p>Each stage outputs a prediction <em>and</em> a confidence score. High-confidence benign traffic exits immediately. Low confidence escalates. When stages experience backlog, confidence thresholds adjust dynamically: low-risk records defer to async processing during congestion, ensuring high-risk traffic always gets full analysis. And alerts are generated immediately with basic info, then progressively enriched over 30 seconds with connection context, historical patterns, and full forensics.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/feature-dependency.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-11" title="Figure 10: Feature caching across tiers. Computing base features once and reusing them downstream turns a 255ms full recompute into 85ms at an 84% cache hit rate."><img src="https://imaddabbura.github.io/posts/mlsys/images/feature-dependency.svg" class="img-fluid figure-img" alt="Figure 10: Feature caching across tiers. Computing base features once and reusing them downstream turns a 255ms full recompute into 85ms at an 84% cache hit rate."></a></p>
<figcaption><strong>Figure 10:</strong> Feature caching across tiers. Computing base features once and reusing them downstream turns a 255ms full recompute into 85ms at an 84% cache hit rate.</figcaption>
</figure>
</div>
</section>
<section id="fix-the-organization-too" class="level3">
<h3 class="anchored" data-anchor-id="fix-the-organization-too">Fix the Organization Too</h3>
<p>Remember: Step 2 told us the root cause was organizational (siloed teams, no end-to-end ownership, features added without production constraints). The progressive architecture only sticks if the organizational structure changes with it. We restructured so ML and platform teams share SLOs, and adding features now requires cross-team cost-benefit approval. Without this, the feature explosion that caused the original bottleneck would have returned within a year.</p>
<p><strong>Key takeaway:</strong> The innovation doesn’t have to be novel to the field. It has to be novel to <em>your</em> system. Progressive analysis is a known pattern; applying it to our specific bottleneck was the breakthrough. But the technical fix and the organizational fix are a package deal.</p>
</section>
</section>
<section id="step-5-prove-it-works" class="level2">
<h2 class="anchored" data-anchor-id="step-5-prove-it-works">Step 5: Prove It Works</h2>
<p>You’ve designed a solution on paper. Before you spend three months building it, spend two weeks proving the riskiest assumption.</p>
<p>An important distinction: a Minimally Viable Experiment (MVE) comes <em>before</em> a Minimally Viable Product (MVP). An MVP builds the smallest usable product. An MVE is smaller: it tests whether the core assumption behind the innovation is even valid. Don’t build anything until you’ve validated the assumption.</p>
<section id="identify-the-riskiest-assumption" class="level3">
<h3 class="anchored" data-anchor-id="identify-the-riskiest-assumption">Identify the Riskiest Assumption</h3>
<p>Ask: <em>what’s the single assumption that, if wrong, kills the entire approach?</em> For our progressive architecture, it was: “Can Tier-1 triage accurately identify benign traffic without missing attacks?” If lightweight features can’t reliably separate benign from suspicious, the whole cascade fails.</p>
<p>Design the smallest test that answers this question. We trained a logistic regression on 5 cheap features and tested on realistic data with known attacks:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1">tier1_features <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb2-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'src_reputation_score'</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pre-computed reputation</span></span>
<span id="cb2-3">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'dst_port'</span>,              <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Destination port number</span></span>
<span id="cb2-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'protocol'</span>,              <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># TCP/UDP/ICMP</span></span>
<span id="cb2-5">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'packet_rate'</span>,           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Packets per second</span></span>
<span id="cb2-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'byte_rate'</span>              <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Bytes per second</span></span>
<span id="cb2-7">]</span>
<span id="cb2-8"></span>
<span id="cb2-9">tier1_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> LogisticRegression(C<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb2-10">tier1_model.fit(X_train[tier1_features], y_train_benign)</span></code></pre></div></div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/mve-experiment-timeline.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-12" title="Figure 11: The 14-day minimum viable experiment, testing the single riskiest assumption before any production build."><img src="https://imaddabbura.github.io/posts/mlsys/images/mve-experiment-timeline.svg" class="img-fluid figure-img" alt="Figure 11: The 14-day minimum viable experiment, testing the single riskiest assumption before any production build."></a></p>
<figcaption><strong>Figure 11:</strong> The 14-day minimum viable experiment, testing the single riskiest assumption before any production build.</figcaption>
</figure>
</div>
</section>
<section id="results" class="level3">
<h3 class="anchored" data-anchor-id="results">Results</h3>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Metric</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Triage rate</td>
<td>68% identified as benign at Tier-1</td>
</tr>
<tr class="even">
<td>False negative rate</td>
<td>0% (no attacks missed)</td>
</tr>
<tr class="odd">
<td>Throughput</td>
<td>95K records/sec</td>
</tr>
<tr class="even">
<td>p95 latency</td>
<td>3ms per record</td>
</tr>
<tr class="odd">
<td>Cache hit rate</td>
<td>84% across stages</td>
</tr>
</tbody>
</table>
</section>
<section id="what-the-iterations-taught-us" class="level3">
<h3 class="anchored" data-anchor-id="what-the-iterations-taught-us">What the Iterations Taught Us</h3>
<p>The MVE revealed things we couldn’t have predicted from design alone:</p>
<ol type="1">
<li><p><strong>Confidence calibration</strong>: Initial triage was too conservative, with 32% of traffic passed to Tier-2 unnecessarily. The model lacked confidence on legitimate-but-unusual ports. Retraining with expanded examples achieved a 71% triage rate without missing attacks.</p></li>
<li><p><strong>Dynamic resource allocation</strong>: Fixed compute allocation caused bottlenecks when traffic patterns shifted. We implemented stages borrowing compute from idle stages, smoothing throughput across load profiles.</p></li>
<li><p><strong>Feature pruning</strong>: 15 Tier-3 features never influenced decisions in production. Removing them increased throughput 30% without affecting detection. Track a feature value score (importance / computational_cost) and prune ruthlessly.</p></li>
</ol>
</section>
<section id="production-rollout-checklist" class="level3">
<h3 class="anchored" data-anchor-id="production-rollout-checklist">Production Rollout Checklist</h3>
<ul>
<li><strong>Shadow mode</strong>: Run progressive pipeline parallel to existing system. Compare decisions, measure divergence. Success: no P1 incidents missed for one week.</li>
<li><strong>Canary (10%)</strong>: Route 10% of traffic through progressive pipeline. A/B test alert quality with analysts. Success: SLOs maintained, analyst preference ≥ baseline.</li>
<li><strong>Gradual expansion</strong>: 10% → 25% → 50% → 75%, holding each level for 48 hours. Automated rollback on any SLO violation.</li>
<li><strong>Full production</strong>: 100% with old system as instant fallback. Document runbooks, train operations team. Success: one week at 100% with all SLOs met.</li>
</ul>
</section>
<section id="gono-go-criteria" class="level3">
<h3 class="anchored" data-anchor-id="gono-go-criteria">Go/No-Go Criteria</h3>
<p>After the MVE, the decision is straightforward: does the riskiest assumption hold? If yes (the cascade correctly separates benign from suspicious), proceed to shadow mode. If the assumption fails, you haven’t wasted months; you’ve spent two weeks learning that you need a different innovation. Go back to Step 4 and challenge a different assumption.</p>
<p><strong>Key takeaway:</strong> A two-week MVE teaches more than two years of production experience. Test your riskiest assumption first.</p>
</section>
</section>
<section id="the-cycle-continues" class="level2">
<h2 class="anchored" data-anchor-id="the-cycle-continues">The Cycle Continues</h2>
<p>Here’s the part that surprises people: solving one constraint doesn’t fix the system forever. It reveals the <em>next</em> constraint. And that’s a feature, not a bug, because you always know exactly what to work on.</p>
<p>With Feature-Tier2 no longer the bottleneck, a new one emerged in our system: alert investigation. SOC analysts averaged 45 minutes per Tier-3 alert. This limited how many sophisticated attacks could be properly investigated. Applying the framework again:</p>
<ul>
<li><strong>Goal → Constraint</strong>: Reduce investigation time to 15 minutes while maintaining decision quality</li>
<li><strong>Constraint → Problem</strong>: Analysts manually correlate across multiple tools and data sources</li>
<li><strong>Problem → Conflict</strong>: Automated enrichment vs human judgment</li>
<li><strong>Conflict → Innovation</strong>: AI-assisted investigation that augments rather than replaces analysts</li>
<li><strong>Innovation → Experiment</strong>: Test on historical alerts with analyst feedback</li>
</ul>
<p>Each cycle makes the system more capable. Here’s where our NTA system ended up after one full pass through the framework:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Metric</th>
<th>Before</th>
<th>After</th>
<th>Change</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Detection time (p95)</td>
<td>47 min</td>
<td>3.2 min</td>
<td>15x faster</td>
</tr>
<tr class="even">
<td>Daily analyst alerts</td>
<td>847</td>
<td>11</td>
<td>98.7% reduction</td>
</tr>
<tr class="odd">
<td>Incidents missed/month</td>
<td>23</td>
<td>0</td>
<td>Eliminated</td>
</tr>
<tr class="even">
<td>Traffic coverage</td>
<td>30%</td>
<td>98%</td>
<td>Full visibility</td>
</tr>
<tr class="odd">
<td>Feature-Tier2 utilization</td>
<td>95%</td>
<td>42%</td>
<td>Headroom restored</td>
</tr>
</tbody>
</table>
<p>The constraint moved, from feature extraction to investigation to response automation, and each move represents the next opportunity for breakthrough improvement.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    A["&lt;b&gt;Goal &amp; Constraint&lt;/b&gt;&lt;br/&gt;SLOs + ledger"] --&gt; B["&lt;b&gt;Understand Why&lt;/b&gt;&lt;br/&gt;Root cause"]
    B --&gt; C["&lt;b&gt;Map Conflict&lt;/b&gt;&lt;br/&gt;Challenge assumptions"]
    C --&gt; D["&lt;b&gt;Innovate&lt;/b&gt;&lt;br/&gt;Best of both sides"]
    D --&gt; E["&lt;b&gt;Experiment&lt;/b&gt;&lt;br/&gt;MVE → rollout"]
    E --&gt; |"Constraint moves"| A

</pre>
</div>
<p></p><figcaption> The Theory of Constraints is a cycle, not a line</figcaption> </figure><p></p>
</div>
</div>
</div>
<p>The Theory of Constraints isn’t another optimization technique. It’s an operating system for continuous improvement. The constraint keeps moving, but so do you.</p>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>Most ML teams are stuck in optimization chaos: tuning components that don’t govern system performance. The Theory of Constraints gives you a way out: find the one bottleneck that sets the ceiling, understand why it’s stuck, and design an innovation that breaks the tradeoff instead of compromising on it.</p>
<p><em>At any moment, only one thing limits your system: Find it &amp; Fix it.</em></p>
<p>If you take one thing from this post, make it this: before your next “optimization” sprint, build the constraint ledger. Measure every stage. Find the row with high utilization and a growing queue. That’s where your effort yields the best results.</p>
</section>
<section id="references" class="level2">
<h2 class="anchored" data-anchor-id="references">References</h2>
<ul>
<li><strong>Goldratt, E. M.</strong> (1984). <a href="https://en.wikipedia.org/wiki/The_Goal_(novel)">The Goal: A Process of Ongoing Improvement</a>. <em>North River Press</em>.</li>
<li><strong>Chandola, V., Banerjee, A., &amp; Kumar, V.</strong> (2009). <a href="https://doi.org/10.1145/1541880.1541882">Anomaly Detection: A Survey</a>. <em>ACM Computing Surveys</em>, 41(3).</li>
<li><strong>Sommer, R. &amp; Paxson, V.</strong> (2010). <a href="https://www.icir.org/robin/papers/oakland10-ml.pdf">Outside the Closed World: On Using Machine Learning for Network Intrusion Detection</a>. <em>IEEE S&amp;P</em>.</li>
<li><strong>Sculley, D. et al.</strong> (2015). <a href="https://papers.nips.cc/paper/2015/hash/86df7dcfd896fcaf2674f757a2463eba-Abstract.html">Hidden Technical Debt in Machine Learning Systems</a>. <em>NeurIPS</em>.</li>
<li><strong>Ahmed, M., Mahmood, A., &amp; Hu, J.</strong> (2016). <a href="https://doi.org/10.1016/j.jnca.2015.11.016">A Survey of Network Anomaly Detection Techniques</a>. <em>Journal of Network and Computer Applications</em>, 60.</li>
<li><strong>Kleppmann, M.</strong> (2017). <a href="https://dataintensive.net/">Designing Data-Intensive Applications</a>. <em>O’Reilly</em>.</li>
<li><strong>Polyzotis, N. et al.</strong> (2018). <a href="https://doi.org/10.1145/3299887.3299891">Data Lifecycle Challenges in Production Machine Learning</a>. <em>SIGMOD Record</em>, 47(2).</li>
<li><strong>Paleyes, A., Urma, R., &amp; Lawrence, N.</strong> (2022). <a href="https://arxiv.org/abs/2011.09926">Challenges in Deploying Machine Learning: A Survey of Case Studies</a>. <em>ACM Computing Surveys</em>, 55(6).</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>ML Systems</category>
  <guid>https://imaddabbura.github.io/posts/mlsys/improving-mlsys-theory-of-constraint.html</guid>
  <pubDate>Sun, 21 Sep 2025 05:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/mlsys/images/ml-toc.png" medium="image" type="image/png" height="96" width="144"/>
</item>
<item>
  <title>Designing Machine Learning Systems from First Principles</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/mlsys/designing-ml-systems.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge growing">growing</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>Fitting a model is the most commoditized skill in machine learning. Gradient boosting on tabular data, a fine-tuned transformer on text: these are an afternoon of work, and the libraries that do them are free, fast, and written by people better at it than you or me. The modelling half of this field has been solved so thoroughly that it is now the easy part.</p>
<p>Everything after <code>model.fit()</code> has not been solved, and that is where most ML projects fail.</p>
<p>The usual response is a checklist. Monitor your features, version your data, watch for drift, etc. Every item is correct, but the list is unsatisfying because it tells you <em>what</em> to do without telling you <em>why those things and not a hundred others</em>.</p>
<p>But production failures are not a grab bag. They have exactly one shape, and once you can write that shape down, the checklist stops being something you memorize and becomes something you derive. Three things that fall out of it:</p>
<ul>
<li>A backend service that is slow 1% of the time, called 100 times per user request, makes <strong>63% of your requests slow</strong>. Your p99 is a property of your architecture, not of any service in it (the arithmetic is here).</li>
<li>A fraud model with 99% sensitivity <em>and</em> 99% specificity, deployed at a 0.1% base rate, is <strong>wrong nine times out of ten every time it fires</strong>. Nothing is broken and no amount of retraining changes it (the arithmetic is here).</li>
<li>Shipping the default 0.5 decision threshold is a silent assertion that a false alarm costs exactly as much as a missed fraud. In a simulation of two million transactions, that assertion costs <strong>6.3x more per transaction</strong> than the threshold the cost matrix implies (the arithmetic is here).</li>
</ul>
<p>None of these is a modelling problem, and none is visible in a holdout metric. They are all versions of the same underlying failure.</p>
<p>This guide comes from two places: a decade of leading and designing ML systems across smart devices, healthcare, and enterprise AI, which is where the learnings come from; and years of reading widely and working the ideas through from first principles. What I have tried to add is the derivations. Where the standard advice hands you a rule of thumb, I show you where it comes from, because a rule you can derive is a rule you also know when to break. And where I expected one thing and the measurement said another, I say so.</p>
<p>The next section works out the frame. Every section after it is an attack on one of the three gaps that frame names. By the end you should be able to:</p>
<ol type="1">
<li>Derive a tail-latency budget from your architecture instead of guessing one</li>
<li>Compute the decision threshold your cost matrix implies, and explain why the default is wrong</li>
<li>Name which of the three mismatches any given production failure belongs to</li>
<li>Choose a retraining cadence from measurement rather than from habit</li>
</ol>
<hr>
</section>
<section id="sec-mismatches" class="level2">
<h2 class="anchored" data-anchor-id="sec-mismatches">The Three Mismatches</h2>
<p>Training produces a function by fitting it to the data you have. Production runs that function on data you do not have yet, and the difference between those two distributions is where nearly everything goes wrong. That difference has a precise shape.</p>
<p>When you train, you choose <img src="https://latex.codecogs.com/png.latex?f"> to minimize an average loss over data you sampled:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Chat%7Bf%7D%20=%20%5Carg%5Cmin_%7Bf%7D%20%5C;%20%5Cfrac%7B1%7D%7Bn%7D%5Csum_%7Bi=1%7D%5E%7Bn%7D%20L_%7B%5Ctext%7Btrain%7D%7D%5Cbig(f(x_i),%5C,%20y_i%5Cbig),%20%5Cqquad%20(x_i,%20y_i)%20%5Csim%20D_%7B%5Ctext%7Btrain%7D%7D"></p>
<p>When you deploy, you are scored on something else entirely:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bwhat%20you%20actually%20pay%7D%20%5C;=%5C;%20%5Cmathbb%7BE%7D_%7B(x,y)%5C,%5Csim%5C,%20D_%7B%5Ctext%7Bserve%7D%7D(t)%7D%5CBig%5B%20L_%7B%5Ctext%7Bbusiness%7D%7D%5Cbig(f(x),%5C,%20y%5Cbig)%20%5CBig%5D"></p>
<p>Compare the two expressions. An expected risk has exactly two arguments, <strong>a distribution and a loss</strong>, so only two things can differ between training and serving. The distribution can differ in two ways, either right now or later:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Mismatch</th>
<th>Formally</th>
<th>Where it bites</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>1</strong></td>
<td><strong>Train <img src="https://latex.codecogs.com/png.latex?%5Cneq"> Serve</strong></td>
<td><img src="https://latex.codecogs.com/png.latex?D_%7B%5Ctext%7Btrain%7D%7D%20%5Cneq%20D_%7B%5Ctext%7Bserve%7D%7D"></td>
<td>Leakage, training/serving skew, batch-vs-streaming features, sampling bias</td>
</tr>
<tr class="even">
<td><strong>2</strong></td>
<td><strong>Metric <img src="https://latex.codecogs.com/png.latex?%5Cneq"> Value</strong></td>
<td><img src="https://latex.codecogs.com/png.latex?L_%7B%5Ctext%7Btrain%7D%7D%20%5Cneq%20L_%7B%5Ctext%7Bbusiness%7D%7D"></td>
<td>AUC that moves no revenue, accuracy under imbalance, symmetric loss under asymmetric cost, median latency under a tail SLO</td>
</tr>
<tr class="odd">
<td><strong>3</strong></td>
<td><strong>Past <img src="https://latex.codecogs.com/png.latex?%5Cneq"> Present</strong></td>
<td><img src="https://latex.codecogs.com/png.latex?D_%7B%5Ctext%7Bserve%7D%7D(t)"> moves with <img src="https://latex.codecogs.com/png.latex?t"></td>
<td>Drift, staleness, feedback loops</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Scope: supervised prediction, any model class
</div>
</div>
<div class="callout-body-container callout-body">
<p><img src="https://latex.codecogs.com/png.latex?f"> is any decision function. A hinge-loss SVM, a gradient-boosted ranker, and a logistic regression face the same three gaps, because the gaps are properties of <strong>the data and the objective, not of the model class.</strong> What varies is which repairs you get: the threshold rule needs a calibrated probability, while importance reweighting and the drift analysis do not.</p>
<p>The real limit is that this is about <strong>supervised prediction</strong>, where a label eventually arrives and <img src="https://latex.codecogs.com/png.latex?L_%7B%5Ctext%7Bbusiness%7D%7D"> is a cost per decision. Reinforcement learning and unsupervised systems have their own failure taxonomies, which aren’t covered by this frame.</p>
</div>
</div>
<hr>
</section>
<section id="sec-requirements" class="level2">
<h2 class="anchored" data-anchor-id="sec-requirements">What Production Demands</h2>
<section id="sec-tail" class="level3">
<h3 class="anchored" data-anchor-id="sec-tail">Why Tail Latency, Not Average Latency?</h3>
<p>During training we optimize <strong>throughput</strong>: push millions of examples through, extract the most learning per unit of compute. We can afford to wait a day for a better model.</p>
<p>Serving inverts this priority. Now we optimize <strong>latency</strong>, and specifically not the average. The number that matters is the tail: p95, p99, sometimes p99.9.</p>
<p>It is tempting to read p99 as “1% of users have a bad time,” which sounds survivable. It is not, and the reason is structural (<a href="https://research.google/pubs/the-tail-at-scale/">Dean and Barroso, 2013</a>).</p>
<p>Suppose a single user request fans out to <img src="https://latex.codecogs.com/png.latex?N"> backend services (a feature service, a lookup, an embedding store, a model server) and each one independently has a 1% chance of being slow. The request is fast only if <em>every</em> service is fast:</p>
<p><img src="https://latex.codecogs.com/png.latex?P(%5Ctext%7Brequest%20is%20slow%7D)%20=%201%20-%20(1%20-%200.01)%5E%7BN%7D"></p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/tail-latency-fanout.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure 1: Tail latency is emergent. A per-service slow rate of 1% (red) produces a slow user request the majority of the time once fan-out reaches 100 services. Dropping the per-service rate to 0.1% (blue) buys back most of it, which is why tail work at the component level pays off superlinearly."><img src="https://imaddabbura.github.io/posts/mlsys/images/tail-latency-fanout.svg" class="img-fluid figure-img" alt="Figure 1: Tail latency is emergent. A per-service slow rate of 1% (red) produces a slow user request the majority of the time once fan-out reaches 100 services. Dropping the per-service rate to 0.1% (blue) buys back most of it, which is why tail work at the component level pays off superlinearly."></a></p>
<figcaption><strong>Figure 1:</strong> Tail latency is emergent. A per-service slow rate of 1% (red) produces a slow <em>user request</em> the majority of the time once fan-out reaches 100 services. Dropping the per-service rate to 0.1% (blue) buys back most of it, which is why tail work at the component level pays off superlinearly.</figcaption>
</figure>
</div>
<table class="caption-top table">
<colgroup>
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
<col style="width: 14%">
</colgroup>
<thead>
<tr class="header">
<th>Fan-out <img src="https://latex.codecogs.com/png.latex?N"></th>
<th>1</th>
<th>10</th>
<th>20</th>
<th>50</th>
<th>100</th>
<th>200</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>P(user request is slow)</td>
<td>1.0%</td>
<td>9.6%</td>
<td>18.2%</td>
<td>39.5%</td>
<td><strong>63.4%</strong></td>
<td>86.6%</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Your p99 is not a property of any service
</div>
</div>
<div class="callout-body-container callout-body">
<p>At a fan-out of 100, a one-in-a-hundred event at the component level becomes a <strong>majority event</strong> at the user level. 63% of requests touch at least one slow service.</p>
<p>This is why a 10 ms operation can take 500 ms once it crosses twenty service boundaries: tail latency is a property of your <em>architecture</em>, not of any component in it. You cannot fix it by optimizing the slowest service. You fix it by reducing fan-out, hedging requests, or making each hop’s p99 extremely good.</p>
</div>
</div>
</section>
<section id="sec-proxies" class="level3">
<h3 class="anchored" data-anchor-id="sec-proxies">ML Metrics Are Proxies</h3>
<p>ML metrics are not business value. They are correlated with it, sometimes, over some range. A recommender’s NDCG is not revenue. A fraud model’s precision is not dollars saved.</p>
<p>I learned this properly on a content recommendation system. We spent three months lifting the ranking model’s AUC from 0.78 to 0.82, which is a real improvement by any offline standard. We deployed it and engagement did not move. The reason took another week to find: nearly all of the gain was in how we ranked items 50 through 100, and users almost never scrolled past item 20. We had spent a quarter optimizing a region of the ranking that almost no one ever looked at.</p>
<p>The problem is NOT AUC, it is that an aggregate metric averages over regions of the input space that carry unequal business weight, and the model will happily buy its gains in the cheap ones. Before you optimize a metric, establish three things with whoever owns the P&amp;L:</p>
<ol type="1">
<li>What user behavior actually drives value?</li>
<li>How does the model’s output change that behavior?</li>
<li>What is the <em>sensitivity</em>: how much business metric per unit of ML metric?</li>
</ol>
<p>Question 3 is the one that usually gets skipped, and it is the only one that tells you whether the project is worth doing. Sometimes the answer is that ML is a small term in a large equation. In a loan approval system, the model estimates creditworthiness, but the approval decision is dominated by regulatory floors, capital availability, and portfolio strategy. A better model may move nothing at all, and it is much cheaper to discover that in a spreadsheet than after two quarters of engineering.</p>
</section>
<section id="sec-pillars" class="level3">
<h3 class="anchored" data-anchor-id="sec-pillars">The Four Pillars</h3>
<p>Production systems need reliability, scalability, maintainability, and adaptability. Most of this is standard software engineering, so I will only dwell on the part that is ML-specific.</p>
<p><strong>Reliability</strong> in ML means more than uptime, because ML systems fail <em>silently</em>. A web server that breaks returns a 500. A model whose feature pipeline breaks returns a confident, plausible, wrong number, and it keeps doing so for months. The defense is a <strong>degradation ladder</strong> that is decided in advance:</p>
<pre><code>primary model  →  simpler/cached model  →  business rules  →  cached prediction  →  safe default</code></pre>
<p>Every level of the system must be implemented and <em>tested</em>, and the lowest/basic level must be safe rather than merely available. A fraud system whose fallback is “approve everything” is not degrading gracefully; it is failing open, and that is expensive.</p>
<p><strong>Scalability</strong> has three axes that interact: model complexity, request volume, and <em>number of models</em>. The third is the one that surprises teams. An architecture tuned for one large model often collapses under three hundred small ones, because per-model overhead (loading, memory, monitoring, versioning) was never on anyone’s radar.</p>
<p><strong>Maintainability</strong> is versioning, and in ML that means versioning code, data, models, configs, and feature definitions, because any one of them drifting silently produces a different system. Six months from now, nobody remembers why that threshold was 0.37.</p>
<p><strong>Adaptability</strong> is the ability to change quickly, which requires modularity, monitoring that tells you <em>when</em> to change, and a safe rollout path (testing in production).</p>
<hr>
</section>
</section>
<section id="sec-framing" class="level2">
<h2 class="anchored" data-anchor-id="sec-framing">Framing the Problem</h2>
<section id="sec-hierarchy" class="level3">
<h3 class="anchored" data-anchor-id="sec-hierarchy">Hierarchical Classification: What It Buys and What It Doesn’t</h3>
<p>For classification with many classes, decomposing into a hierarchy (coarse categories first, specialized models at the leaves) is often the right move.</p>
<p>First, <strong>different features at different levels</strong>. Top-level routing can use broad, cheap features. Leaf models can use narrow features that only make sense within their subtree (“screen size” is meaningful for phones and meaningless for groceries).</p>
<p>Second, <strong>the option to abstain</strong>. If the top-level classifier is not confident, you can stop and return the coarse label. In production, a confident wrong answer at the leaf (“iPhone 13”) is usually worse than a vague right one (“Electronics”). Flat classifiers do not give you this choice for free.</p>
<p>Third, <strong>maintainability</strong>. You can retrain one subtree without touching the rest, and errors localize to a level.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>What hierarchy does not buy you: a smaller dataset
</div>
</div>
<div class="callout-body-container callout-body">
<p>It is commonly claimed that hierarchical classification slashes your data requirements. With a rule of thumb of ~100 samples per class, a flat 10,000-class problem needs ~1M samples, and the hierarchy supposedly needs a fraction of that.</p>
<p><strong>This is wrong.</strong> The leaf models still have to separate the fine-grained classes. If you want to distinguish 10,000 products, <em>something</em> in your system must see enough examples of each of the 10,000. Pooling data helps the <em>internal</em> nodes, where many leaf classes collapse into one coarse label. It does nothing for the leaves.</p>
<p>What hierarchy actually reduces is the <em>difficulty of each decision</em>, not the <em>total supervision required</em>. Each classifier solves an easier problem over a more homogeneous input distribution with a purpose-built feature set, which is a substantial benefit.</p>
</div>
</div>
</section>
<section id="sec-multilabel" class="level3">
<h3 class="anchored" data-anchor-id="sec-multilabel">Multi-Label: Split or Joint?</h3>
<p>If the labels are independent, separate binary classifiers give you flexibility: update, debug, and tune each one alone. If the labels are correlated, a joint model captures the correlation and does better.</p>
<p>Labels are almost never independent. Assume joint unless you have checked.</p>
</section>
<section id="sec-objectives" class="level3">
<h3 class="anchored" data-anchor-id="sec-objectives">Multiple Objectives: Decouple Them</h3>
<p>When a system must serve several objectives (relevance, engagement, revenue), the tempting move is one model with a blended loss. A single model with three objectives baked into its weights forces you to retrain the model every time the business changes its mind about the trade-off, which it will do quarterly.</p>
<p>Train one model per objective. Combine at serving time with weights:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> score(item, user_ctx, business_ctx, models, rules):</span>
<span id="cb2-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""One model per objective; trade-off specified in config NOT in weights."""</span></span>
<span id="cb2-3">    scores <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {name: m.predict(item) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, m <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> models.items()}</span>
<span id="cb2-4"></span>
<span id="cb2-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Changing the business trade-off requires new deployment NOT retraining</span></span>
<span id="cb2-6">    weights <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_weights(user_ctx, business_ctx)</span>
<span id="cb2-7">    blended <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(weights[name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> s <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> name, s <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> scores.items())</span>
<span id="cb2-8"></span>
<span id="cb2-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> apply_business_rules(blended, item, rules)</span></code></pre></div></div>
<p>This buys four things: you can retune priorities without retraining, update one model without touching the others, debug a single objective in isolation, and use a different architecture per objective. The cost is that you now serve three models instead of one (scalability, axis three), and it is worth paying: the flexibility buys you more than the extra compute costs.</p>
<hr>
</section>
</section>
<section id="sec-data" class="level2">
<h2 class="anchored" data-anchor-id="sec-data">Data: Sources, Transport, and Freshness</h2>
<section id="sec-chaos" class="level3">
<h3 class="anchored" data-anchor-id="sec-chaos">First-, Second-, and Third-Party Data</h3>
<p>Data gets more valuable and more chaotic the closer it sits to you. The three tiers differ in how they break.</p>
<p><strong>First-party data</strong> (collected directly from your users) is the most valuable, because it is about <em>your</em> users doing <em>your</em> thing. It is also the filthiest data you will ever handle. Users leave fields blank, type <code>asdf@asdf.com</code>, and behave in ways no schema anticipated.</p>
<p>On one system I found that 15% of users were born on January 1st. Our date picker defaulted to January 1st and most people did not bother changing it. Therefore, January 1st now has two meanings (<code>NULL</code> and people who were actually born on January 1st) and we can extract more features out of “date of birth”</p>
<p>The rule for first-party data is defensive processing. Validate everything and assume every field is adversarial, not because users are malicious but because interfaces leak their defaults into your data.</p>
<p><strong>Second-party data</strong> (from partners) breaks on semantics rather than syntax. One partner’s “active user” is a 30-day login; another’s is a 90-day purchase. Nothing in the data will tell you this. Build validation that checks distributions, not just types, and alert when a partner’s field shifts.</p>
<p><strong>Third-party data</strong> gives you coverage at the price of staleness, unknown bias, and legal exposure. For example, a purchased demographic set may be two years old. Treat it as a dependency that could be withdrawn at any time.</p>
</section>
<section id="sec-transport" class="level3">
<h3 class="anchored" data-anchor-id="sec-transport">Data Passing Modes: Database, Service, Streaming</h3>
<p>Three transports, in increasing order of power and operational cost.</p>
<p><strong>Database-backed</strong> is the simplest: features live in a table, you query them at request time. Fine for prototypes and relaxed latency budgets. It scales badly, and worse, it <em>couples your ML availability to your database availability</em>. A slow query does not delay one prediction. It fills your connection pool and takes down the service.</p>
<p><strong>Service-based</strong> (REST/gRPC) is what most production systems use, and it is the right default. Independent scaling, independent deploys, language freedom. The failure mode is dependency sprawl: prediction calls feature service calls user service calls database, and each hop is a chance to be slow. Reread the fan-out arithmetic before you add another hop.</p>
<p><strong>Streaming transport</strong> (Kafka and friends) decouples data production from consumption, which is what you need when features must be computed continuously rather than on demand. The payoff is that expensive features get precomputed and are simply <em>there</em> when the request arrives.</p>
<p>It is also a standing operational commitment. Out-of-order events, late arrivals, exactly-once semantics, and cluster operations are now your problem, permanently. Price that in honestly before choosing it, because the cost does not show up in the prototype and never goes away afterward.</p>
</section>
<section id="sec-batch-streaming" class="level3">
<h3 class="anchored" data-anchor-id="sec-batch-streaming">Batch and Streaming Features</h3>
<p><strong>Batch features</strong> (also called static) are computed periodically over large datasets: lifetime value, 90-day purchase histogram, demographic aggregates. Because nothing is waiting on them, they can be arbitrarily expensive. They are stable, cheap to serve, and <em>stale</em>.</p>
<p><strong>Streaming features</strong> (dynamic) capture the present: items viewed in the last ten minutes, current session depth, current location. They must fit inside the request budget, so they are simple by necessity.</p>
<p>Most real systems need both, and the moment you have both, you have created a new source of Mismatch. The batch pipeline computes <code>avg_order_value</code> in Spark. The streaming pipeline computes it in Flink. They disagree about how to handle nulls, or about whether the window is inclusive, and now your model is trained on one definition and served another. Nobody notices, because both numbers look reasonable.</p>
<p>This is the single most common silent failure in production ML, and the section on training/serving skew covers what to do about it.</p>
<hr>
</section>
</section>
<section id="sec-sampling-labels" class="level2">
<h2 class="anchored" data-anchor-id="sec-sampling-labels">Sampling and Labels</h2>
<section id="sec-sampling" class="level3">
<h3 class="anchored" data-anchor-id="sec-sampling">Sampling Strategies</h3>
<p>The default instinct is to grab the most recent data because it is convenient. This is <strong>nonprobability sampling</strong> and it is the source of some production headaches.</p>
<p>The rest of the toolkit, briefly, because the choice is usually obvious once you name the constraint:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Method</th>
<th>Use when</th>
<th>Watch out for</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Random</strong></td>
<td>You have plenty of every class</td>
<td>Misses rare events entirely at low prevalence</td>
</tr>
<tr class="even">
<td><strong>Stratified</strong></td>
<td>Important segments must be represented</td>
<td>Too many strata dimensions gives you meaningless micro-strata</td>
</tr>
<tr class="odd">
<td><strong>Weighted</strong></td>
<td>Some examples matter more (recency, customer value)</td>
<td>Wrong weights inject bias you will not detect offline</td>
</tr>
<tr class="even">
<td><strong>Reservoir</strong></td>
<td>Sampling from an unbounded stream in one pass</td>
<td>Uniform over the stream, which may not be what you want</td>
</tr>
<tr class="odd">
<td><strong>Importance</strong></td>
<td>Training distribution <img src="https://latex.codecogs.com/png.latex?%5Cneq"> target distribution</td>
<td>High variance when the two distributions barely overlap</td>
</tr>
</tbody>
</table>
</section>
<section id="sec-feedback" class="level3">
<h3 class="anchored" data-anchor-id="sec-feedback">Feedback Signals: Volume, Delay, Noise</h3>
<p>Labels from user behavior come in tiers, and the tiers trade off against each other in a way you cannot escape:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Signal</th>
<th>Examples</th>
<th>Volume</th>
<th>Delay</th>
<th>Noise</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Implicit</strong></td>
<td>views, hovers, scroll depth</td>
<td>High</td>
<td>Instant</td>
<td>High</td>
</tr>
<tr class="even">
<td><strong>Engagement</strong></td>
<td>clicks, dwell time, shares</td>
<td>Medium</td>
<td>Minutes to hours</td>
<td>Medium</td>
</tr>
<tr class="odd">
<td><strong>Explicit</strong></td>
<td>ratings, purchases, returns</td>
<td>Low</td>
<td>Days to weeks</td>
<td>Low</td>
</tr>
</tbody>
</table>
<p>An implicit signal is abundant but unreliable: a user viewed an item because it was at the top of the page, not because they wanted it. An explicit signal is honest and arrives too late to steer anything.</p>
<p>The resolution is to use all three at different timescales rather than picking one. Implicit signals drive rapid adaptation and exploration. Engagement signals drive short-cycle updates. Explicit signals drive periodic retraining and, critically, <strong>calibration</strong>, because they are the only ones you actually trust.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    U["User activity stream"] --&gt; A["Implicit&lt;br/&gt;views, hovers, scroll"]
    U --&gt; C["Engagement&lt;br/&gt;clicks, dwell, shares"]
    U --&gt; E["Explicit&lt;br/&gt;ratings, purchases"]

    A --&gt;|"streaming"| M1["Online learner&lt;br/&gt;fast, noisy"]
    C --&gt;|"hourly mini-batch"| M2["Nearline trainer"]
    E --&gt;|"daily/weekly batch"| M3["Batch trainer&lt;br/&gt;ground truth + calibration"]

    M1 --&gt; S["Model registry"]
    M2 --&gt; S
    M3 --&gt; S
    S --&gt; P["Production model"]
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
</section>
<section id="sec-no-labels" class="level3">
<h3 class="anchored" data-anchor-id="sec-no-labels">When You Do Not Have Labels</h3>
<p>Labels are expensive. Four alternative in rough order of how often they work.</p>
<p><strong>Transfer learning</strong> is the first thing to try, and it usually wins. Start from a model pretrained on a related task and fine-tune. The amount of task-specific data you need is often shockingly small: hundreds of examples, not hundreds of thousands, because the pretrained model already has the representation and you are only teaching it the decision.</p>
<p><strong>Weak supervision</strong> replaces hand-labeling with <em>labeling functions</em>: heuristics, patterns, external knowledge, each individually noisy. A generative model learns how much to trust each one and resolves their disagreements (<a href="https://arxiv.org/abs/1711.10160">Ratner et al., 2017</a>). I used this for content moderation, where the labeling functions were things like profanity lists, all-caps ratios, “reported by 3+ distinct users,” and known-bad domains. No single function was better than mediocre. Combined, they were good enough to train on.</p>
<p><strong>Semi-supervision</strong> bootstraps from a small labeled set: train, predict on unlabeled data, keep the confident predictions, retrain. It works, but it has a dangerous failure mode. The model is being trained on its own beliefs, so its errors are self-reinforcing. Confidence goes up while accuracy goes down, and the metric you would use to catch this is the one that has been corrupted. Always hold out a genuinely labeled validation set and stop when it degrades.</p>
<p><strong>Active learning</strong> spends your labeling budget where it buys the most: uncertain examples, boundary cases, cluster representatives. The chicken-and-egg problem is that selecting informative examples requires a model, and the model requires labels, so start with a small random seed set and iterate.</p>
<hr>
</section>
</section>
<section id="sec-imbalance" class="level2">
<h2 class="anchored" data-anchor-id="sec-imbalance">Class Imbalance and the Cost of Errors</h2>
<section id="sec-base-rate" class="level3">
<h3 class="anchored" data-anchor-id="sec-base-rate">Why Accuracy Fails Under Imbalance</h3>
<p>Everyone knows the first-order version: a model that always predicts “not fraud” scores 99.9% accuracy and catches nothing. The usual response, switch to precision and recall, does not go deep enough, because <strong>even a genuinely good model produces a mostly-useless alert queue</strong>, and no choice of metric changes that.</p>
<p>Take a fraud model with 99% sensitivity and 99% specificity. Excellent by any standard. At prevalence <img src="https://latex.codecogs.com/png.latex?p">, its precision is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7BPrecision%7D%20=%20%5Cfrac%7B%5Ctext%7BTPR%7D%20%5Ccdot%20p%7D%7B%5Ctext%7BTPR%7D%20%5Ccdot%20p%20+%20%5Ctext%7BFPR%7D%20%5Ccdot%20(1-p)%7D%0A"></p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/precision-vs-prevalence.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure 2: The same 99%/99% model at different base rates. Nothing about the model changes across this curve. Only the prevalence does."><img src="https://imaddabbura.github.io/posts/mlsys/images/precision-vs-prevalence.svg" class="img-fluid figure-img" alt="Figure 2: The same 99%/99% model at different base rates. Nothing about the model changes across this curve. Only the prevalence does."></a></p>
<figcaption><strong>Figure 2:</strong> The same 99%/99% model at different base rates. Nothing about the model changes across this curve. Only the prevalence does.</figcaption>
</figure>
</div>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Prevalence</th>
<th>Precision</th>
<th>False alarms</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>10%</td>
<td>91.7%</td>
<td>8.3%</td>
</tr>
<tr class="even">
<td>1%</td>
<td><strong>50.0%</strong></td>
<td>50.0%</td>
</tr>
<tr class="odd">
<td>0.1%</td>
<td>9.0%</td>
<td>91.0%</td>
</tr>
<tr class="even">
<td>0.01%</td>
<td>1.0%</td>
<td>99.0%</td>
</tr>
</tbody>
</table>
<p>At 1% prevalence, a 99%/99% model is <strong>a coin flip</strong>. At 0.1%, nine out of every ten alerts you send a human are wrong.</p>
</section>
<section id="sec-threshold" class="level3">
<h3 class="anchored" data-anchor-id="sec-threshold">Why 0.5 Is the Wrong Threshold</h3>
<p>Every classifier ships with a decision threshold. Most ship with 0.5, and 0.5 is almost always wrong.</p>
<p>Suppose a missed fraud costs <img src="https://latex.codecogs.com/png.latex?C_%7BFN%7D"> and a false alarm costs <img src="https://latex.codecogs.com/png.latex?C_%7BFP%7D"> (an analyst’s time, an annoyed customer). Given a calibrated probability <img src="https://latex.codecogs.com/png.latex?p"> that a transaction is fraudulent, flag it when the expected cost of flagging is lower than the expected cost of not:</p>
<p><img src="https://latex.codecogs.com/png.latex?p%20%5Ccdot%20C_%7BFN%7D%20%3E%20(1%20-%20p)%20%5Ccdot%20C_%7BFP%7D"></p>
<p>Solve for <img src="https://latex.codecogs.com/png.latex?p">:</p>
<p><img src="https://latex.codecogs.com/png.latex?p%20%3E%20%5Cfrac%7BC_%7BFP%7D%7D%7BC_%7BFP%7D%20+%20C_%7BFN%7D%7D%20=%20t%5E%7B*%7D"></p>
<p>That is the whole derivation (<a href="https://cseweb.ucsd.edu/~elkan/rescale.pdf">Elkan, 2001</a>). The optimal threshold does not depend on your model at all. It depends only on the relative cost of the two errors.</p>
<p>Read a few cases off it:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th><img src="https://latex.codecogs.com/png.latex?C_%7BFP%7D"></th>
<th><img src="https://latex.codecogs.com/png.latex?C_%7BFN%7D"></th>
<th><img src="https://latex.codecogs.com/png.latex?t%5E%7B*%7D"></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>$10</td>
<td>$1,000</td>
<td>0.99%</td>
</tr>
<tr class="even">
<td>$10</td>
<td>$100</td>
<td>9.1%</td>
</tr>
<tr class="odd">
<td>$1</td>
<td>$1</td>
<td><strong>50.0%</strong></td>
</tr>
<tr class="even">
<td>$100</td>
<td>$10</td>
<td>90.9%</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>The default threshold is a claim about your business
</div>
</div>
<div class="callout-body-container callout-body">
<p><img src="https://latex.codecogs.com/png.latex?t%5E%7B*%7D%20=%200.5"> exactly when <img src="https://latex.codecogs.com/png.latex?C_%7BFP%7D%20=%20C_%7BFN%7D">.</p>
<p>So shipping <code>model.predict()</code> is not a neutral engineering default. It is an assertion that <strong>a missed fraud costs exactly as much as a false alarm</strong>. For most problems that assertion is wrong by two orders of magnitude, and it gets made implicitly, without anyone deciding it.</p>
</div>
</div>
<p>How much does this actually cost? I simulated two million transactions from a calibrated fraud model (<img src="https://latex.codecogs.com/png.latex?C_%7BFP%7D%20=%20%5C$10">, <img src="https://latex.codecogs.com/png.latex?C_%7BFN%7D%20=%20%5C$1%7B,%7D000">, prevalence 0.11%) and swept the threshold:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/cost-vs-threshold.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure 3: Expected cost per transaction against decision threshold. The cost basin is wide and flat, and t^* sits in the middle of it. The default threshold of 0.5 sits well outside it, on the wall."><img src="https://imaddabbura.github.io/posts/mlsys/images/cost-vs-threshold.svg" class="img-fluid figure-img" alt="Figure 3: Expected cost per transaction against decision threshold. The cost basin is wide and flat, and t^* sits in the middle of it. The default threshold of 0.5 sits well outside it, on the wall."></a></p>
<figcaption><strong>Figure 3:</strong> Expected cost per transaction against decision threshold. The cost basin is wide and flat, and <img src="https://latex.codecogs.com/png.latex?t%5E*"> sits in the middle of it. The default threshold of 0.5 sits well outside it, on the wall.</figcaption>
</figure>
</div>
<p>At <img src="https://latex.codecogs.com/png.latex?t%5E%7B*%7D%20=%200.0099"> the system costs <strong>$0.119 per transaction</strong>. At the default 0.5 it costs <strong>$0.757</strong>. Shipping the default is a <strong>6.3x</strong> cost penalty. On a million transactions a day, that is roughly $640,000 of avoidable cost every day.</p>
<p>Two things stand out in that figure, and the second matters more:</p>
<ol type="1">
<li>The empirical minimum lands on <img src="https://latex.codecogs.com/png.latex?t%5E%7B*%7D">, as the derivation says it must.</li>
<li><strong>The basin is flat.</strong> Anywhere from roughly 0.002 to 0.05 is nearly optimal. You do not need to compute <img src="https://latex.codecogs.com/png.latex?t%5E%7B*%7D"> precisely. You need to not be at 0.5.</li>
</ol>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Importance of calibration
</div>
</div>
<div class="callout-body-container callout-body">
<p>The derivation assumed <img src="https://latex.codecogs.com/png.latex?p"> is a <em>calibrated probability</em>: when the model says 0.01, the event happens 1% of the time. If the model is overconfident, <img src="https://latex.codecogs.com/png.latex?t%5E%7B*%7D"> is computed against a number that does not mean what it says, and the whole thing collapses.</p>
<p>Modern neural networks are systematically overconfident (<a href="https://arxiv.org/abs/1706.04599">Guo et al., 2017</a>). So calibration is not a finishing touch you apply if there is time. It is the precondition for making a cost-optimal decision at all, and it is why the calibration section exists.</p>
</div>
</div>
</section>
<section id="sec-resampling" class="level3">
<h3 class="anchored" data-anchor-id="sec-resampling">Data-Level Fixes and Their Limits</h3>
<p>With the threshold question settled, most of the resampling literature reads differently. If you can fix the decision rule for free, why rebalance the data at all?</p>
<p>Sometimes you should, because at severe imbalance the minority class has too few examples for the model to <em>learn a boundary</em> at all, which is a different problem from <em>placing</em> the boundary. But be clear about which problem you have.</p>
<ul>
<li><strong>Undersampling</strong> the majority class is fast and throws away real information, including the hard negatives that define the boundary.</li>
<li><strong>Oversampling</strong> the minority preserves everything and encourages overfitting: duplicated examples add confidence, not information. Worse, if you duplicate <em>before</em> splitting, the same example lands in train and validation, and your validation score is now fiction (duplicate leakage).</li>
<li><strong>SMOTE</strong> (<a href="https://arxiv.org/abs/1106.1813">Chawla et al., 2002</a>) interpolates between minority examples. It works for continuous features and produces nonsense for categorical or discrete ones. The midpoint of two user profiles is not a user.</li>
<li><strong>Tomek links</strong> remove majority examples sitting right on the boundary. Cleaner boundaries, at the price of deleting exactly the ambiguous cases production will throw at you.</li>
</ul>
<p>What has worked best for me is <strong>two-phase training</strong>: learn the patterns on balanced data, then fine-tune on the natural distribution at a reduced learning rate to fix calibration.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Phase 1: balanced data, so the model actually sees the minority class</span></span>
<span id="cb3-2">model.fit(balance(X_train, y_train))</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Phase 2: natural distribution at 0.1x LR, so the outputs mean something.</span></span>
<span id="cb3-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Without this, the model's probabilities reflect the resampled prior,</span></span>
<span id="cb3-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># not the real one, and t* is computed against a lie.</span></span>
<span id="cb3-7">model.set_learning_rate(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> lr)</span>
<span id="cb3-8">model.fit(X_train, y_train)</span></code></pre></div></div>
<p>A model trained only on rebalanced data has learned the <em>rebalanced prior</em>, so its probabilities are wrong by exactly the resampling ratio, which breaks the threshold rule.</p>
</section>
<section id="sec-loss" class="level3">
<h3 class="anchored" data-anchor-id="sec-loss">Algorithm-Level Fixes: Cost-Sensitive and Focal Loss</h3>
<p>Often cleaner than touching the data.</p>
<p><strong>Cost-sensitive learning</strong> puts the cost matrix directly in the loss, which is the honest version of what the threshold rule does after the fact.</p>
<p><strong>Class-balanced loss</strong> weights examples by inverse class frequency. Simple, effective, and the weights are a hyperparameter you now have to tune.</p>
<p><strong>Focal loss</strong> (<a href="https://arxiv.org/abs/1708.02002">Lin et al., 2017</a>) down-weights examples the model already gets right, so its attention flows to the hard ones. Since majority examples are usually the easy ones, this rebalances <em>implicitly</em>, without a frequency table. For extreme imbalance it often works when explicit reweighting does not.</p>
<hr>
</section>
</section>
<section id="sec-features" class="level2">
<h2 class="anchored" data-anchor-id="sec-features">Feature Engineering</h2>
<section id="sec-missing" class="level3">
<h3 class="anchored" data-anchor-id="sec-missing">Missing Values</h3>
<p>Before anything else: <strong>find out what “missing” means in your system.</strong> It is frequently not <code>NULL</code>. It is <code>-999</code>, or <code>""</code>, or <code>"N/A"</code>, or <code>1900-01-01</code>, or <code>0</code> in a column where zero is also a legitimate value. Until you have audited this, every statistic you compute is wrong.</p>
<p>Then ask <em>why</em> it is missing, because the mechanism determines the fix:</p>
<ul>
<li><strong>MNAR</strong> (Missing Not At Random): missingness depends on the unobserved value itself. High earners decline to state income. The missingness <em>is</em> the signal.</li>
<li><strong>MAR</strong> (Missing At Random): missingness depends on other observed variables. Mobile users skip long forms. Predictable from what you do have.</li>
<li><strong>MCAR</strong> (Missing Completely At Random): genuinely random. A dropped packet. Rare in practice, and if you think your data is MCAR, look harder.</li>
</ul>
<p><strong>Always add a missing indicator.</strong> It follows directly from the MNAR definition.</p>
<p>Let <img src="https://latex.codecogs.com/png.latex?M"> be the binary missingness mask for a feature. Under MNAR, <img src="https://latex.codecogs.com/png.latex?M"> depends on the unobserved value, which means:</p>
<p><img src="https://latex.codecogs.com/png.latex?P(Y%20%5Cmid%20X_%7B%5Ctext%7Bobs%7D%7D,%20M=1)%20%5Cneq%20P(Y%20%5Cmid%20X_%7B%5Ctext%7Bobs%7D%7D,%20M=0)"></p>
<p>The two populations have different label distributions <em>given everything else you observed</em>. So <img src="https://latex.codecogs.com/png.latex?M"> carries information that is not recoverable from <img src="https://latex.codecogs.com/png.latex?X_%7B%5Ctext%7Bobs%7D%7D">, and therefore not recoverable from any imputed value, because imputation is a function of <img src="https://latex.codecogs.com/png.latex?X_%7B%5Ctext%7Bobs%7D%7D">. <strong>Imputation destroys <img src="https://latex.codecogs.com/png.latex?M">. The indicator preserves it.</strong></p>
<p>This also tells you exactly when the rule does not apply: under MCAR, the two distributions are equal, <img src="https://latex.codecogs.com/png.latex?M"> is independent of <img src="https://latex.codecogs.com/png.latex?Y">, and the indicator is pure noise. That is the only case where you can skip it, and it is the case you almost never have.</p>
<p>From this, several practical guidelines follow:</p>
<ul>
<li><strong>Do not drop columns</strong> for being 30% missing. That column is informative for the 70%, and its missingness is informative for the rest.</li>
<li><strong>Be careful dropping rows.</strong> Under MAR or MNAR you are deleting a <em>segment</em>, and it is usually a segment you care about.</li>
<li><strong>Median/mode imputation plus an indicator</strong> is a strong baseline and beats most of what people replace it with.</li>
<li><strong>Predictive imputation</strong> is a model inside your model. Fit it on training data only to avoid leakage(preprocessing leakage).</li>
</ul>
</section>
<section id="sec-scaling" class="level3">
<h3 class="anchored" data-anchor-id="sec-scaling">Scaling</h3>
<p>The most know scaling technique is <code>StandardScaler</code>, which has two problems:</p>
<p>The first is that <strong>outliers destroy your scaling parameters</strong> before they ever touch your model. One value of <img src="https://latex.codecogs.com/png.latex?10%5E9"> in a column that otherwise ranges over <img src="https://latex.codecogs.com/png.latex?%5B0,%20100%5D"> drags the mean, inflates the standard deviation, and compresses every real value into a sliver near zero. Clip first (1st and 99th percentile), <em>then</em> compute statistics. In that order.</p>
<p>The second is that <strong>training-time statistics go stale</strong>. You standardized using a mean from six months ago, but user behavior moves continuously in productionand.</p>
<p>Match the transform to the distribution:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Distribution</th>
<th>Transform</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Normal</td>
<td>Standardization (z-score)</td>
</tr>
<tr class="even">
<td>Uniform</td>
<td>Min-max to <img src="https://latex.codecogs.com/png.latex?%5B0,%201%5D"></td>
</tr>
<tr class="odd">
<td>Heavy-tailed</td>
<td>Robust scaling: median and IQR</td>
</tr>
<tr class="even">
<td>Power-law</td>
<td>Log first, then scale</td>
</tr>
<tr class="odd">
<td>Unknown</td>
<td>Quantile / rank transform</td>
</tr>
</tbody>
</table>
</section>
<section id="sec-encoding" class="level3">
<h3 class="anchored" data-anchor-id="sec-encoding">Encoding</h3>
<p>One-hot is fine until the cardinality is 10,000 and your feature matrix explodes. Target encoding (replace each category with its mean target) is clever and leaks: the target of the row you are encoding is inside the encoding. If you do use it, compute the encoding out-of-fold.</p>
<p>The <strong>hashing trick</strong> is what tends to survive contact with production. Hash the category into a fixed number of buckets. Bounded dimensionality, no vocabulary to maintain, and (this is the part that matters) <strong>unseen categories at serving time just work</strong>, because hashing is defined for inputs you have never observed. One-hot and target encoding both have to be told what to do with a new category.</p>
<p>A collision costs a little accuracy, which is a far better failure than an unhandled new category crashing the request path at serving time.</p>
</section>
<section id="sec-crossing" class="level3">
<h3 class="anchored" data-anchor-id="sec-crossing">Crossing</h3>
<p>Crossing features (latitude × longitude, category × region) lets linear models learn interactions. The combinatorics are brutal: two 100-value features cross to 10,000 combinations, most of which appear a handful of times or never.</p>
<p>Cross selectively, from domain knowledge, and monitor the cardinality of the result. If a cross produces a long tail of singleton values, hash it or group it.</p>
</section>
<section id="sec-skew" class="level3">
<h3 class="anchored" data-anchor-id="sec-skew">Training/Serving Skew</h3>
<p>You compute <code>avg_session_length</code> in a Pandas notebook for training. Someone reimplements it in Scala for the serving path. They handle a null differently, or round differently, or use a half-open interval where you used a closed one. The model now sees a feature at serving time that is subtly not the one it was trained on. Nothing throws an error and both numbers look reasonable, so performance degrades quietly, and every investigation starts at the model, which is not where the problem is.</p>
<p>There is exactly one robust fix: the training path and the serving path must execute <strong>the same code</strong>, not two separate implementations that are meant to agree.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>What a feature store actually gives you
</div>
</div>
<div class="callout-body-container callout-body">
<p>This is what feature stores (Feast, Tecton) are <em>for</em>, and they are not just a database. A feature store’s actual contribution is that it makes the feature definition a single artifact with two execution paths (a batch one for training, a low-latency one for serving) generated from one definition, plus point-in-time-correct joins so that when you build a training set you get the feature values <em>as they were at the prediction timestamp</em>, not as they are now.</p>
<p>That second property is a leakage prevention mechanism (temporal leakage). Most teams can hand-roll the first property, but very few hand-rolls point-in-time correctness without getting it wrong at least once.</p>
<p>Whether you need one is a scale question, but you need the two <em>properties</em> especially if you have batch and streaming features.</p>
</div>
</div>
<hr>
</section>
</section>
<section id="sec-leakage" class="level2">
<h2 class="anchored" data-anchor-id="sec-leakage">Data Leakage</h2>
<p>Leakage has caused more of the production failures I have personally debugged than any other single cause. It is uniquely dangerous because it does not look like a bug.</p>
<section id="sec-temporal-leakage" class="level3">
<h3 class="anchored" data-anchor-id="sec-temporal-leakage">Temporal Leakage</h3>
<p>Temporal leakage is using information from after the prediction time to make the prediction. Stated that way it sounds impossible to do by accident, and yet it is one of the easiest mistakes to make.</p>
<p>I debugged a churn model that scored 99% accuracy in validation. In production it fell to 60%.</p>
<p>The issue was a feature called <code>account_status</code>, which was computed by a nightly job <em>after</em> churn was recorded. The model had not learned to predict churn. It had learned to recognize customers who had already churned. Offline, that field makes the model look 99% accurate. Online, at prediction time, it always reads “active,” so the model has learned nothing it can use.</p>
<p>Any aggregate can hide this. <code>days_since_last_purchase</code> computed relative to <em>today</em> instead of relative to the prediction date. <code>lifetime_value</code> that includes the transaction you are trying to predict. The question to ask about every single feature is: <strong>at the moment I need to make this prediction in production, does this value exist yet?</strong></p>
<p><strong>Always split by time, never at random</strong>, for any problem with a temporal component. Your validation set must come from strictly after your training set, because that is the shape of the production task.</p>
</section>
<section id="sec-other-leakage" class="level3">
<h3 class="anchored" data-anchor-id="sec-other-leakage">Preprocessing, Duplicate, and Group Leakage</h3>
<p><strong>Preprocessing leakage.</strong> You computed the scaler’s mean over the full dataset, then split. The test set’s statistics are now baked into the training transform. Same for imputation values, feature selection, and text vocabularies. The rule: fit every transform on training data only, then apply the frozen transform to test.</p>
<p><strong>Duplicate leakage.</strong> The same record (or a near-duplicate) appears in both train and test. Deduplicate before splitting, and check for fuzzy duplicates, not just exact ones.</p>
<p><strong>Group leakage.</strong> Correlated records straddle the split. Ten photos of the same person, split across train and test. Multiple transactions from one user on one day. The model learns to identify the <em>group</em> rather than the <em>pattern</em>. Therefore, split by group, not by row.</p>
</section>
<section id="sec-adversarial" class="level3">
<h3 class="anchored" data-anchor-id="sec-adversarial">Detecting Leakage: Adversarial Validation</h3>
<p>The best general-purpose leakage detector is a classifier that tries to tell your training set apart from your test set.</p>
<p>The logic is: <strong>if train and test are drawn from the same distribution, no classifier can separate them better than chance.</strong> Any model, given infinite capacity and infinite data, achieves AUC 0.5. So an AUC materially above 0.5 is <em>evidence</em> that the two sets differ, and the features the classifier leans on are the ones that differ.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sklearn.ensemble <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> RandomForestClassifier</span>
<span id="cb4-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sklearn.model_selection <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> cross_val_score</span>
<span id="cb4-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np, pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb4-4"></span>
<span id="cb4-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> adversarial_validation(X_train, X_test, threshold<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.7</span>):</span>
<span id="cb4-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""AUC ~0.5 means train and test are indistinguishable, which is what we want.</span></span>
<span id="cb4-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Materially higher means they differ, and the top features say how."""</span></span>
<span id="cb4-8">    X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.concat([X_train, X_test], ignore_index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb4-9">    is_test <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.r_[np.zeros(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(X_train)), np.ones(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(X_test))]</span>
<span id="cb4-10"></span>
<span id="cb4-11">    clf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> RandomForestClassifier(n_estimators<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span>, random_state<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb4-12">    auc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cross_val_score(clf, X, is_test, cv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, scoring<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"roc_auc"</span>).mean()</span>
<span id="cb4-13"></span>
<span id="cb4-14">    clf.fit(X, is_test)</span>
<span id="cb4-15">    suspects <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.Series(clf.feature_importances_, index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>X.columns).nlargest(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span>
<span id="cb4-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> auc, (suspects <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> auc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> threshold <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>)</span></code></pre></div></div>
<p>The 0.7 is a practical tolerance for finite-sample noise. Treat anything above ~0.6 as worth a look.</p>
<p>Two cheaper checks are worth running first.</p>
<ul>
<li><strong>Feature importance</strong>: if one feature dominates every other, be suspicious. Genuine predictive features are rarely that dominant, and a feature that is “too good” usually contains the answer.</li>
<li><strong>Ablation</strong>: drop one feature, retrain, and see if performance collapses. Genuine predictive signal is usually spread across many features, whereas leakage tends to concentrate in one.</li>
</ul>
<hr>
</section>
</section>
<section id="sec-evaluation" class="level2">
<h2 class="anchored" data-anchor-id="sec-evaluation">Evaluation Beyond Accuracy</h2>
<section id="sec-baselines" class="level3">
<h3 class="anchored" data-anchor-id="sec-baselines">Baselines</h3>
<p>Start with the dumbest thing that could work, and make each subsequent model beat the last:</p>
<ol type="1">
<li><strong>Random / mean.</strong> Establishes the floor. If you cannot beat this, something is broken.</li>
<li><strong>Majority class / historical average.</strong> Captures base rates. “Predict yesterday’s value” is a shockingly strong baseline for time series and beats a lot of deep learning.</li>
<li><strong>Domain heuristics.</strong> Flag transactions over $X. Flag users idle for <img src="https://latex.codecogs.com/png.latex?Y"> days. These encode real knowledge and are often most of the achievable value.</li>
<li><strong>Simple ML.</strong> Logistic regression, gradient-boosted trees.</li>
</ol>
<p>Then ask whether the complex model’s margin over step 4 is worth its cost, where “cost” includes serving latency, debugging difficulty, retraining time, and the engineer-months it will consume for as long as it lives.</p>
<p>Simple models win in production more often than not. They are faster, more interpretable, degrade more gracefully, and fail in ways you can reason about.</p>
</section>
<section id="sec-calibration" class="level3">
<h3 class="anchored" data-anchor-id="sec-calibration">Calibration</h3>
<p>The threshold derivation already made the case: <strong>the cost-optimal threshold is only meaningful on calibrated probabilities.</strong> If your model says 0.70 and the event happens 40% of the time, then every downstream decision keyed to that number is wrong, and the model’s ranking being excellent does not save you.</p>
<p>Check with a reliability diagram: bucket predictions by predicted probability, and plot predicted against observed frequency. A calibrated model sits on the diagonal.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/reliability-diagram.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Figure 4: A reliability diagram. The calibrated model (green) tracks the diagonal: when it says 0.7, the event happens 70% of the time. The overconfident model (red) is flatter, so a stated 0.9 corresponds to a true 72%. The threshold rule in the previous section is computed against the x-axis; only the green model makes it safe."><img src="https://imaddabbura.github.io/posts/mlsys/images/reliability-diagram.svg" class="img-fluid figure-img" alt="Figure 4: A reliability diagram. The calibrated model (green) tracks the diagonal: when it says 0.7, the event happens 70% of the time. The overconfident model (red) is flatter, so a stated 0.9 corresponds to a true 72%. The threshold rule in the previous section is computed against the x-axis; only the green model makes it safe."></a></p>
<figcaption><strong>Figure 4:</strong> A reliability diagram. The calibrated model (green) tracks the diagonal: when it says 0.7, the event happens 70% of the time. The overconfident model (red) is flatter, so a stated 0.9 corresponds to a true 72%. The threshold rule in the previous section is computed against the x-axis; only the green model makes it safe.</figcaption>
</figure>
</div>
<p>Fix with <strong>Platt scaling</strong> (fit a logistic regression on the model’s outputs) or <strong>isotonic regression</strong> (fit a monotonic step function; more flexible, needs more data). Fit either on a held-out set, never on training data.</p>
<p>Modern networks tend to be overconfident, and the effect got <em>worse</em> as models got bigger and more accurate (<a href="https://arxiv.org/abs/1706.04599">Guo et al., 2017</a>). Accuracy and calibration are separate properties. You have to check both.</p>
</section>
<section id="sec-slices" class="level3">
<h3 class="anchored" data-anchor-id="sec-slices">Slice-Based Evaluation</h3>
<p>An aggregate metric is an average over a population, and averages hide the segments that matter.</p>
<p>I deployed a model that performed well overall and terribly for new users. We had trained and tuned on users with rich histories, because that was where most of the data was, and the model learned to lean hard on history. New users had none. The aggregate metric was fine, because new users were a small fraction of sessions. They were also the entire growth channel, and the model we shipped was pushing them away.</p>
<p>Evaluate on slices, always, and choose them by business importance rather than by data volume: new versus returning, geography, device, language, high-value versus low-value customers, and the rare-but-critical cases. A model that is 2% better on average and 15% worse on new users is a worse model.</p>
</section>
<section id="sec-behavioral" class="level3">
<h3 class="anchored" data-anchor-id="sec-behavioral">Behavioral and Robustness Testing</h3>
<p><strong>Behavioral tests</strong> encode domain knowledge as assertions about the model’s behavior, independent of any metric (<a href="https://arxiv.org/abs/2005.04118">Ribeiro et al., 2020</a>):</p>
<ul>
<li><strong>Monotonicity</strong>: raising income should not lower the loan approval probability. If it does, the model is broken no matter what the AUC says.</li>
<li><strong>Invariance</strong>: changing a customer’s name should not change their credit decision.</li>
<li><strong>Directional expectation</strong>: adding a known-fraudulent signal should raise the fraud score.</li>
</ul>
<p>These catch spurious correlations that validation metrics won’t catch.</p>
<p><strong>Robustness tests</strong> perturb inputs slightly and check that predictions do not swing wildly. Production is full of noise: sensor error, truncation, retries, typos. A model that is unstable under small perturbations will be unstable in production.</p>
<hr>
</section>
</section>
<section id="sec-serving" class="level2">
<h2 class="anchored" data-anchor-id="sec-serving">Serving</h2>
<section id="sec-serving-modes" class="level3">
<h3 class="anchored" data-anchor-id="sec-serving-modes">Batch, Online, and the Hybrid</h3>
<p><strong>Batch prediction</strong> precomputes everything on a schedule and serves from a lookup table. No latency problem, arbitrary model complexity, cheap serving. Three costs: storage grows with users x items, most of what you compute is never used, and every prediction is stale by up to one cycle. Staleness is worse than it sounds, because intent moves fast. A user shopping for a laptop this morning may be shopping for a monitor now, and a nightly batch cannot reflect that.</p>
<p><strong>Online prediction</strong> computes on demand. Always fresh, nothing wasted, and now you are inside a latency budget.</p>
<p>The counterintuitive part is that in an online system, <strong>feature computation usually dominates latency, not model inference.</strong> The model is a few matrix multiplies; the features need a fan-out to several services and a cache miss, which is the fan-out problem again. Optimizing the model while features are the bottleneck is a common and costly mistake, and the subject of a <a href="../../posts/mlsys/improving-mlsys-theory-of-constraint.html">separate post on finding the constraint that governs a system</a>.</p>
<p>So: <strong>hybrid</strong>. Precompute the expensive, slow-moving features in batch (user embeddings, 90-day aggregates). Compute the cheap, fast-moving ones online (current session, current context). Join at request time. This is what most mature systems converge on, and keeping the batch and online paths consistent is exactly what feature stores are for.</p>
</section>
<section id="sec-compression" class="level3">
<h3 class="anchored" data-anchor-id="sec-compression">Compression</h3>
<p>Models are wildly over-parameterized, which is good news when you need them smaller. (If you want the mechanics underneath any of this, I <a href="../../posts/mlsys/dl-systems.html">built a small PyTorch from scratch</a> and the memory and kernel trade-offs are all there.)</p>
<ul>
<li><strong>Quantization</strong>: 32-bit floats to 8-bit ints. ~4x smaller, usually negligible accuracy loss. Try this first; it is nearly free.</li>
<li><strong>Pruning</strong>: remove low-magnitude weights. A large fraction of parameters can often go with little damage, and pruning sometimes <em>improves</em> generalization by acting as a regularizer.</li>
<li><strong>Distillation</strong> (<a href="https://arxiv.org/abs/1503.02531">Hinton et al., 2015</a>): train a small student on the large teacher’s soft outputs. The soft targets carry more information than hard labels (they encode the teacher’s uncertainty across classes), which is why the student can outperform the same architecture trained from scratch. Compression ratios vary a lot by task.</li>
</ul>
<p>The unifying observation is that <strong>model capacity and useful capacity are different quantities</strong>, and compression removes the difference.</p>
</section>
<section id="sec-edge" class="level3">
<h3 class="anchored" data-anchor-id="sec-edge">Cloud and Edge</h3>
<p>Cloud gives you flexibility, elastic scale, and instant updates, and charges you network round-trip time (often tens of milliseconds before your model does any work at all), bandwidth, and a privacy surface.</p>
<p>Edge eliminates the round trip and the privacy problem, and constrains you to a device with a fraction of the memory, no GPU, and a limited power budget.</p>
<p>The pattern converging in practice is <strong>partitioning</strong>: a small model on device for the immediate response, a large model in the cloud for the accurate one, with the UI designed to accommodate both.</p>
<hr>
</section>
</section>
<section id="sec-after-deployment" class="level2">
<h2 class="anchored" data-anchor-id="sec-after-deployment">After Deployment: Monitoring, Drift, and Retraining</h2>
<p>Deployment is the beginning of the system’s life, not the end of the project. Most of what determines whether a model succeeds happens here, after the metrics on the holdout set stop mattering.</p>
<section id="sec-silent-failures" class="level3">
<h3 class="anchored" data-anchor-id="sec-silent-failures">ML Systems Fail Silently</h3>
<p>Ordinary software failures are transparent. Servers crash, requests 500, alerts fire, etc. You can handle these with ordinary SRE practice: redundancy, health checks, circuit breakers, gradual rollout. If you do not have these, ML-specific failures are not your biggest problem.</p>
<p>Some ML-specific failures:</p>
<ul>
<li><strong>Pipeline corruption.</strong> A join key changes, a timestamp format shifts, an upstream system starts emitting a placeholder -&gt; Features become garbage.</li>
<li><strong>Training/serving skew.</strong> Two implementations of one feature, quietly disagreeing.</li>
<li><strong>Silent degradation.</strong> Distribution drifts, accuracy decays, and with no ground-truth labels arriving you have no way to see it.</li>
<li><strong>Feedback loops.</strong> The model’s outputs become its own future training data. A recommender only gets feedback on what it recommended, so it learns that what it recommends is what users want. The system converges on a self-confirming worldview, and the metric that would reveal this has been corrupted by the same loop.</li>
</ul>
<p>Every one of these leaves the system running and responsive while its outputs are wrong.</p>
</section>
<section id="sec-monitoring" class="level3">
<h3 class="anchored" data-anchor-id="sec-monitoring">Four Layers of Monitoring</h3>
<p>You need all four, and the value is in the connections between them.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Layer</th>
<th>Watch</th>
<th>Tells you</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>System</strong></td>
<td>Latency, throughput, error rate, saturation</td>
<td><em>That</em> something is wrong</td>
</tr>
<tr class="even">
<td><strong>Data</strong></td>
<td>Feature ranges, missing rates, new categorical values, schema</td>
<td>Whether the inputs changed</td>
</tr>
<tr class="odd">
<td><strong>Model</strong></td>
<td>Prediction distribution, confidence, feature importance</td>
<td>Whether the model’s behavior changed</td>
</tr>
<tr class="even">
<td><strong>Business</strong></td>
<td>Conversion, revenue, fraud caught, engagement</td>
<td>Whether any of it matters</td>
</tr>
</tbody>
</table>
<p>A drop in the business metric traces to a shift in the prediction distribution, which traces to a feature whose missing-rate jumped, which traces to an upstream schema change that shipped on Tuesday. With only the business layer, you can tell something is wrong but not what. With only the system layer, every dashboard is green while the model quietly fails.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph TB
    S["System / pipeline&lt;br/&gt;schema change ships (root cause)"]
    D["Data layer&lt;br/&gt;feature missing-rate jumps"]
    M["Model layer&lt;br/&gt;prediction distribution shifts"]
    B["Business layer&lt;br/&gt;conversion drops (what you see first)"]
    S --&gt; D --&gt; M --&gt; B
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>Causation runs bottom to top: a low-level change propagates up into a business symptom. Detection runs the other way: you notice the business number first and trace back down. A single layer gives you one end of that chain; you need all four to connect them.</p>
</section>
<section id="sec-drift" class="level3">
<h3 class="anchored" data-anchor-id="sec-drift">Why There Are Exactly Three Kinds of Drift</h3>
<p>Mismatch 3 was “<img src="https://latex.codecogs.com/png.latex?D_%7B%5Ctext%7Bserve%7D%7D"> moved.” The question is <em>moved how?</em> We factorize the <strong>data</strong> distribution, which exists whether or not your model estimates it, so none of this depends on a probabilistic model.</p>
<p>The three types of drift are a consequence of the fact that the joint distribution factorizes two ways:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(X,%20Y)%20%5C;=%5C;%20%5Cunderbrace%7BP(Y%20%5Cmid%20X)%7D_%7B%5Ctext%7Bconcept%7D%7D%20%5Ccdot%20%5Cunderbrace%7BP(X)%7D_%7B%5Ctext%7Bcovariate%7D%7D%0A%5C;=%5C;%20%5Cunderbrace%7BP(X%20%5Cmid%20Y)%7D_%7B%5Ctext%7Bclass-conditional%7D%7D%20%5Ccdot%20%5Cunderbrace%7BP(Y)%7D_%7B%5Ctext%7Blabel%7D%7D%0A"></p>
<p>Drift is the question of <em>which factor moved</em>. The two factorizations enumerate the possibilities exhaustively (<a href="https://doi.org/10.1016/j.patcog.2011.06.019">Moreno-Torres et al., 2012</a>):</p>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Type</th>
<th>What moves</th>
<th>What stays</th>
<th>Consequence</th>
<th>Fix</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Covariate shift</strong></td>
<td><img src="https://latex.codecogs.com/png.latex?P(X)"></td>
<td><img src="https://latex.codecogs.com/png.latex?P(Y%20%5Cmid%20X)"></td>
<td>The learned function is still <em>correct</em>, you are just evaluating it in new places</td>
<td>Reweight training data; retrain if the new inputs fall outside the old support</td>
</tr>
<tr class="even">
<td><strong>Label shift</strong></td>
<td><img src="https://latex.codecogs.com/png.latex?P(Y)"></td>
<td><img src="https://latex.codecogs.com/png.latex?P(X%20%5Cmid%20Y)"></td>
<td>Ranking is still correct; the posterior is off by exactly the prior ratio</td>
<td><strong>Recalibrate.</strong> The model is fine</td>
</tr>
<tr class="odd">
<td><strong>Concept drift</strong></td>
<td><img src="https://latex.codecogs.com/png.latex?P(Y%20%5Cmid%20X)"></td>
<td>(nothing you can lean on)</td>
<td>The relationship you learned is now false</td>
<td><strong>Retrain.</strong> Nothing else works</td>
</tr>
</tbody>
</table>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/drift-types.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Figure 5: The three shifts, with the same two classes and decision boundary in each panel. Covariate shift moves the input region (the boundary is still right, but serving now lands where training was sparse). Label shift changes the class balance (the ranking is fine, the base rate is not). Concept drift moves the boundary itself (the shaded band is where labels have flipped). Only the third invalidates the learned function."><img src="https://imaddabbura.github.io/posts/mlsys/images/drift-types.svg" class="img-fluid figure-img" alt="Figure 5: The three shifts, with the same two classes and decision boundary in each panel. Covariate shift moves the input region (the boundary is still right, but serving now lands where training was sparse). Label shift changes the class balance (the ranking is fine, the base rate is not). Concept drift moves the boundary itself (the shaded band is where labels have flipped). Only the third invalidates the learned function."></a></p>
<figcaption><strong>Figure 5:</strong> The three shifts, with the same two classes and decision boundary in each panel. Covariate shift moves the input region (the boundary is still right, but serving now lands where training was sparse). Label shift changes the class balance (the ranking is fine, the base rate is not). Concept drift moves the boundary itself (the shaded band is where labels have flipped). Only the third invalidates the learned function.</figcaption>
</figure>
</div>
<p>That third row is the dangerous one, and the table explains why: no amount of reweighting or recalibration can repair a function that is estimating the wrong thing.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Rule out your own bug before calling it drift
</div>
</div>
<div class="callout-body-container callout-body">
<p>Most “drift” is a bug. A pipeline change, a config rollout, a new app version that reorders a form, an upstream schema migration. All of these look exactly like drift in your monitoring.</p>
<p>Check your own deploy log before you retrain. Retraining on corrupted features does not fix anything and it launders the bug into the model, where it is much harder to find.</p>
</div>
</div>
</section>
<section id="sec-drift-detection" class="level3">
<h3 class="anchored" data-anchor-id="sec-drift-detection">Detecting Drift</h3>
<p>The obvious approach is a statistical test (Kolmogorov-Smirnov, chi-square) on each feature’s distribution. This is a trap, and the reason is subtle.</p>
<p><strong>The power of any statistical test goes to 1 as <img src="https://latex.codecogs.com/png.latex?n%20%5Cto%20%5Cinfty"> for any nonzero effect.</strong> At 10 million requests a day, every feature will differ from its training distribution at <img src="https://latex.codecogs.com/png.latex?p%20%3C%200.001">, every single day, because no two empirical distributions are ever exactly equal and you have enough samples to prove it.</p>
<p>The fix is to <strong>threshold on effect size, not significance</strong>. Population Stability Index is the standard choice:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb5-2"></span>
<span id="cb5-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> psi(expected, actual, bins<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>):</span>
<span id="cb5-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Population Stability Index. Effect size, not a p-value, so it does not</span></span>
<span id="cb5-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    get more alarming just because you have more traffic.</span></span>
<span id="cb5-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    &lt; 0.1 stable | 0.1-0.2 investigate | &gt; 0.2 act"""</span></span>
<span id="cb5-7">    cuts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.quantile(expected, np.linspace(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, bins <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb5-8">    cuts[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], cuts[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>np.inf, np.inf</span>
<span id="cb5-9"></span>
<span id="cb5-10">    e <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.histogram(expected, cuts)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(expected)</span>
<span id="cb5-11">    a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.histogram(actual, cuts)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(actual)</span>
<span id="cb5-12">    e, a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.clip(e, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>), np.clip(a, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># avoid log(0)</span></span>
<span id="cb5-13"></span>
<span id="cb5-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> e) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.log(a <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> e)))</span></code></pre></div></div>
<p>The other approach is <strong>adversarial validation again</strong>, applied to reference-versus-current data instead of train-versus-test. It is more robust than per-feature tests because it catches shifts in the <em>joint</em> distribution that no marginal test would see, and it tells you which features moved.</p>
<p>Performance monitoring is the ground truth, and it requires labels, which are delayed or absent precisely when you most need them.</p>
</section>
<section id="sec-retraining" class="level3">
<h3 class="anchored" data-anchor-id="sec-retraining">How Often to Retrain</h3>
<p><strong>Stateless retraining</strong> rebuilds from scratch on a recent window. Reproducible, no catastrophic forgetting, clean lineage. Expensive and slow to adapt.</p>
<p><strong>Stateful training</strong> updates the existing model incrementally. Cheap and fast-adapting. Risks catastrophic forgetting, is permanently corruptible by bad data, and makes “what did this model actually learn from?” hard to answer.</p>
<p>Most mature systems do both: incremental updates for responsiveness, periodic full retrains for stability and lineage.</p>
<p>Trigger retraining on a combination of signals, because each one alone has a gap:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Trigger</th>
<th>Strength</th>
<th>Gap</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Time-based</strong></td>
<td>Predictable floor</td>
<td>Retrains when nothing changed; misses fast shifts</td>
</tr>
<tr class="even">
<td><strong>Performance-based</strong></td>
<td>Directly tied to what you care about</td>
<td>Reactive: the damage is already done. Needs labels</td>
</tr>
<tr class="odd">
<td><strong>Volume-based</strong></td>
<td>Adapts to variable data rates</td>
<td>Volume is not the same as new information</td>
</tr>
<tr class="even">
<td><strong>Drift-based</strong></td>
<td>Proactive, fires before performance drops</td>
<td>Only as good as your drift detector</td>
</tr>
</tbody>
</table>
</section>
<section id="sec-testing-prod" class="level3">
<h3 class="anchored" data-anchor-id="sec-testing-prod">Testing in Production</h3>
<p>Each stage catches a different class of problem.</p>
<ol type="1">
<li><strong>Holdout.</strong> Necessary, wildly insufficient. Tells you the model learned patterns. Says nothing about production.</li>
<li><strong>Backtesting.</strong> Evaluate on the most recent data available. If a model trained on last year fails on last month, it will fail tomorrow.</li>
<li><strong>Shadow deployment.</strong> Run the new model alongside the old on real traffic, serving <em>none</em> of its predictions. You get real latency, real errors, real feature distributions, real infrastructure behavior, and zero user risk. This is the highest-value stage in the list and the most frequently skipped. Run it for at least a week.</li>
<li><strong>Canary.</strong> Route 1% of traffic, then 5%, 25%, 50%, with automatic rollback on technical <em>and</em> business metrics. A problem at 1% is an incident report. The same problem at 100% is an outage.</li>
<li><strong>A/B test.</strong> The only stage that measures business impact. Fix the sample size, success criteria, and duration <em>before</em> you start, and then do not peek, because peeking at a running experiment and stopping when it looks good is how you ship noise (<a href="https://experimentguide.com/">Kohavi et al., 2020</a>).</li>
<li><strong>Interleaving.</strong> For ranking, mix results from both models into one list for the same user. Removes between-user variance and detects smaller effects with far less traffic.</li>
</ol>
</section>
<section id="sec-cost" class="level3">
<h3 class="anchored" data-anchor-id="sec-cost">The True Cost of Ownership</h3>
<p><strong>Training</strong> costs labeling (usually the biggest single line item), the hundreds of failed experiments behind each success, and the engineer-months.</p>
<p><strong>Serving</strong> costs feature computation and storage, network, caching, monitoring, and the on-call rotation, of which model inference is often a minority.</p>
<p><strong>Humans</strong> cost the most: development, incident response, debugging, stakeholder communication, and the knowledge transfer when someone leaves.</p>
<p>In conclusion: <strong>a simple model on more expensive hardware is frequently cheaper than a complex model that requires a dedicated team.</strong> Optimize for total cost, and count the humans.</p>
<hr>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<section id="key-takeaways" class="level3">
<h3 class="anchored" data-anchor-id="key-takeaways">Key Takeaways</h3>
<ol type="1">
<li><p><strong>Tail latency is a property of your architecture, not your model.</strong> At a fan-out of 100, a 1% per-service slow rate makes 63% of user requests slow. You cannot optimize your way out of that one service at a time.</p></li>
<li><p><strong>The default 0.5 threshold asserts that your two errors cost the same.</strong> They almost never do. <img src="https://latex.codecogs.com/png.latex?t%5E%7B*%7D%20=%20C_%7BFP%7D%20/%20(C_%7BFP%7D%20+%20C_%7BFN%7D)">, the cost basin around it is flat, and 0.5 is usually outside it. In the threshold simulation, shipping the default cost 6.3x more per transaction.</p></li>
<li><p><strong>Calibration is a precondition, not a polish step.</strong> Every cost-optimal decision rule assumes the probabilities mean what they say, and modern networks are overconfident by default.</p></li>
<li><p><strong>Under low prevalence, even an excellent model produces a mostly-false alert queue.</strong> That is arithmetic, not a modelling failure, and you must design the human workflow around it.</p></li>
<li><p><strong>Drift is a question of which factor of <img src="https://latex.codecogs.com/png.latex?P(X,Y)"> moved.</strong> Covariate and label shift are bookkeeping problems that reweighting and recalibration solve. Concept drift means your function is wrong, and only retraining helps.</p></li>
<li><p><strong>Training/serving skew is the most common silent failure in production ML.</strong> The only robust fix is that both paths execute the same code.</p></li>
<li><p><strong>Data quality beats model sophistication, every time.</strong> I have never seen a complex model rescue bad data. I have often seen a simple model succeed on good data.</p></li>
<li><p><strong>The model is 10% of the system</strong> (<a href="https://papers.nips.cc/paper/2015/hash/86df7dcfd896fcaf2674f757a2463eba-Abstract.html">Sculley et al., 2015</a>). The rest is pipelines, features, serving, monitoring, and people.</p></li>
</ol>
</section>
<section id="a-roadmap-for-a-new-system" class="level3">
<h3 class="anchored" data-anchor-id="a-roadmap-for-a-new-system">A Roadmap for a New System</h3>
<ol type="1">
<li><strong>Write down the cost matrix.</strong> What does a false positive cost? A false negative? If nobody can answer, you do not yet have a problem specification, and every threshold you pick afterward is a guess.</li>
<li><strong>Build the baselines</strong> and measure the business metric they produce. This is your real bar, and sometimes it is already high enough that you can stop.</li>
<li><strong>Establish the latency budget from the architecture</strong>, using the fan-out arithmetic, before you choose a model class.</li>
<li><strong>Build the feature pipeline so that training and serving share one code path.</strong> This is the cheapest it will ever be to do. Retrofitting it costs ten times more.</li>
<li><strong>Split by time, and by group.</strong> Then run adversarial validation to confirm you have not leaked.</li>
<li><strong>Train, calibrate, and set the threshold from step 1.</strong> In that order.</li>
<li><strong>Evaluate on slices</strong>, not just aggregates, with the slices chosen by business importance.</li>
<li><strong>Shadow deploy for a week.</strong> Then canary. Then A/B.</li>
<li><strong>Instrument all four monitoring layers before you take traffic</strong>, not after your first incident.</li>
<li><strong>Measure your data half-life</strong> and set the retraining cadence from the curve.</li>
</ol>
<p>The gap between a notebook and a production system is real, and almost none of it is modelling. It is the work of keeping a fitted function useful against a world that keeps changing, and every step above is part of that work.</p>
<hr>
</section>
</section>
<section id="references-resources" class="level2">
<h2 class="anchored" data-anchor-id="references-resources">References &amp; Resources</h2>
<ul>
<li><strong>Elkan, C.</strong> (2001). <a href="https://cseweb.ucsd.edu/~elkan/rescale.pdf">The Foundations of Cost-Sensitive Learning</a>. <em>IJCAI</em>.</li>
<li><strong>Chawla, N. et al.</strong> (2002). <a href="https://arxiv.org/abs/1106.1813">SMOTE: Synthetic Minority Over-sampling Technique</a>. <em>JAIR</em>, 16.</li>
<li><strong>Moreno-Torres, J. et al.</strong> (2012). <a href="https://doi.org/10.1016/j.patcog.2011.06.019">A Unifying View on Dataset Shift in Classification</a>. <em>Pattern Recognition</em>, 45(1).</li>
<li><strong>Dean, J. &amp; Barroso, L. A.</strong> (2013). <a href="https://research.google/pubs/the-tail-at-scale/">The Tail at Scale</a>. <em>CACM</em>, 56(2).</li>
<li><strong>Hinton, G., Vinyals, O., &amp; Dean, J.</strong> (2015). <a href="https://arxiv.org/abs/1503.02531">Distilling the Knowledge in a Neural Network</a>.</li>
<li><strong>Sculley, D. et al.</strong> (2015). <a href="https://papers.nips.cc/paper/2015/hash/86df7dcfd896fcaf2674f757a2463eba-Abstract.html">Hidden Technical Debt in Machine Learning Systems</a>. <em>NeurIPS</em>.</li>
<li><strong>Breck, E. et al.</strong> (2017). <a href="https://research.google/pubs/pub46555/">The ML Test Score: A Rubric for ML Production Readiness</a>. <em>IEEE Big Data</em>.</li>
<li><strong>Guo, C. et al.</strong> (2017). <a href="https://arxiv.org/abs/1706.04599">On Calibration of Modern Neural Networks</a>. <em>ICML</em>.</li>
<li><strong>Lin, T.-Y. et al.</strong> (2017). <a href="https://arxiv.org/abs/1708.02002">Focal Loss for Dense Object Detection</a>. <em>ICCV</em>.</li>
<li><strong>Ratner, A. et al.</strong> (2017). <a href="https://arxiv.org/abs/1711.10160">Snorkel: Rapid Training Data Creation with Weak Supervision</a>. <em>VLDB</em>, 11(3).</li>
<li><strong>Burkov, A.</strong> (2020). <a href="http://www.mlebook.com/"><em>Machine Learning Engineering</em></a>.</li>
<li><strong>Kohavi, R., Tang, D., &amp; Xu, Y.</strong> (2020). <a href="https://experimentguide.com/"><em>Trustworthy Online Controlled Experiments</em></a>.</li>
<li><strong>Ribeiro, M. et al.</strong> (2020). <a href="https://arxiv.org/abs/2005.04118">Beyond Accuracy: Behavioral Testing of NLP Models with CheckList</a>. <em>ACL</em>.</li>
<li><strong>Chen, C. et al.</strong> (2022). <a href="https://www.oreilly.com/library/view/reliable-machine-learning/9781098106218/"><em>Reliable Machine Learning</em></a>.</li>
<li><strong>Huyen, C.</strong> (2022). <a href="https://www.oreilly.com/library/view/designing-machine-learning/9781098107956/"><em>Designing Machine Learning Systems</em></a>.</li>
<li><strong>Zinkevich, M.</strong> <a href="https://developers.google.com/machine-learning/guides/rules-of-ml">Rules of Machine Learning</a>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>ML Systems</category>
  <guid>https://imaddabbura.github.io/posts/mlsys/designing-ml-systems.html</guid>
  <pubDate>Wed, 29 Jan 2025 06:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/mlsys/images/mlsys.svg" medium="image" type="image/svg+xml"/>
</item>
<item>
  <title>Hard-Learned Lessons in Shipping Software (AI/ML) Projects</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/product-management/shipping-software-projects.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge growing">growing</span></div>
<section id="why-ml-projects-fail-to-ship" class="level2">
<h2 class="anchored" data-anchor-id="why-ml-projects-fail-to-ship">Why ML Projects Fail to Ship</h2>
<p>Some ML projects I’ve worked on shipped six months late. Others shipped and quietly died in production. A few never shipped at all, and those are the ones I keep coming back to. I’ve been through this as an individual contributor and as the person leading the team. For a long time I blamed the usual suspects: unclear requirements, technical debt, underestimating complexity. The real cause, I eventually realized, was more structural, and it looked the same from both seats.</p>
<p>A web feature has a clear definition of done: the button appears, the form submits, the data is saved. An ML feature doesn’t. “Improve recommendation accuracy” could mean another week of training runs, another architecture experiment, another round of feature engineering, indefinitely. Unlike traditional software, where the solution space is bounded by the spec, ML projects have an effectively unbounded search space. Every model can be made larger, every feature set more complete, every training run longer.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart TD
    subgraph trad ["Traditional Software"]
        direction LR
        t1["Define Feature"] --&gt; t2["Build"] --&gt; t3["Ship"]
    end
    subgraph ml ["ML Without Constraints"]
        direction LR
        m1["Vague Goal"] --&gt; m2["Experiment #1"]
        m2 --&gt;|"+0.5% accuracy"| m3["Experiment #2"]
        m3 --&gt;|"+0.2% accuracy"| m4["Experiment #3"]
        m4 -.-&gt;|"one more try..."| m2
    end
</pre>
</div>
<p></p><figcaption> Traditional software has a bounded end state. ML projects without defined constraints loop indefinitely: each experiment looks like progress.</figcaption> </figure><p></p>
</div>
</div>
</div>
<p>This produces a predictable failure mode: a project that <em>looks</em> like it’s making progress (models training, experiments running, metrics moving) but never ships.</p>
<p>The root cause, I’ve come to believe, is a category error. ML projects sit uncomfortably between research and engineering. Research is unbounded by design: you keep going until you understand something. Engineering is bounded by design: you keep going until it ships. The teams that deliver consistently have made a deliberate choice about which one they’re doing. The ones that don’t, haven’t, and so they run a research process inside an engineering context, indefinitely.</p>
<p>What follows is what I’ve learned (sometimes from my own failures, sometimes from watching teams I was leading repeat patterns I’d already lived through) about how to actually make that choice.</p>
</section>
<section id="define-the-target-before-writing-a-line-of-code" class="level2">
<h2 class="anchored" data-anchor-id="define-the-target-before-writing-a-line-of-code">Define the Target Before Writing a Line of Code</h2>
<p>The most consistent mistake I’ve made, and watched others make, is starting before the goal is properly defined. It doesn’t feel like a mistake at the time. There’s energy, there’s a general direction, there’s a team ready to move. But you can’t constrain an undefined goal. The first structural requirement for shipping is a precise definition of version one: not the roadmap, not the vision, but version one.</p>
<p>Write it as a falsifiable criterion: <em>“A model that identifies churn risk 14 days in advance with precision ≥ 70% on the holdout set, deployable via the existing prediction service.”</em> That’s a definition. “Improve churn prediction” is not; it’s a direction, and directions don’t ship.</p>
<p>Before writing code, three questions force the definition:</p>
<p><strong>Who is this for, specifically?</strong> A customer-facing recommendation system for mobile users has different input distributions, latency constraints, and acceptable error modes than an internal analyst tool. “Users in general” means nobody in particular, and a system designed for nobody in particular gets spec’d indefinitely.</p>
<p><strong>What does version one accomplish, and what does it explicitly not do?</strong> The second half matters as much as the first. Scope creep in ML is insidious because experiments feel like progress: an additional feature, a new architecture variant, a cleaned edge case. Each looks like forward motion. The out-of-scope list is what makes the in-scope list real.</p>
<p><strong>What are the success criteria, written down and falsifiable?</strong> Precision ≥ 0.70. Latency ≤ 100ms at p99. Deployable on the existing serving infrastructure. Criteria that can be verified make it possible to call the project done. Criteria that can’t (“good enough,” “production-ready,” “performs well”) guarantee the project never ends.</p>
<p>Working backwards from these answers also produces the project structure. Once you know what version one must accomplish, you can enumerate prerequisite questions: <em>What training data do we need? What does the evaluation harness look like? How does it plug into production?</em> Each answer either gets scheduled or gets cut. Vague goals don’t allow this decomposition: they keep the surface area perpetually open.</p>
</section>
<section id="make-time-the-constraint-not-scope" class="level2">
<h2 class="anchored" data-anchor-id="make-time-the-constraint-not-scope">Make Time the Constraint, Not Scope</h2>
<p>The natural instinct is to treat scope as fixed and deadline as flexible. This is exactly backwards.</p>
<p>Scope in an ML project is not fixed: it’s infinitely expandable. There’s always a better architecture to try, a cleaner way to handle edge cases, a feature that might help. Teams treat scope as the constraint because it feels <em>owned</em>: the team wrote the requirements, agreed on them, and changing them feels like abandoning a commitment. Deadlines, by contrast, feel externally imposed and therefore more negotiable, something to push when the work “isn’t ready yet.”</p>
<p>I’ve been in this meeting many times, sometimes as the engineer watching the deadline move, more often as the person responsible for it. The deadline slips, then slips again, then becomes a standing item on the weekly status call.</p>
<p>Flip the constraint. Treat the deadline as fixed and scope as the variable. This changes the question from <em>“when will we be done with everything we planned?”</em> to <em>“what’s the most important thing we can ship by this date?”</em> The second question forces real prioritization. The model that trains in four hours ships; the one that takes 24 hours doesn’t. The feature built on existing infrastructure stays; the one that requires a new data pipeline gets cut.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Deadlines as a Design Tool
</div>
</div>
<div class="callout-body-container callout-body">
<p>A deadline doesn’t dictate quality; it dictates scope. The discipline is specifically about protecting the deadline from <em>scope expansion</em>, not accelerating the work. When a new requirement surfaces mid-project, the question isn’t <em>“can we fit it in?”</em>; it’s <em>“what does it displace?”</em></p>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Version One Is Supposed to Be Small
</div>
</div>
<div class="callout-body-container callout-body">
<p>Version one of most production models is smaller, faster, and more constrained than anything the team initially imagined. Good. The goal of version one isn’t to build the best possible system; it’s to establish the deployment path, validate production integration, and generate real usage data. The best possible system comes later, built on what version one teaches you.</p>
</div>
</div>
</section>
<section id="decompose-until-done-is-unambiguous" class="level2">
<h2 class="anchored" data-anchor-id="decompose-until-done-is-unambiguous">Decompose Until Done Is Unambiguous</h2>
<p>Once you have a target and a deadline, break the project into tasks: not work items, not epics, tasks. The distinction matters: a task has an unambiguous definition of done. A project doesn’t.</p>
<p>“Train a production NLP model” is a project. Tasks are: - <em>“Label 500 training examples from the January logs”</em>: done or not done. - <em>“Achieve F1 ≥ 0.82 on the validation split”</em>: done or not done. - <em>“Write the endpoint that accepts raw text and returns a classification”</em>: done or not done.</p>
<p>If you can’t tell whether a piece of work is finished without discussion, break it down further.</p>
<p>With tasks in hand, ruthlessly prioritize against the version one criteria. Not everything is equally important, and pretending otherwise is how projects stall:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Category</th>
<th>Description</th>
<th>Rule</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Must-have</strong></td>
<td>System cannot ship without this</td>
<td>Do first, never cut</td>
</tr>
<tr class="even">
<td><strong>Should-have</strong></td>
<td>Meaningfully improves the product</td>
<td>Include if time allows</td>
</tr>
<tr class="odd">
<td><strong>Nice-to-have</strong></td>
<td>Incremental gain, no blocker</td>
<td>Version two</td>
</tr>
<tr class="even">
<td><strong>Gold-plating</strong></td>
<td>No clear user benefit</td>
<td>Cut immediately</td>
</tr>
</tbody>
</table>
<p>The failure mode is treating “should-haves” as “must-haves.” It happens because, deep down, the team doesn’t believe version two is coming. If this feels like the only shot, every improvement feels essential. But that has it backwards: version two only exists because version one shipped. Holding version one hostage to version two’s requirements is how you guarantee neither does.</p>
</section>
<section id="validate-the-core-assumption-before-building-the-system" class="level2">
<h2 class="anchored" data-anchor-id="validate-the-core-assumption-before-building-the-system">Validate the Core Assumption Before Building the System</h2>
<p>Every ML project rests on a single load-bearing assumption: <em>“Does a model trained on this data actually produce useful predictions for this problem?”</em> Everything else (the serving infrastructure, the feature pipeline, the retraining loop, the monitoring dashboard) only matters if the answer is yes.</p>
<p>I’ve fallen into this trap early in my career, and led teams into it later. The pattern is always the same: stand up a feature store, design a training pipeline, architect a serving layer, then train the model and discover the data doesn’t support the prediction task, or the signal is too weak, or the problem is better solved without ML at all. Months of infrastructure work, none of it applicable to the revised approach. The infrastructure trap is just as easy to fall into when you’re the one setting the direction as when you’re the one doing the building.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart TD
    subgraph wrong ["Common Mistake"]
        direction LR
        w1["Feature Store"] --&gt; w2["Training Pipeline"] --&gt; w3["Model Registry"] --&gt; w4["Model"] --&gt; w5["Works?"]
    end
    subgraph right ["Correct Order"]
        direction LR
        r1["Validate&lt;br/&gt;Approach"] --&gt; r2["Establish&lt;br/&gt;Deploy Path"] --&gt; r3["Build&lt;br/&gt;Infrastructure"] --&gt; r4["Ship"]
    end
</pre>
</div>
<p></p><figcaption> The infrastructure trap: building the full system before validating the approach. The correct order validates cheaply first, then invests in infrastructure.</figcaption> </figure><p></p>
</div>
</div>
</div>
<p>The minimum viable experiment: train a simple baseline on a slice of the data, evaluate it against a manually-labeled holdout, and show the results to at least one person who’d actually use the output. Logistic regression, a small neural net, a fine-tuned pretrained model, whatever takes days, not months. If the results are promising, the infrastructure investment is justified. If not, you’ve learned the most important thing about the project for the cost of two weeks. I develop this validate-the-riskiest-assumption-first idea into a full method in <a href="../../posts/mlsys/improving-mlsys-theory-of-constraint.html">Improving ML Systems with the Theory of Constraints</a>.</p>
<p>This also determines tool choice during validation. <code>scikit-learn</code>, <code>PyTorch</code>, pre-trained transformers from HuggingFace: these represent thousands of engineering hours and are battle-tested at scale. Custom architectures and bespoke training loops are justified when profiling data shows standard tools can’t meet your requirements. That data doesn’t exist before validation. Building custom infrastructure before validating the approach is the fastest way to spend six months on something nobody uses.</p>
</section>
<section id="ship-then-compound" class="level2">
<h2 class="anchored" data-anchor-id="ship-then-compound">Ship, Then Compound</h2>
<p>Once version one meets the criteria, ship it, even if it’s not perfect.</p>
<p>Every model I’ve shipped has surprised me in production. Not because the evaluation was wrong, but because it was measuring the wrong things. Your holdout set measures what you measured. Real users do things you didn’t anticipate: edge cases you didn’t label, inputs from distributions you didn’t sample, and above all, they surface which errors actually matter. A model that’s 92% accurate on the evaluation set might be systematically wrong on the 8% of inputs that are disproportionately important to users. You won’t know that until the model is deployed.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    A["Ship&lt;br/&gt;Imperfect v1"] --&gt; B["Real&lt;br/&gt;Usage Data"]
    B --&gt; C["Discover&lt;br/&gt;Actual Failures"]
    C --&gt; D["Targeted&lt;br/&gt;Fixes"]
    D --&gt; E["Ship&lt;br/&gt;Better v2"]
    E --&gt; B
</pre>
</div>
<p></p><figcaption> The iteration loop: each shipped version surfaces real failures that targeted improvements address, compounding over time.</figcaption> </figure><p></p>
</div>
</div>
</div>
<p>Ship versions that meet the bar, not versions that approach some imagined ceiling. Version one will be wrong in ways you didn’t anticipate; I’ve never shipped one that wasn’t, and I’ve never led a team that did. One of the harder things about leading engineers through this is convincing them that shipping something imperfect isn’t a compromise; it’s the whole point. The failures you discover in production are the ones that matter. Find them early, when fixing them is fast, not late, when the system is load-bearing and everything is entangled.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Each Version Enables the Next
</div>
</div>
<div class="callout-body-container callout-body">
<p>Real usage reveals the failures that matter: not the ones you hypothesized in the design doc, but the ones users actually encounter. Their feedback tells you which improvements are worth making. Infrastructure built for version one scales to version two. The teams that ship consistently aren’t the ones with better planning processes; they’re the ones who’ve completed more cycles of this loop.</p>
</div>
</div>
</section>
<section id="key-takeaways" class="level2">
<h2 class="anchored" data-anchor-id="key-takeaways">Key Takeaways</h2>
<ol type="1">
<li><p><strong>Decide whether you’re doing research or engineering before you start.</strong> ML projects that don’t make this distinction run research processes in engineering contexts, indefinitely.</p></li>
<li><p><strong>Define version one as a falsifiable criterion.</strong> Precision ≥ X. Latency ≤ Y. Deployable on Z. Criteria that can’t be verified guarantee the project never ends.</p></li>
<li><p><strong>Treat deadline as fixed, scope as variable.</strong> The question is always: <em>“What’s the most important thing we can ship by this date?”</em></p></li>
<li><p><strong>Decompose until done is unambiguous.</strong> If you can’t tell whether a task is finished without discussion, it’s not a task; it’s a project.</p></li>
<li><p><strong>Validate the core assumption before building infrastructure.</strong> Does the model work on this data? Answer that first, with the simplest possible tools. Everything else comes after.</p></li>
<li><p><strong>Ship the imperfect version.</strong> Offline evaluation measures what you measured. Real usage reveals what you missed. Each shipped version enables the next.</p></li>
</ol>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Machine Learning</category>
  <category>Deep Learning</category>
  <category>Software Engineering</category>
  <guid>https://imaddabbura.github.io/posts/product-management/shipping-software-projects.html</guid>
  <pubDate>Sun, 05 Jan 2025 06:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/product-management/shipping-software-projects.png" medium="image" type="image/png" height="84" width="144"/>
</item>
<item>
  <title>From Forgetting to Fluency: How to Learn Smarter, Not Harder</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/personal-growth/notes-on-learning.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>In today’s fast-paced world, the ability to learn effectively and retain information has become more crucial than ever. Whether you’re a student preparing for exams, a professional mastering new skills, or simply someone seeking personal growth, finding ways to optimize learning and improve memory recall can make a significant difference in achieving your goals.</p>
<p>Fortunately, research in neuroscience and cognitive psychology has shed light on strategies that enhance how we absorb and retain knowledge. There are numerous methods to make learning not only more efficient but also more enjoyable. This article explores a variety of evidence-based approaches to optimize learning and strengthen recall and hopefully you’ll find actionable insights to elevate your learning journey.</p>
</section>
<section id="learning-techniques" class="level2">
<h2 class="anchored" data-anchor-id="learning-techniques">Learning Techniques</h2>
<ul>
<li>Effective instructions should match the content not the learning styles. For example, cooking instruction should use hands-on practices even if the student is a visual learner.</li>
<li>Learning means that a change made to long-term memory.</li>
<li>Human memory is not as precise/reliable as computer memory. It has <code>read-and-update</code>. Reading memory will lead to strengthen and modify the fetched information especially if the information is recently learned.</li>
<li>Stored information is stored in interconnected neural pathways. If we try to access targeted information, we activate a pathway of neurons to access the information which leads to spread the activation to other connected pathways that may not be related to the target information. This spreading activation leave related pathways primed for activation for hours . As a result:
<ul>
<li>Spreading activation leads to related but imprecise information to be conflated with target information, which leads to unreliable recall of information.</li>
<li>Because pathways stay primed for hours, it helps us with problem solving when we step away to work on something else, go for a walk, or take a shower, and the two unrelated areas connect in the middle.</li>
</ul></li>
<li>There are two types of memory:
<ul>
<li><strong>Long-term memory</strong> where information is permanently stored and is unlimited. It is analogous to disk storage.</li>
<li><strong>Working (short-term) memory</strong> is used to solve problems. It is analogous to CPU’s registers. The bigger the working memory, the faster we can learn. It is roughly fixed at birth.</li>
</ul></li>
<li><strong>Chunking</strong> is when we relate information together as one piece. The more we combine information as one piece (chunk), the easier it is to reason about and solve problems related to it. This is due to the fact that we can store pointers to such chunks in the working memory and access such chunks in long-term memory if needed. Therefore, it is critical to decompose difficult tasks into smaller pieces (chunks) when learning, which later will be chunked together as we practice.</li>
<li>The difference between experts and beginners is that experts remember and recognize patterns to help them solve problems. However, beginners read code line by line to understand what it is doing or how to approach solving problems. Therefore, to achieve proficiency in programming, you need to read/write and work with a lot of code to be exposed to more patterns as well as programming using different programming paradigms/languages.</li>
<li>To understand a concept, we may need to go from abstract to diverse set of concrete examples and back to abstract. This helps us with chunking and treating all the concrete examples as different views of the abstract concept. Once we understand the concrete examples, we can connect it back to the abstract concept.</li>
<li><strong>Spacing</strong> and <strong>Repetition</strong> are keys for learning. We learn problem-solving concepts best by spacing out their practice across multiple sessions, multiple days, and ideally, multiple weeks. Practice helps us connect the text in the problem to the concept and applying the concept to solve the problem.</li>
<li>Concentration after <strong>90 minutes</strong> is hard due to neuro-chemical balance in the brain. It is recommended to rest/sleep/walk after the 90 minutes so the information gets consolidated. Don’t work on other tasks, talk to others, or browse the internet.</li>
<li>Even if we can access information on the internet, it is advisable to understand concepts we deal with frequently so the brain can form connections and help us understand deeper concepts. Also, it is much better to try to recall information from long-term memory than search for it on the internet especially if we are not experts.</li>
<li>Problem-solving is not a generic skill. It is domain-specific skill. This means that a good chess player may not be a good problem-solver in programming or other domains. As a result, to get better at programming problem-solving, learn to solve programming problems.</li>
<li>There is no clear predictor in programming ability other than experience.</li>
<li>Growth mindset, learning to overcome setbacks and failures, and practice is all you need to be successful in your career. You will have to always evaluate your learning strategies to get the best outcome.</li>
</ul>
</section>
<section id="recommendations" class="level2">
<h2 class="anchored" data-anchor-id="recommendations">Recommendations</h2>
<ul>
<li>For recruiting:
<ul>
<li>There are no good proxies for programming ability, look at their previous work or test them on authentic programming tasks.</li>
<li>At least among young developers, years of experience may not be a very reliable measure of ability.</li>
</ul></li>
<li>For learning and training:
<ul>
<li>Reading a lot of code will help become a more efficient programmer.</li>
<li>Experts are not always the best at training beginners.</li>
<li>Learning takes time, including time between learning sessions. Intense cramming is not effective, but spaced repetition is.</li>
<li>Similarly, spending time away from a problem can help to solve it.</li>
<li>Just because you can find it through an Internet search or generative AI tool does not mean learning has become obsolete.</li>
<li>Use examples to go between abstract concepts and concrete learnable facts.</li>
<li>Seeking to succeed (rather than avoid failure) and believing that ability is changeable are important factors in resilience and learning.</li>
</ul></li>
</ul>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>As we’ve explored various approaches to optimize learning and enhance recall, it’s clear that the journey to effective learning is both dynamic and multifaceted. By incorporating evidence-based strategies such as spaced repetition, and active engagement, individuals can significantly improve their ability to absorb and retain information.</p>
<p>Embracing a growth mindset and being open to adapting your strategies will further empower you to navigate the complexities of acquiring knowledge. In conclusion, by implementing these innovative approaches, you can transform your learning experience into a more productive and rewarding endeavor. Start applying these insights today and watch as your ability to learn and recall information flourishes!</p>
</section>
<section id="further-reading" class="level2">
<h2 class="anchored" data-anchor-id="further-reading">Further Reading</h2>
<ul>
<li><em>Why Don’t Students Like School?</em> by Daniel T. Willingham provides a short and readable explanation of many of the principles of memory and how the brain works.</li>
<li><em>The Programmer’s Brain</em> by Felienne Hermans et al.c relates these concepts to programming and describes how techniques for learning and revision that are used at school can still apply to professional development.</li>
<li><em>How Learning Happens: Seminal Works in Educational Psychology and What They Mean in Practice</em> by Paul A. Kirschner and Carl Hendrick provides a tour through influential papers, explaining them in plain language and the implications and linkages between them.</li>
<li><a href="https://cacm.acm.org/magazines/2024/1/278891-10-things-software-developers-should-learn-about-learning/fulltext"><em>10 Things Software Developers Should Learn about Learning</em></a> by Neil C. C. Brown, Felienne F. J. Hermans, and Lauren E. Margulieux.</li>
</ul>
<p>#personal-growth #career-advice</p>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Problem Solving</category>
  <guid>https://imaddabbura.github.io/posts/personal-growth/notes-on-learning.html</guid>
  <pubDate>Fri, 13 Sep 2024 05:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/personal-growth/learning.svg" medium="image" type="image/svg+xml"/>
</item>
<item>
  <title>Why Your Final Layer Shouldn’t Have Softmax</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/dl/why-not-softmax.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="a-common-silent-mistake" class="level2">
<h2 class="anchored" data-anchor-id="a-common-silent-mistake">A Common, Silent Mistake</h2>
<p>If you’ve built a classifier in PyTorch, you’ve probably seen <code>nn.Softmax</code> and <code>nn.CrossEntropyLoss</code> in the same codebase. You may have even used them together: softmax at the end of the model, cross-entropy as the loss. The code runs, the loss decreases, the model converges, and everything looks fine.</p>
<p>But something is wrong: <code>nn.CrossEntropyLoss</code> already applies softmax internally. Applying it again in the model’s final layer means softmax is computed twice, and the gradients computed during backpropagation are the gradients of the wrong function. The model still learns, just more slowly, less stably, and to a worse optimum.</p>
<p>This post unpacks <em>why</em>, starting with what softmax actually does, then working through the numerical stability mechanism that motivates keeping raw logits, and finishing with a clear picture of when softmax belongs and when it doesn’t.</p>
</section>
<section id="what-softmax-does" class="level2">
<h2 class="anchored" data-anchor-id="what-softmax-does">What Softmax Does</h2>
<p>The softmax function takes a vector of raw scores (<strong>logits</strong>) and squashes them into a probability distribution:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bsoftmax%7D(z_i)%20=%20%5Cfrac%7Be%5E%7Bz_i%7D%7D%7B%5Csum_j%20e%5E%7Bz_j%7D%7D"></p>
<p>The outputs are in <img src="https://latex.codecogs.com/png.latex?%5B0,%201%5D"> and sum to 1. For a ten-class classifier, softmax turns a vector like <img src="https://latex.codecogs.com/png.latex?%5B2.1,%5C%20-0.3,%5C%200.8,%5C%20%5Cldots%5D"> into a proper probability distribution over the ten classes. This seems like exactly the right thing to do before computing a loss that expects probabilities.</p>
<p>The problem isn’t what softmax does. It’s <em>where</em> you do it, and whether the operation downstream already does it better.</p>
</section>
<section id="the-log-sum-exp-trick" class="level2">
<h2 class="anchored" data-anchor-id="the-log-sum-exp-trick">The Log-Sum-Exp Trick</h2>
<p>To understand why <code>CrossEntropyLoss</code> wants raw logits, we need to look at what it computes. Cross-entropy loss for the true class <img src="https://latex.codecogs.com/png.latex?y"> is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BL%7D%20=%20-%5Clog%5Cleft(%5Cfrac%7Be%5E%7Bz_y%7D%7D%7B%5Csum_j%20e%5E%7Bz_j%7D%7D%5Cright)%20=%20-z_y%20+%20%5Clog%5Csum_j%20e%5E%7Bz_j%7D"></p>
<p>The second term, <img src="https://latex.codecogs.com/png.latex?%5Clog%5Csum_j%20e%5E%7Bz_j%7D">, is the <strong>log-sum-exp (LSE)</strong>, and it’s numerically dangerous. If any logit is large, the exponent overflows to <code>inf</code> before the log can bring it back down:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb1-2">z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1001.0</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1002.0</span>])</span>
<span id="cb1-3">torch.softmax(z, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># → tensor([nan, nan, nan])</span></span></code></pre></div></div>
<p>The standard fix is the <strong>log-sum-exp trick</strong>: subtract the maximum logit before exponentiating.</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Clog%5Csum_j%20e%5E%7Bz_j%7D%20=%20c%20+%20%5Clog%5Csum_j%20e%5E%7Bz_j%20-%20c%7D,%20%5Cquad%20c%20=%20%5Cmax_j%20z_j"></p>
<p>Subtracting <img src="https://latex.codecogs.com/png.latex?c%20=%20%5Cmax_j%20z_j"> keeps all terms in <img src="https://latex.codecogs.com/png.latex?%5Be%5E%7B-%5Cinfty%7D,%5C%201%5D">, never overflowing, never underflowing. The mathematical result is identical; the numerical result is stable.</p>
<p>This is exactly what <code>nn.CrossEntropyLoss</code> does internally. It doesn’t apply softmax and then compute cross-entropy; it <strong>fuses both operations</strong> into one numerically stable pass using the LSE trick. Passing raw logits is what makes this possible.</p>
<p>If you apply softmax first, the loss function receives <img src="https://latex.codecogs.com/png.latex?p_i%20=%20e%5E%7Bz_i%7D/%5Csum%20e%5E%7Bz_j%7D"> instead of raw logits and then applies its own log-softmax to those values, effectively computing <img src="https://latex.codecogs.com/png.latex?%5Clog(%5Ctext%7Bsoftmax%7D(%5Ctext%7Bsoftmax%7D(z)))">. The numbers are wrong and the gradients are wrong.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    subgraph bad ["Wrong: pre-applied softmax"]
        direction LR
        z1["Logits z"] --&gt; s1["nn.Softmax"] --&gt; p1["Probs p"] --&gt; ce1["CrossEntropyLoss&lt;br/&gt;log-softmax(p)"]
    end
    subgraph good ["Right: raw logits"]
        direction LR
        z2["Logits z"] --&gt; ce2["CrossEntropyLoss&lt;br/&gt;log-softmax(z), fused and stable"]
    end
</pre>
</div>
<p></p><figcaption> Left: pre-applying softmax breaks the fused computation, producing gradients of the wrong function. Right: raw logits let CrossEntropyLoss apply the log-sum-exp trick internally.</figcaption> </figure><p></p>
</div>
</div>
</div>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>The Practical Rule
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>nn.CrossEntropyLoss</code> (PyTorch) and <code>tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)</code> (TensorFlow) both expect <strong>raw logits</strong>. The loss handles the stable, fused computation internally. Don’t apply softmax to the final layer of a classifier.</p>
</div>
</div>
</section>
<section id="multi-label-classification-the-wrong-prior" class="level2">
<h2 class="anchored" data-anchor-id="multi-label-classification-the-wrong-prior">Multi-Label Classification: The Wrong Prior</h2>
<p>Softmax enforces <strong>competition</strong> between classes: increasing one class’s probability necessarily decreases the others. This is the correct structure for single-label tasks (exactly one class is true) and entirely the wrong structure for multi-label tasks, where multiple classes can be true simultaneously.</p>
<p>Consider a document classifier that assigns topics like “machine learning,” “software engineering,” and “career advice.” A document can belong to all three. Softmax forces these to compete: pushing “machine learning” up automatically pushes the others down. This competition is the wrong structure for the task.</p>
<p>There’s a deeper problem. Because softmax outputs always sum to 1, the model is structurally forced to predict high confidence for exactly one class, regardless of the input. If an image contains no objects from the training categories, softmax still redistributes its probability mass across the classes and picks a winner. If an image contains three objects, softmax still collapses to one. It has no way to say “multiple things are present” or “nothing relevant is here”: the sum-to-one constraint makes both answers impossible.</p>
<p>For multi-label classification, the correct output is <strong>sigmoid</strong>, applied independently per class:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Csigma(z_i)%20=%20%5Cfrac%7B1%7D%7B1%20+%20e%5E%7B-z_i%7D%7D"></p>
<p>Each output is an independent probability in <img src="https://latex.codecogs.com/png.latex?%5B0,%201%5D"> with no constraint that they sum to 1. Use <code>nn.BCEWithLogitsLoss</code> (which applies sigmoid internally with the same kind of numerical-stability fusion) rather than sigmoid in the model followed by <code>nn.BCELoss</code>.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Choosing the Right Output Layer
</div>
</div>
<div class="callout-body-container callout-body">
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Task</th>
<th>Loss function</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Single-label classification</td>
<td><code>nn.CrossEntropyLoss</code></td>
<td>Expects raw logits; applies log-softmax internally</td>
</tr>
<tr class="even">
<td>Multi-label classification</td>
<td><code>nn.BCEWithLogitsLoss</code></td>
<td>Expects raw logits; applies sigmoid internally</td>
</tr>
<tr class="odd">
<td>Binary classification</td>
<td><code>nn.BCEWithLogitsLoss</code></td>
<td>Same as above</td>
</tr>
<tr class="even">
<td>Probabilities at inference</td>
<td>Apply <code>softmax</code> <em>after</em> training</td>
<td>Not during training</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<section id="softmax-and-overconfidence" class="level2">
<h2 class="anchored" data-anchor-id="softmax-and-overconfidence">Softmax and Overconfidence</h2>
<p>Softmax is sensitive to the <strong>scale</strong> of the logits, not just their relative ordering. Logits <img src="https://latex.codecogs.com/png.latex?%5B3,%5C%201,%5C%200%5D"> and <img src="https://latex.codecogs.com/png.latex?%5B300,%5C%20100,%5C%200%5D"> produce the same ranking but very different softmax outputs: the scaled version concentrates nearly all probability mass on the top class. As training progresses, logit magnitudes tend to grow, and softmax increasingly exaggerates these differences. On linearly separable data those magnitudes grow without bound, which I derive in <a href="../../posts/dl/why-cross-entropy-never-zero.html">Why Cross-Entropy Never Reaches Zero</a>.</p>
<p>The result is systematic overconfidence: a model that outputs near-100% probability on examples it gets wrong. The <a href="https://arxiv.org/abs/1706.04599">Guo et al.&nbsp;2017 calibration paper</a> showed this is a consistent property of modern neural networks, not a training artifact.</p>
<p>The standard fix is <strong>temperature scaling</strong>: divide logits by a learned scalar <img src="https://latex.codecogs.com/png.latex?T%20%3E%201"> before applying softmax at inference time.</p>
<p><img src="https://latex.codecogs.com/png.latex?p_i%20=%20%5Ctext%7Bsoftmax%7D(z_i%20/%20T)"></p>
<p><img src="https://latex.codecogs.com/png.latex?T%20%3E%201"> flattens the distribution (less confident); <img src="https://latex.codecogs.com/png.latex?T%20%3C%201"> sharpens it. <img src="https://latex.codecogs.com/png.latex?T"> is fit on a held-out validation set after training finishes. Crucially, this only works if the model was trained on raw logits: the scale information that temperature scaling adjusts is preserved through training and only consumed at inference.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>The Calibration Connection
</div>
</div>
<div class="callout-body-container callout-body">
<p>Post-hoc calibration methods (temperature scaling, Platt scaling, isotonic regression) all operate on the raw logit magnitudes that accumulate through training. If your output layer applies softmax during training, the scale information is destroyed before calibration is attempted: the calibration methods have nothing useful to fit.</p>
</div>
</div>
</section>
<section id="when-softmax-belongs" class="level2">
<h2 class="anchored" data-anchor-id="when-softmax-belongs">When Softmax Belongs</h2>
<p>Removing softmax from the final classification layer doesn’t mean it’s always wrong; it means the structure it imposes (mutual exclusivity, sum-to-one) has to match what the computation actually needs.</p>
<p><strong>Attention mechanisms.</strong> The scaled dot-product attention in Transformers applies softmax to produce a distribution over positions. This is exactly right: each query should distribute its weight across keys, and the competition structure is intentional. There’s no fused loss downstream computing log-softmax again. I walk through scaled dot-product attention in <a href="../../posts/nlp/Transformer-Architecture-Explained.html">The Transformer Architecture: A Deep Dive</a>.</p>
<p><strong>Contrastive learning.</strong> Methods like CLIP apply softmax across the batch as part of the contrastive loss. The within-batch competition is the learning signal.</p>
<p><strong>Inference-time probabilities.</strong> If downstream code requires calibrated probabilities (confidence thresholds, ensemble averaging, displaying to users), apply softmax to the final logits after the forward pass, outside the model:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> torch.no_grad():</span>
<span id="cb2-2">    logits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model(x)</span>
<span id="cb2-3">    probs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.softmax(logits, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span></code></pre></div></div>
<p>The pattern: softmax belongs when the distribution semantics genuinely fit the computation, and when nothing downstream is already computing a fused version of it.</p>
</section>
<section id="key-takeaways" class="level2">
<h2 class="anchored" data-anchor-id="key-takeaways">Key Takeaways</h2>
<ol type="1">
<li><p><strong>Don’t apply softmax in your model’s final layer for classification.</strong> <code>nn.CrossEntropyLoss</code> expects raw logits and applies a fused, numerically stable log-softmax internally using the log-sum-exp trick. Pre-applying softmax computes gradients of the wrong function.</p></li>
<li><p><strong>The numerical instability is real and silent.</strong> Large logits overflow naive softmax: you get <code>nan</code> losses and corrupted gradients, often without a clear error. The fused implementation avoids this entirely.</p></li>
<li><p><strong>Multi-label tasks need sigmoid, not softmax.</strong> Softmax enforces mutual exclusivity. For tasks where multiple labels are simultaneously valid, use <code>nn.BCEWithLogitsLoss</code> with raw logits.</p></li>
<li><p><strong>Overconfidence is a logit scale problem.</strong> Softmax exaggerates differences as magnitudes grow through training. Temperature scaling is the standard fix, but only if raw logit scale is preserved through training.</p></li>
<li><p><strong>Softmax has legitimate uses.</strong> Attention weights, contrastive losses, and inference-time probability outputs are correct applications. The question is always whether competition semantics fit the problem, and whether a fused stable implementation already handles the math downstream.</p></li>
</ol>
</section>
<section id="resources" class="level2">
<h2 class="anchored" data-anchor-id="resources">Resources</h2>
<ul>
<li><a href="https://pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html">PyTorch: CrossEntropyLoss</a>.</li>
<li><strong>Guo, C. et al.</strong> (2017). <a href="https://arxiv.org/abs/1706.04599">On Calibration of Modern Neural Networks</a>. <em>ICML</em>.</li>
<li><strong>Goodfellow, I., Bengio, Y., &amp; Courville, A.</strong> (2016). <a href="https://www.deeplearningbook.org/contents/mlp.html">Deep Learning, Chapter 6</a>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Deep Learning</category>
  <guid>https://imaddabbura.github.io/posts/dl/why-not-softmax.html</guid>
  <pubDate>Sun, 09 Jun 2024 05:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/dl/images/softmax-img.png" medium="image" type="image/png" height="79" width="144"/>
</item>
<item>
  <title>Building GPT(2/3) from Scratch: Turning Theory into a Working Transformer</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/nlp/GPT2-From-Scratch.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="sec-intro" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="sec-intro">Introduction</h2>
<p>There’s an old saying in engineering: “You don’t really understand something until you can build it.” This has never been more true than in the era of LLMs. While we’ve previously explored the foundational concepts in my post on the Transformer architecture <a href="../../posts/nlp/Transformer-Architecture-Explained.html">explained here</a>, true understanding comes from implementation. That’s why today, we’re building a GPT-style model (the 124M variant) from scratch in PyTorch.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>This post pairs well with my <a href="../../posts/nlp/Transformer-Architecture-Explained.html">Transformer Architecture deep dive</a>: read that first if attention mechanisms are new to you.</p>
</div></div><p>This post has a different focus than my last “from scratch” endeavor, where I built an <a href="https://github.com/ImadDabbura/tiny-pytorch">entire deep learning framework</a> to grasp the low-level mechanics of autograd and tensor ops. Here, we’ll use PyTorch’s primitives to focus on what makes GPT special: multi-head attention, positional encodings, and the specific architectural decisions that enable language understanding.</p>
</section>
<section id="sec-gpt" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="sec-gpt">GPT-2 and GPT-3</h2>
<p>GPT (Generative Pre-trained Transformer) models, developed by OpenAI, represent a breakthrough in natural language processing. <a href="https://d4mucfpksywv.cloudfront.net/better-language-models/language_models_are_unsupervised_multitask_learners.pdf">GPT-2</a>, released in 2019, demonstrated that a transformer-based model trained on vast amounts of text could generate remarkably coherent and contextually relevant content. <a href="http://arxiv.org/abs/2005.14165">GPT-3</a>, its successor, scaled this approach to 175 billion parameters and showed emergent capabilities like few-shot learning and complex reasoning. Both models share the same fundamental architecture: stacked transformer decoder blocks that predict the next token in a sequence, trained on the simple objective of minimizing prediction error across massive text corpora.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>The 124M variant has 12 layers, 12 heads, and 768-dim embeddings. GPT-3 scaled to 96 layers, 96 heads, and 12288-dim: same architecture, 1000x parameters.</p>
</div></div><p>The 124M parameter version we’ll be building captures the essential architecture while remaining computationally tractable for individual developers, though even at this “small” scale, you’ll quickly discover why the ML community spends so much time optimizing both training efficiency and model performance.</p>
<p>By the end of this journey, you won’t just know how transformers work; you’ll have built the critical components with your own hands, optimized the training loop, and watched your model evolve from random noise to coherent text generation. Let’s begin.</p>
</section>
<section id="sec-implementation" class="level2">
<h2 class="anchored" data-anchor-id="sec-implementation">Implementation</h2>
<p>Beyond the architecture itself, we use a few optimizations that make a large difference to training throughput:</p>
<ul>
<li><p><strong>TensorFloat32 (TF32)</strong>: NVIDIA’s precision format that keeps FP32’s 8-bit exponent but truncates the mantissa from 23 bits to 10. Matrix multiplications then run on tensor cores at roughly 2-3x the throughput of true FP32, with no measurable quality loss. One line of code enables it.</p></li>
<li><p><strong>BFloat16 with Autocast</strong>: Mixed precision training using brain floating-point format, which maintains the same exponent range as FP32 but reduces mantissa precision. Combined with automatic mixed precision (AMP), this halves memory usage and speeds up training.</p></li>
<li><p><strong>torch.compile</strong>: PyTorch 2.0’s just-in-time compilation, which traces the model into a graph, fuses operations, and generates kernels that avoid needless round trips to memory. It is a single line to enable, at the cost of a slow first step while compilation happens.</p></li>
<li><p><strong><a href="https://arxiv.org/abs/2205.14135">Flash Attention</a></strong>: An algorithmic improvement that computes attention without ever materializing the full <img src="https://latex.codecogs.com/png.latex?T%20%5Ctimes%20T"> score matrix. It streams the softmax, carrying a running maximum and normalizer for each row so blocks of keys can be folded in as they are read, which drops attention memory from <img src="https://latex.codecogs.com/png.latex?O(n%5E2)"> to <img src="https://latex.codecogs.com/png.latex?O(n)">.</p></li>
<li><p><strong>Fused AdamW</strong>: A single-kernel implementation of the AdamW optimizer that reduces memory reads/writes by computing all parameter updates in one pass, cutting the time of the optimizer step by up to half.</p></li>
<li><p><strong>Annealed Learning Rate</strong>: A linear warmup followed by cosine decay, the schedule that has become standard for transformers. We’ll derive why the warmup is there instead of treating it as convention.</p></li>
<li><p><strong>Weight Decay Only on Matrices</strong>: Weight decay applies to the weight matrices in Linear and Embedding layers, and not to biases or layer normalization parameters. Those 1D parameters set scale and offset rather than mixing inputs, so pulling them toward zero fights the layer instead of regularizing it.</p></li>
<li><p><strong>Vocabulary Padding</strong>: Rounding the vocabulary from 50257 up to 50304 so the dimension divides evenly by 64. We add embedding rows that can never be emitted, and every matmul touching the vocab dimension gets faster.</p></li>
<li><p><strong>Distributed Data Parallelism (DDP)</strong>: Scaling training across multiple GPUs using PyTorch’s DDP, including gradient synchronization, correct data sharding, and keeping model state consistent across devices.</p></li>
</ul>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Data Movement Is The Bottleneck
</div>
</div>
<div class="callout-body-container callout-body">
<p>TF32, bfloat16, flash attention, fused AdamW, and vocabulary padding look like five unrelated tricks, but they are one trick applied five times. A modern GPU can do arithmetic far faster than it can move operands to and from memory, so most training steps are memory-bound, not compute-bound. Every optimization here either moves fewer bytes (lower precision, padded dimensions that tile cleanly) or moves them fewer times (fusing operations so intermediates never leave fast memory). Flash attention performs <em>more</em> arithmetic than the naive implementation and is faster anyway, because it stops writing the <img src="https://latex.codecogs.com/png.latex?T%20%5Ctimes%20T"> score matrix to memory.</p>
</div>
</div>
<p>The GPT-2 paper omits certain architectural details and hyperparameter specifications, so we refer to the GPT-3 paper to fill these gaps. The core architecture is consistent between the two models, which is what makes the substitution safe.</p>
<section id="sec-setup" class="level3">
<h3 class="anchored" data-anchor-id="sec-setup">Setup</h3>
<p>We start with imports and two small helpers. <code>listify</code> normalizes arguments, and the <code>annealer</code> decorator lets us compose the warmup-then-cosine learning rate schedule further down.</p>
<div id="660e14ce-2270-4cf4-9078-36759f24db29" class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> inspect</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> math</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb1-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> time</span>
<span id="cb1-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dataclasses <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> dataclass</span>
<span id="cb1-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> functools <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> partial, wraps</span>
<span id="cb1-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Callable, Iterable</span>
<span id="cb1-8"></span>
<span id="cb1-9"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> tiktoken</span>
<span id="cb1-10"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb1-11"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch.distributed <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> dist</span>
<span id="cb1-12"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch.nn <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> nn</span>
<span id="cb1-13"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch.nn.functional <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> F</span>
<span id="cb1-14"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch.optim <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> opt</span>
<span id="cb1-15"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> torch.distributed <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> destroy_process_group, init_process_group</span>
<span id="cb1-16"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> torch.nn.parallel <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> DistributedDataParallel <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> DDP</span></code></pre></div></div>
</details>
</div>
<div id="f585c521" class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> listify(obj):</span>
<span id="cb2-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> obj <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb2-3">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> []</span>
<span id="cb2-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(obj, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb2-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [obj]</span>
<span id="cb2-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(obj, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>):</span>
<span id="cb2-7">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> obj</span>
<span id="cb2-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(obj, Iterable):</span>
<span id="cb2-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(obj)</span>
<span id="cb2-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb2-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [obj]</span></code></pre></div></div>
</details>
</div>
<div id="4fd6e093-b414-4fb9-a165-04ae9e312c03" class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> annealer(func: Callable):</span>
<span id="cb3-2">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@wraps</span>(func)</span>
<span id="cb3-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> annealer_wrapper(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>args, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>kwargs):</span>
<span id="cb3-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> partial(func, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>args, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>kwargs)</span>
<span id="cb3-5"></span>
<span id="cb3-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> annealer_wrapper</span>
<span id="cb3-7"></span>
<span id="cb3-8"></span>
<span id="cb3-9"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@annealer</span></span>
<span id="cb3-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lin_sched(start, end, pos):</span>
<span id="cb3-11">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Linear scheduler."""</span></span>
<span id="cb3-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (end <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> start) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> pos</span>
<span id="cb3-13"></span>
<span id="cb3-14"></span>
<span id="cb3-15"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@annealer</span></span>
<span id="cb3-16"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> cos_sched(start, end, pos):</span>
<span id="cb3-17">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Cosine scheduler."""</span></span>
<span id="cb3-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> math.cos(math.pi <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> pos))) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (end <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> start) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb3-19"></span>
<span id="cb3-20"></span>
<span id="cb3-21"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> combine_scheds(pcts, scheds):</span>
<span id="cb3-22">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Combine multiple schedulers, each run for a given percentage of the</span></span>
<span id="cb3-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    training process.</span></span>
<span id="cb3-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb3-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(pcts) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(scheds), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Each scheduler should have its `pct`."</span></span>
<span id="cb3-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(pcts) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Sum of the `pcts` should be equal to 1."</span></span>
<span id="cb3-28">    pcts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> listify(pcts))</span>
<span id="cb3-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> (pcts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">all</span>(), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"All percentages should be non-negative."</span></span>
<span id="cb3-30">    pcts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cumsum(pcts, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb3-31"></span>
<span id="cb3-32">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _inner(pos):</span>
<span id="cb3-33">        idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> pcts).nonzero().<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>()</span>
<span id="cb3-34">        actual_pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> pcts[idx]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (pcts[idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> pcts[idx])</span>
<span id="cb3-35">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> scheds[idx](actual_pos)</span>
<span id="cb3-36"></span>
<span id="cb3-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> _inner</span></code></pre></div></div>
</details>
</div>
</section>
<section id="sec-config" class="level3">
<h3 class="anchored" data-anchor-id="sec-config">Configuration</h3>
<p>One dataclass holds every architectural and training hyperparameter. The values are GPT-2 124M: 12 layers, 12 heads, 768-dimensional embeddings, and a 1024-token context.</p>
<div id="96f98324-58a9-49f1-9ad1-789c1a6b20e4" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span></span>
<span id="cb4-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> GPTConfig:</span>
<span id="cb4-3">    block_sz: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Sequence length</span></span>
<span id="cb4-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Originally 50000 BPE merges + 256 byte tokens + 1 for &lt;|endoftext|&gt; token</span></span>
<span id="cb4-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># which will delimit different documents. This token's index is 50256</span></span>
<span id="cb4-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># However, we found that using 50257 as the vocab size is not a multiple of</span></span>
<span id="cb4-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 64 and we could improve efficiency and performance (through better</span></span>
<span id="cb4-8">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># occupancy) if we round up to the closest multiple of 64, which is 50304.</span></span>
<span id="cb4-9">    vocab_sz: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50257</span></span>
<span id="cb4-10">    n_layer:  <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Number of layers</span></span>
<span id="cb4-11">    n_embd:   <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">768</span>   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Embedding dimension</span></span>
<span id="cb4-12">    n_head:   <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Number of attention heads</span></span>
<span id="cb4-13">    lr:       <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3e-4</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Good for big models</span></span>
<span id="cb4-14">    batch_sz: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span></span>
<span id="cb4-15">    dropout:  <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb4-16">    bias:     <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span></code></pre></div></div>
</div>
</section>
<section id="sec-mlp" class="level3">
<h3 class="anchored" data-anchor-id="sec-mlp">The Feed-Forward Network</h3>
<p>The MLP runs on each token independently, with no interaction between positions. Because it projects out to <code>4 x n_embd</code> and back, it holds roughly two-thirds of the model’s parameters.</p>
<div id="f41875c6" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> MLP(nn.Module):</span>
<span id="cb5-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config: GPTConfig):</span>
<span id="cb5-3">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Point-wise feed-forward network that applies non-linearity</span></span>
<span id="cb5-4">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># on every token separately. THERE IS NO INTERACTION BETWEEN TOKENS.</span></span>
<span id="cb5-5">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This is where almost all the capacity and non-linearities of the </span></span>
<span id="cb5-6">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># model come from especially when we project it to 4 x n_embd</span></span>
<span id="cb5-7">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb5-8">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_fc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.n_embd, config.n_embd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)</span>
<span id="cb5-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Found to be better than ReLU in terms of gradient saturation</span></span>
<span id="cb5-10">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.gelu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.GELU(approximate<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tanh"</span>)</span>
<span id="cb5-11">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_proj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.n_embd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, config.n_embd)</span>
<span id="cb5-12">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dropout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Dropout(config.dropout)</span>
<span id="cb5-13">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_proj.NANOGPT_SCALE_INIT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-14"></span>
<span id="cb5-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb5-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dropout(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_proj(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.gelu(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_fc(x))))</span></code></pre></div></div>
</div>
</section>
<section id="sec-attention" class="level3">
<h3 class="anchored" data-anchor-id="sec-attention">Causal Self-Attention</h3>
<p>Attention is the one operation that lets tokens exchange information. All three projections live in a single fused <code>c_attn</code> matrix that we split after the matmul, and <code>F.scaled_dot_product_attention</code> supplies both Flash Attention and the causal mask.</p>
<div id="a14dee4c" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> CausalSelfAttention(nn.Module):</span>
<span id="cb6-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config: GPTConfig):</span>
<span id="cb6-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb6-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_head <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> config.n_head</span>
<span id="cb6-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_embd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> config.n_embd</span>
<span id="cb6-6">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># For optimization purposes, QKV matrics will be in one big matrix</span></span>
<span id="cb6-7">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># that will be split after we do matmul with X</span></span>
<span id="cb6-8">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_attn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.n_embd, config.n_embd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>config.bias)</span>
<span id="cb6-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Main job of this projection is for the interaction between heads</span></span>
<span id="cb6-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># because they worked in isolation -&gt; cross-head communication</span></span>
<span id="cb6-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># that would lead to suppressing or amplify the combination</span></span>
<span id="cb6-12">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_proj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.n_embd, config.n_embd, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>config.bias)</span>
<span id="cb6-13">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.attn_dropout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Dropout(config.dropout)</span>
<span id="cb6-14">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.resid_dropout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Dropout(config.dropout)</span>
<span id="cb6-15">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dropout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> config.dropout</span>
<span id="cb6-16">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_proj.NANOGPT_SCALE_INIT <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-17">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span><span class="al" style="color: #AD0000;
background-color: null;
font-style: inherit;">NOTE</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">: Mask is not needed when we use Pytorch's Flash attention</span></span>
<span id="cb6-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># self.register_buffer(</span></span>
<span id="cb6-19">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "mask",</span></span>
<span id="cb6-20">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     torch.tril(torch.ones(config.block_sz, config.block_sz))</span></span>
<span id="cb6-21">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># )</span></span>
<span id="cb6-22"></span>
<span id="cb6-23">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb6-24">        B, T, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x.shape</span>
<span id="cb6-25">        qkv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_attn(x)</span>
<span id="cb6-26">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># q/k/v is B x T x n_embd each</span></span>
<span id="cb6-27">        q, k, v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.split(qkv, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_embd, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb6-28">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Reshape q/k/v to B x n_head x T x (n_embd / n_head)</span></span>
<span id="cb6-29">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># So each head would be learning different kind of relationships</span></span>
<span id="cb6-30">        q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q.view(B, T, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_head, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_embd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_head).transpose(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb6-31">        k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> k.view(B, T, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_head, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_embd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_head).transpose(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb6-32">        v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> v.view(B, T, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_head, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_embd <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_head).transpose(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb6-33">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># attn is B x T x T</span></span>
<span id="cb6-34">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># attn = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.shape[-1]))</span></span>
<span id="cb6-35">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Mask out future tokens</span></span>
<span id="cb6-36">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># attn = attn.masked_fill(self.mask[:T, :T] == 0, float("-inf"))</span></span>
<span id="cb6-37">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># attn = self.attn_dropout(F.softmax(attn, dim=-1))</span></span>
<span id="cb6-38">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># y is B x T x n_embd</span></span>
<span id="cb6-39">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># y = attn @ v</span></span>
<span id="cb6-40">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Uses Flash attention that never materialize attention matrices for</span></span>
<span id="cb6-41">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># each head and is aware of the memory hierarchy and tries to reduce</span></span>
<span id="cb6-42">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># read/writes with more FLOPs -&gt; Speed up since we're memory bound</span></span>
<span id="cb6-43">        y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> F.scaled_dot_product_attention(q, k, v, is_causal<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb6-44">        y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> y.transpose(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>).contiguous().view(B, T, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.n_embd)</span>
<span id="cb6-45">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.resid_dropout(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.c_proj(y))</span></code></pre></div></div>
</div>
</section>
<section id="sec-block" class="level3">
<h3 class="anchored" data-anchor-id="sec-block">The Transformer Block</h3>
<p>A block wraps attention and the MLP in residual connections. The normalization sits <em>before</em> each sublayer instead of after, which departs from <a href="https://arxiv.org/abs/1706.03762">Attention Is All You Need</a>. That ordering keeps the residual path an unobstructed identity from input to output, so gradients reach the early layers without crossing a normalization at every step. Deep stacks are much easier to train this way.</p>
<div id="85779fd0" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Block(nn.Module):</span>
<span id="cb7-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config: GPTConfig):</span>
<span id="cb7-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb7-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ln_1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(config.n_embd)</span>
<span id="cb7-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ln_2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(config.n_embd)</span>
<span id="cb7-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.mlp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> MLP(config)</span>
<span id="cb7-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.attn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> CausalSelfAttention(config)</span>
<span id="cb7-8"></span>
<span id="cb7-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb7-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Use Pre-layer normalization which deviates from the</span></span>
<span id="cb7-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># transformer original paper that uses post-layer normalization.</span></span>
<span id="cb7-12">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This should help stabilize training</span></span>
<span id="cb7-13">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.attn(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ln_1(x))</span>
<span id="cb7-14">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.mlp(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ln_2(x))</span>
<span id="cb7-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x</span></code></pre></div></div>
</div>
</section>
<section id="sec-model" class="level3">
<h3 class="anchored" data-anchor-id="sec-model">The Full Model</h3>
<p>This class assembles the embeddings, the stack of blocks, and the language modeling head. It also carries <a href="https://arxiv.org/abs/1608.05859">weight tying</a> between the token embedding and the output head, which accounts for 38.6M of the model’s 124M parameters, the residual-aware initialization scaling derived below, and weight decay applied only to matrices. One thing to notice in <code>forward</code>: it hands <strong>raw logits</strong> to <code>F.cross_entropy</code> and never applies a softmax of its own, for the reasons in <a href="../../posts/dl/why-not-softmax.html">Why Your Final Layer Shouldn’t Have Softmax</a>.</p>
<div id="20a4b2f4" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> GPT2(nn.Module):</span>
<span id="cb8-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config: GPTConfig):</span>
<span id="cb8-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb8-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.config <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> config</span>
<span id="cb8-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.ModuleDict(</span>
<span id="cb8-6">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>(</span>
<span id="cb8-7">                wte<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>nn.Embedding(config.vocab_sz, config.n_embd),</span>
<span id="cb8-8">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Attention operation is a permutation equivariant, this means that</span></span>
<span id="cb8-9">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># if we permute the input then the corresponding output will be</span></span>
<span id="cb8-10">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># permuted in exactly the same way. In other words, attention mechanism</span></span>
<span id="cb8-11">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># is not aware of the relative ordering of the tokens. Therefore, we</span></span>
<span id="cb8-12">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># need some way to encode the positions of the tokens in each sequence.</span></span>
<span id="cb8-13">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This is where positional encoding comes into play.</span></span>
<span id="cb8-14">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Here we use a simple positional encoding that is a simple</span></span>
<span id="cb8-15">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># embedding of the position of the token in the sequence.</span></span>
<span id="cb8-16">                wpe<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>nn.Embedding(config.block_sz, config.n_embd),</span>
<span id="cb8-17">                h<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>nn.ModuleList(</span>
<span id="cb8-18">                    [Block(config) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(config.n_layer)]</span>
<span id="cb8-19">                ),</span>
<span id="cb8-20">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Final layer norm after all transformer layers</span></span>
<span id="cb8-21">                ln_f<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>nn.LayerNorm(config.n_embd),</span>
<span id="cb8-22">            )</span>
<span id="cb8-23">        )</span>
<span id="cb8-24">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Sometimes called classifier</span></span>
<span id="cb8-25">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lm_head <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.n_embd, config.vocab_sz, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb8-26"></span>
<span id="cb8-27">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Weight sharing between the token embedding layer and</span></span>
<span id="cb8-28">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># last linear layer (LM head classifier). The rationale is</span></span>
<span id="cb8-29">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># that tokens that are semantically similar to each other in</span></span>
<span id="cb8-30">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the embedding space should have similar probabilities in the</span></span>
<span id="cb8-31">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># softmax of the LM head layer.</span></span>
<span id="cb8-32">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This is also one of the biggest matrices in the model: 50257 * 768 =</span></span>
<span id="cb8-33">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 38.6M parameters, ~31% of the model's 124M total. Without tying, the</span></span>
<span id="cb8-34">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># same model would carry that matrix twice and weigh in at ~163M.</span></span>
<span id="cb8-35">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.wte.weight <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lm_head.weight</span>
<span id="cb8-36">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">apply</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._init_weights)</span>
<span id="cb8-37"></span>
<span id="cb8-38">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _init_weights(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, module):</span>
<span id="cb8-39">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># The following initialization comes from gpt2 src code</span></span>
<span id="cb8-40">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span><span class="al" style="color: #AD0000;
background-color: null;
font-style: inherit;">NOTE</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">: Because token embedding and classifier weights are shared,</span></span>
<span id="cb8-41">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># our initialization logic will initialize the weight matrix twice</span></span>
<span id="cb8-42">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># but shouldn't be an issue since they're being initialized with the</span></span>
<span id="cb8-43">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># same std and mean</span></span>
<span id="cb8-44">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(module, nn.Linear):</span>
<span id="cb8-45">            std <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span></span>
<span id="cb8-46">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># We're changing std because residual path affect std</span></span>
<span id="cb8-47">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># by increasing it on every layer so we need to adjust</span></span>
<span id="cb8-48">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># it so we still have the same std = 0.02</span></span>
<span id="cb8-49">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">hasattr</span>(module, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NANOGPT_SCALE_INIT"</span>):</span>
<span id="cb8-50">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># `2` here because every layer has two blocks:</span></span>
<span id="cb8-51">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   - Attention block</span></span>
<span id="cb8-52">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   - MLP block</span></span>
<span id="cb8-53">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># `N` is the number of layers in the model (n_layer)</span></span>
<span id="cb8-54">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Since they are independent, variance of the sum of the two</span></span>
<span id="cb8-55">                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># blocks is the sum of the variances</span></span>
<span id="cb8-56">                std <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.config.n_layer) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span></span>
<span id="cb8-57">            nn.init.normal_(module.weight, std<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>std)</span>
<span id="cb8-58">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> module.bias <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb8-59">                nn.init.zeros_(module.bias)</span>
<span id="cb8-60">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(module, nn.Embedding):</span>
<span id="cb8-61">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># We're initializing the token and positional embeddings</span></span>
<span id="cb8-62">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># with the same std but the paper initialized the positional</span></span>
<span id="cb8-63">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># embedding with std = 0.01</span></span>
<span id="cb8-64">            nn.init.normal_(module.weight, std<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>)</span>
<span id="cb8-65"></span>
<span id="cb8-66">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, targets<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb8-67">        T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb8-68">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> (</span>
<span id="cb8-69">            T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.config.block_sz</span>
<span id="cb8-70">        ), <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Sequence length must be &lt;= </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>config<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>block_sz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, got </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>T<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb8-71">        pos_emb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.wpe(</span>
<span id="cb8-72">            torch.arange(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, T, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">long</span>, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>x.device)</span>
<span id="cb8-73">        )</span>
<span id="cb8-74">        tok_emb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.wte(x)</span>
<span id="cb8-75">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pos_emb <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> tok_emb</span>
<span id="cb8-76">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.h:</span>
<span id="cb8-77">            x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> block(x)</span>
<span id="cb8-78">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.transformer.ln_f(x)</span>
<span id="cb8-79">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># logits is B x T x vocab_sz</span></span>
<span id="cb8-80">        logits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lm_head(x)</span>
<span id="cb8-81">        loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb8-82">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> targets <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb8-83">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># F.cross_entropy expects the 2nd dimension to be probabilities</span></span>
<span id="cb8-84">            loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> F.cross_entropy(</span>
<span id="cb8-85">                logits.view(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.config.vocab_sz), targets.view(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb8-86">            )</span>
<span id="cb8-87">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> logits, loss</span>
<span id="cb8-88"></span>
<span id="cb8-89">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> configure_optimizer(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, weight_decay, lr, device):</span>
<span id="cb8-90">        params_dict <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb8-91">            pn: p <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> pn, p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.named_parameters() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> p.requires_grad</span>
<span id="cb8-92">        }</span>
<span id="cb8-93">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># We're not applying weight decay to bias and layer norm parameters</span></span>
<span id="cb8-94">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># And any 1D parameters. Therefore, we are ONLY applying weight decay</span></span>
<span id="cb8-95">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># to the weight matrices in Embedding and Linear layers</span></span>
<span id="cb8-96">        decay_params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [p <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> params_dict.values() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> p.ndim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb8-97">        nondecay_params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [p <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> params_dict.values() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> p.ndim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb8-98">        params_groups <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb8-99">            {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"params"</span>: decay_params, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"weight_decay"</span>: weight_decay},</span>
<span id="cb8-100">            {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"params"</span>: nondecay_params, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"weight_decay"</span>: <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>},</span>
<span id="cb8-101">        ]</span>
<span id="cb8-102">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fused AdamW is available for PyTorch 2.0+</span></span>
<span id="cb8-103">        fused_available <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fused"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> inspect.signature(opt.AdamW).parameters</span>
<span id="cb8-104">        use_fused <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fused_available <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cuda"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> device</span>
<span id="cb8-105">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> opt.AdamW(</span>
<span id="cb8-106">            params_groups, lr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>lr, betas<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.9</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.95</span>), eps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-8</span>, fused<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>use_fused</span>
<span id="cb8-107">        )</span>
<span id="cb8-108"></span>
<span id="cb8-109">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@torch.no_grad</span></span>
<span id="cb8-110">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, idxs: torch.Tensor, max_tokens: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>):</span>
<span id="cb8-111">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_tokens):</span>
<span id="cb8-112">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># x would be B x T x vocab_sz (At most we would have</span></span>
<span id="cb8-113">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># block_sz tokens since we're using fixed block_sz for the</span></span>
<span id="cb8-114">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># positional embedding</span></span>
<span id="cb8-115">            idxs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> idxs[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.config.block_sz :]</span>
<span id="cb8-116">            logits, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>(idxs)</span>
<span id="cb8-117">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get probs for last token to predict next token</span></span>
<span id="cb8-118">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This would be B x vocab_sz</span></span>
<span id="cb8-119">            logits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> logits[:, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, :]</span>
<span id="cb8-120">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Apply softmax to get probabilities</span></span>
<span id="cb8-121">            probs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> F.softmax(logits, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb8-122">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pick top 50 prob -&gt; we would never pick tokens with</span></span>
<span id="cb8-123">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># very small probs (right tails) -&gt; B x 50</span></span>
<span id="cb8-124">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># probs/idxs are sorted in descending order</span></span>
<span id="cb8-125">            topk_probs, topk_idxs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.topk(probs, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span>, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb8-126">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Sample 1 token from the top 50 tokens -&gt; idx is B x 1</span></span>
<span id="cb8-127">            idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.multinomial(topk_probs, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb8-128">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get the vocab idx as `multinomial` returns only indices that</span></span>
<span id="cb8-129">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># corresponds to the given array</span></span>
<span id="cb8-130">            idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.gather(topk_idxs, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, idx)</span>
<span id="cb8-131">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span><span class="al" style="color: #AD0000;
background-color: null;
font-style: inherit;">TODO</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">: We should check for end_of_text token and break out of</span></span>
<span id="cb8-132">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the loop (stop generation) even if we have not reached max_tokens</span></span>
<span id="cb8-133">            idxs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cat([idxs, idx], dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb8-134">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> idxs</span></code></pre></div></div>
</div>
</section>
<section id="sec-init-scaling" class="level3">
<h3 class="anchored" data-anchor-id="sec-init-scaling">Why Depth Changes the Initialization</h3>
<p>The line <code>std *= (2 * self.config.n_layer) ** -0.5</code> in <code>_init_weights</code> looks like a tuned constant, but it falls out of how the residual stream accumulates.</p>
<p>A block writes into the stream twice, in sequence, exactly as <code>Block.forward</code> does:</p>
<p><img src="https://latex.codecogs.com/png.latex?x_n'%20=%20x_%7Bn-1%7D%20+%20a_n,%20%5Cqquad%20x_n%20=%20x_n'%20+%20m_n"></p>
<p>with <img src="https://latex.codecogs.com/png.latex?a_n%20=%20%5Cmathrm%7Battn%7D(%5Cmathrm%7BLN%7D_1(x_%7Bn-1%7D))"> and <img src="https://latex.codecogs.com/png.latex?m_n%20=%20%5Cmathrm%7BMLP%7D(%5Cmathrm%7BLN%7D_2(x_n'))">. These are not independent draws, since <img src="https://latex.codecogs.com/png.latex?m_n"> is computed from <img src="https://latex.codecogs.com/png.latex?x_n'">, which already contains <img src="https://latex.codecogs.com/png.latex?a_n">. What lets their variances add is the output projection: each sublayer ends in a <code>c_proj</code> initialized zero-mean and independent of its input, so <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D%5Bm_n%20%5Cmid%20x_n'%5D%20=%200"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BCov%7D(x_n',%20m_n)%20=%200">. A write is uncorrelated with the stream it lands on even though it was computed from it. Pre-normalization then fixes each write’s size: <code>attn</code> and <code>MLP</code> see unit variance however wide the stream has grown, so <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BVar%7D(a_n)%20=%20%5Cmathrm%7BVar%7D(m_n)%20=%20%5Csigma%5E2"> at every depth. Applying this at each step and unrolling over <img src="https://latex.codecogs.com/png.latex?N"> blocks, from embeddings at <img src="https://latex.codecogs.com/png.latex?%5Csigma_0%5E2">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BVar%7D(x_N)%20=%20%5Csigma_0%5E2%20+%202N%5Csigma%5E2"></p>
<p>So the standard deviation grows like <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7B2N%7D">, set by depth alone. With <img src="https://latex.codecogs.com/png.latex?%5Csigma%20=%20%5Csigma_0%20=%200.02"> and <img src="https://latex.codecogs.com/png.latex?N%20=%2012">, the stream leaves the stack at <img src="https://latex.codecogs.com/png.latex?0.02%5Csqrt%7B25%7D%20=%200.1">, five times the width it entered with. Choosing <img src="https://latex.codecogs.com/png.latex?%5Csigma%20=%20%5Csigma_0(2N)%5E%7B-1/2%7D"> instead makes the accumulated term <img src="https://latex.codecogs.com/png.latex?2N%20%5Ccdot%20%5Csigma_0%5E2/(2N)%20=%20%5Csigma_0%5E2">, so <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7BVar%7D(x_N)%20=%202%5Csigma_0%5E2"> and total growth is <img src="https://latex.codecogs.com/png.latex?%5Csqrt2"> at any depth. The 2 counts the writes per block and the <img src="https://latex.codecogs.com/png.latex?-1/2"> undoes the square root. It is also why <code>NANOGPT_SCALE_INIT</code> is set on <code>c_proj</code> in <code>MLP</code> and <code>CausalSelfAttention</code> and nowhere else: those are the two projections that write into the stream.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/residual-init-scaling.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure 1: Standard deviation of the residual stream after each block, simulated with random writes at 768 dimensions and plotted against the closed form derived above. Markers are the simulation, the pale bands are the theory. Without the correction the stream leaves a 12-block stack 5.0x wider than it entered; scaling each sublayer’s output projection by (2N)^{-1/2} holds the growth to 1.41x, which is \sqrt2, at any depth."><img src="https://imaddabbura.github.io/posts/nlp/images/residual-init-scaling.svg" class="img-fluid figure-img" style="width:100.0%" alt="Figure 1: Standard deviation of the residual stream after each block, simulated with random writes at 768 dimensions and plotted against the closed form derived above. Markers are the simulation, the pale bands are the theory. Without the correction the stream leaves a 12-block stack 5.0x wider than it entered; scaling each sublayer’s output projection by (2N)^{-1/2} holds the growth to 1.41x, which is \sqrt2, at any depth."></a></p>
<figcaption><strong>Figure 1:</strong> Standard deviation of the residual stream after each block, simulated with random writes at 768 dimensions and plotted against the closed form derived above. Markers are the simulation, the pale bands are the theory. Without the correction the stream leaves a 12-block stack 5.0x wider than it entered; scaling each sublayer’s output projection by <img src="https://latex.codecogs.com/png.latex?(2N)%5E%7B-1/2%7D"> holds the growth to 1.41x, which is <img src="https://latex.codecogs.com/png.latex?%5Csqrt2">, at any depth.</figcaption>
</figure>
</div>
</section>
<section id="sec-data" class="level3">
<h3 class="anchored" data-anchor-id="sec-data">Data Loading</h3>
<p>The loader tokenizes the corpus once with tiktoken’s GPT-2 BPE encoding (which I build from scratch in <a href="../../posts/nlp/BPE-Tokenizer.html">Byte Pair Encoding</a>, and place among the alternatives in <a href="../../posts/nlp/Tokenization-Strategies.html">Tokenization Strategies</a>), then hands out contiguous windows. Sharding is where this gets subtle. Each process must take a disjoint slice, and all processes must wrap around together; otherwise the ranks drift out of step and train on overlapping data with nothing to signal it.</p>
<div id="b10a62ed" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> DataLoaderLight:</span>
<span id="cb9-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(</span>
<span id="cb9-3">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>,</span>
<span id="cb9-4">        file_path: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>,</span>
<span id="cb9-5">        batch_sz: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>,</span>
<span id="cb9-6">        block_sz: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>,</span>
<span id="cb9-7">        process_rank: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,</span>
<span id="cb9-8">        number_processes: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb9-9">    ) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb9-10">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.batch_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> batch_sz</span>
<span id="cb9-11">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.block_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> block_sz</span>
<span id="cb9-12">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.process_rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> process_rank</span>
<span id="cb9-13">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.number_processes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> number_processes</span>
<span id="cb9-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">open</span>(file_path, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"r"</span>) <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> f:</span>
<span id="cb9-15">            text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> f.read()</span>
<span id="cb9-16">        encoder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tiktoken.get_encoding(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt2"</span>)</span>
<span id="cb9-17">        tokens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor(encoder.encode(text), dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">long</span>)</span>
<span id="cb9-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># We can truncate the tokens list/tensor based on a multiple of</span></span>
<span id="cb9-19">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># `batch_sz x block_sz x number_processes`. This is useful</span></span>
<span id="cb9-20">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># for multi-node training and mimics the behavior of DataLoader's</span></span>
<span id="cb9-21">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># `drop_last` parameter. Think of it as for every iteration we sample</span></span>
<span id="cb9-22">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># batch_sz x block_sz x number_processes input tokens where each</span></span>
<span id="cb9-23">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># process would get batch_sz x block_sz tokens -&gt; Each iteration would</span></span>
<span id="cb9-24">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># lead to all processes running the same load and no process is idling</span></span>
<span id="cb9-25">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tokens_per_process <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.batch_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.block_sz</span>
<span id="cb9-26">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tokens_per_iteration <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb9-27">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tokens_per_process <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.number_processes</span>
<span id="cb9-28">        )</span>
<span id="cb9-29">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Every global iteration predicts `tokens_per_iteration` labels, so</span></span>
<span id="cb9-30">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># it needs one additional token to label the final input token.</span></span>
<span id="cb9-31">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.num_batches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb9-32">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(tokens) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb9-33">        ) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tokens_per_iteration</span>
<span id="cb9-34">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.num_batches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:</span>
<span id="cb9-35">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(</span>
<span id="cb9-36">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dataset must contain at least "</span></span>
<span id="cb9-37">                <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>tokens_per_iteration <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> tokens"</span></span>
<span id="cb9-38">            )</span>
<span id="cb9-39">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tokens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tokens[:<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.num_batches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tokens_per_iteration <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb9-40">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Loaded </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tokens)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> tokens"</span>)</span>
<span id="cb9-41">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"1 epoch = </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> batches"</span>)</span>
<span id="cb9-42">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.current_batch <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb9-43"></span>
<span id="cb9-44">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__len__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb9-45">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.num_batches</span>
<span id="cb9-46"></span>
<span id="cb9-47">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> next_batch(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb9-48">        global_start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.current_batch <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tokens_per_iteration</span>
<span id="cb9-49">        process_start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb9-50">            global_start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.process_rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tokens_per_process</span>
<span id="cb9-51">        )</span>
<span id="cb9-52">        process_end <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> process_start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tokens_per_process <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb9-53">        buf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.tokens[process_start:process_end]</span>
<span id="cb9-54">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> buf[:<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].view(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.batch_sz, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.block_sz)</span>
<span id="cb9-55">        y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> buf[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:].view(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.batch_sz, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.block_sz)</span>
<span id="cb9-56">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Each process will process batch_sz x block_sz tokens in each</span></span>
<span id="cb9-57">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># iteration -&gt; with number_processes processes, total tokens processed</span></span>
<span id="cb9-58">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># in each iteration is batch_sz x block_sz x number_processes. In the</span></span>
<span id="cb9-59">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># case of one process, total tokens would be batch_sz x block_sz</span></span>
<span id="cb9-60">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Similar to DataLoader's `drop_last` parameter, we drop the last</span></span>
<span id="cb9-61">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># batch if it's not a multiple of batch_sz x block_sz x number_processes.</span></span>
<span id="cb9-62">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Tracking the global batch directly makes every process reset together</span></span>
<span id="cb9-63">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># after consuming the same number of complete global batches.</span></span>
<span id="cb9-64">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.current_batch <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.current_batch <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.num_batches</span>
<span id="cb9-65">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x, y</span></code></pre></div></div>
</div>
</section>
<section id="sec-ddp" class="level3">
<h3 class="anchored" data-anchor-id="sec-ddp">Distributed Setup</h3>
<p><code>torchrun</code> sets <code>RANK</code>, <code>LOCAL_RANK</code>, and <code>WORLD_SIZE</code> in the environment. This cell reads them to initialize the process group and pin each process to its own device.</p>
<div id="7913deb5" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">###########</span></span>
<span id="cb10-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Distributed Data Parallel</span></span>
<span id="cb10-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">###########</span></span>
<span id="cb10-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Distributed Data Parallel let us run the same model (replica) on different GPUs,</span></span>
<span id="cb10-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># where each GPU would work on a different slice of data. After we do the backward</span></span>
<span id="cb10-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># pass, we average the gradients across all processes (GPUs) and synchronize all</span></span>
<span id="cb10-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># parameters across all devices. We use allReduce op to do this and communicate the</span></span>
<span id="cb10-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># updates with all processes.</span></span>
<span id="cb10-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Each process would go through the same code from top to bottom not aware there</span></span>
<span id="cb10-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># are other processes running the same thing on other devices</span></span>
<span id="cb10-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span></span>
<span id="cb10-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># torchrun command sets the following environment variables:</span></span>
<span id="cb10-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># RANK: Id of the process in the process group. It is an int 0 - WORLD_SIZE</span></span>
<span id="cb10-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># LOCAL_RANK: In the case of multi-nodes, LOCAL_RANK is the id of</span></span>
<span id="cb10-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#             the process in the same node. Example: If we have a node</span></span>
<span id="cb10-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#             with 4 GPUs, the first process will have LOCAL_RANK = 0</span></span>
<span id="cb10-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#             but RANK of this process may not be 0 if we are running</span></span>
<span id="cb10-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#             on multiple nodes.</span></span>
<span id="cb10-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#             This is useful when we have multiple nodes and we want to</span></span>
<span id="cb10-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#             run the processes on different GPUs in the same node.</span></span>
<span id="cb10-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#             In this case, we can set the LOCAL_RANK to the GPU id in the</span></span>
<span id="cb10-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#             node.</span></span>
<span id="cb10-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># WORLD_SIZE: Total number of processes</span></span>
<span id="cb10-24">ddp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"RANK"</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Check if it is a ddp run</span></span>
<span id="cb10-25"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> ddp:</span>
<span id="cb10-26">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># DDP requires CUDA so we need to set the device for each process</span></span>
<span id="cb10-27">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># so only one process can run per device</span></span>
<span id="cb10-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> torch.cuda.is_available(), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DDP requires CUDA"</span></span>
<span id="cb10-29">    init_process_group(backend<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nccl"</span>)</span>
<span id="cb10-30">    ddp_rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"RANK"</span>))</span>
<span id="cb10-31">    ddp_local_rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"LOCAL_RANK"</span>))</span>
<span id="cb10-32">    ddp_world_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"WORLD_SIZE"</span>))</span>
<span id="cb10-33">    device <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"cuda:</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>ddp_local_rank<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb10-34">    torch.cuda.set_device(device)</span>
<span id="cb10-35">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Master process will do more things such as checkpointing and logging</span></span>
<span id="cb10-36">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># while other processes would assist in the computations.</span></span>
<span id="cb10-37">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># It always has RANK = 0</span></span>
<span id="cb10-38">    master_process <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ddp_rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb10-39"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb10-40">    ddp_rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb10-41">    ddp_local_rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb10-42">    ddp_world_size <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb10-43">    master_process <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb10-44">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> torch.cuda.is_available():</span>
<span id="cb10-45">        device <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cuda"</span></span>
<span id="cb10-46">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> torch.mps.is_available():</span>
<span id="cb10-47">        device <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mps"</span></span>
<span id="cb10-48">        torch.mps.manual_seed(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1337</span>)</span>
<span id="cb10-49">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb10-50">        device <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cpu"</span></span>
<span id="cb10-51"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(device)</span></code></pre></div></div>
</div>
<div id="62f97bb7" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">torch.manual_seed(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1337</span>)</span>
<span id="cb11-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> torch.cuda.is_available():</span>
<span id="cb11-3">    torch.cuda.manual_seed(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1337</span>)</span></code></pre></div></div>
</div>
</section>
<section id="sec-optimizer" class="level3">
<h3 class="anchored" data-anchor-id="sec-optimizer">Model and Optimizer</h3>
<p>Here we instantiate the model with the padded vocabulary, then build the fused AdamW optimizer and the learning rate schedule it will follow.</p>
<div id="aff7b226" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">##########</span></span>
<span id="cb12-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Initialize model and optimizer</span></span>
<span id="cb12-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">##########</span></span>
<span id="cb12-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># GPU kernels tile in powers of two, so dimensions that divide evenly by</span></span>
<span id="cb12-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 64 or 128 make far better use of:</span></span>
<span id="cb12-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># • Tensor Cores</span></span>
<span id="cb12-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># • Memory coalescing</span></span>
<span id="cb12-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># • Shared memory bank alignment</span></span>
<span id="cb12-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># • Warp scheduling</span></span>
<span id="cb12-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Here we round vocab_sz from 50257 up to 50304, the next multiple of 64.</span></span>
<span id="cb12-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 50304 is not itself a power of two (it is 64 x 786); what matters is that</span></span>
<span id="cb12-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the dimension divides evenly, so the matmul tiles without a ragged</span></span>
<span id="cb12-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># remainder. We pay for a few hundred embedding rows that can never be</span></span>
<span id="cb12-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># emitted and get faster kernels everywhere the vocab dimension appears.</span></span>
<span id="cb12-15">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> GPT2(GPTConfig(vocab_sz<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50304</span>)).to(device)</span>
<span id="cb12-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Speed up model by building static graph that analyzes all ops</span></span>
<span id="cb12-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># and optimizes them such as fusing some of them to avoid unnecessary</span></span>
<span id="cb12-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># trips to memory. Uncomment to enable: it costs a slow first step while</span></span>
<span id="cb12-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the graph is traced and compiled, which is why it is off by default here.</span></span>
<span id="cb12-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># model = torch.compile(model)</span></span>
<span id="cb12-21"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> ddp:</span>
<span id="cb12-22">    model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> DDP(model, device_ids<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[ddp_local_rank])</span>
<span id="cb12-23">raw_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.module <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> ddp <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> model</span>
<span id="cb12-24">max_lr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3e-4</span></span>
<span id="cb12-25">min_lr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> max_lr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span></span>
<span id="cb12-26">warmup_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span></span>
<span id="cb12-27">max_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span></span>
<span id="cb12-28">sched <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> combine_scheds(</span>
<span id="cb12-29">    [warmup_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> max_steps, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> (warmup_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> max_steps)],</span>
<span id="cb12-30">    [lin_sched(min_lr, max_lr), cos_sched(max_lr, min_lr)],</span>
<span id="cb12-31">)</span>
<span id="cb12-32">optimizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> raw_model.configure_optimizer(</span>
<span id="cb12-33">    weight_decay<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>, lr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>max_lr, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>device</span>
<span id="cb12-34">)</span></code></pre></div></div>
</div>
</section>
<section id="sec-train" class="level3">
<h3 class="anchored" data-anchor-id="sec-train">The Training Loop</h3>
<p>The loop is where the optimizations come together: gradient accumulation to reach a 0.5M-token batch, bfloat16 autocast, TF32 matmuls, gradient clipping, and the learning rate schedule. Two details in this loop are easy to get wrong in ways that produce no error and no obviously bad number.</p>
<div id="5363775f" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">##########</span></span>
<span id="cb13-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Run training loop</span></span>
<span id="cb13-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#########</span></span>
<span id="cb13-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span><span class="al" style="color: #AD0000;
background-color: null;
font-style: inherit;">NOTE</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">: In order to run 0.5M (from GPT3 paper) tokens per fwd/bwd iteration,</span></span>
<span id="cb13-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># we need to use gradient accumulation because we can't fit it in almost</span></span>
<span id="cb13-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># any commodity GPU -&gt; We only do backward after we loop through ~0.5M tokens.</span></span>
<span id="cb13-7">total_batch_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">19</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># closest number to 0.5M</span></span>
<span id="cb13-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> total_batch_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span> (GPTConfig.batch_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> GPTConfig.block_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ddp_world_size) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"total batch size must be divisible by micro batch_sz x block_sz x ddp_world_size"</span></span>
<span id="cb13-9">grad_accum_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> total_batch_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> (</span>
<span id="cb13-10">    GPTConfig.batch_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> GPTConfig.block_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ddp_world_size</span>
<span id="cb13-11">)</span>
<span id="cb13-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> master_process:</span>
<span id="cb13-13">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Total desired batch size: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>total_batch_sz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-14">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Calculated gradient accumulation steps: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>grad_accum_steps<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-15"></span>
<span id="cb13-16">train_dl <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> DataLoaderLight(</span>
<span id="cb13-17">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tinyshakespeare.txt"</span>,</span>
<span id="cb13-18">    batch_sz<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>GPTConfig.batch_sz,</span>
<span id="cb13-19">    block_sz<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>GPTConfig.block_sz,</span>
<span id="cb13-20">    process_rank<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ddp_rank,</span>
<span id="cb13-21">    number_processes<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ddp_world_size</span>
<span id="cb13-22">)</span>
<span id="cb13-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pytorch will use TensorFloat32 if available, else use FP32</span></span>
<span id="cb13-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># But the weights will still be stored using FP32 with less precision</span></span>
<span id="cb13-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (10 bits for mantissa instead of 23). It is just the</span></span>
<span id="cb13-26"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># operations would be executed as TF32 if available</span></span>
<span id="cb13-27">torch.set_float32_matmul_precision(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"high"</span>)</span>
<span id="cb13-28"></span>
<span id="cb13-29"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># `autocast` wants a device TYPE ("cuda"), not a full device string. DDP</span></span>
<span id="cb13-30"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># assigns each process "cuda:{local_rank}", so comparing against "cuda"</span></span>
<span id="cb13-31"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># directly would silently skip mixed precision on every multi-GPU run.</span></span>
<span id="cb13-32">device_type <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cuda"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> device.startswith(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cuda"</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> device</span>
<span id="cb13-33"></span>
<span id="cb13-34"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> step <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_steps):</span>
<span id="cb13-35">    start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.time()</span>
<span id="cb13-36">    optimizer.zero_grad()</span>
<span id="cb13-37">    loss_accum <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb13-38">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> macro_step <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(grad_accum_steps):</span>
<span id="cb13-39">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Draw a fresh micro-batch on every accumulation step. Fetching it</span></span>
<span id="cb13-40">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># outside this loop would add up `grad_accum_steps` copies of the</span></span>
<span id="cb13-41">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># same gradient and leave the effective batch at batch_sz x block_sz</span></span>
<span id="cb13-42">        x, y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> train_dl.next_batch()</span>
<span id="cb13-43">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x.to(device)</span>
<span id="cb13-44">        y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> y.to(device)</span>
<span id="cb13-45">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> device_type <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cuda"</span>:</span>
<span id="cb13-46">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Tensors that will be greatly affected by less precision such</span></span>
<span id="cb13-47">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># as loss, layernorm would still be in FP32 while others such</span></span>
<span id="cb13-48">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># as attention weights would be in BF16</span></span>
<span id="cb13-49">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> torch.autocast(device_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>device_type, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.bfloat16):</span>
<span id="cb13-50">                logits, loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model(x, y)</span>
<span id="cb13-51">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb13-52">            logits, loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model(x, y)</span>
<span id="cb13-53">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Just accumulating gradients yield to summation of objective but</span></span>
<span id="cb13-54">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># we want mean so we weight each loss by 1/grad_accum_steps</span></span>
<span id="cb13-55">        loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/=</span> grad_accum_steps</span>
<span id="cb13-56">        loss_accum <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> loss.detach()</span>
<span id="cb13-57">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># To avoid syncing the gradients between the processes after every</span></span>
<span id="cb13-58">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># macro step, we disable it and only allows the sync up of</span></span>
<span id="cb13-59">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># gradients after we finish all gradient accumulation in each</span></span>
<span id="cb13-60">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># process</span></span>
<span id="cb13-61">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> ddp:</span>
<span id="cb13-62">            model.require_backward_grad_sync <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb13-63">                macro_step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> grad_accum_steps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb13-64">            )</span>
<span id="cb13-65">        loss.backward()</span>
<span id="cb13-66">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Each process would have its own loss_accum tensor, so to get the</span></span>
<span id="cb13-67">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># average loss_accum across all processes, we want to compute the</span></span>
<span id="cb13-68">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># average of all loss_accum in all processes</span></span>
<span id="cb13-69">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> ddp:</span>
<span id="cb13-70">        dist.all_reduce(loss_accum, op<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dist.ReduceOp.AVG)</span>
<span id="cb13-71">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Clips gradient to global norm. It is very useful to avoid having a</span></span>
<span id="cb13-72">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># very high loss for some (bad) batch(es) which would lead to very high</span></span>
<span id="cb13-73">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># gradients and huge updates</span></span>
<span id="cb13-74">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span><span class="al" style="color: #AD0000;
background-color: null;
font-style: inherit;">NOTE</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">: In the beginning of training it is normal to have high norms</span></span>
<span id="cb13-75">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># as the model is initialized randomly</span></span>
<span id="cb13-76">    norm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.utils.clip_grad_norm_(model.parameters(), <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span>)</span>
<span id="cb13-77"></span>
<span id="cb13-78">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span><span class="al" style="color: #AD0000;
background-color: null;
font-style: inherit;">TODO</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">: Use ParamScheduler from `cmn_ai`</span></span>
<span id="cb13-79">    lr <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sched(step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> max_steps)</span>
<span id="cb13-80">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> pg <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> optimizer.param_groups:</span>
<span id="cb13-81">        pg[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lr"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lr</span>
<span id="cb13-82">    optimizer.step()</span>
<span id="cb13-83">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># CUDA launches kernels asynchronously, so without this barrier we would</span></span>
<span id="cb13-84">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># be timing how long it takes to QUEUE the work, not to finish it</span></span>
<span id="cb13-85">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> device_type <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"cuda"</span>:</span>
<span id="cb13-86">        torch.cuda.synchronize()</span>
<span id="cb13-87">    end <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.time()</span>
<span id="cb13-88">    elapsed_time <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> end <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> start</span>
<span id="cb13-89">    token_per_sec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb13-90">        GPTConfig.batch_sz</span>
<span id="cb13-91">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> GPTConfig.block_sz</span>
<span id="cb13-92">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> grad_accum_steps</span>
<span id="cb13-93">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ddp_world_size</span>
<span id="cb13-94">    ) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (elapsed_time)</span>
<span id="cb13-95">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb13-96">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"step </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>step<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, loss: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>loss_accum<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>item()<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, lr </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.4e}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, norm: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>norm<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, time: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>elapsed_time<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">s, tok/sec: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>token_per_sec<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb13-97">    )</span>
<span id="cb13-98"></span>
<span id="cb13-99"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> ddp:</span>
<span id="cb13-100">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Kills all processes</span></span>
<span id="cb13-101">    destroy_process_group()</span></code></pre></div></div>
</div>
<p><code>combine_scheds</code> runs a linear warmup over the first 20% of steps, then hands off to cosine decay for the remaining 80%.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/lr-schedule.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure 2: The learning rate this run uses, computed by the same lin_sched/cos_sched/combine_scheds code defined above. Linear warmup from 3e-5 to the 3e-4 peak over 10 of 50 steps, then cosine decay back to a tenth of peak."><img src="https://imaddabbura.github.io/posts/nlp/images/lr-schedule.svg" class="img-fluid figure-img" style="width:100.0%" alt="Figure 2: The learning rate this run uses, computed by the same lin_sched/cos_sched/combine_scheds code defined above. Linear warmup from 3e-5 to the 3e-4 peak over 10 of 50 steps, then cosine decay back to a tenth of peak."></a></p>
<figcaption><strong>Figure 2:</strong> The learning rate this run uses, computed by the same <code>lin_sched</code>/<code>cos_sched</code>/<code>combine_scheds</code> code defined above. Linear warmup from 3e-5 to the 3e-4 peak over 10 of 50 steps, then cosine decay back to a tenth of peak.</figcaption>
</figure>
</div>
<p>Warmup exists because AdamW divides each update by a running estimate of the gradient’s second moment. For the first few steps that denominator is built from almost no samples, so a full-size learning rate applies a large update scaled by a quantity nobody has measured yet. Starting small buys time for the estimate to stabilize. The cosine decay at the other end shrinks the step size as the model approaches a minimum, so the last updates are small enough to settle into it.</p>
</section>
<section id="sec-sampling" class="level3">
<h3 class="anchored" data-anchor-id="sec-sampling">Sampling from the Model</h3>
<p>With training finished, we can sample from the model. Generation reuses the same forward pass, run one token at a time, with each sampled token appended to the context and fed back in.</p>
<div id="gen-sample" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Sample from the trained model. `generate` crops the context to block_sz,</span></span>
<span id="cb14-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># takes the logits at the final position, and samples from the top 50 tokens</span></span>
<span id="cb14-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># so we never draw from the long tail of near-zero probabilities.</span></span>
<span id="cb14-4">enc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tiktoken.get_encoding(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt2"</span>)</span>
<span id="cb14-5">prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tensor(enc.encode(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hello, I'm a language model,"</span>), dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">long</span>)</span>
<span id="cb14-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Generate 4 independent continuations from the same prompt</span></span>
<span id="cb14-7">prompt <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> prompt.unsqueeze(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>).repeat(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).to(device)</span>
<span id="cb14-8"></span>
<span id="cb14-9">raw_model.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span>()</span>
<span id="cb14-10">out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> raw_model.generate(prompt, max_tokens<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>)</span>
<span id="cb14-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> row <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> out:</span>
<span id="cb14-12">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(enc.decode(row.tolist()))</span>
<span id="cb14-13">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>)</span></code></pre></div></div>
</div>
</section>
</section>
<section id="sec-conclusion" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="sec-conclusion">Conclusion</h2>
<p>We’ve come a long way in this journey, from implementing the core transformer architecture with multi-head attention and positional encodings, to building an efficient training pipeline complete with modern optimizations like flash attention, mixed precision training, and distributed parallelism. We’ve debugged exploding gradients, optimized memory usage, and watched our model evolve from producing random gibberish to generating coherent text.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>If you want to go deeper on the optimization side, see my <a href="https://imaddabbura.github.io/dl-tips-tricks.html">DL Tips &amp; Tricks</a> reference, which covers many of the heuristics used here.</p>
</div></div><p>Along the way, we’ve gained deep insights into why each component exists and how they work together to create these remarkable language models.</p>
<p>I hope this deep dive has been as illuminating for you as it has been for me. Writing this implementation forced me to confront gaps in my own understanding and solidified concepts that previously felt abstract. There’s something uniquely satisfying about seeing your hand-built transformer successfully predict its first coherent sentence: a moment where theory becomes understanding.</p>
<p>If you’ve made it this far, thank you for joining me on this journey. I’d love to hear about your experiences implementing transformers, any bugs you’ve encountered, optimizations you’ve discovered, or questions this post might have raised. Feel free to reach out with feedback, corrections, or insights: the best part of sharing these implementations is learning from the community’s collective wisdom. Happy building!</p>
<section id="sec-next" class="level3">
<h3 class="anchored" data-anchor-id="sec-next">Where to Go Next</h3>
<p>The loop above trains on tinyshakespeare for 50 steps, which is enough to watch the loss fall and nothing more. To turn it into a real run:</p>
<ol type="1">
<li>Swap the dataset for something at pretraining scale (FineWeb-Edu is the usual choice) and pre-tokenize it into sharded <code>.npy</code> files, so the loader memory-maps shards instead of holding the corpus in RAM.</li>
<li>Hold out a validation split and evaluate on it every few hundred steps. Training loss alone will not tell you when you are overfitting a small corpus.</li>
<li>Enable <code>torch.compile</code>, then re-measure. Take the throughput number <em>after</em> the first step, since that one pays for compilation.</li>
<li>Add checkpointing of model, optimizer, and dataloader position, so a run can resume. At real scale, jobs get preempted.</li>
<li>Once memory becomes the binding constraint instead of time, add activation checkpointing to trade recomputation for a larger micro-batch.</li>
</ol>
</section>
</section>
<section id="sec-resources" class="level2">
<h2 class="anchored" data-anchor-id="sec-resources">Resources</h2>
<ul>
<li><strong>Vaswani, A. et al.</strong> (2017). <a href="https://arxiv.org/abs/1706.03762">Attention Is All You Need</a>. <em>NeurIPS</em>.</li>
<li><strong>Press, O. &amp; Wolf, L.</strong> (2017). <a href="https://arxiv.org/abs/1608.05859">Using the Output Embedding to Improve Language Models</a>. <em>EACL</em>.</li>
<li><strong>Radford, A. et al.</strong> (2019). <a href="https://d4mucfpksywv.cloudfront.net/better-language-models/language_models_are_unsupervised_multitask_learners.pdf">Language Models are Unsupervised Multitask Learners</a>. (GPT-2)</li>
<li><strong>Brown, T. et al.</strong> (2020). <a href="http://arxiv.org/abs/2005.14165">Language Models are Few-Shot Learners</a>. <em>NeurIPS</em>. (GPT-3)</li>
<li><strong>Dao, T. et al.</strong> (2022). <a href="https://arxiv.org/abs/2205.14135">FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness</a>. <em>NeurIPS</em>.</li>
<li><strong>Karpathy, A.</strong> (2024). <a href="https://www.youtube.com/watch?v=l8pRSuU81PU">Let’s reproduce GPT-2 (124M)</a>. <em>YouTube</em>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>NLP</category>
  <guid>https://imaddabbura.github.io/posts/nlp/GPT2-From-Scratch.html</guid>
  <pubDate>Wed, 10 Apr 2024 05:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/nlp/images/gpt2.png" medium="image" type="image/png" height="81" width="144"/>
</item>
<item>
  <title>Byte Pair Encoding from Scratch</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/nlp/BPE-Tokenizer.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="why-tokenization-matters" class="level2">
<h2 class="anchored" data-anchor-id="why-tokenization-matters">Why Tokenization Matters</h2>
<p>When you type “unhappiness” into ChatGPT, the model doesn’t see the word “unhappiness.” It sees something like <code>["un", "happ", "iness"]</code>, three <strong>tokens</strong> that were chosen by an algorithm months before the model was even trained. That algorithm decided, based on statistics from a massive training corpus, that these three pieces are the right granularity. The tokenizer is its own model with its own training run, fit once on its own corpus to compress text into a fixed vocabulary, then frozen before the language model sees a single example. Not individual characters (too many tokens, too little meaning per token). Not whole words (too many unique words, no way to handle words never seen in training). Subwords: the sweet spot.</p>
<p>This isn’t a minor preprocessing detail. Tokenization defines <strong>what the model can see</strong>: the model perceives the world only through tokens, and a surprising share of language-model failures (trouble with arithmetic, spelling, and rare languages) are really tokenizer failures. Consider three strategies on the same sentence:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Strategy</th>
<th>“The cat sat unhappily” becomes</th>
<th>Tokens</th>
<th>Vocab Size</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Character-level</strong></td>
<td><code>["T","h","e"," ","c","a","t"," ","s","a","t"," ","u","n","h","a","p","p","i","l","y"]</code></td>
<td>21</td>
<td>~256</td>
</tr>
<tr class="even">
<td><strong>Word-level</strong></td>
<td><code>["The", "cat", "sat", "unhappily"]</code></td>
<td>4</td>
<td>100,000+</td>
</tr>
<tr class="odd">
<td><strong>Subword (BPE)</strong></td>
<td><code>["The", " cat", " sat", " un", "happ", "ily"]</code></td>
<td>6</td>
<td>~50,000</td>
</tr>
</tbody>
</table>
<p>With characters, a fixed context window of 2048 tokens covers ~400 words. With subwords, the same window covers ~1500 words, nearly 4× more context for the model to reason over. Word-level is compact but brittle: “unhappily” might never appear in training data, making it an <code>&lt;UNK&gt;</code> token the model is completely blind to. But “un”, “happ”, and “ily” almost certainly do appear, and the model can compose meaning from pieces it knows.</p>
<p>The algorithm that learns these splits is <strong>Byte Pair Encoding (BPE)</strong>, originally a data compression technique (<a href="https://www.derczynski.com/papers/archive/BPE_Gage.pdf">Gage, 1994</a>), adapted for NLP by <a href="https://arxiv.org/abs/1508.07909">Sennrich et al.&nbsp;(2016)</a>, and now used in GPT-2, GPT-3/4, LLaMA, and most modern language models. In this post, we’ll understand how it works, implement it from scratch, and see how GPT-2 refined the basic algorithm for production. For the broader picture, I compare character, word, and subword tokenization in <a href="../../posts/nlp/Tokenization-Strategies.html">Breaking Text Apart (The Smart Way)</a>.</p>
</section>
<section id="how-bpe-works" class="level2">
<h2 class="anchored" data-anchor-id="how-bpe-works">How BPE Works</h2>
<p>The core insight is simple: if two symbols frequently appear next to each other, they probably belong together. Merge them into a single token, then look for the next most frequent pair, and repeat. It’s exactly how you’d compress a text file: find repeated patterns and replace them with shorter symbols. Frequent patterns get absorbed into single tokens; rare patterns stay as smaller pieces.</p>
<p>Think of it like learning abbreviations. If you keep writing “machine learning” in your notes, you’d eventually start writing “ML.” Then if “ML model” keeps appearing, maybe you’d abbreviate that too. BPE does the same thing, but systematically and bottom-up, starting from the smallest units (bytes) and building up to subwords.</p>
<section id="seeing-it-in-action" class="level3">
<h3 class="anchored" data-anchor-id="seeing-it-in-action">Seeing It in Action</h3>
<p>Before formalizing the algorithm, let’s watch it work on a real example. Consider a tiny training corpus containing the words <code>"low lower lowest"</code>:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Step</th>
<th>Token Sequence</th>
<th>Most Frequent Pair</th>
<th>New Token</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Start</td>
<td><code>l o w _ l o w e r _ l o w e s t</code></td>
<td>-</td>
<td>-</td>
</tr>
<tr class="even">
<td>Merge 1</td>
<td><code>lo w _ lo w e r _ lo w e s t</code></td>
<td><code>(l, o)</code> → <code>lo</code></td>
<td>3×</td>
</tr>
<tr class="odd">
<td>Merge 2</td>
<td><code>low _ low e r _ low e s t</code></td>
<td><code>(lo, w)</code> → <code>low</code></td>
<td>3×</td>
</tr>
<tr class="even">
<td>Merge 3</td>
<td><code>low _ lowe r _ lowe s t</code></td>
<td><code>(low, e)</code> → <code>lowe</code></td>
<td>2×</td>
</tr>
</tbody>
</table>
<p>BPE discovered that <code>l</code> and <code>o</code> always appear together, then that <code>lo</code> and <code>w</code> always appear together, building up <code>low</code> as a token, effectively learning the word stem. Then it found <code>lowe</code> as a shared prefix of “lower” and “lowest.” Without any linguistic rules, purely from frequency, BPE learned morphological structure.</p>
<p>Notice what happened in merge 2: the algorithm merged <code>lo</code> with <code>w</code>, where <code>lo</code> itself was created in merge 1. BPE builds tokens <strong>hierarchically</strong>: later merges compose earlier ones. We can visualize this as a tree:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph BT
    l["l (byte)"] --&gt; lo["lo (merge 1)"]
    o["o (byte)"] --&gt; lo
    lo --&gt; low["low (merge 2)"]
    w["w (byte)"] --&gt; low
    low --&gt; lowe["lowe (merge 3)"]
    e["e (byte)"] --&gt; lowe

</pre>
</div>
<p></p><figcaption> BPE merges build tokens bottom-up. Each merge composes two existing tokens into a new one, forming a hierarchy from bytes to subwords.</figcaption> </figure><p></p>
</div>
</div>
</div>
<p>Each level in the tree depends on the levels below it. This is why merge order matters during encoding: you can’t build <code>low</code> until <code>lo</code> exists.</p>
</section>
<section id="the-algorithm" class="level3">
<h3 class="anchored" data-anchor-id="the-algorithm">The Algorithm</h3>
<p>With the intuition in place, here’s the formal procedure:</p>
<ol type="1">
<li><p><strong>Initialize</strong>: Start with a base vocabulary of all 256 byte values (0–255). Every string can be represented as bytes, so this guarantees full coverage: no <code>&lt;UNK&gt;</code> tokens, ever.</p></li>
<li><p><strong>Count pairs</strong>: Scan the corpus and count every adjacent pair of tokens.</p></li>
<li><p><strong>Merge the most frequent pair</strong>: Create a new token for it and replace all occurrences in the corpus.</p></li>
<li><p><strong>Repeat</strong> steps 2–3 until you’ve done <code>vocab_size - 256</code> merges.</p></li>
</ol>
<p>The output is a <strong>merge table</strong>: an ordered list of pair → token mappings. This table <em>is</em> the tokenizer.</p>
</section>
<section id="training-vs.-encoding-a-subtle-difference" class="level3">
<h3 class="anchored" data-anchor-id="training-vs.-encoding-a-subtle-difference">Training vs.&nbsp;Encoding: A Subtle Difference</h3>
<p>There’s an important asymmetry between how BPE <em>learns</em> merges (training) and how it <em>applies</em> them to new text (encoding).</p>
<p>During <strong>training</strong>, we always merge the globally most <em>frequent</em> pair; that’s how we decide which merges to learn. But during <strong>encoding</strong>, we apply merges in the <em>order they were learned</em>, not by their frequency in the new text.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Encoding Replays Merges: It Doesn’t Re-learn Them
</div>
</div>
<div class="callout-body-container callout-body">
<p>A common misconception is that encoding finds the most frequent pair in the new text and merges it. It doesn’t. Encoding applies the <em>training-time</em> merges in their original order. The token <code>low</code> only exists after <code>lo</code> has been created (merge 1). If we tried to merge <code>(lo, w)</code> before creating <code>lo</code>, we’d never find the pair. In the implementation, this shows up as <code>min(stats, key=lambda p: self.merges.get(p, float("inf")))</code>: picking the pair with the <em>lowest merge index</em>, not the highest frequency.</p>
</div>
</div>
</section>
<section id="why-bytes-not-characters" class="level3">
<h3 class="anchored" data-anchor-id="why-bytes-not-characters">Why Bytes, Not Characters?</h3>
<p>Starting from bytes (0–255) rather than Unicode code points is a practical decision. Unicode has over 150,000 code points, an impractically large base vocabulary. By working at the byte level, we start with just 256 symbols and can represent <em>any</em> string in <em>any</em> language or script, since UTF-8 encodes every Unicode code point as one to four of these bytes. BPE merges then learn to compose bytes into characters, characters into subwords, and subwords into common words, all driven by frequency in the training data.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>The Multilingual Tax
</div>
</div>
<div class="callout-body-container callout-body">
<p>This is a property of the tokenizer’s training data, not of the language. English dominates most tokenizer corpora, so English merges into long, dense tokens while an underrepresented script like Korean stays fragmented into many short ones. The same sentence then costs several times more tokens in Korean than in English. Two costs follow. The model burns more of its fixed context window on the same meaning, so within one window an English token effectively attends to more real content than a Korean one; and it runs more forward passes for that sentence, so inference is more expensive. It is a well-documented source of multilingual inefficiency (<a href="https://arxiv.org/abs/2311.09071">Petrov et al., 2023</a>), and it traces back entirely to which languages were frequent when the tokenizer was trained.</p>
</div>
</div>
</section>
<section id="vocabulary-size-a-key-hyperparameter" class="level3">
<h3 class="anchored" data-anchor-id="vocabulary-size-a-key-hyperparameter">Vocabulary Size: A Key Hyperparameter</h3>
<p>How many merges should we do? This is the vocabulary size, and it’s a meaningful trade-off:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Vocab Size</th>
<th>Tokens per Text</th>
<th>Embedding Table</th>
<th>Character</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Small</strong> (~1k)</td>
<td>Many - close to character-level</td>
<td>Tiny</td>
<td>Better generalization on rare words, but sequences are long and training is slow</td>
</tr>
<tr class="even">
<td><strong>Medium</strong> (~32k–50k)</td>
<td>Moderate - good compression</td>
<td>Manageable</td>
<td>The sweet spot for most models (GPT-2: 50k, LLaMA: 32k)</td>
</tr>
<tr class="odd">
<td><strong>Large</strong> (~100k+)</td>
<td>Few - common phrases become single tokens</td>
<td>Very large</td>
<td>Risk of overfitting to training distribution; rare tokens get poorly trained embeddings</td>
</tr>
</tbody>
</table>
<p>Larger vocabularies mean each token carries more information, so sequences are shorter and the model sees more context per forward pass. But each token also needs an embedding vector, so the embedding table grows linearly, and so does the output head: the final layer scores every candidate token with a dot product, so a wider vocabulary makes the last layer and its softmax more expensive at every step. And tokens that appear rarely in training will have poorly learned embeddings: they’ve simply not been seen enough times.</p>
<p>There is a subtler ceiling than cost. The Transformer does a fixed amount of work per token, so compressing more text into each token eventually packs in more than a single forward pass can unpack. A token that stands for a whole common phrase leaves the model no intermediate steps in which to reason about its parts. The sweet spot is therefore not as much compression as the hardware allows: it is the point where tokens are dense enough to give long-range context but still coarse enough that one forward pass can digest each one.</p>
<p>Most modern models settle in the 32k–100k range. GPT-2 uses ~50k tokens. LLaMA uses 32k. GPT-4 reportedly uses ~100k. The right size depends on the training data, the target languages, and the compute budget.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>BPE Is Adaptive Computation
</div>
</div>
<div class="callout-body-container callout-body">
<p>There is a deeper way to read what merging buys you. The Transformer spends the same compute on every token: one slot in the residual stream, one forward pass. BPE decides how much text each of those fixed-cost slots has to cover. Common words are merged into a single token and get one forward pass; rare words survive as several tokens and get several. Because a word’s rarity tracks its information content, this is the right allocation almost by accident: the tokenizer routes more of the model’s compute toward the surprising parts of the input and less toward the boilerplate. Merging also hands the model priors for free, since a word arriving as <code>un</code> + <code>happ</code> + <code>iness</code> means morphology is baked into the input rather than relearned from scratch.</p>
</div>
</div>
</section>
</section>
<section id="implementation" class="level2">
<h2 class="anchored" data-anchor-id="implementation">Implementation</h2>
<p>Let’s turn the algorithm into code. The <code>BPETokenizer</code> class below has four core methods, each mapping directly to a step we’ve discussed:</p>
<ul>
<li><strong><code>train</code></strong> (the learning loop): encode the corpus to bytes, then greedily merge the most frequent pair <code>vocab_size - 256</code> times. Each merge is recorded in <code>self.merges</code> as a <code>(pair) → index</code> mapping. This ordered dictionary <em>is</em> the tokenizer.</li>
<li><strong><code>encode</code></strong> (the encoding step): convert new text to bytes, then apply merges in <em>learned order</em> (earliest first, using the <code>min</code> trick we discussed). This is where training-order matters: we pick the pair with the smallest merge index, not the most frequent.</li>
<li><strong><code>decode</code></strong> (the inverse): look up each token ID in the vocabulary to get its byte sequence, concatenate, and decode back to a string. Not every byte sequence is valid UTF-8 (a merge can produce bytes that don’t form a legal character), so decoding passes <code>errors="replace"</code>, substituting <code>�</code> rather than crashing.</li>
<li><strong><code>_get_stats</code> / <code>_merge</code></strong>: Helpers that count adjacent pairs and replace a specific pair with its merged token throughout a sequence.</li>
</ul>
<p>One implementation detail: <code>_build_vocab</code> relies on Python 3.7+ dictionary insertion order. Since merges are inserted chronologically, iterating <code>self.merges</code> replays them in order: each merged token is the byte-concatenation of its two parents, which must already exist in the vocabulary.</p>
<div id="1517b8b6-f809-44a3-a368-2ecf7073c0d5" class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Iterable</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> requests</span></code></pre></div></div>
</details>
</div>
<div id="a672ce35-a11c-4452-92fa-09b54198aa31" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> BPETokenizer:</span>
<span id="cb2-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Byte-pair encoder."""</span></span>
<span id="cb2-3"></span>
<span id="cb2-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, vocab_sz: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb2-5">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb2-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Args:</span></span>
<span id="cb2-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">            vocab_sz (int): Vocabulary size.</span></span>
<span id="cb2-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        """</span></span>
<span id="cb2-9">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.vocab_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vocab_sz</span>
<span id="cb2-10">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.vocab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb2-11">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.merges <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb2-12"></span>
<span id="cb2-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> train(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, text: Iterable[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>]):</span>
<span id="cb2-14">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Train Byte-pair encoder."""</span></span>
<span id="cb2-15">        ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(text.encode(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"utf-8"</span>))</span>
<span id="cb2-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> idx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">256</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.vocab_sz):</span>
<span id="cb2-17">            stats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._get_stats(ids)</span>
<span id="cb2-18">            pair <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(stats, key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>stats.get)</span>
<span id="cb2-19">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.merges[pair] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> idx</span>
<span id="cb2-20">            ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._merge(ids, pair, idx)</span>
<span id="cb2-21">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.vocab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._build_vocab(ids)</span>
<span id="cb2-22"></span>
<span id="cb2-23">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> encode(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, text):</span>
<span id="cb2-24">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Encode string to bytes using vocabulary built during training."""</span></span>
<span id="cb2-25">        ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(text.encode(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"utf-8"</span>))</span>
<span id="cb2-26"></span>
<span id="cb2-27">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## If text is empty or has one character -&gt; it is already encoded from previous step</span></span>
<span id="cb2-28">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ids) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:</span>
<span id="cb2-29">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## stats is used only for getting pairs next to each other</span></span>
<span id="cb2-30">            stats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._get_stats(ids)</span>
<span id="cb2-31">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Because we built vocab (and merges) bottom-up, we need to encode</span></span>
<span id="cb2-32">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## idx from smallest index because some later pairs depend on pairs</span></span>
<span id="cb2-33">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## occured before. In other words, more frequent pairs would have</span></span>
<span id="cb2-34">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## lower indices that less frequent pairs -&gt; To get the fist pairs</span></span>
<span id="cb2-35">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## we need to get the lowest indices first before moving on.</span></span>
<span id="cb2-36">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## If a pair doesn't exist, it wouldn't participate in the list</span></span>
<span id="cb2-37">            pair <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(stats, key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> p: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.merges.get(p, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"inf"</span>)))</span>
<span id="cb2-38">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> pair <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.merges:</span>
<span id="cb2-39">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## No more pairs to merge</span></span>
<span id="cb2-40">            idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.merges[pair]</span>
<span id="cb2-41">            ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>._merge(ids, pair, idx)</span>
<span id="cb2-42">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ids</span>
<span id="cb2-43"></span>
<span id="cb2-44">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> decode(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, tokens: Iterable[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]):</span>
<span id="cb2-45">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Decode tokens into string using the vocabulary built during training."""</span></span>
<span id="cb2-46">        tokens <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">b""</span>.join(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.vocab[idx] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> idx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tokens)</span>
<span id="cb2-47">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## It is important to replace tokens that were not seen during training</span></span>
<span id="cb2-48">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## with `?`; otherwise, it would fail</span></span>
<span id="cb2-49">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> tokens.decode(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"utf-8"</span>, errors<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"replace"</span>)</span>
<span id="cb2-50"></span>
<span id="cb2-51">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _get_stats(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ids: Iterable[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]):</span>
<span id="cb2-52">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Get pair counts."""</span></span>
<span id="cb2-53">        counts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb2-54">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> pair <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(ids, ids[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:]):</span>
<span id="cb2-55">            counts[pair] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> counts.get(pair, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb2-56">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> counts</span>
<span id="cb2-57"></span>
<span id="cb2-58">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _merge(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ids: Iterable[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>], pair: Iterable[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>], idx: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>):</span>
<span id="cb2-59">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Merge pairs that match `pair` with new index `idx`."""</span></span>
<span id="cb2-60">        newids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb2-61">        i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb2-62">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ids):</span>
<span id="cb2-63">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ids) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> pair[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> ids[i] <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> pair[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> ids[i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]:</span>
<span id="cb2-64">                newids.append(idx)</span>
<span id="cb2-65">                i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb2-66">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb2-67">                newids.append(ids[i])</span>
<span id="cb2-68">                i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb2-69">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> newids</span>
<span id="cb2-70"></span>
<span id="cb2-71">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> _build_vocab(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, ids: Iterable[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]):</span>
<span id="cb2-72">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Build vocabulary from 0-255 bytes and merges."""</span></span>
<span id="cb2-73">        vocab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {idx: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bytes</span>([idx]) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> idx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">256</span>)}</span>
<span id="cb2-74">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Here we assume the items returned would be in the same order they were inserted.</span></span>
<span id="cb2-75">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## This is Okay Python 3.7+</span></span>
<span id="cb2-76">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (p0, p1), idx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.merges.items():</span>
<span id="cb2-77">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## This would be a concatenation of the bytes</span></span>
<span id="cb2-78">            vocab[idx] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> vocab[p0] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> vocab[p1]</span>
<span id="cb2-79">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> vocab</span></code></pre></div></div>
</div>
<div id="a26b9c11-cf65-45ca-a03a-7f2acef56f13" class="cell" data-execution_count="23">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> requests.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://docs.python.org/3/library/stdtypes.html#bytes.decode"</span>).text</span></code></pre></div></div>
</div>
<div id="4368c372-793e-4ca7-be14-c8e62b9c9ca9" class="cell" data-execution_count="24">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">tokenizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> BPETokenizer(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">300</span>)</span></code></pre></div></div>
</div>
<div id="6a4e7562-ca4c-4e81-907c-70479b2448ce" class="cell" data-execution_count="25">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">tokenizer.train(text)</span></code></pre></div></div>
</div>
<div id="3ba82c99-8c22-4c9e-be22-717b6be33ab3" class="cell" data-execution_count="26">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">tokenizer.decode(tokenizer.encode(text)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> text</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="26">
<pre><code>True</code></pre>
</div>
</div>
<div id="c051bad1" class="cell" data-execution_count="41">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Compression rate: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(text.encode(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'utf-8'</span>))) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(tokenizer.encode(text))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">x"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Compression rate: 1.9x</code></pre>
</div>
</div>
<div id="8f6f6a89" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">BPETokenizer(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>).train(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"imad"</span>)</span></code></pre></div></div>
</div>
</section>
<section id="from-vanilla-bpe-to-gpt-2s-tokenizer" class="level2">
<h2 class="anchored" data-anchor-id="from-vanilla-bpe-to-gpt-2s-tokenizer">From Vanilla BPE to GPT-2’s Tokenizer</h2>
<p>The implementation above is vanilla byte-level BPE: it works, but it has a practical problem. Because merges are purely frequency-driven, the algorithm doesn’t respect word boundaries. The word “play” might appear in the corpus as “play.”, “play!”, “play,”, and “play”, and BPE will learn separate tokens for each variant, wasting vocabulary slots on what is essentially the same word with different punctuation.</p>
<p>GPT-2 (<a href="https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf">Radford et al., 2019</a>) introduced a key refinement: <strong>pre-tokenization with a regex pattern</strong> that splits text into chunks <em>before</em> BPE runs. The regex prevents merges from crossing certain boundaries: letters can’t merge with digits, punctuation stays separate from words, and spaces attach to the <em>beginning</em> of words rather than the end.</p>
<p>The GPT-2 regex pattern:</p>
<pre><code>'(?:[sdmt]|ll|ve|re)| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+</code></pre>
<p>This ensures that:</p>
<ul>
<li><strong>Contractions</strong> are split cleanly: “don’t” → <code>["don", "'t"]</code></li>
<li><strong>Spaces attach to the next word</strong>: ” hello” stays together, preserving word boundaries</li>
<li><strong>Punctuation stays isolated</strong>: “play!” → <code>["play", "!"]</code> instead of learning “play!” as one token</li>
<li><strong>Digits don’t merge with letters</strong>: “h3llo” → <code>["h", "3", "llo"]</code></li>
</ul>
<p>BPE then runs <em>within</em> each chunk independently. The result: a much cleaner vocabulary where tokens correspond to linguistically meaningful units rather than artifacts of adjacent punctuation.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Try It Yourself
</div>
</div>
<div class="callout-body-container callout-body">
<p>Use <a href="https://tiktokenizer.vercel.app">Tiktokenizer</a> to see how GPT-2 and GPT-4 tokenize arbitrary text. Try pasting the same sentence in English and another language, and you’ll immediately see the multilingual tokenization tax in action: the non-English version will use significantly more tokens for the same meaning.</p>
</div>
</div>
<p>This pre-tokenization pattern has been refined in later models. GPT-4 uses a <a href="https://github.com/openai/tiktoken">more sophisticated pattern</a> that handles apostrophes, numbers, and whitespace more carefully, and also limits the length of digit sequences to avoid learning overly specific number tokens. The core idea remains the same: constrain where merges can happen to produce a more useful vocabulary. I build a working GPT-2 on top of a tokenizer like this in <a href="../../posts/nlp/GPT2-From-Scratch.html">Building GPT-2 from Scratch</a>.</p>
</section>
<section id="references-resources" class="level2">
<h2 class="anchored" data-anchor-id="references-resources">References &amp; Resources</h2>
<ul>
<li><strong>Gage, P.</strong> (1994). <a href="https://www.derczynski.com/papers/archive/BPE_Gage.pdf">A New Algorithm for Data Compression</a>. <em>The C Users Journal</em>.</li>
<li><strong>Sennrich, R. et al.</strong> (2016). <a href="https://arxiv.org/abs/1508.07909">Neural Machine Translation of Rare Words with Subword Units</a>. <em>ACL</em>.</li>
<li><strong>Radford, A. et al.</strong> (2019). <a href="https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf">Language Models are Unsupervised Multitask Learners</a>.</li>
<li><strong>Karpathy, A.</strong> (2024). <a href="https://www.youtube.com/watch?v=zduSFxRajkE">Let’s build the GPT Tokenizer</a>.</li>
<li><a href="https://www.reedbeta.com/blog/programmers-intro-to-unicode/">A Programmer’s Introduction to Unicode</a>.</li>
<li><a href="https://utf8everywhere.org/">UTF-8 Everywhere</a>.</li>
<li><a href="https://tiktokenizer.vercel.app">Tiktokenizer</a>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>NLP</category>
  <guid>https://imaddabbura.github.io/posts/nlp/BPE-Tokenizer.html</guid>
  <pubDate>Wed, 10 Apr 2024 05:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/nlp/images/bpe-tokenizer.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>The RAG Optimization Playbook</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/nlp/improving-rag.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>RAG-based applications have so many components and moving parts that seems impossible to optimize or know where to start. Add to that the fact that the field changes so fast, which makes it super hard to keep up. So I’ve gathered few ideas over time to improve RAG-based applications from reading research papers and implementations I’ve deployed in the past.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>The list will keep changing as I learn/implement new things</p>
</div>
</div>
</section>
<section id="ideas" class="level2">
<h2 class="anchored" data-anchor-id="ideas">Ideas</h2>
<ul>
<li>Metadata filtering is key for good RAG apps</li>
<li>For an MVP, it is a good idea to use both bi-encoders and full-text search such as Tf-Idf and BM25 and combine them</li>
<li>ColBERT reranker is great and less sensitive to chunking</li>
<li>Have at least 50 chars overlapping in chunks when splitting to not cut-off context</li>
<li>If you have data and compute, always fine-tune both encoders if you can</li>
<li>Use <code>sentence-transformer</code> to fine-tune embedding models
<ul>
<li>We typically use triplet loss where for each query we would have positive and negative examples. We want the negative examples to be hard negatives -&gt; very close to positive examples so the model can learn to differentiate between them</li>
</ul></li>
<li>Large/New LLM not necessarily are good embedding models and mayn’t be worth the latency. LLMs with ~1B is enough for most cases</li>
<li>Challenges with embedding models:
<ul>
<li>Mayn’t transfer to your domain</li>
<li>Fixed vocabulary used when model was trained</li>
<li>Because chunk/doc is represented in one vector which is combination of all tokens in the chunk/doc, the output vector may dilute the meaning especially for long texts -&gt; Be careful about chunking strategy</li>
</ul></li>
<li>Always start with a baseline such as BM25 (Best Match 25)</li>
<li>Build your own gold dataset and check its correlation with synthetic dataset generated from LLMs</li>
<li>Chunking beyond 256 tokens will affect high precision search because it will dilute the vector representation because embedding models were not trained on long contexts such as BERT-based encoders</li>
<li>Feedback of how users are liking the app is key to guide us where we should focus our efforts to improve the app
<ul>
<li>Satisfaction ratings such as “How did we do today” or “Did we answer your question”</li>
</ul></li>
<li>Monitor <code>cosine</code> similarity between embeddings of query and retrieved docs and reranking scores that come from reranker (cohere)</li>
<li>Use clustering of questions using tools such as LDA or BERT-Topic to cluster questions into topics and focus on largest topics (by count) that have lowest means of cosine and feedback</li>
<li>We have two kinds of topics:
<ul>
<li>Content topics: Topics that we don’t have inventory of documents about such topics</li>
<li>Capability topics: Topics that reader will never be able to generate if we don’t capture them in our docs and docs metadata and include them in the prompt. For example, “Who last updated the pricing document” is asking about last modified date/person</li>
</ul></li>
<li>Build classifier to classify questions real-time for better observability and better react to sudden changes in usage</li>
<li>Generate synthetic data (questions) for topics we’re not doing great job at and evaluate new improvements on the generated questions
<ul>
<li>This can be done by providing random chunk from docs that belong to topics we’re trying to improve to decent LLM and ask to generate questions</li>
</ul></li>
<li>We can use LLM to get metadata about docs/objects</li>
<li>Lancedb is a good vector database to use for small/scale workloads</li>
<li>BM25 (full text search) outperforms similarity search when questions are just searching for file names. They may have similar performance with similarity search baseline.
<ul>
<li>It is always helpful to include BM25</li>
</ul></li>
<li>We can do citation through prompting and attaching IDs to chunks</li>
<li>Fine-tune embedding model is key for domain-specific RAGs
<ul>
<li>With recent increase in context window sizes, chunk size of 800 and 30% overlap is recommended</li>
</ul></li>
</ul>
</section>
<section id="resources" class="level2">
<h2 class="anchored" data-anchor-id="resources">Resources</h2>
<ul>
<li><a href="https://arxiv.org/abs/2309.10621">Large language models can accurately predict searcher preferences</a></li>
<li><a href="https://arxiv.org/abs/2406.06519">UMBRELA: UMbrela is the (Open-Source Reproduction of the) Bing RELevance Assessor</a></li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>NLP</category>
  <guid>https://imaddabbura.github.io/posts/nlp/improving-rag.html</guid>
  <pubDate>Tue, 05 Mar 2024 06:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/nlp/images/rag.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Inside Python’s Modules and Packages: The Machinery Behind import</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/swe/Modules-And-Packages.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>Every Python program you have ever written begins with <code>import</code>. You type <code>import pandas as pd</code> or <code>from mypackage.utils import clean</code>, the name resolves, and you move on. But <em>what actually happens</em> in the fraction of a second between that statement and the module being ready to use? Where does Python look? Why can two directories with the same name merge into a single package? How does <code>import</code> tell a built-in module apart from a <code>.py</code> file on disk from a compiled extension, and why can you <em>intercept</em> that decision to, say, install a missing package on the fly?</p>
<p>The import system is one of the most powerful and least understood parts of Python. It is not magic: it is a small, well-defined pipeline of objects (<strong>finders</strong>, <strong>loaders</strong>, and <strong>specs</strong>) that you can inspect, extend, and replace at runtime. Understanding it is the difference between someone who <em>uses</em> imports and someone who can debug a <code>ModuleNotFoundError</code> that makes no sense, build a plugin system, or reason about why a reload left their program in a broken state.</p>
<p>By the end of this post, you will be able to:</p>
<ol type="1">
<li>Trace exactly what happens when Python executes <code>import x</code>, step by step.</li>
<li>Explain the difference between a module and a package, and between regular and namespace packages, and <em>why</em> both exist.</li>
<li>Read a <code>ModuleSpec</code> and follow the <code>finder → loader → module</code> pipeline that produces it.</li>
<li>Extend the import system yourself: trace imports, lazy-load expensive modules, and hook <code>sys.meta_path</code>.</li>
<li>Avoid the classic traps: reload “zombies”, brittle absolute imports, and cache corruption.</li>
</ol>
<p>We start with the ten-thousand-foot view of what <code>import</code> does, then work downward: the object model of modules and packages, how Python <em>finds</em> them (<code>sys.path</code>), how it <em>loads</em> them (specs, finders, loaders), how source gets compiled and cached, the hooks that let you bend the machinery, running packages as programs, and finally the failure modes worth knowing. Every claim is backed by a live snippet you can run yourself.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Versions and environment
</div>
</div>
<div class="callout-body-container callout-body">
<p>The examples were run on <strong>CPython 3.14</strong>. The import system has been stable since its “reboot” in Python 3.3–3.4 (<a href="https://peps.python.org/pep-0451/">PEP 451</a>), so everything here applies to any modern Python 3. Output that shows filesystem paths or the contents of <code>sys.meta_path</code> is environment-specific. Yours will differ in the details, not the shape.</p>
</div>
</div>
<div id="abbc231c" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.622894Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.622827Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.626134Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.625678Z&quot;}}" data-execution_count="1">
<details class="code-fold">
<summary>Setup: a scratch workspace for the live demos (click to expand)</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> sys, os, tempfile, textwrap, importlib, importlib.util, types, builtins</span>
<span id="cb1-2"></span>
<span id="cb1-3">WORKSPACE <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tempfile.mkdtemp(prefix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"import-demo-"</span>)</span>
<span id="cb1-4">sys.path.insert(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, WORKSPACE)</span>
<span id="cb1-5"></span>
<span id="cb1-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> write_module(relpath, source):</span>
<span id="cb1-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Write a module (or package file) under WORKSPACE; return its path."""</span></span>
<span id="cb1-8">    path <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> os.path.join(WORKSPACE, relpath)</span>
<span id="cb1-9">    os.makedirs(os.path.dirname(path) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> WORKSPACE, exist_ok<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb1-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">open</span>(path, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"w"</span>) <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> f:</span>
<span id="cb1-11">        f.write(textwrap.dedent(source).lstrip())</span>
<span id="cb1-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> path</span></code></pre></div></div>
</details>
</div>
</section>
<section id="what-import-actually-does" class="level2">
<h2 class="anchored" data-anchor-id="what-import-actually-does">1. What <code>import</code> Actually Does</h2>
<p>Before dissecting any individual piece, hold the whole pipeline in your head. When you write <code>import spam</code>, Python runs four steps in order:</p>
<ol type="1">
<li><strong>Search:</strong> walk a chain of <em>finders</em>, asking each “do you know how to locate <code>spam</code>?” The winner returns a <strong>spec</strong>: metadata describing where the module is and how to load it.</li>
<li><strong>Create:</strong> build an empty module object (<code>types.ModuleType</code>) and set its attributes (<code>__name__</code>, <code>__file__</code>, <code>__spec__</code>, …).</li>
<li><strong>Execute:</strong> run the module’s code <em>with the module’s own <code>__dict__</code> as its global namespace</em>. This is what populates it with functions, classes, and variables.</li>
<li><strong>Bind:</strong> bind the resulting module object to a name in the scope that issued the import.</li>
</ol>
<p>Two facts do most of the work here. First, the finished module is cached in <strong><code>sys.modules</code></strong> <em>before</em> it is executed, and every later import of the same name is a plain dictionary lookup, not a re-execution:</p>
<div id="7e7962e5" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.627732Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.627636Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.631286Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.630749Z&quot;}}" data-execution_count="2">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> json</span>
<span id="cb2-2">first <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sys.modules[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"json"</span>]</span>
<span id="cb2-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> json                 <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a second import is a dict lookup, not a re-run</span></span>
<span id="cb2-4">first <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> sys.modules[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"json"</span>]</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="2">
<pre><code>True</code></pre>
</div>
</div>
<p><code>sys.modules</code> is why importing a module a thousand times costs almost nothing, and why a module’s top-level code runs exactly once per interpreter session. Second, “executing the module” is literally running its file top to bottom. There is no separate “definition mode.” A module <em>is</em> the accumulated side effects of running its source.</p>
<blockquote class="blockquote">
<p><em><code>import</code> is not a keyword that reads a file. It is a cache lookup that, on a miss, runs a find → create → execute → bind pipeline and memoizes the result.</em></p>
</blockquote>
</section>
<section id="modules-and-packages-the-object-model" class="level2">
<h2 class="anchored" data-anchor-id="modules-and-packages-the-object-model">2. Modules and Packages: The Object Model</h2>
<section id="a-module-is-just-a-namespace-object" class="level3">
<h3 class="anchored" data-anchor-id="a-module-is-just-a-namespace-object">2.1 A module is just a namespace object</h3>
<p>A module is not a special language construct; it is an ordinary object of type <code>types.ModuleType</code> whose attributes live in its <code>__dict__</code>. Accessing <code>json.loads</code> is a dictionary lookup in <code>json.__dict__</code>; setting an attribute on a module writes to that same dict:</p>
<div id="1ff443b6" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.632343Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.632264Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.634503Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.634126Z&quot;}}" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> json</span>
<span id="cb4-2">json.custom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">123</span>                 <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># identical to json.__dict__["custom"] = 123</span></span>
<span id="cb4-3"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>(json), json.<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__name__</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"loads"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> json.__dict__, json.__dict__[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"custom"</span>]</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="3">
<pre><code>(module, 'json', True, 123)</code></pre>
</div>
</div>
<p><code>module.x = 10</code> and <code>module.__dict__["x"] = 10</code> are the same operation. This is why a module can play so many roles at once: a namespace, an execution environment for statements, and a container of globals. Every module carries a handful of dunder attributes that the import system sets on it:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Attribute</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>__name__</code></td>
<td>Fully-qualified module name (<code>"json"</code>, <code>"os.path"</code>)</td>
</tr>
<tr class="even">
<td><code>__file__</code></td>
<td>Path to the source file, if it has one (built-ins don’t)</td>
</tr>
<tr class="odd">
<td><code>__doc__</code></td>
<td>The module docstring</td>
</tr>
<tr class="even">
<td><code>__dict__</code></td>
<td>The module’s namespace: everything defined at top level</td>
</tr>
<tr class="odd">
<td><code>__package__</code></td>
<td>The package this module belongs to (see §2.2)</td>
</tr>
<tr class="even">
<td><code>__path__</code></td>
<td><strong>Present only on packages:</strong> the search path for submodules</td>
</tr>
<tr class="odd">
<td><code>__spec__</code></td>
<td>The <code>ModuleSpec</code> used to load it (see §4)</td>
</tr>
<tr class="even">
<td><code>__loader__</code></td>
<td>The loader object that executed it</td>
</tr>
</tbody>
</table>
</section>
<section id="a-package-is-a-module-with-a-__path__" class="level3">
<h3 class="anchored" data-anchor-id="a-package-is-a-module-with-a-__path__">2.2 A package is a module with a <code>__path__</code></h3>
<p><strong>A package is simply a module that has a <code>__path__</code> attribute.</strong> <code>__path__</code> is a list of directories to search when importing that package’s <em>sub</em>modules; it is to a package what <code>sys.path</code> is to the interpreter. A plain module has no <code>__path__</code>; a package does, and that is what lets <code>import foo.bar</code> know where to find <code>bar</code>.</p>
<blockquote class="blockquote">
<p><em>All packages are modules, but not all modules are packages; only packages have <code>__path__</code>.</em></p>
</blockquote>
<p>For <code>__package__</code>: a package’s <code>__package__</code> equals its own <code>__name__</code>; a top-level module’s is the empty string; a submodule’s is its parent package’s name.</p>
</section>
<section id="regular-vs.-namespace-packages" class="level3">
<h3 class="anchored" data-anchor-id="regular-vs.-namespace-packages">2.3 Regular vs.&nbsp;namespace packages</h3>
<p>A <strong>regular package</strong> is a directory containing an <code>__init__.py</code>. Importing it runs that <code>__init__.py</code>, and its namespace becomes the package. Crucially, when the finder locates a regular package <strong>it stops searching</strong>: the first <code>__init__.py</code> of that name on <code>sys.path</code> wins.</p>
<p>A <strong>namespace package</strong> is a directory <em>without</em> <code>__init__.py</code> (<a href="https://peps.python.org/pep-0420/">PEP 420</a>). It behaves completely differently during search: the finder does <strong>not</strong> stop at the first match. It scans <em>all</em> of <code>sys.path</code>, collecting every directory of that name, and, if it never finds a regular package with that name, fuses the collected directories into a single package whose <code>__path__</code> spans every one of them.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Regular package</th>
<th>Namespace package</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Marker</td>
<td>Has <code>__init__.py</code></td>
<td>No <code>__init__.py</code></td>
</tr>
<tr class="even">
<td>Search behavior</td>
<td>Stops at first match</td>
<td>Scans all of <code>sys.path</code>, collects every match</td>
</tr>
<tr class="odd">
<td><code>__init__.py</code> runs</td>
<td>Yes</td>
<td>N/A</td>
</tr>
<tr class="even">
<td><code>__path__</code></td>
<td>Single directory</td>
<td><code>_NamespacePath</code> spanning multiple directories</td>
</tr>
<tr class="odd">
<td>Priority</td>
<td>Always wins over a namespace package of the same name</td>
<td>Used only if no regular package shadows it</td>
</tr>
<tr class="even">
<td>Use case</td>
<td>Normal, self-contained package</td>
<td>One logical package split across installs (plugin ecosystems)</td>
</tr>
</tbody>
</table>
<p><strong>The same package name can pull submodules from completely different directories.</strong> Let’s prove it. We create two separate directories, each holding a <code>pck/</code> folder with <em>no</em> <code>__init__.py</code>, and put a different submodule in each:</p>
<div id="50e394bc" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.635390Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.635324Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.639530Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.638964Z&quot;}}" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Two directories, each with a `pck/` folder that has NO __init__.py -&gt; namespace pkg</span></span>
<span id="cb6-2">write_module(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"locA/pck/mod.py"</span>,  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"X = 100"</span>)</span>
<span id="cb6-3">write_module(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"locB/pck/test.py"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"X = 200"</span>)</span>
<span id="cb6-4">sys.path[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>WORKSPACE<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">/locA"</span>, <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>WORKSPACE<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">/locB"</span>]</span>
<span id="cb6-5"></span>
<span id="cb6-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pck.mod <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> X <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> from_A</span>
<span id="cb6-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pck.test <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> X <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> from_B</span>
<span id="cb6-8">from_A, from_B, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(sys.modules[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pck"</span>].__path__)</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="4">
<pre><code>(100,
 200,
 ['/var/folders/hn/9dlqvjvx427_3m7wj0vxmpx80000gn/T/import-demo-v_vgrtrz/locA/pck',
  '/var/folders/hn/9dlqvjvx427_3m7wj0vxmpx80000gn/T/import-demo-v_vgrtrz/locB/pck'])</code></pre>
</div>
</div>
<p>The single name <code>pck</code> now resolves to a package whose <code>__path__</code> lists <strong>both</strong> temp directories, and <code>pck.mod</code> and <code>pck.test</code> were imported from different places on disk. This is exactly how large namespaced ecosystems (like the <code>google.*</code> cloud libraries) let independently-installed distributions contribute modules under one shared name.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Regular packages win and shadow the rest
</div>
</div>
<div class="callout-body-container callout-body">
<p>Python scans the entire <code>sys.path</code> before concluding a package is a namespace package. If <em>any</em> directory on the path contains that name with an <code>__init__.py</code>, the regular package wins and every namespace candidate is discarded. A stray <code>__init__.py</code> in the wrong place can silently “capture” a name you expected to be a namespace package.</p>
</div>
</div>
</section>
<section id="what-__init__.py-is-for" class="level3">
<h3 class="anchored" data-anchor-id="what-__init__.py-is-for">2.4 What <code>__init__.py</code> is for</h3>
<p>Beyond marking a directory as a regular package, <code>__init__.py</code> runs the first time the package is imported, which makes it the natural place to shape the package’s public surface. Common uses:</p>
<ul>
<li><strong>Re-export</strong> the useful names from submodules so callers write <code>from mypkg import Thing</code> instead of <code>from mypkg.internal.things import Thing</code>.</li>
<li><strong>Assemble <code>__all__</code></strong> from submodules to control <code>from mypkg import *</code>, e.g.&nbsp;<code>__all__ = submod_a.__all__ + submod_b.__all__</code> (import the submodules first so their <code>__all__</code> exists).</li>
<li><strong>Initialize package-level state</strong>, or occasionally monkeypatch another module.</li>
</ul>
<p>Keep <code>__init__.py</code> cheap: because it runs on first import, expensive work there is paid by <em>every</em> program that imports the package, whether or not it needs that work.</p>
</section>
<section id="dotted-imports-bind-submodules-to-their-parents" class="level3">
<h3 class="anchored" data-anchor-id="dotted-imports-bind-submodules-to-their-parents">2.5 Dotted imports bind submodules to their parents</h3>
<p>Importing <code>foo.bar.baz</code> imports <code>foo</code>, then <code>foo.bar</code>, then <code>foo.bar.baz</code>, in that order, caching each of the three separately in <code>sys.modules</code>. A subtle but important side effect: <strong>when a submodule is loaded, it is bound as an attribute on its parent package.</strong> After <code>import os.path</code>, the parent module <code>os</code> gains a <code>path</code> attribute pointing at the submodule:</p>
<div id="1c10794c" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.640788Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.640710Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.642903Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.642403Z&quot;}}" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os.path</span>
<span id="cb8-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"os.path"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> sys.modules, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">hasattr</span>(os, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"path"</span>), os.path <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> sys.modules[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"os.path"</span>]</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="5">
<pre><code>(True, True, True)</code></pre>
</div>
</div>
<p>This is why <code>import os.path</code> lets you write <code>os.path.join(...)</code> even though you only named <code>os</code>: the submodule attached itself to its parent during import.</p>
</section>
</section>
<section id="finding-modules-sys.path-and-the-search-space" class="level2">
<h2 class="anchored" data-anchor-id="finding-modules-sys.path-and-the-search-space">3. Finding Modules: <code>sys.path</code> and the Search Space</h2>
<p>For file-based imports, the search space is <strong><code>sys.path</code></strong>: an ordered list of locations (directories, zip files, <code>.egg</code> archives) that Python walks <strong>front to back, first match wins</strong>. If nothing matches, you get <code>ModuleNotFoundError</code>.</p>
<div id="c2254507" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.643764Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.643693Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.646367Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.645871Z&quot;}}" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">sys.path[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>]</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="6">
<pre><code>['/var/folders/hn/9dlqvjvx427_3m7wj0vxmpx80000gn/T/import-demo-v_vgrtrz/locA',
 '/var/folders/hn/9dlqvjvx427_3m7wj0vxmpx80000gn/T/import-demo-v_vgrtrz/locB',
 '/var/folders/hn/9dlqvjvx427_3m7wj0vxmpx80000gn/T/import-demo-v_vgrtrz',
 '/opt/homebrew/Cellar/python@3.14/3.14.6/Frameworks/Python.framework/Versions/3.14/lib/python314.zip',
 '/opt/homebrew/Cellar/python@3.14/3.14.6/Frameworks/Python.framework/Versions/3.14/lib/python3.14',
 '/opt/homebrew/Cellar/python@3.14/3.14.6/Frameworks/Python.framework/Versions/3.14/lib/python3.14/lib-dynload']</code></pre>
</div>
</div>
<p>Where do these entries come from? Python assembles <code>sys.path</code> at startup from several sources:</p>
<ul>
<li><strong>The script’s directory</strong> (or the current working directory in an interactive session), added at the <em>front</em>, which is why a local <code>math.py</code> shadows the standard library.</li>
<li><strong><code>PYTHONPATH</code></strong>, if set, prepended to the front.</li>
<li><strong>The standard library</strong>, located relative to the interpreter.</li>
<li><strong>Site packages:</strong> where third-party installs live, added by <code>site.py</code>.</li>
<li><strong><code>.pth</code> files</strong> in site-packages: each line names a directory to append. Package managers and editable installs use this hook to inject paths.</li>
</ul>
<p>Two values anchor the whole scheme. <strong><code>sys.prefix</code></strong> is where the Python installation lives (its landmark is the standard library’s <code>os.py</code>); <strong><code>sys.exec_prefix</code></strong> is where platform-specific compiled binaries live (landmark: the <code>lib-dynload</code> directory). A virtual environment is, at heart, a directory with its own <code>sys.prefix</code> and <code>site-packages</code>, sharing the base interpreter’s binary.</p>
<div id="e65a6007" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.647482Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.647398Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.649271Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.648965Z&quot;}}" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1">sys.prefix, sys.exec_prefix</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="7">
<pre><code>('/Users/imad/dev/dl-venv', '/Users/imad/dev/dl-venv')</code></pre>
</div>
</div>
<p>A few command-line switches and environment knobs shape all of this:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Flag / mechanism</th>
<th>Effect on the import environment</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>python -m pkg.mod</code></td>
<td>Run a module/package as <code>__main__</code> (relative imports work; see §7)</td>
</tr>
<tr class="even">
<td><code>python -S</code></td>
<td>Skip <code>site.py</code>: no site-packages, no <code>.pth</code> processing</td>
</tr>
<tr class="odd">
<td><code>python -v</code> / <code>-vv</code></td>
<td>Trace import activity (what Python tries, and where)</td>
</tr>
<tr class="even">
<td><code>PYTHONPATH=…</code></td>
<td>Prepend directories to <code>sys.path</code></td>
</tr>
<tr class="odd">
<td><code>python -m venv env</code></td>
<td>Create a virtual environment with its own prefix and site-packages</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span><code>sys.path</code> is only half the story
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>sys.path</code> answers “<em>where</em> on the filesystem do I look?”, but it is consulted by only <em>one</em> of the finders on <code>sys.meta_path</code> (the <code>PathFinder</code>). Built-in and frozen modules never touch <code>sys.path</code>. The real dispatch layer is <code>sys.meta_path</code>, which we get to in §4.4.</p>
</div>
</div>
</section>
<section id="loading-modules-specs-finders-and-loaders" class="level2">
<h2 class="anchored" data-anchor-id="loading-modules-specs-finders-and-loaders">4. Loading Modules: Specs, Finders, and Loaders</h2>
<section id="the-modulespec-a-modules-blueprint" class="level3">
<h3 class="anchored" data-anchor-id="the-modulespec-a-modules-blueprint">4.1 The <code>ModuleSpec</code>: a module’s blueprint</h3>
<p>Since the Python 3.4 import reboot (<a href="https://peps.python.org/pep-0451/">PEP 451</a>), the central object of the import system is the <strong><code>ModuleSpec</code></strong>: the metadata bundle that says <em>what</em> a module is and <em>how</em> to load it. You can obtain one without importing anything via <code>importlib.util.find_spec</code>:</p>
<div id="73e39e9c" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.650162Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.650101Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.651987Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.651664Z&quot;}}" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1">importlib.util.find_spec(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sys"</span>)        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a built-in: origin is 'built-in', no file</span></span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="8">
<pre><code>ModuleSpec(name='sys', loader=&lt;class '_frozen_importlib.BuiltinImporter'&gt;, origin='built-in')</code></pre>
</div>
</div>
<div id="085e6043" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.652769Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.652710Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.655092Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.654738Z&quot;}}" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1">spec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> importlib.util.find_spec(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pandas"</span>)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a source package on disk</span></span>
<span id="cb16-2">spec.name, spec.origin, spec.submodule_search_locations</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="9">
<pre><code>('pandas',
 '/Users/imad/dev/dl-venv/lib/python3.14/site-packages/pandas/__init__.py',
 ['/Users/imad/dev/dl-venv/lib/python3.14/site-packages/pandas'])</code></pre>
</div>
</div>
<p>The contrast tells the story: <code>sys</code> is a built-in with <code>origin='built-in'</code> and no file; <code>pandas</code> is a source package whose <code>origin</code> is its <code>__init__.py</code> and whose <code>submodule_search_locations</code> is the <code>__path__</code> it will receive. The spec’s important fields:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>Field</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>name</code></td>
<td>Fully-qualified module name</td>
</tr>
<tr class="even">
<td><code>loader</code></td>
<td>The object that will execute the module</td>
</tr>
<tr class="odd">
<td><code>origin</code></td>
<td>Where it comes from (<code>'built-in'</code>, a file path, …)</td>
</tr>
<tr class="even">
<td><code>submodule_search_locations</code></td>
<td>The future <code>__path__</code>: non-<code>None</code> only for packages</td>
</tr>
<tr class="odd">
<td><code>parent</code></td>
<td>Enclosing package name</td>
</tr>
<tr class="even">
<td><code>cached</code></td>
<td>Location of the compiled <code>.pyc</code>, if any</td>
</tr>
</tbody>
</table>
</section>
<section id="the-import-algorithm-in-pseudocode" class="level3">
<h3 class="anchored" data-anchor-id="the-import-algorithm-in-pseudocode">4.2 The import algorithm, in pseudocode</h3>
<p>Stripped to essentials, importing a not-yet-cached module looks like this:</p>
<div id="6f92d2f1" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.655959Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.655901Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.658174Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.657496Z&quot;}}" data-execution_count="10">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> sys</span>
<span id="cb18-2"></span>
<span id="cb18-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> import_module(name):</span>
<span id="cb18-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> sys.modules:              <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. cache hit -&gt; done</span></span>
<span id="cb18-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> sys.modules[name]</span>
<span id="cb18-6"></span>
<span id="cb18-7">    spec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> find_spec(name)               <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. search: finders -&gt; spec (or ImportError)</span></span>
<span id="cb18-8">    module <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> importlib.util.module_from_spec(spec)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. create the empty module</span></span>
<span id="cb18-9">    sys.modules[name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> module           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. cache BEFORE executing (circular-safe)</span></span>
<span id="cb18-10">    spec.loader.exec_module(module)      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5. execute the body into module.__dict__</span></span>
<span id="cb18-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> sys.modules[name]             <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 6. return cached (may differ if code swapped it)</span></span></code></pre></div></div>
</div>
<p>Two details are worth internalizing. The module is <strong>cached before it is executed</strong>, so a circular import finds a partially-initialized module rather than looping forever. And the <em>cached</em> object is what’s returned: a module that reassigns <code>sys.modules[__name__]</code> to something else during execution can hand back a different object than the one that started loading.</p>
</section>
<section id="finders-and-loaders-create-vs.-execute-are-decoupled" class="level3">
<h3 class="anchored" data-anchor-id="finders-and-loaders-create-vs.-execute-are-decoupled">4.3 Finders and loaders: create vs.&nbsp;execute are decoupled</h3>
<p>The pipeline splits cleanly in two. A <strong>finder</strong> answers “can I locate this name?” and returns a spec. A <strong>loader</strong> does the work, and even that is split: <code>create_module(spec)</code> builds the empty module object, and <code>exec_module(module)</code> runs its code. <code>importlib.util.module_from_spec</code> gives you the created-but-not-executed module; nothing runs until you call <code>exec_module</code>:</p>
<div id="5292fa9f" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.659045Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.658976Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.662031Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.661627Z&quot;}}" data-execution_count="11">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb19-1">write_module(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"heavy.py"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb19-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    print("&gt;&gt;&gt; heavy.py body is executing")</span></span>
<span id="cb19-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    VALUE = 42</span></span>
<span id="cb19-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span>)</span>
<span id="cb19-5">spec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> importlib.util.find_spec(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"heavy"</span>)</span>
<span id="cb19-6">heavy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> importlib.util.module_from_spec(spec)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># created, but NOT executed</span></span>
<span id="cb19-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">hasattr</span>(heavy, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"VALUE"</span>)                          <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># -&gt; False: the body hasn't run</span></span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="11">
<pre><code>False</code></pre>
</div>
</div>
<div id="1a305a64" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.663368Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.663296Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.665988Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.665630Z&quot;}}" data-execution_count="12">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb21-1">spec.loader.exec_module(heavy)                   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># now the body runs</span></span>
<span id="cb21-2">heavy.VALUE</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>&gt;&gt;&gt; heavy.py body is executing</code></pre>
</div>
<div class="cell-output cell-output-display" data-execution_count="12">
<pre><code>42</code></pre>
</div>
</div>
<p>This decoupling is exactly what makes lazy loading (§6.3) possible: you can hold a fully-formed module object and defer the expensive <code>exec_module</code> until someone actually touches it.</p>
</section>
<section id="sys.meta_path-the-real-controller" class="level3">
<h3 class="anchored" data-anchor-id="sys.meta_path-the-real-controller">4.4 <code>sys.meta_path</code>: the real controller</h3>
<p>Every import is dispatched through <strong><code>sys.meta_path</code></strong>, an ordered list of <strong>meta path finders</strong>. Python asks each one, front to back, <code>find_spec(name, path, target)</code> until one returns a spec:</p>
<div id="fa076ceb" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.666934Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.666870Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.668818Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.668490Z&quot;}}" data-execution_count="13">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb24-1">sys.meta_path</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="13">
<pre><code>[&lt;_distutils_hack.DistutilsMetaFinder at 0x1096146e0&gt;,
 &lt;_virtualenv._Finder at 0x109614590&gt;,
 _frozen_importlib.BuiltinImporter,
 _frozen_importlib.FrozenImporter,
 _frozen_importlib_external.PathFinder,
 &lt;six._SixMetaPathImporter at 0x10a44e900&gt;]</code></pre>
</div>
</div>
<p>The three that always ship with CPython are <code>BuiltinImporter</code> (compiled-in modules like <code>sys</code>), <code>FrozenImporter</code> (modules frozen into the binary, including <code>importlib</code> itself), and <code>PathFinder</code>, the only one that consults <code>sys.path</code>. Libraries and tools can prepend or append their own finders, so you may also see entries from setuptools or editable installs. Conceptually the search is just:</p>
<div id="a8180019" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.669624Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.669566Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.671232Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.670890Z&quot;}}" data-execution_count="14">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb26-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> find_spec(name, path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, target<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb26-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> finder <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> sys.meta_path:</span>
<span id="cb26-3">        spec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> finder.find_spec(name, path, target)</span>
<span id="cb26-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> spec <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb26-5">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> spec</span>
<span id="cb26-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ModuleNotFoundError</span>(name)</span></code></pre></div></div>
</div>
<p><code>PathFinder</code> in turn delegates to <strong><code>sys.path_hooks</code></strong>: each <code>sys.path</code> entry is handed to these hooks to produce a path-specific finder (a directory gets a <code>FileFinder</code>; a <code>.zip</code> gets a <code>zipimporter</code>). The results are memoized in <code>sys.path_importer_cache</code>.</p>
<div id="26476c84" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.672034Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.671974Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.673733Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.673355Z&quot;}}" data-execution_count="15">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb27-1">sys.path_hooks</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="15">
<pre><code>[zipimport.zipimporter,
 &lt;function _frozen_importlib_external.FileFinder.path_hook.&lt;locals&gt;.path_hook_for_FileFinder(path)&gt;]</code></pre>
</div>
</div>
<p>This layered design (<code>meta_path</code> → <code>PathFinder</code> → <code>path_hooks</code> → per-entry finder → loader) is the seam that makes §6 possible.</p>
</section>
</section>
<section id="compilation-and-the-.pyc-cache" class="level2">
<h2 class="anchored" data-anchor-id="compilation-and-the-.pyc-cache">5. Compilation and the <code>.pyc</code> Cache</h2>
<p>When a loader executes a source module, it doesn’t interpret the <code>.py</code> text directly: it first <strong>compiles</strong> the source to bytecode (a code object), then executes that. Compilation isn’t free, so CPython caches the result: the bytecode is written to a <code>.pyc</code> file inside a <code>__pycache__/</code> directory next to the source (<a href="https://peps.python.org/pep-3147/">PEP 3147</a>). On the next import, if the cache is still valid, Python skips compilation and loads the bytecode directly, which is why the <em>second</em> startup of a program that imports a big library is noticeably faster than the first.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/module-compilation.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure 1: A .pyc file is a small header followed by the marshalled code object. The header holds a magic number (a version tag; bytecode from a different Python version is rejected), plus the source’s mtime and size. On import, Python compares those against the current source file; if either changed, the cache is stale and the module is recompiled. (Slide from David Beazley’s “Modules and Packages: Live and Let Die!”)"><img src="https://imaddabbura.github.io/posts/swe/images/module-compilation.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="500" alt="Figure 1: A .pyc file is a small header followed by the marshalled code object. The header holds a magic number (a version tag; bytecode from a different Python version is rejected), plus the source’s mtime and size. On import, Python compares those against the current source file; if either changed, the cache is stale and the module is recompiled. (Slide from David Beazley’s “Modules and Packages: Live and Let Die!”)"></a></p>
</figure>
</div>
<figcaption><strong>Figure 1:</strong> A <code>.pyc</code> file is a small header followed by the marshalled code object. The header holds a <strong>magic number</strong> (a version tag; bytecode from a different Python version is rejected), plus the source’s <strong>mtime</strong> and <strong>size</strong>. On import, Python compares those against the current source file; if either changed, the cache is stale and the module is recompiled. <em>(Slide from David Beazley’s “Modules and Packages: Live and Let Die!”)</em></figcaption>
</figure>
</div>
<p><code>importlib.util</code> exposes the pieces of this scheme directly: the version tag, the magic number that gates cache validity, and the function that maps a source path to its cached bytecode path.</p>
<div id="9b8f2a13" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.674535Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.674466Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.676297Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.675948Z&quot;}}" data-execution_count="16">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb29-1">(sys.implementation.cache_tag,</span>
<span id="cb29-2"> importlib.util.MAGIC_NUMBER,</span>
<span id="cb29-3"> importlib.util.cache_from_source(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"widget.py"</span>))</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="16">
<pre><code>('cpython-314', b'+\x0e\r\n', '__pycache__/widget.cpython-314.pyc')</code></pre>
</div>
</div>
<p>The <code>cache_tag</code> (<code>cpython-314</code>) is embedded in the <code>.pyc</code> filename so that multiple Python versions can share a source tree without clobbering each other’s bytecode. Invalidation is normally based on the source’s modification time and size, but Python also supports <strong>hash-based</strong> <code>.pyc</code> files (comparing a hash of the source instead), which make builds reproducible when timestamps aren’t reliable.</p>
</section>
<section id="bending-the-machinery" class="level2">
<h2 class="anchored" data-anchor-id="bending-the-machinery">6. Bending the Machinery</h2>
<p>Because finders are just objects on a list, you can insert your own and change what <code>import</code> <em>means</em>. Two placements matter: <strong>prepend</strong> to <code>sys.meta_path</code> to intercept every import before the real finders run, or <strong>append</strong> to catch imports that would otherwise fail.</p>
<section id="tracing-every-import" class="level3">
<h3 class="anchored" data-anchor-id="tracing-every-import">6.1 Tracing every import</h3>
<p>A finder that prints and then returns <code>None</code> observes imports without handling them; returning <code>None</code> politely defers to the finders behind it:</p>
<div id="81906988" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.677132Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.677071Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.680361Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.680019Z&quot;}}" data-execution_count="17">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb31-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Watcher:</span>
<span id="cb31-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"A meta-path finder that observes imports, then defers by returning None."</span></span>
<span id="cb31-3">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@classmethod</span></span>
<span id="cb31-4">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> find_spec(cls, name, path, target<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb31-5">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"find_spec -&gt;"</span>, name)</span>
<span id="cb31-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># None =&gt; let the finders behind us handle it</span></span>
<span id="cb31-7"></span>
<span id="cb31-8">write_module(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"observed.py"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"VALUE = 1"</span>)</span>
<span id="cb31-9">sys.meta_path.insert(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, Watcher)       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># prepend: we see every import first</span></span>
<span id="cb31-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb31-11">    <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> observed                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a fresh module, so the finder actually fires</span></span>
<span id="cb31-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">finally</span>:</span>
<span id="cb31-13">    sys.meta_path.remove(Watcher)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>find_spec -&gt; observed</code></pre>
</div>
</div>
<p>The same idea one level up (wrapping <code>builtins.__import__</code>) intercepts the <code>import</code> statement itself:</p>
<div id="6770c1b8" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.681300Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.681236Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.683881Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.683494Z&quot;}}" data-execution_count="18">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb33" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb33-1">_real_import <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> builtins.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">__import__</span></span>
<span id="cb33-2"></span>
<span id="cb33-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> traced_import(name, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>args, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>kwargs):</span>
<span id="cb33-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> sys.modules:        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># only announce first-time imports</span></span>
<span id="cb33-5">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"importing:"</span>, name)</span>
<span id="cb33-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> _real_import(name, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>args, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>kwargs)</span>
<span id="cb33-7"></span>
<span id="cb33-8">builtins.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">__import__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> traced_import</span>
<span id="cb33-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb33-10">    <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> graphlib</span>
<span id="cb33-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">finally</span>:</span>
<span id="cb33-12">    builtins.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">__import__</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> _real_import</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>importing: graphlib</code></pre>
</div>
</div>
</section>
<section id="auto-installing-missing-packages" class="level3">
<h3 class="anchored" data-anchor-id="auto-installing-missing-packages">6.2 Auto-installing missing packages</h3>
<p>Append a finder to the <em>end</em> of <code>sys.meta_path</code> and it becomes a last resort, consulted only after every real finder has failed. Here is the (in)famous auto-installer: on the first miss for a top-level name, it pip-installs the package and returns its now-findable spec.</p>
<div id="052eb5a1" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.684672Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.684612Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.686573Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.686264Z&quot;}}" data-execution_count="19">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb35" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb35-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> subprocess</span>
<span id="cb35-2"></span>
<span id="cb35-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> AutoInstall:</span>
<span id="cb35-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"Last-resort finder: pip-install a missing top-level module on first miss."</span></span>
<span id="cb35-5">    _tried <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>()</span>
<span id="cb35-6"></span>
<span id="cb35-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@classmethod</span></span>
<span id="cb35-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> find_spec(cls, name, path, target<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb35-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> path <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> name <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> cls._tried:</span>
<span id="cb35-10">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># only top-level names, and only try once</span></span>
<span id="cb35-11">        cls._tried.add(name)</span>
<span id="cb35-12">        subprocess.check_call([sys.executable, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-m"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pip"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"install"</span>, name])</span>
<span id="cb35-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> importlib.util.find_spec(name)</span>
<span id="cb35-14"></span>
<span id="cb35-15">sys.meta_path.append(AutoInstall)      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># append =&gt; consulted only after real finders fail</span></span></code></pre></div></div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Cute, not production
</div>
</div>
<div class="callout-body-container callout-body">
<p>Auto-installing at import time is a great way to <em>understand</em> <code>meta_path</code>, and a terrible idea in real software: it runs the network and mutates the environment as a side effect of an <code>import</code>, with no version pinning and no reproducibility. Use it to learn; use a real dependency manager to ship.</p>
</div>
</div>
</section>
<section id="lazy-imports" class="level3">
<h3 class="anchored" data-anchor-id="lazy-imports">6.3 Lazy imports</h3>
<p>The create/execute split from §4.3 turns lazy loading into a few lines. The standard library ships <code>importlib.util.LazyLoader</code>, which wraps a loader so the module’s body doesn’t run until its first attribute access:</p>
<div id="37ff2121" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.687335Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.687275Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.690342Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.690061Z&quot;}}" data-execution_count="20">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb36" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb36-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lazy_import(name):</span>
<span id="cb36-2">    spec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> importlib.util.find_spec(name)</span>
<span id="cb36-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> spec <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb36-4">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ModuleNotFoundError</span>(name)</span>
<span id="cb36-5">    spec.loader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> importlib.util.LazyLoader(spec.loader)</span>
<span id="cb36-6">    module <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> importlib.util.module_from_spec(spec)</span>
<span id="cb36-7">    sys.modules[name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> module</span>
<span id="cb36-8">    spec.loader.exec_module(module)    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># arms lazy loading; the body still hasn't run</span></span>
<span id="cb36-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> module</span>
<span id="cb36-10"></span>
<span id="cb36-11">write_module(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"expensive.py"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb36-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    print("&gt;&gt;&gt; expensive.py body is executing")</span></span>
<span id="cb36-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ANSWER = 99</span></span>
<span id="cb36-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span>)</span>
<span id="cb36-15"></span>
<span id="cb36-16">mod <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lazy_import(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"expensive"</span>)</span>
<span id="cb36-17"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bound lazily, nothing above printed yet"</span>)</span>
<span id="cb36-18">mod.ANSWER                             <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># first attribute access triggers execution</span></span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>bound lazily, nothing above printed yet
&gt;&gt;&gt; expensive.py body is executing</code></pre>
</div>
<div class="cell-output cell-output-display" data-execution_count="20">
<pre><code>99</code></pre>
</div>
</div>
<p>Nothing printed when we “imported” <code>expensive</code>: the body ran only when we touched <code>mod.ANSWER</code>. Internally, <code>LazyLoader</code> uses exactly the trick you’d invent yourself: it returns a module whose <code>__getattr__</code> triggers the real <code>exec_module</code> on first access, then swaps its own class back to a plain module so subsequent accesses are normal. This is how libraries with heavy optional dependencies keep <code>import</code> cheap.</p>
</section>
</section>
<section id="running-packages-as-programs-__main__-and--m" class="level2">
<h2 class="anchored" data-anchor-id="running-packages-as-programs-__main__-and--m">7. Running Packages as Programs: <code>__main__</code> and <code>-m</code></h2>
<p>The <code>__main__</code> module is a special case. It is initialized directly at interpreter startup (like <code>sys</code> and <code>builtins</code>), and <em>how</em> it’s initialized depends on how you launched Python. Run a script and <code>__main__</code> is that script; start a REPL and <code>__main__</code> is the interactive session.</p>
<p>The <code>-m</code> flag is where this intersects the import system. <code>python -m pkg.mod</code> <strong>imports</strong> <code>pkg</code> (running its <code>__init__.py</code>), then runs <code>pkg.mod</code> as <code>__main__</code>, but with <code>__package__</code> set correctly, so <strong>relative imports work</strong>. Run the same file as a bare path (<code>python path/to/mod.py</code>) and it has no package context, so relative imports fail. This mechanism is behind <code>python -m pdb script.py</code>, <code>python -m http.server</code>, and <code>python -m venv</code>.</p>
<p>A directory or zip file can itself be executable if it contains a <strong><code>__main__.py</code></strong>: <code>python pkg/</code> (or <code>python -m pkg</code>) looks for <code>pkg/__main__.py</code> and runs it as the entry point, with the relevant <code>__init__.py</code> files still executing first.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/executable-submodules.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure 2: Executable submodules. A __main__.py inside a subpackage turns it into a runnable entry point: python -m spam.test executes spam/test/__main__.py (after running spam/__init__.py and spam/test/__init__.py), while a subpackage without one, like spam.core, is import-only. (Slide from David Beazley’s “Modules and Packages: Live and Let Die!”)"><img src="https://imaddabbura.github.io/posts/swe/images/executable-submodules.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="450" alt="Figure 2: Executable submodules. A __main__.py inside a subpackage turns it into a runnable entry point: python -m spam.test executes spam/test/__main__.py (after running spam/__init__.py and spam/test/__init__.py), while a subpackage without one, like spam.core, is import-only. (Slide from David Beazley’s “Modules and Packages: Live and Let Die!”)"></a></p>
</figure>
</div>
<figcaption><strong>Figure 2:</strong> Executable submodules. A <code>__main__.py</code> inside a subpackage turns it into a runnable entry point: <code>python -m spam.test</code> executes <code>spam/test/__main__.py</code> (after running <code>spam/__init__.py</code> and <code>spam/test/__init__.py</code>), while a subpackage without one, like <code>spam.core</code>, is import-only. <em>(Slide from David Beazley’s “Modules and Packages: Live and Let Die!”)</em></figcaption>
</figure>
</div>
<p>Whether <code>__main__.__spec__</code> is set reflects all of this: launched with <code>-m</code> (or as a directory or zip), <code>__spec__</code> holds the corresponding module spec; launched as a plain script or with <code>-c</code>, it is <code>None</code>.</p>
</section>
<section id="gotchas-and-failure-modes" class="level2">
<h2 class="anchored" data-anchor-id="gotchas-and-failure-modes">8. Gotchas and Failure Modes</h2>
<section id="reload-creates-zombies" class="level3">
<h3 class="anchored" data-anchor-id="reload-creates-zombies">8.1 <code>reload</code> creates zombies</h3>
<p><code>importlib.reload</code> does not give you a clean slate. It re-executes the module’s source <em>into the same <code>__dict__</code></em>, so names from the previous version linger, and, worse, any <code>class</code> statement produces a <strong>brand-new class object</strong> while the old instances keep pointing at the old one. The two classes share a name but are not the same object:</p>
<div id="7f2cbd6a" class="cell" data-quarto-private-1="{&quot;key&quot;:&quot;execution&quot;,&quot;value&quot;:{&quot;iopub.execute_input&quot;:&quot;2026-07-06T15:45:26.691249Z&quot;,&quot;iopub.status.busy&quot;:&quot;2026-07-06T15:45:26.691191Z&quot;,&quot;iopub.status.idle&quot;:&quot;2026-07-06T15:45:26.694752Z&quot;,&quot;shell.execute_reply&quot;:&quot;2026-07-06T15:45:26.694135Z&quot;}}" data-execution_count="21">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb39" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb39-1">write_module(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"widget.py"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb39-2"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    print("widget.py body ran")</span></span>
<span id="cb39-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    class Widget:</span></span>
<span id="cb39-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        pass</span></span>
<span id="cb39-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span>)</span>
<span id="cb39-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> widget</span>
<span id="cb39-7">old <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> widget.Widget()</span>
<span id="cb39-8"></span>
<span id="cb39-9"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> importlib <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reload</span></span>
<span id="cb39-10"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">reload</span>(widget)                         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># re-runs the body into the SAME module dict</span></span>
<span id="cb39-11">new <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> widget.Widget()</span>
<span id="cb39-12"></span>
<span id="cb39-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Same class *name*, but a different class object -&gt; old instances are "zombies":</span></span>
<span id="cb39-14">old.<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__class__</span>, new.<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">__class__</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>(old) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>(new), <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(old, widget.Widget)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>widget.py body ran
widget.py body ran</code></pre>
</div>
<div class="cell-output cell-output-display" data-execution_count="21">
<pre><code>(widget.Widget, widget.Widget, False, False)</code></pre>
</div>
</div>
<p>Both objects report their class as <code>widget.Widget</code>, yet <code>type(old) is type(new)</code> is <code>False</code> and <code>isinstance(old, widget.Widget)</code> is <code>False</code>: <code>old</code> is now an instance of a class that no longer exists under that name. Reload also does <strong>not</strong> re-import already-loaded submodules (a <code>reload</code>-ed module that does <code>import pandas</code> keeps the old <code>pandas</code>). This is why reload is a REPL convenience, not a hot-swap mechanism: for anything stateful, restart the interpreter.</p>
</section>
<section id="prefer-relative-imports-inside-a-package" class="level3">
<h3 class="anchored" data-anchor-id="prefer-relative-imports-inside-a-package">8.2 Prefer relative imports inside a package</h3>
<p>Absolute imports (<code>from mypackage.sub import thing</code>) hard-code the package’s name into every import statement. Rename the package and every one of them breaks. Relative imports (<code>from .sub import thing</code>) refer to location, not name, and survive renames. Relative imports must use the <code>from … import …</code> form: <code>import .sub</code> is a syntax error, and a leading dot counts one level per dot (<code>from ..pkg import x</code> goes up two).</p>
</section>
<section id="the-cache-is-mutable" class="level3">
<h3 class="anchored" data-anchor-id="the-cache-is-mutable">8.3 The cache is mutable</h3>
<p><code>sys.modules</code> is a plain dict, which is powerful and dangerous. Deleting a key forces a genuine reimport next time (a cleaner reset than <code>reload</code>). Aliasing a module under a second name (<code>sys.modules["new"] = sys.modules["old"]</code>) makes both names resolve to the same object. But setting an entry to <code>None</code> poisons that name: Python treats it as “known to be unimportable” and raises <code>ModuleNotFoundError</code>. Reach into the cache deliberately, not casually.</p>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>The import system rewards study out of all proportion to its size. Underneath <code>import x</code> is a four-step pipeline (find, create, execute, bind) dispatched through ordered lists of finders you can read and rewrite at runtime. Once you can see that pipeline, a whole class of problems stops being mysterious: shadowed modules, namespace packages that pick up the wrong directory, reloads that leave broken objects behind, plugin systems that must discover code dynamically.</p>
<section id="key-takeaways" class="level3">
<h3 class="anchored" data-anchor-id="key-takeaways">Key Takeaways</h3>
<ol type="1">
<li><strong><code>import</code> is a cached pipeline, not a keyword that reads a file.</strong> Find → create → execute → bind, with <code>sys.modules</code> short-circuiting everything after the first import.</li>
<li><strong>A package is just a module with <code>__path__</code>.</strong> Regular packages (<code>__init__.py</code>) stop the search and win; namespace packages (no <code>__init__.py</code>) merge directories from across <code>sys.path</code>.</li>
<li><strong><code>sys.path</code> is only the <code>PathFinder</code>’s input.</strong> The real dispatch layer is <code>sys.meta_path</code> → <code>PathFinder</code> → <code>sys.path_hooks</code> → per-entry finder → loader.</li>
<li><strong>The <code>ModuleSpec</code> is the hub.</strong> Finders produce specs; loaders consume them; create and execute are decoupled, which is what makes lazy loading possible.</li>
<li><strong>Source is compiled once and cached</strong> as version-tagged bytecode in <code>__pycache__</code>, validated against the source’s timestamp and size.</li>
<li><strong>Everything is pluggable.</strong> Prepend a finder to intercept imports, append one to catch failures: that’s all tracing, lazy imports, and auto-install really are.</li>
<li><strong>Know the traps.</strong> <code>reload</code> creates zombie objects, absolute imports are brittle to renames, and writing to <code>sys.modules</code> can corrupt the cache.</li>
</ol>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>The machinery is stable
</div>
</div>
<div class="callout-body-container callout-body">
<p>The spec/finder/loader design has been in place since Python 3.4 and hasn’t fundamentally changed since. Learn it once and it applies to every modern Python: the contents of <code>sys.meta_path</code> vary by environment, but the shape of the pipeline does not.</p>
</div>
</div>
</section>
</section>
<section id="references-resources" class="level2">
<h2 class="anchored" data-anchor-id="references-resources">References &amp; Resources</h2>
<ul>
<li><strong>Python documentation.</strong> <a href="https://docs.python.org/3/reference/import.html">The import system</a>. The authoritative reference for everything here.</li>
<li><strong>Python documentation.</strong> <a href="https://docs.python.org/3/library/importlib.html"><code>importlib</code></a> and <a href="https://docs.python.org/3/library/importlib.html#module-importlib.util"><code>importlib.util</code></a>. The APIs used throughout this post.</li>
<li><strong>David Beazley.</strong> <a href="https://dabeaz.com/modulepackage/"><em>Modules and Packages: Live and Let Die!</em></a> (PyCon 2015). The talk this post grew out of; the tracing, auto-install, and lazy-import demos trace back to it, and both figures are from its slides.</li>
<li><strong>PEP 328.</strong> <a href="https://peps.python.org/pep-0328/">Imports: Multi-Line and Absolute/Relative</a>.</li>
<li><strong>PEP 420.</strong> <a href="https://peps.python.org/pep-0420/">Implicit Namespace Packages</a>.</li>
<li><strong>PEP 451.</strong> <a href="https://peps.python.org/pep-0451/">A ModuleSpec Type for the Import System</a>. The 3.4 “import reboot” that made specs central.</li>
<li><strong>PEP 3147.</strong> <a href="https://peps.python.org/pep-3147/">PYC Repository Directories</a>. Why compiled files live in <code>__pycache__</code>.</li>
<li><strong>Python documentation.</strong> <a href="https://docs.python.org/3/library/venv.html"><code>venv</code></a>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Software Engineering</category>
  <guid>https://imaddabbura.github.io/posts/swe/Modules-And-Packages.html</guid>
  <pubDate>Fri, 09 Feb 2024 06:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/swe/images/modules-packages-image.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Automatic Differentiation Demystified</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/mlsys/automatic-differentiation.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="what-automatic-differentiation-is" class="level2">
<h2 class="anchored" data-anchor-id="what-automatic-differentiation-is">What Automatic Differentiation Is</h2>
<p>Every time you train a neural network, something computes exact derivatives through millions of operations automatically. You call <code>loss.backward()</code> and gradients appear, but <em>how</em>? And why does training a 7B-parameter LLM consume 5x more GPU memory than running inference on it?</p>
<p>The answer to both questions is <strong>Automatic Differentiation (AD)</strong>: a family of techniques for computing exact derivatives through arbitrary code, efficiently. Understanding it changes how you reason about memory budgets, gradient flow failures, and why certain training tricks (<a href="https://arxiv.org/abs/1604.06174">gradient checkpointing</a>, <a href="https://arxiv.org/abs/1710.03740">mixed precision</a>) exist at all.</p>
<p>There are two fundamentally different approaches, <strong>forward mode</strong> and <strong>reverse mode</strong>, and the choice between them explains why deep learning frameworks are built the way they are.</p>
</section>
<section id="why-not-just-use-calculus-or-finite-differences" class="level2">
<h2 class="anchored" data-anchor-id="why-not-just-use-calculus-or-finite-differences">Why Not Just Use Calculus or Finite Differences?</h2>
<p>Before getting to AD, it helps to understand what it replaced.</p>
<p><strong>Numerical differentiation</strong> approximates the derivative using finite differences: <img src="https://latex.codecogs.com/png.latex?f'(x)%20%5Capprox%20%5Cfrac%7Bf(x+h)%20-%20f(x)%7D%7Bh%7D"> for some small <img src="https://latex.codecogs.com/png.latex?h">. It’s dead simple but has two fatal flaws: it requires one extra forward pass <em>per parameter</em> (catastrophic for millions of parameters), and floating-point subtraction of nearly-equal numbers amplifies numerical error badly.</p>
<p><strong>Symbolic differentiation</strong> (what a computer algebra system does) applies calculus rules to produce a closed-form derivative expression. It’s exact, but the resulting expressions grow exponentially with computation depth: a 100-layer network would produce a gradient expression no machine could reasonably evaluate.</p>
<p>AD is neither. It applies the chain rule mechanically at each elementary operation, accumulating intermediate values rather than symbolic expressions. The result is exact (to floating-point precision) and efficient: no expression explosion, no extra passes per parameter.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Three Ways to Differentiate Code
</div>
</div>
<div class="callout-body-container callout-body">
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Method</th>
<th>Accuracy</th>
<th>Cost</th>
<th>Practical for ML?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Numerical (finite diff)</td>
<td>Approximate</td>
<td>1 extra pass per input</td>
<td>Too slow</td>
</tr>
<tr class="even">
<td>Symbolic</td>
<td>Exact</td>
<td>Expression explosion</td>
<td>Intractable</td>
</tr>
<tr class="odd">
<td>AD, forward mode</td>
<td>Exact</td>
<td>1 pass per input</td>
<td>Only if inputs are few</td>
</tr>
<tr class="even">
<td>AD, reverse mode</td>
<td>Exact</td>
<td>1 pass per output</td>
<td>Standard choice</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<section id="forward-mode-ad" class="level2">
<h2 class="anchored" data-anchor-id="forward-mode-ad">Forward Mode AD</h2>
<p>Forward mode AD propagates <strong>derivatives alongside values</strong> as computation flows from inputs to outputs. At each operation, it tracks not just the result but how sensitive that result is to a chosen input.</p>
<p>The elegant implementation uses <strong>dual numbers</strong> (<a href="https://doi.org/10.1137/1.9780898717761">Griewank &amp; Walther, 2008</a>): instead of a scalar <img src="https://latex.codecogs.com/png.latex?x">, carry a pair <img src="https://latex.codecogs.com/png.latex?(x,%5C%20%5Cdot%7Bx%7D)"> where <img src="https://latex.codecogs.com/png.latex?%5Cdot%7Bx%7D"> represents the derivative of <img src="https://latex.codecogs.com/png.latex?x"> with respect to some chosen input <img src="https://latex.codecogs.com/png.latex?x_i">. Operations on dual numbers automatically propagate the derivative via the chain rule; you never write it explicitly:</p>
<p><img src="https://latex.codecogs.com/png.latex?f(a%20+%20b%5Cvarepsilon)%20%5Capprox%20f(a)%20+%20f'(a)%5Ccdot%20b%5Cvarepsilon%20%5Cqquad%20(%5Cvarepsilon%5E2%20=%200)"></p>
<p>The <img src="https://latex.codecogs.com/png.latex?%5Cvarepsilon"> coefficient carries the derivative forward through every arithmetic operation.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    x1["x₁&lt;br/&gt;(x₁, ẋ₁=1)"] --&gt; mul["×"]
    x2["x₂&lt;br/&gt;(x₂, ẋ₂=0)"] --&gt; mul
    mul --&gt;|"(x₁x₂, x₂·1)"| add["+"]
    x3["x₃&lt;br/&gt;(x₃, ẋ₃=0)"] --&gt; add
    add --&gt;|"(x₁x₂+x₃, x₂)"| L["L&lt;br/&gt;∂L/∂x₁ = x₂"]
</pre>
</div>
<p></p><figcaption> Forward mode propagates (value, derivative) pairs from inputs to output. The derivative component tracks sensitivity w.r.t. one chosen input. Here, the seed is set for x₁, so x₂’s dot is 0.</figcaption> </figure><p></p>
</div>
</div>
</div>
<p>The critical limitation: the initial <strong>seed vector</strong>, the <img src="https://latex.codecogs.com/png.latex?(0,%5Cldots,1,%5Cldots,0)"> that selects which input you’re differentiating with respect to, means one forward pass gives you the sensitivity with respect to <em>one</em> input. Getting gradients for all <img src="https://latex.codecogs.com/png.latex?n"> inputs requires <img src="https://latex.codecogs.com/png.latex?n"> passes.</p>
<p>For a 7B-parameter LLM, that’s 7 billion passes to compute a single gradient update. Forward mode is not the answer for ML.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>When Forward Mode Wins
</div>
</div>
<div class="callout-body-container callout-body">
<p>Forward mode is efficient when <strong>outputs greatly outnumber inputs</strong>, the opposite of ML. It shines in scientific computing: a simulation with 3 input parameters and 10,000 output metrics needs only 3 forward passes, not 10,000. In ML the ratio is reversed: millions of inputs (parameters), one output (scalar loss). Reverse mode exists to handle exactly this case.</p>
</div>
</div>
</section>
<section id="reverse-mode-ad" class="level2">
<h2 class="anchored" data-anchor-id="reverse-mode-ad">Reverse Mode AD</h2>
<p>Reverse mode flips the direction. Instead of asking “how does changing this input affect the output?”, it asks “how much did each node contribute to this output?”</p>
<p>The key insight: for a scalar output (a loss function), <strong>one backward pass distributes gradient credit back to every node in the graph simultaneously</strong>. One pass. All gradients. This is <a href="https://www.nature.com/articles/323533a0">backpropagation</a>.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart TD
    subgraph fwd ["① Forward Pass: compute and store"]
        direction LR
        x["x"] --&gt; mul["mul"] --&gt; add["add"] --&gt; L["L (scalar)"]
        w["w"] --&gt; mul
        b["b"] --&gt; add
    end
    subgraph bwd ["② Backward Pass: propagate gradients"]
        direction RL
        dL["∂L/∂L = 1"] --&gt; dadd["∂L/∂add"] --&gt; dmul["∂L/∂mul"]
        dmul --&gt; dx["∂L/∂x"]
        dmul --&gt; dw["∂L/∂w"]
        dadd --&gt; db["∂L/∂b"]
    end
    fwd --&gt; bwd
</pre>
</div>
<p></p><figcaption> Reverse mode runs two phases: a forward pass that computes and stores all intermediate values, then a backward pass that propagates ∂L/∂· back to every node.</figcaption> </figure><p></p>
</div>
</div>
</div>
<section id="the-unavoidable-memory-cost" class="level3">
<h3 class="anchored" data-anchor-id="the-unavoidable-memory-cost">The Unavoidable Memory Cost</h3>
<p>Here’s the catch. To compute gradients during the backward pass, each operation needs its <strong>inputs from the forward pass</strong>. For a <code>mul</code> node computing <img src="https://latex.codecogs.com/png.latex?z%20=%20w%20%5Ccdot%20x">, the backward step needs both <img src="https://latex.codecogs.com/png.latex?w"> and <img src="https://latex.codecogs.com/png.latex?x"> to distribute credit:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20w%7D%20=%20x%20%5Ccdot%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20z%7D,%20%5Cqquad%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20x%7D%20=%20w%20%5Ccdot%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20z%7D"></p>
<p>So the framework must <strong>keep every intermediate tensor alive</strong> until the backward pass consumes it. The consequence:</p>
<ul>
<li><strong>Inference</strong>: each layer’s activations can be discarded once the next layer is computed → memory is roughly <img src="https://latex.codecogs.com/png.latex?O(1)"> in depth</li>
<li><strong>Training</strong>: all activations must survive until their gradient is computed → memory is <img src="https://latex.codecogs.com/png.latex?O(N)"> in depth</li>
</ul>
<p>This is why training a transformer consumes so much more memory than running inference on it. At large batch sizes, forward activations alone can dwarf the parameter memory.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Why Your GPU OOMs During Training But Not Inference
</div>
</div>
<div class="callout-body-container callout-body">
<p>During inference, each layer’s output overwrites the previous buffer: memory stays roughly constant regardless of model depth. During training, every layer’s output must survive until the backward pass reaches it. A 24-layer transformer holds 24 layers of activations simultaneously. Scale batch size by 4x and activation memory scales 4x too, while parameters don’t budge and activations do. This is the first thing to check when you hit an OOM that doesn’t happen at inference time.</p>
</div>
</div>
</section>
<section id="gradient-checkpointing-buying-memory-back-with-compute" class="level3">
<h3 class="anchored" data-anchor-id="gradient-checkpointing-buying-memory-back-with-compute">Gradient Checkpointing: Buying Memory Back with Compute</h3>
<p>The standard solution to activation memory pressure is <strong>gradient checkpointing</strong> (<a href="https://arxiv.org/abs/1604.06174">Chen et al., 2016</a>) (also called activation recomputation): don’t store all activations during the forward pass. Store only at segment boundaries (<strong>checkpoints</strong>) and recompute intermediate activations on-the-fly during the backward pass when they’re needed.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    subgraph s1 ["Segment 1"]
        L1["Layer 1"] --&gt; L2["Layer 2"] --&gt; L3["Layer 3"]
    end
    subgraph s2 ["Segment 2"]
        L4["Layer 4"] --&gt; L5["Layer 5"] --&gt; L6["Layer 6"]
    end
    s1 --&gt;|"✓ checkpoint"| s2
</pre>
</div>
<p></p><figcaption> Checkpointing stores activations only at segment boundaries (green). During backward, each segment re-runs its forward pass to recover the discarded intermediates.</figcaption> </figure><p></p>
</div>
</div>
</div>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Strategy</th>
<th>Activation memory</th>
<th>Compute overhead</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>No checkpointing</td>
<td><img src="https://latex.codecogs.com/png.latex?O(N)"> layers</td>
<td>None</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?%5Csqrt%7BN%7D"> checkpoints</td>
<td><img src="https://latex.codecogs.com/png.latex?O(%5Csqrt%7BN%7D)"> layers</td>
<td>~1 extra forward pass</td>
</tr>
<tr class="odd">
<td>Recompute everything</td>
<td><img src="https://latex.codecogs.com/png.latex?O(1)"></td>
<td>Up to <img src="https://latex.codecogs.com/png.latex?N"> extra forward passes</td>
</tr>
</tbody>
</table>
<p>The sweet spot for most LLM training is <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7BN%7D"> checkpoints: roughly one extra forward pass in exchange for a meaningful memory reduction. This is what <code>torch.utils.checkpoint.checkpoint_sequential</code> implements.</p>
</section>
</section>
<section id="forward-vs.-reverse-mode" class="level2">
<h2 class="anchored" data-anchor-id="forward-vs.-reverse-mode">Forward vs.&nbsp;Reverse Mode</h2>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Forward Mode</th>
<th>Reverse Mode</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Passes needed</strong></td>
<td>1 per input variable</td>
<td>1 per output variable</td>
</tr>
<tr class="even">
<td><strong>Best for</strong></td>
<td>Few inputs, many outputs</td>
<td>Many inputs, few outputs (ML)</td>
</tr>
<tr class="odd">
<td><strong>Memory overhead</strong></td>
<td>Low - no stored intermediates</td>
<td>High - all intermediates stored</td>
</tr>
<tr class="even">
<td><strong>What frameworks use</strong></td>
<td>Occasionally for Jacobians</td>
<td>Always for gradient-based training</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>The Jacobian Perspective
</div>
</div>
<div class="callout-body-container callout-body">
<p>Forward mode naturally computes a <strong>Jacobian-vector product (JVP)</strong>: the full Jacobian multiplied by a chosen input direction. Reverse mode naturally computes a <strong>vector-Jacobian product (VJP)</strong>: a chosen output direction multiplied by the full Jacobian. For a scalar loss, the VJP with direction <img src="https://latex.codecogs.com/png.latex?%5B1%5D"> gives you the complete gradient vector in one pass. This is the mathematical reason reverse mode dominates ML training.</p>
</div>
</div>
</section>
<section id="what-breaks-in-practice" class="level2">
<h2 class="anchored" data-anchor-id="what-breaks-in-practice">What Breaks in Practice</h2>
<p><strong>Gradient flow failures.</strong> In reverse mode, gradients are products of local Jacobians chained across all layers. If any factor is consistently small (saturating activations, poor initialization) or large (unbounded weights), the gradient signal degrades before reaching early layers. This is the vanishing/exploding gradient problem: it’s not specific to RNNs; it’s a structural property of deep reverse-mode computation.</p>
<p><strong>Silent NaN propagation.</strong> A NaN anywhere in the forward pass propagates silently through the computation graph. During backward, every gradient flowing through the affected node becomes NaN, and the weight update corrupts the entire model. Use <code>torch.autograd.set_detect_anomaly(True)</code> to get a traceback pointing to the originating operation, invaluable for tracking these down.</p>
<p><strong>In-place operations on tensors with gradients.</strong> In-place ops (e.g., <code>x += 1</code>) can modify a tensor that the backward pass expects to find unchanged. PyTorch raises a runtime error when it detects this, but the error message can be confusing. The fix is simple: avoid in-place ops on any tensor that requires gradients, or clone before modifying.</p>
</section>
<section id="key-takeaways" class="level2">
<h2 class="anchored" data-anchor-id="key-takeaways">Key Takeaways</h2>
<ol type="1">
<li><p><strong>AD is not numerical or symbolic differentiation.</strong> It applies the chain rule exactly at each elementary operation: no approximation, no expression explosion.</p></li>
<li><p><strong>Forward mode needs one pass per input; reverse mode needs one pass per output.</strong> For ML (scalar loss, millions of parameters), reverse mode wins unconditionally.</p></li>
<li><p><strong>The cost of reverse mode is memory.</strong> Every intermediate tensor from the forward pass must stay alive for the backward pass. This is the root cause of training using far more memory than inference.</p></li>
<li><p><strong>Gradient checkpointing trades compute for memory.</strong> Store only at segment boundaries, recompute the rest during backward. Expect roughly one extra forward pass overhead for a meaningful memory reduction.</p></li>
<li><p><strong>Most gradient problems are reverse-mode problems.</strong> Vanishing/exploding gradients, NaN propagation, and in-place op errors all stem from how reverse-mode AD chains local Jacobians through the computation graph. Understanding the mechanism is the fastest path to diagnosing them.</p></li>
</ol>
</section>
<section id="references" class="level2">
<h2 class="anchored" data-anchor-id="references">References</h2>
<ul>
<li><strong>Rumelhart, D., Hinton, G., &amp; Williams, R.</strong> (1986). <a href="https://www.nature.com/articles/323533a0">Learning Representations by Back-Propagating Errors</a>. <em>Nature</em>, 323.</li>
<li><strong>Griewank, A. &amp; Walther, A.</strong> (2008). <a href="https://doi.org/10.1137/1.9780898717761">Evaluating Derivatives: Principles and Techniques of Algorithmic Differentiation</a>. <em>SIAM</em>.</li>
<li><strong>Chen, T. et al.</strong> (2016). <a href="https://arxiv.org/abs/1604.06174">Training Deep Nets with Sublinear Memory Cost</a>.</li>
<li><strong>Micikevicius, P. et al.</strong> (2018). <a href="https://arxiv.org/abs/1710.03740">Mixed Precision Training</a>. <em>ICLR</em>.</li>
<li><strong>Baydin, A. et al.</strong> (2018). <a href="https://arxiv.org/abs/1502.05767">Automatic Differentiation in Machine Learning: a Survey</a>. <em>JMLR</em>, 18.</li>
<li><strong>Paszke, A. et al.</strong> (2019). <a href="https://arxiv.org/abs/1912.01703">PyTorch: An Imperative Style, High-Performance Deep Learning Library</a>. <em>NeurIPS</em>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>ML Systems</category>
  <guid>https://imaddabbura.github.io/posts/mlsys/automatic-differentiation.html</guid>
  <pubDate>Sat, 03 Feb 2024 06:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/mlsys/images/automatic-differentiation-image.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Git from the Inside Out</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/swe/Advanced-Git.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>Git’s entire data model fits in one sentence: an append-only, content-addressable object store of blobs, trees, and commits, with mutable pointers (branches, tags, HEAD) layered on top. That is it. Every command - <code>commit</code>, <code>merge</code>, <code>rebase</code>, <code>reset</code> - is either creating objects, moving pointers, or both.</p>
<p>Most Git tutorials skip this and teach a bag of CLI recipes. The result: developers memorize commands without understanding what they do, and panic when something goes wrong. This post takes the opposite approach. It starts from the internal data model (the <em>plumbing</em>) and builds up to the daily commands (the <em>porcelain</em>). Once you see that a branch is a 41-byte file containing a commit hash, operations like merge and rebase become intuitive pointer manipulations rather than incantations.</p>
<p>By the end of this post, you will be able to:</p>
<ol type="1">
<li>Explain the three object types (blob, tree, commit) and how they form a content-addressable filesystem</li>
<li>Trace what <code>git add</code> and <code>git commit</code> do at the object level, step by step</li>
<li>Understand branching, merging, and rebasing as pointer operations on a DAG</li>
<li>Use <code>reset</code>, <code>rebase -i</code>, <code>bisect</code>, and <code>cherry-pick</code> with confidence - knowing exactly what each one moves</li>
<li>Follow a professional feature-branch workflow from creation to fast-forward merge</li>
</ol>
<p>We start with the object model, then build up through references, branches, remotes, history inspection, history rewriting, and the professional workflow that ties it all together. The diagram below is the complete mental model - every section that follows is an operation on this structure.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    HEAD["HEAD"] --&gt; BR["branch&lt;br/&gt;&lt;small&gt;(refs/heads/main)&lt;/small&gt;"]
    BR --&gt; C2["commit"] --&gt; C1["commit"]
    C2 --&gt; T["tree&lt;br/&gt;&lt;small&gt;(root dir)&lt;/small&gt;"]
    T --&gt; B1["blob&lt;br/&gt;&lt;small&gt;(file)&lt;/small&gt;"]
    T --&gt; T2["tree&lt;br/&gt;&lt;small&gt;(subdir)&lt;/small&gt;"]
    T2 --&gt; B2["blob"]
    TAG["tag v1.0&lt;br/&gt;&lt;small&gt;(refs/tags/)&lt;/small&gt;"] --&gt; C1

    style HEAD fill:#d9534f,stroke:#333,color:#fff
    style BR fill:#f9d71c,stroke:#333,color:#000
    style TAG fill:#f9d71c,stroke:#333,color:#000
    style C2 fill:#7eb8da,stroke:#333,color:#000
    style C1 fill:#7eb8da,stroke:#333,color:#000
    style T fill:#90c695,stroke:#333,color:#000
    style T2 fill:#90c695,stroke:#333,color:#000
    style B1 fill:#e8a87c,stroke:#333,color:#000
    style B2 fill:#e8a87c,stroke:#333,color:#000
</pre>
</div>
<p></p><figcaption> <strong>Overview:</strong> The complete Git mental model. Objects (blobs, trees, commits) form an immutable DAG. References (branches, tags, HEAD) are mutable pointers into it. Every command you learn in this post creates objects, moves references, or both.</figcaption> </figure><p></p>
</div>
</div>
</div>
</section>
<section id="the-object-model-how-git-actually-stores-data" class="level2">
<h2 class="anchored" data-anchor-id="the-object-model-how-git-actually-stores-data">1. The Object Model: How Git Actually Stores Data</h2>
<p>Every version control system must answer a fundamental question: <em>how do you store the state of a project at a point in time?</em> Some systems store deltas - the difference between consecutive versions. Git takes a radically different approach: it stores <strong>snapshots</strong>. Every commit captures the complete state of every file in the project. Files that have not changed are not duplicated; Git simply reuses the existing object. This snapshot model is what makes branching and merging fast: a branch is just a pointer to an existing snapshot (no need to replay deltas to reconstruct state), and a three-way merge can directly compare three complete trees rather than reconstructing them from a chain of differences.</p>
<p>Everything Git stores lives inside the <code>.git</code> directory at the root of your project. Delete that directory, and the entire history is gone. Keep it, and you have a complete, self-contained database of every version of every file ever committed.</p>
<section id="content-addressable-storage" class="level3">
<h3 class="anchored" data-anchor-id="content-addressable-storage">1.1 Content-Addressable Storage</h3>
<p>The key design decision that makes Git work is <strong>content-addressable storage</strong>. Every object Git stores - every file, every directory listing, every commit - is identified by the SHA-1 hash of its contents. The hash is a 40-character hexadecimal string (160 bits), and it serves as both the object’s name and its address in the database.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pseudocode for Git's storage model</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> store(obj):</span>
<span id="cb1-3">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sha1(obj)</span>
<span id="cb1-4">    objects[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compress(obj)</span>
<span id="cb1-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span></span>
<span id="cb1-6"></span>
<span id="cb1-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> load(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>):</span>
<span id="cb1-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> decompress(objects[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>])</span></code></pre></div></div>
<p>Git uses the first two characters of the hash as a directory name and the remaining 38 as a filename, all stored under <code>.git/objects/</code>. For example, a hash of <code>ee5941ab3c...</code> is stored at <code>.git/objects/ee/5941ab3c...</code>.</p>
<p>This design gives Git three properties for free:</p>
<ul>
<li><strong>Deduplication.</strong> Two files with identical contents produce the same hash, so Git stores only one copy. Rename a file? The blob stays the same; only the parent tree changes.</li>
<li><strong>Integrity.</strong> Any corruption - even a single flipped bit - changes the hash, so Git detects it immediately.</li>
<li><strong>Immutability.</strong> Objects are addressed by their content. You cannot change an object without changing its address. This append-only property is why committed data is almost always recoverable.</li>
</ul>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Git Is a Content-Addressable Filesystem
</div>
</div>
<div class="callout-body-container callout-body">
<p>At its core, Git is not a “version control system” - it is a content-addressable filesystem with a version control UI built on top. Understanding this is the single most important mental shift for mastering Git. Every command you run - <code>commit</code>, <code>merge</code>, <code>rebase</code>, <code>reset</code> - is ultimately an operation on this object store.</p>
</div>
</div>
</section>
<section id="blobs-file-contents-without-names" class="level3">
<h3 class="anchored" data-anchor-id="blobs-file-contents-without-names">1.2 Blobs: File Contents Without Names</h3>
<p>A <strong>blob</strong> (binary large object) stores the raw contents of a single file - nothing more. No filename, no permissions, no metadata. Just bytes.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb2-1">type blob <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>byte<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span></code></pre></div></div>
<p>When Git computes the hash of a blob, it prepends a header: the string <code>"blob"</code>, a space, the content length in bytes, and a null byte. The SHA-1 of this combined string becomes the blob’s address:</p>
<pre><code>SHA-1("blob 60\0" + file_contents) → ee5941ab3c...</code></pre>
<p>Because blobs store only contents and not names, two files with identical contents - even in different directories, even with different names - map to the <strong>same blob</strong>. Git stores it exactly once.</p>
<div id="1c534b66-7804-4eed-a8fd-ef6124cc6d4e" class="cell" data-execution_count="33">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Inspect a blob - git cat-file -p shows the raw content</span></span>
<span id="cb4-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># First, find a blob hash from the current tree</span></span>
<span id="cb4-3"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>git cat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>p HEAD<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">^</span>{tree} <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> tail <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>100755 blob 1b4e54a00da0021ed03ce4facf67cea88d230300    til.qmd
040000 tree 02a16aeaeaa4ae9113498e37832f78ece2bb27f4    til</code></pre>
</div>
</div>
<div id="74a3cdfe-0593-4a24-b7d9-e8c840025299" class="cell" data-execution_count="37">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>zlib<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>flate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>uncompress <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">b</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">4e54</span><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">a00da0021ed03ce4facf67cea88d230300</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> head <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>blob 528---
title: Today Imad Learned</code></pre>
</div>
</div>
</section>
<section id="trees-directory-snapshots" class="level3">
<h3 class="anchored" data-anchor-id="trees-directory-snapshots">1.3 Trees: Directory Snapshots</h3>
<p>A <strong>tree</strong> object represents a directory. It contains a list of entries, where each entry maps a name (filename or subdirectory name) to either a blob or another tree, along with a file mode (permissions).</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// A directory maps names to blobs or subtrees</span></span>
<span id="cb8-2">type tree <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> list<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;(</span>mode<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> hash<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)&gt;</span></span></code></pre></div></div>
<p>Each line in a tree object looks like:</p>
<pre><code>100644 blob a1b2c3d4...  README.md
040000 tree e5f6a7b8...  src/</code></pre>
<p>Trees have several important properties:</p>
<ul>
<li><strong>Trees do not store their own name.</strong> A tree’s name is assigned by its <em>parent</em> tree. The root tree - the top-level directory of the project - has no name at all, which is why renaming your repository’s local directory has zero effect on Git.</li>
<li><strong>Empty directories are invisible.</strong> A tree must contain at least one entry. Git cannot track an empty directory. The common workaround is placing a <code>.gitkeep</code> file inside it.</li>
<li><strong>Renaming is cheap.</strong> If you rename a subdirectory, only the parent tree changes. The subtree object and everything below it remain untouched - they have the same hashes and the same addresses.</li>
<li><strong>Trees are themselves hashed.</strong> The hash of a tree is computed over its list of entries. Change any entry (add a file, rename something, update a blob hash), and the tree gets a new hash - which propagates up to every parent tree, all the way to the root.</li>
</ul>
<div id="00283c46" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Inspect the root tree of the latest commit</span></span>
<span id="cb10-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>git cat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>p HEAD<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">^</span>{tree} <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> head <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>100755 blob bb6df347bcb140d911763ede8d70c72981c6b760    .gitignore
100755 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    .nojekyll
100755 blob 14d0bc4d92bee96cd7b778e97222ed8f18b5b8c5    404.md</code></pre>
</div>
</div>
</section>
<section id="commits-snapshots-with-context" class="level3">
<h3 class="anchored" data-anchor-id="commits-snapshots-with-context">1.4 Commits: Snapshots with Context</h3>
<p>A <strong>commit</strong> object ties everything together. It points to a root tree (the snapshot), records who made the change, when, and why, and links to its parent commit(s) to form a history.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb12-1">type commit <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">struct</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb12-2">    tree<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>    hash           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// pointer to the root tree (the snapshot)</span></span>
<span id="cb12-3">    parent<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>  list<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>hash<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// zero parents (initial), one (normal), or two+ (merge)</span></span>
<span id="cb12-4">    author<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>  string         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// who wrote the change</span></span>
<span id="cb12-5">    committer<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> string       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// who applied the change</span></span>
<span id="cb12-6">    message<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> string         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// why the change was made (commit message)</span></span>
<span id="cb12-7"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>A commit does <strong>not</strong> store diffs. When you run <code>git diff</code> between two commits, Git compares their root trees on the fly and computes the difference on demand. This is a key design choice: storing snapshots makes branching and merging fast at the cost of slightly more storage (mitigated by deduplication and packfiles).</p>
<p>Because unchanged files keep the same blob hash, and unchanged directories keep the same tree hash, most of a commit’s tree structure is <em>shared</em> with its parent. A commit that changes one file out of a thousand creates exactly one new blob, one new tree for its parent directory, and new trees up to the root - everything else is reused via identical hashes.</p>
<div id="5f2bdd33-c82c-4437-9872-733229bf48ff" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Inspect a commit object - shows tree, parent, author, message</span></span>
<span id="cb13-2"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span>git cat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">file</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>p HEAD</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>tree 94ab6b930e1b2be4b0e68d6eb14f81671498f14d
parent 84381f8ce551c786c0a2b1564cf9a3d8e48319a4
author ImadDabbura &lt;imad.dabbura@hotmail.com&gt; 1775674279 -0500
committer ImadDabbura &lt;imad.dabbura@hotmail.com&gt; 1775674279 -0500

feat: Add few more tips/tricks</code></pre>
</div>
</div>
</section>
<section id="the-three-states-modified-staged-committed" class="level3">
<h3 class="anchored" data-anchor-id="the-three-states-modified-staged-committed">1.5 The Three States: Modified, Staged, Committed</h3>
<p>Git manages files through three distinct states, mediated by three areas:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 24%">
<col style="width: 40%">
<col style="width: 36%">
</colgroup>
<thead>
<tr class="header">
<th>Area</th>
<th>Location</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Working directory</strong></td>
<td>Your project files on disk</td>
<td>Where you edit files</td>
</tr>
<tr class="even">
<td><strong>Staging area (index)</strong></td>
<td><code>.git/index</code></td>
<td>A draft of the next commit snapshot</td>
</tr>
<tr class="odd">
<td><strong>Repository (object store)</strong></td>
<td><code>.git/objects/</code></td>
<td>The permanent, immutable database</td>
</tr>
</tbody>
</table>
<p>The lifecycle of a change:</p>
<ol type="1">
<li><strong>Modified.</strong> You edit a file in your working directory. Git knows it has changed (by comparing its hash to the index) but has not recorded the change anywhere.</li>
<li><strong>Staged.</strong> You run <code>git add file</code>. Git computes the SHA-1 of the file, compresses and stores the blob in <code>.git/objects/</code>, and records the hash in the index file. The index is now a draft of what the next commit’s tree will look like.</li>
<li><strong>Committed.</strong> You run <code>git commit</code>. Git reads the index, builds tree objects for every directory, creates a commit object pointing to the root tree (with parent, author, and message), and updates the current branch to point at the new commit.</li>
</ol>
<p>Files also fall into two tracking categories:</p>
<ul>
<li><strong>Tracked</strong>: files that exist in the last commit or in the staging area. They can be modified, staged, or committed.</li>
<li><strong>Untracked</strong>: files Git does not know about. They appear in <code>git status</code> but are not included in commits until explicitly added.</li>
</ul>
</section>
<section id="putting-it-all-together-a-worked-example" class="level3">
<h3 class="anchored" data-anchor-id="putting-it-all-together-a-worked-example">1.6 Putting It All Together: A Worked Example</h3>
<p>Let’s trace exactly what happens when you create a file and commit it.</p>
<p><strong>Step 1 - Create a file:</strong></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb15-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">echo</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hello, Git"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> greeting.txt</span></code></pre></div></div>
<p>The file exists only in your working directory. Git status shows it as <em>untracked</em>.</p>
<p><strong>Step 2 - Stage the file (<code>git add greeting.txt</code>):</strong></p>
<p>Git does three things:</p>
<ol type="1">
<li>Computes the SHA-1: <code>SHA-1("blob 11\0Hello, Git\n")</code> → <code>ab3f...</code></li>
<li>Compresses the content and stores it at <code>.git/objects/ab/3f...</code></li>
<li>Adds an entry to <code>.git/index</code>: <code>100644 blob ab3f... greeting.txt</code></li>
</ol>
<p><strong>Step 3 - Commit (<code>git commit -m "Add greeting"</code>):</strong></p>
<p>Git does four things:</p>
<ol type="1">
<li>Reads the index and creates a tree object listing <code>greeting.txt → ab3f...</code> → tree hash <code>d8e7...</code></li>
<li>Creates a commit object: <code>tree d8e7..., parent &lt;previous HEAD&gt;, author ..., message "Add greeting"</code> → commit hash <code>f1a2...</code></li>
<li>Stores both the tree and commit as compressed objects in <code>.git/objects/</code></li>
<li>Updates <code>.git/refs/heads/main</code> to contain <code>f1a2...</code></li>
</ol>
<p>The result is a chain: <strong>branch → commit → tree → blob(s)</strong>.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    B["main&lt;br/&gt;&lt;small&gt;refs/heads/main&lt;/small&gt;"] --&gt; C["commit f1a2...&lt;br/&gt;&lt;small&gt;tree: d8e7...&lt;/small&gt;&lt;br/&gt;&lt;small&gt;parent: 9c3b...&lt;/small&gt;&lt;br/&gt;&lt;small&gt;msg: Add greeting&lt;/small&gt;"]
    C --&gt; T["tree d8e7...&lt;br/&gt;&lt;small&gt;greeting.txt → ab3f...&lt;/small&gt;"]
    T --&gt; BL["blob ab3f...&lt;br/&gt;&lt;small&gt;Hello, Git&lt;/small&gt;"]
    C --&gt; PC["commit 9c3b...&lt;br/&gt;&lt;small&gt;(parent commit)&lt;/small&gt;"]

    style B fill:#f9d71c,stroke:#333,color:#000
    style C fill:#7eb8da,stroke:#333,color:#000
    style T fill:#90c695,stroke:#333,color:#000
    style BL fill:#e8a87c,stroke:#333,color:#000
    style PC fill:#7eb8da,stroke:#333,color:#000
</pre>
</div>
<p></p><figcaption> <strong>Figure 1:</strong> The Git object model. A branch points to a commit, which points to a root tree, which points to blobs (files) and subtrees (directories). Each object is identified by the SHA-1 hash of its contents.</figcaption> </figure><p></p>
</div>
</div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Objects, Refs, HEAD - That’s All There Is
</div>
</div>
<div class="callout-body-container callout-body">
<p>The entire Git data model consists of three object types (blobs, trees, commits), mutable references (branches, tags, remote-tracking branches), and a single HEAD pointer. Every Git command - no matter how complex - is ultimately a combination of creating objects and moving references. Once this clicks, Git stops being mysterious.</p>
</div>
</div>
</section>
</section>
<section id="references-human-readable-pointers" class="level2">
<h2 class="anchored" data-anchor-id="references-human-readable-pointers">2. References: Human-Readable Pointers</h2>
<p>Raw SHA-1 hashes are precise but unwieldy - nobody wants to type <code>f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0</code> every time they refer to a commit. <strong>References</strong> (refs) solve this by providing human-readable names that map to commit hashes. They are stored as plain-text files under <code>.git/refs/</code>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># References are simply a map from names to commit hashes</span></span>
<span id="cb16-2">references <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">map</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>string, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">hash</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb16-3"></span>
<span id="cb16-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> update_reference(name, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>):</span>
<span id="cb16-5">    references[name] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span></span>
<span id="cb16-6"></span>
<span id="cb16-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> read_reference(name):</span>
<span id="cb16-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> references[name]</span></code></pre></div></div>
<p>There are three kinds of references, each serving a distinct role.</p>
<section id="branches-heads" class="level3">
<h3 class="anchored" data-anchor-id="branches-heads">2.1 Branches (Heads)</h3>
<p>A <strong>branch</strong> is a file in <code>.git/refs/heads/</code> that contains the SHA-1 hash of a single commit - the tip of that branch. When you create a branch, Git creates a 41-byte file (40 hex chars + newline). When you make a commit on that branch, Git updates the file to point to the new commit. That is the <em>entire</em> implementation of branching.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb17-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Creating a branch is literally creating a file</span></span>
<span id="cb17-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> cat .git/refs/heads/main</span>
<span id="cb17-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0</span></span></code></pre></div></div>
<p>This is why branches in Git are so cheap: they are not copies of your codebase, not snapshots, not deltas - they are 41-byte text files. Creating a thousand branches costs roughly 41 KB of disk space.</p>
</section>
<section id="head-the-singleton-pointer" class="level3">
<h3 class="anchored" data-anchor-id="head-the-singleton-pointer">2.2 HEAD: The Singleton Pointer</h3>
<p><strong>HEAD</strong> is a special reference that identifies where you currently are. Unlike branches, there is only ever one HEAD - it is a singleton. It lives at <code>.git/HEAD</code> and typically contains a <em>symbolic reference</em> to a branch:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb18-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> cat .git/HEAD</span>
<span id="cb18-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ref:</span> refs/heads/main</span></code></pre></div></div>
<p>This tells Git: “the current branch is <code>main</code>.” When you commit, Git follows the chain: HEAD → <code>refs/heads/main</code> → update that file with the new commit hash.</p>
<p>When you check out a specific commit (not a branch), HEAD points directly at that commit hash instead of a branch name. This is a <strong>detached HEAD</strong> state - you are no longer on any branch, and new commits will not be reachable from any branch unless you explicitly create one.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Detached HEAD
</div>
</div>
<div class="callout-body-container callout-body">
<p>In detached HEAD state, any commits you make will become unreachable (and eventually garbage-collected) once you switch to a branch - unless you create a branch pointing to them first. If you find yourself in detached HEAD, Git tells you: simply run <code>git switch -c new-branch-name</code> to save your work.</p>
</div>
</div>
</section>
<section id="tags-immutable-bookmarks" class="level3">
<h3 class="anchored" data-anchor-id="tags-immutable-bookmarks">2.3 Tags: Immutable Bookmarks</h3>
<p><strong>Tags</strong> are references stored in <code>.git/refs/tags/</code>. Like branches, they point to commits - but unlike branches, they are meant to be <strong>immutable</strong>. A tag marks a fixed point in history (typically a release: <code>v1.0</code>, <code>v2.3.1</code>).</p>
<p>Git supports two kinds of tags:</p>
<ul>
<li><strong>Lightweight tags</strong> are simple files containing a commit hash - identical in structure to a branch, just never updated.</li>
<li><strong>Annotated tags</strong> are full Git objects stored in <code>.git/objects/</code>. They contain the tagger’s name and email, a timestamp, a message, and optionally a GPG signature. The tag file in <code>refs/tags/</code> then points to this tag object, which in turn points to the commit. Annotated tags are recommended because they preserve who tagged, when, and why.</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb19-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Lightweight: just a pointer</span></span>
<span id="cb19-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git tag v0.1</span>
<span id="cb19-3"></span>
<span id="cb19-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Annotated: a full object with metadata</span></span>
<span id="cb19-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git tag <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-a</span> v1.0 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"First stable release"</span></span>
<span id="cb19-6"></span>
<span id="cb19-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Tag a historical commit</span></span>
<span id="cb19-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git tag <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-a</span> v0.5 ca21323</span></code></pre></div></div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Tags Are Not Pushed by Default
</div>
</div>
<div class="callout-body-container callout-body">
<p>Running <code>git push</code> does not transfer tags to the remote. You must explicitly push them: <code>git push origin v1.0</code> (one tag) or <code>git push origin --tags</code> (all tags). This is a deliberate safety measure - tags are meant to be curated, not automatically propagated.</p>
</div>
</div>
</section>
<section id="the-ref-hierarchy" class="level3">
<h3 class="anchored" data-anchor-id="the-ref-hierarchy">2.4 The Ref Hierarchy</h3>
<p>All references live under <code>.git/refs/</code> in a clean hierarchy:</p>
<pre><code>.git/refs/
├── heads/          # Local branches
│   ├── main
│   └── feature-x
├── tags/           # Tags
│   ├── v1.0
│   └── v2.0
└── remotes/        # Remote-tracking branches
    └── origin/
        ├── main
        └── feature-x</code></pre>
<p>The following table summarizes the three reference types:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 21%">
<col style="width: 19%">
<col style="width: 19%">
<col style="width: 21%">
<col style="width: 17%">
</colgroup>
<thead>
<tr class="header">
<th>Reference</th>
<th>Location</th>
<th>Mutable?</th>
<th>Points to</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Branch</strong></td>
<td><code>refs/heads/</code></td>
<td>Yes - updated on every commit</td>
<td>Latest commit on the branch</td>
<td>Track the moving tip of a line of development</td>
</tr>
<tr class="even">
<td><strong>Tag</strong></td>
<td><code>refs/tags/</code></td>
<td>No - fixed once created</td>
<td>A specific commit (or tag object)</td>
<td>Mark releases and milestones</td>
</tr>
<tr class="odd">
<td><strong>HEAD</strong></td>
<td><code>.git/HEAD</code></td>
<td>Yes - changes on checkout</td>
<td>A branch (symbolic) or commit (detached)</td>
<td>Identify where you are right now</td>
</tr>
<tr class="even">
<td><strong>Remote branch</strong></td>
<td><code>refs/remotes/</code></td>
<td>Yes - updated on fetch/pull</td>
<td>Last known commit on the remote</td>
<td>Bookmark the remote’s state</td>
</tr>
</tbody>
</table>
<p>With the object model and reference system in place, we can now see how Git uses them for its most common operation: branching and merging.</p>
</section>
</section>
<section id="branching-and-merging" class="level2">
<h2 class="anchored" data-anchor-id="branching-and-merging">3. Branching and Merging</h2>
<p>Branching is where Git’s lightweight object model pays off. Because a branch is just a 41-byte file, creating one is instantaneous. Because commits share unchanged objects, branches consume almost no additional storage. This makes it practical to create a branch for every feature, every bug fix, every experiment - and to merge or discard them freely.</p>
<section id="creating-and-switching-branches" class="level3">
<h3 class="anchored" data-anchor-id="creating-and-switching-branches">3.1 Creating and Switching Branches</h3>
<p>Creating a branch copies the current commit hash into a new file:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb21-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Create a new branch (does not switch to it)</span></span>
<span id="cb21-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git branch feature-login</span>
<span id="cb21-3"></span>
<span id="cb21-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Create and switch in one step (modern syntax)</span></span>
<span id="cb21-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git switch <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> feature-login</span>
<span id="cb21-6"></span>
<span id="cb21-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Legacy equivalent (still works, but overloaded)</span></span>
<span id="cb21-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git checkout <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-b</span> feature-login</span></code></pre></div></div>
<p>When you switch branches, Git does two things: (1) updates HEAD to point at the new branch, and (2) rewrites your working directory to match that branch’s latest commit. Files are added, removed, and modified automatically.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span><code>git switch</code> vs <code>git checkout</code>
</div>
</div>
<div class="callout-body-container callout-body">
<p>The <code>switch</code> and <code>restore</code> commands were introduced in Git 2.23 (2019) to replace the overloaded <code>checkout</code> command, which handled both branch switching and file restoration. The modern equivalents: <code>git switch</code> for branches, <code>git restore</code> for files. Both <code>checkout</code> forms still work, but <code>switch</code>/<code>restore</code> are clearer and safer - <code>switch</code> refuses to overwrite uncommitted changes by default.</p>
</div>
</div>
</section>
<section id="fast-forward-merges" class="level3">
<h3 class="anchored" data-anchor-id="fast-forward-merges">3.2 Fast-Forward Merges</h3>
<p>The simplest merge scenario: you created a branch, made commits, and <em>nobody else committed to the base branch</em> in the meantime. The base branch’s tip is a direct ancestor of your branch’s tip - the history is linear.</p>
<p>In this case, Git does a <strong>fast-forward merge</strong>: it simply moves the base branch pointer forward to your branch’s tip. No new commit is created, no objects are created, no merge logic runs. It is literally updating a 41-byte file.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb22-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git switch main</span>
<span id="cb22-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git merge feature-login <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ff-only</span></span></code></pre></div></div>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    A["A"] --&gt; B["B"] --&gt; C["C&lt;br/&gt;&lt;small&gt;main (before)&lt;/small&gt;"] --&gt; D["D"] --&gt; E["E&lt;br/&gt;&lt;small&gt;feature-login&lt;/small&gt;&lt;br/&gt;&lt;small&gt;main (after)&lt;/small&gt;"]

    style A fill:#7eb8da,stroke:#333,color:#000
    style B fill:#7eb8da,stroke:#333,color:#000
    style C fill:#7eb8da,stroke:#333,color:#000
    style D fill:#90c695,stroke:#333,color:#000
    style E fill:#90c695,stroke:#333,color:#000
</pre>
</div>
<p></p><figcaption> <strong>Figure 2:</strong> Fast-forward merge. Main’s pointer simply advances to the feature branch’s tip. No merge commit is created.</figcaption> </figure><p></p>
</div>
</div>
</div>
<p>The <code>--ff-only</code> flag tells Git: “only merge if a fast-forward is possible; otherwise, abort.” This is a good default for teams that value linear history.</p>
</section>
<section id="three-way-merges" class="level3">
<h3 class="anchored" data-anchor-id="three-way-merges">3.3 Three-Way Merges</h3>
<p>When both branches have diverged - the base branch has commits that your feature branch does not, and vice versa - Git cannot fast-forward. Instead, it performs a <strong>three-way merge</strong> using three reference points:</p>
<ol type="1">
<li>The <strong>common ancestor</strong> - the most recent commit reachable from both branches</li>
<li>The <strong>tip of the base branch</strong> (e.g., <code>main</code>)</li>
<li>The <strong>tip of the feature branch</strong></li>
</ol>
<p>Git compares each branch’s tip against the common ancestor to determine what changed on each side. If changes do not overlap (different files, or different regions of the same file), Git combines them automatically and creates a <strong>merge commit</strong> - a commit with <em>two parents</em> - that ties the histories together.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb23-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git switch main</span>
<span id="cb23-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git merge feature-login</span></code></pre></div></div>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    A["A"] --&gt; B["B"]
    B --&gt; C["C"] --&gt; D["D&lt;br/&gt;&lt;small&gt;main&lt;/small&gt;"]
    B --&gt; E["E"] --&gt; F["F&lt;br/&gt;&lt;small&gt;feature&lt;/small&gt;"]
    D --&gt; M["M&lt;br/&gt;&lt;small&gt;merge commit&lt;/small&gt;"]
    F --&gt; M

    style B fill:#f9d71c,stroke:#333,color:#000
    style D fill:#7eb8da,stroke:#333,color:#000
    style F fill:#90c695,stroke:#333,color:#000
    style M fill:#e8a87c,stroke:#333,color:#000
</pre>
</div>
<p></p><figcaption> <strong>Figure 3:</strong> Three-way merge. Git finds the common ancestor (B), compares both tips against it, combines the changes, and creates a merge commit (M) with two parents.</figcaption> </figure><p></p>
</div>
</div>
</div>
</section>
<section id="merge-conflicts" class="level3">
<h3 class="anchored" data-anchor-id="merge-conflicts">3.4 Merge Conflicts</h3>
<p>When both branches modify the <em>same lines</em> of the <em>same file</em>, Git cannot determine which version is correct. It pauses the merge, marks the conflicting regions in the file with conflict markers (<code>&lt;&lt;&lt;&lt;&lt;&lt;&lt;</code>, <code>=======</code>, <code>&gt;&gt;&gt;&gt;&gt;&gt;&gt;</code>), and asks you to resolve them manually.</p>
<p>The resolution workflow:</p>
<ol type="1">
<li>Open the conflicting file(s) and edit the conflict markers to produce the desired result</li>
<li>Stage the resolved files: <code>git add &lt;file&gt;</code></li>
<li>Complete the merge: <code>git commit</code> (this creates the merge commit)</li>
</ol>
<p>Alternatively, abort the merge entirely: <code>git merge --abort</code> restores the state before the merge began.</p>
<p>For complex conflicts, visual merge tools like <code>vimdiff</code>, VS Code’s built-in merge editor, or dedicated tools like <code>meld</code> can help. Configure your preferred tool with <code>git mergetool</code>.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Merge Strategies
</div>
</div>
<div class="callout-body-container callout-body">
<p>Git supports several merge strategies beyond the default <code>ort</code> (formerly <code>recursive</code>). The most useful alternatives: <code>ours</code> (keep our version entirely, discarding their changes - useful for marking a branch as “merged” without taking its content) and <code>octopus</code> (for merging more than two branches at once). To resolve conflicts in favor of the other side, use the strategy <em>option</em> <code>-X theirs</code> (note: this is an option to the default <code>ort</code> strategy, not a standalone strategy). For example: <code>git merge -X theirs feature-branch</code>.</p>
</div>
</div>
<p>Branching and merging work locally. The next section extends these ideas to collaboration across repositories - remotes, tracking branches, and the fetch/push protocol.</p>
</section>
</section>
<section id="remote-branches-and-collaboration" class="level2">
<h2 class="anchored" data-anchor-id="remote-branches-and-collaboration">4. Remote Branches and Collaboration</h2>
<p>Git is <em>distributed</em> - every clone is a complete, independent repository with its own history, branches, and object store. Collaboration happens by synchronizing objects and references between repositories. The machinery for this is remotes, remote-tracking branches, and the fetch/push protocol.</p>
<section id="the-remote-model" class="level3">
<h3 class="anchored" data-anchor-id="the-remote-model">4.1 The Remote Model</h3>
<p>A <strong>remote</strong> is a named URL pointing to another Git repository. When you clone a repo, Git automatically creates a remote called <code>origin</code> pointing to the source URL. You can have multiple remotes - for example, <code>origin</code> for your fork and <code>upstream</code> for the original project.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb24-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># List remotes</span></span>
<span id="cb24-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git remote <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-v</span></span>
<span id="cb24-3"></span>
<span id="cb24-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add a second remote</span></span>
<span id="cb24-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git remote add upstream https://github.com/original/repo.git</span></code></pre></div></div>
<p><strong>Remote-tracking branches</strong> live under <code>.git/refs/remotes/&lt;remote&gt;/</code> and act as read-only bookmarks of where each branch was on the remote the last time you communicated with it. You never update them directly with <code>git commit</code> - Git manages them automatically during <code>fetch</code> and <code>push</code>.</p>
</section>
<section id="tracking-branches" class="level3">
<h3 class="anchored" data-anchor-id="tracking-branches">4.2 Tracking Branches</h3>
<p>A <strong>tracking branch</strong> (or “upstream branch”) is a local branch that is linked to a remote-tracking branch. This link tells <code>git pull</code> where to fetch from and <code>git push</code> where to push to, without specifying the remote and branch name every time.</p>
<p>Tracking is set up automatically in several cases:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb25-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Cloning: 'main' automatically tracks 'origin/main'</span></span>
<span id="cb25-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git clone https://github.com/user/repo.git</span>
<span id="cb25-3"></span>
<span id="cb25-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Switching to a remote branch name creates a tracking branch</span></span>
<span id="cb25-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git switch feature-x  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># creates local 'feature-x' tracking 'origin/feature-x'</span></span>
<span id="cb25-6"></span>
<span id="cb25-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Explicit tracking setup</span></span>
<span id="cb25-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git branch <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--set-upstream-to</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>origin/feature-x</span>
<span id="cb25-9"></span>
<span id="cb25-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Push and set upstream in one step</span></span>
<span id="cb25-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git push <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-u</span> origin feature-x</span></code></pre></div></div>
<p>You can also track branches from different remotes, or use different local and remote names:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb26-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Track a branch from a different remote</span></span>
<span id="cb26-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git switch <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> my-local-name upstream/their-branch</span></code></pre></div></div>
</section>
<section id="cloning-what-actually-happens" class="level3">
<h3 class="anchored" data-anchor-id="cloning-what-actually-happens">4.3 Cloning: What Actually Happens</h3>
<p>When you run <code>git clone https://github.com/user/repo.git</code>, Git performs four steps:</p>
<ol type="1">
<li>Creates a directory named <code>repo/</code> with a <code>.git/</code> subdirectory</li>
<li>Downloads the <em>entire</em> object store - every blob, tree, and commit in the history</li>
<li>Creates remote-tracking branches under <code>refs/remotes/origin/</code> for every branch on the remote</li>
<li>Creates a local <code>main</code> branch tracking <code>origin/main</code> and checks it out</li>
</ol>
<p>The critical implication: cloning downloads <strong>all versions of every file ever committed</strong>. If someone committed a 500 MB binary three years ago and then deleted it, that blob is still in the history and gets cloned. For repositories with very long histories, <code>git clone --depth N</code> creates a <strong>shallow clone</strong> with only the last N commits.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Shallow Clones and CI
</div>
</div>
<div class="callout-body-container callout-body">
<p>Shallow clones (<code>--depth 1</code>) are common in CI/CD pipelines where you only need the latest code to build and test. They are dramatically faster to clone but cannot perform operations that require full history (like <code>git log</code> across all time or <code>git bisect</code> to the beginning). Use <code>git fetch --unshallow</code> to convert a shallow clone to a full one when needed.</p>
</div>
</div>
</section>
<section id="pushing-and-pulling" class="level3">
<h3 class="anchored" data-anchor-id="pushing-and-pulling">4.4 Pushing and Pulling</h3>
<p>The three synchronization commands serve distinct purposes:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 21%">
<col style="width: 26%">
<col style="width: 30%">
<col style="width: 21%">
</colgroup>
<thead>
<tr class="header">
<th>Command</th>
<th>Direction</th>
<th>What it does</th>
<th>Merges?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>git fetch</code></td>
<td>Remote → Local</td>
<td>Downloads new objects and updates remote-tracking branches</td>
<td>No</td>
</tr>
<tr class="even">
<td><code>git pull</code></td>
<td>Remote → Local</td>
<td>Runs <code>fetch</code>, then merges (or rebases) the tracking branch</td>
<td>Yes</td>
</tr>
<tr class="odd">
<td><code>git push</code></td>
<td>Local → Remote</td>
<td>Uploads new objects and updates remote branch pointers</td>
<td>No</td>
</tr>
</tbody>
</table>
<p><strong>Fetch</strong> is always safe - it only downloads data and updates bookmarks. It never touches your working directory or local branches.</p>
<p><strong>Pull</strong> is <code>fetch</code> + <code>merge</code> (or <code>fetch</code> + <code>rebase</code> if configured). Because it merges, it can create merge commits or conflicts.</p>
<p><strong>Push</strong> uploads your commits and asks the remote to update its branch pointer. If the remote has commits you do not have (someone else pushed first), the push is rejected - you must pull and integrate their changes first.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb27-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fetch all branches from origin</span></span>
<span id="cb27-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git fetch origin</span>
<span id="cb27-3"></span>
<span id="cb27-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pull (fetch + merge) the current tracking branch</span></span>
<span id="cb27-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git pull</span>
<span id="cb27-6"></span>
<span id="cb27-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Push the current branch to its upstream</span></span>
<span id="cb27-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git push</span>
<span id="cb27-9"></span>
<span id="cb27-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Push with a different remote branch name</span></span>
<span id="cb27-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git push origin local-branch:remote-branch</span>
<span id="cb27-12"></span>
<span id="cb27-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Delete a remote branch</span></span>
<span id="cb27-14"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git push origin <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--delete</span> old-branch</span></code></pre></div></div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Configure fetch.prune
</div>
</div>
<div class="callout-body-container callout-body">
<p>By default, remote-tracking branches for deleted remote branches linger forever in your local repo. Set <code>git config --global fetch.prune true</code> to automatically clean them up on every fetch or pull.</p>
</div>
</div>
<p>With local and remote operations covered, the next step is learning to navigate the history that these operations produce.</p>
</section>
</section>
<section id="inspecting-and-searching-history" class="level2">
<h2 class="anchored" data-anchor-id="inspecting-and-searching-history">5. Inspecting and Searching History</h2>
<p>Git’s immutable, content-addressable history is not just a safety net - it is a powerful investigative tool. Every commit, every line change, every contributor is recorded and searchable. This section covers the tools for navigating that history.</p>
<section id="git-log-viewing-history" class="level3">
<h3 class="anchored" data-anchor-id="git-log-viewing-history">5.1 git log: Viewing History</h3>
<p><code>git log</code> is the primary tool for browsing commit history. Its power comes from filtering and formatting options:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb28-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Visual overview: graph, one line per commit, all branches</span></span>
<span id="cb28-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git log <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--all</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--decorate</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--graph</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--oneline</span></span>
<span id="cb28-3"></span>
<span id="cb28-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Last 5 commits</span></span>
<span id="cb28-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git log <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-5</span></span>
<span id="cb28-6"></span>
<span id="cb28-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Custom format</span></span>
<span id="cb28-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git log <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--pretty</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>format:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'%C(yellow)%h%C(reset) - %an [%C(green)%ar%C(reset)] %s'</span></span>
<span id="cb28-9"></span>
<span id="cb28-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Commits affecting a specific file</span></span>
<span id="cb28-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git log <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--oneline</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--</span> path/to/file.py</span>
<span id="cb28-12"></span>
<span id="cb28-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Search commit messages (extended regex, case-insensitive)</span></span>
<span id="cb28-14"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git log <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-E</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-i</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--grep</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'fix.*login'</span></span></code></pre></div></div>
<p>Two especially powerful search modes:</p>
<ul>
<li><strong><code>git log -S "term"</code></strong> (the “pickaxe”): finds commits that <em>changed</em> the number of occurrences of a literal string. If a function was added or removed, this finds the commit.</li>
<li><strong><code>git log -G "regex"</code></strong>: like <code>-S</code> but matches a regex pattern against the diff, finding commits where the patch itself matches.</li>
</ul>
<p><code>git show</code> displays a single commit’s metadata and diff in one view - the quickest way to understand what a commit did:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb29-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Show the latest commit's diff</span></span>
<span id="cb29-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git show</span>
<span id="cb29-3"></span>
<span id="cb29-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Show a specific commit</span></span>
<span id="cb29-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git show a1b2c3d</span></code></pre></div></div>
</section>
<section id="git-blame-per-line-attribution" class="level3">
<h3 class="anchored" data-anchor-id="git-blame-per-line-attribution">5.2 git blame: Per-Line Attribution</h3>
<p><code>git blame</code> annotates each line of a file with the commit that last modified it, who did it, and when. It is indispensable for understanding <em>why</em> code looks the way it does.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb30" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb30-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Full blame</span></span>
<span id="cb30-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git blame path/to/file.py</span>
<span id="cb30-3"></span>
<span id="cb30-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Restrict to a line range</span></span>
<span id="cb30-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git blame <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-L</span> 50,75 path/to/file.py</span>
<span id="cb30-6"></span>
<span id="cb30-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Detect code moved or copied from other files</span></span>
<span id="cb30-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git blame <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-C</span> path/to/file.py</span></code></pre></div></div>
<p>The <code>-C</code> flag is particularly powerful: if a block of code was copied from another file in the same commit, <code>blame -C</code> traces through the copy and attributes the lines to their true origin - not the commit that moved them.</p>
</section>
<section id="git-grep-searching-across-time" class="level3">
<h3 class="anchored" data-anchor-id="git-grep-searching-across-time">5.3 git grep: Searching Across Time</h3>
<p><code>git grep</code> searches file contents within Git’s tracked universe. Unlike standalone tools like <code>grep</code> or <code>ripgrep</code>, it can search any committed tree - not just the current working directory. This makes it invaluable for answering questions like “when did we last use this deprecated API?” or “does this pattern exist in the v1.0 release?”</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb31-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Search current working directory with context</span></span>
<span id="cb31-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git grep <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-n</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-p</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--break</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--heading</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pattern"</span></span>
<span id="cb31-3"></span>
<span id="cb31-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Search a specific commit or tag</span></span>
<span id="cb31-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git grep <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"deprecated_function"</span> v1.0</span>
<span id="cb31-6"></span>
<span id="cb31-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Search across all branches</span></span>
<span id="cb31-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git grep <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"TODO"</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$(</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> branch <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-r</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
<p>The <code>-p</code> flag shows the function/method name containing each match - far more useful than bare line numbers when scanning results. Combined with <code>--break</code> and <code>--heading</code>, the output is grouped by file with clear visual separation.</p>
</section>
<section id="commit-ranges-..-and" class="level3">
<h3 class="anchored" data-anchor-id="commit-ranges-..-and">5.4 Commit Ranges: .., …, and ^</h3>
<p>When inspecting history, you often need to specify <em>ranges</em> of commits - for example, “what is on my branch that is not on main?” Git provides several notations for this:</p>
<p><strong>Parent references:</strong></p>
<ul>
<li><code>HEAD^</code> - the parent of HEAD. For merge commits with multiple parents, <code>HEAD^1</code> is the first parent (the branch you merged <em>into</em>), <code>HEAD^2</code> is the second parent (the branch you merged <em>from</em>).</li>
<li><code>HEAD~N</code> - the Nth ancestor following first-parent links. <code>HEAD~3</code> means “go back 3 commits along the first-parent chain.” Equivalent to <code>HEAD^^^</code>.</li>
</ul>
<p><strong>Range operators:</strong></p>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 23%">
<col style="width: 30%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Syntax</th>
<th>Meaning</th>
<th>Equivalent</th>
<th>Use case</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>A..B</code></td>
<td>Commits reachable from B but not A</td>
<td><code>B ^A</code></td>
<td>“What’s new on B since it diverged from A?”</td>
</tr>
<tr class="even">
<td><code>A...B</code></td>
<td>Commits reachable from A <em>or</em> B but not <em>both</em></td>
<td>-</td>
<td>“What’s unique to each branch?” (symmetric difference)</td>
</tr>
<tr class="odd">
<td><code>A B ^C</code></td>
<td>Reachable from A or B but not C</td>
<td>-</td>
<td>Multi-point exclusion</td>
</tr>
</tbody>
</table>
<p>The most common use: <code>git log main..feature</code> shows the commits on your feature branch that are not yet on main - exactly what a pull request would contain.</p>
<p>These operators work with <code>git diff</code> too - not just <code>git log</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb32" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb32-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Diff between two commits</span></span>
<span id="cb32-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git diff HEAD~3..HEAD</span>
<span id="cb32-3"></span>
<span id="cb32-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Diff between branches (what would the PR contain?)</span></span>
<span id="cb32-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git diff main..feature</span>
<span id="cb32-6"></span>
<span id="cb32-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Diff with a specific file</span></span>
<span id="cb32-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git diff main..feature <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--</span> path/to/file.py</span></code></pre></div></div>
</section>
<section id="git-bisect-binary-search-for-bugs" class="level3">
<h3 class="anchored" data-anchor-id="git-bisect-binary-search-for-bugs">5.5 git bisect: Binary Search for Bugs</h3>
<p>When a bug exists in the current commit but not in a commit from weeks ago, somewhere in between is the commit that introduced it. Searching linearly through hundreds of commits is impractical. <code>git bisect</code> performs a <strong>binary search</strong>, cutting the search space in half at each step.</p>
<p><strong>Manual workflow:</strong></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb33" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb33-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git bisect start</span>
<span id="cb33-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git bisect bad                  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># current commit has the bug</span></span>
<span id="cb33-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git bisect good v1.0            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># this tag was known to be good</span></span>
<span id="cb33-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Git checks out the midpoint commit</span></span>
<span id="cb33-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># You test it, then tell Git:</span></span>
<span id="cb33-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git bisect good                 <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># or 'git bisect bad'</span></span>
<span id="cb33-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Repeat until Git identifies the first bad commit</span></span>
<span id="cb33-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git bisect reset                <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># return to where you started</span></span></code></pre></div></div>
<p><strong>Automated workflow</strong> - the real power of bisect:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb34" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb34-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># git bisect start &lt;bad-commit&gt; &lt;good-commit&gt;</span></span>
<span id="cb34-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git bisect start HEAD v1.0</span>
<span id="cb34-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git bisect run pytest tests/test_login.py</span></code></pre></div></div>
<p>Git checks out each midpoint commit and runs your test script. Exit code 0 means “good,” non-zero means “bad.” Git narrows the range automatically until it finds the exact commit that introduced the failure. For a history of 1000 commits, this takes at most ~10 steps.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Bisect Is Logarithmic
</div>
</div>
<div class="callout-body-container callout-body">
<p>Binary search through N commits takes at most <img src="https://latex.codecogs.com/png.latex?%5Clceil%20%5Clog_2%20N%20%5Crceil"> steps. For 1024 commits, that is 10 steps. Combined with an automated test script, <code>git bisect run</code> can pinpoint a regression in seconds - even across months of history. It is one of Git’s most underused yet powerful features.</p>
</div>
</div>
<p>Inspecting history is read-only - it does not change anything. The next section covers the tools that <em>do</em> change history: amending, rebasing, reverting, and resetting.</p>
</section>
</section>
<section id="rewriting-history" class="level2">
<h2 class="anchored" data-anchor-id="rewriting-history">6. Rewriting History</h2>
<p>Git’s immutable object model means that “rewriting history” is slightly misleading - you never change existing commits. Instead, you create <em>new</em> commits with different content or parentage, and move branch pointers to the new chain. The old commits still exist in the object store (and are visible via <code>reflog</code>) until garbage collection removes them.</p>
<p>This distinction matters: it means history rewriting is always recoverable, at least until <code>git gc</code> runs. The <code>reflog</code> is your safety net.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Commits Are Immutable
</div>
</div>
<div class="callout-body-container callout-body">
<p>Everything committed in Git can almost always be recovered. Even commits on deleted branches or overwritten with <code>--amend</code> are still in the object store and visible via <code>git reflog</code>. The only data that is truly unrecoverable is <em>uncommitted</em> work - changes in your working directory or staging area that were never committed. This is why frequent, small commits are the safest workflow.</p>
</div>
</div>
<section id="amending-commits" class="level3">
<h3 class="anchored" data-anchor-id="amending-commits">6.1 Amending Commits</h3>
<p>The simplest form of history rewriting: fixing the most recent commit. This is useful when you forgot to stage a file, made a typo in the message, or want to add a small correction that belongs with the last commit.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb35" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb35-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Change the commit message</span></span>
<span id="cb35-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git commit <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--amend</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Better message"</span></span>
<span id="cb35-3"></span>
<span id="cb35-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add forgotten files to the last commit (keep the same message)</span></span>
<span id="cb35-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git add forgotten-file.py</span>
<span id="cb35-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git commit <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--amend</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--no-edit</span></span></code></pre></div></div>
<p>Under the hood, <code>--amend</code> creates an entirely new commit object (new hash) with the same parent as the original. The original commit still exists but is no longer reachable from any branch.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Aborting a Commit
</div>
</div>
<div class="callout-body-container callout-body">
<p>If you are writing a commit message in your editor and decide to cancel, exit with a non-zero status. In Vim: <code>:cq</code> (quit with error). Git receives the error and aborts the commit.</p>
</div>
</div>
</section>
<section id="rebase" class="level3">
<h3 class="anchored" data-anchor-id="rebase">6.2 Rebase</h3>
<p>Where merging <em>joins</em> two histories with a merge commit, <strong>rebasing</strong> <em>replays</em> one history on top of another - producing a linear sequence with no merge commit. This is the mechanism that makes fast-forward merges possible after histories have diverged.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb36" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb36-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git switch feature-branch</span>
<span id="cb36-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git rebase main</span></code></pre></div></div>
<p>Here is what Git does under the hood:</p>
<ol type="1">
<li>Finds the common ancestor of <code>feature-branch</code> and <code>main</code></li>
<li>Collects the commits unique to <code>feature-branch</code> (from the ancestor to the tip)</li>
<li>Saves the diffs introduced by each of those commits</li>
<li>Moves to the tip of <code>main</code></li>
<li>Replays each diff as a <em>new</em> commit (new hash, same message) on top of <code>main</code></li>
<li>Updates <code>feature-branch</code> to point at the newest replayed commit</li>
</ol>
<p>The result: your feature branch’s commits now sit directly ahead of <code>main</code>, as if you had started your work from <code>main</code>’s current tip. A fast-forward merge is now possible.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    A["A"] --&gt; B["B"] --&gt; C["C&lt;br/&gt;&lt;small&gt;main&lt;/small&gt;"]
    C --&gt; D'["D'"] --&gt; E'["E'&lt;br/&gt;&lt;small&gt;feature (rebased)&lt;/small&gt;"]

    style A fill:#7eb8da,stroke:#333,color:#000
    style B fill:#7eb8da,stroke:#333,color:#000
    style C fill:#7eb8da,stroke:#333,color:#000
    style D' fill:#90c695,stroke:#333,color:#000
    style E' fill:#90c695,stroke:#333,color:#000
</pre>
</div>
<p></p><figcaption> <strong>Figure 5:</strong> Rebase replays feature commits on top of main. The original commits (D, E) are replaced by new commits (D’, E’) with the same diffs but new hashes and a new base.</figcaption> </figure><p></p>
</div>
</div>
</div>
<p>If a conflict arises during replay, Git pauses and lets you resolve it for that specific commit, then continue with <code>git rebase --continue</code>. To abort entirely: <code>git rebase --abort</code>.</p>
</section>
<section id="interactive-rebase" class="level3">
<h3 class="anchored" data-anchor-id="interactive-rebase">6.3 Interactive Rebase</h3>
<p>Interactive rebase (<code>git rebase -i</code>) is the most powerful tool for crafting clean history. It presents a list of commits and lets you reorder, squash, edit, split, or drop any of them.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb37" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb37-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Rebase the last 5 commits interactively</span></span>
<span id="cb37-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git rebase <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-i</span> HEAD~5</span>
<span id="cb37-3"></span>
<span id="cb37-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Rebase everything on the feature branch since it diverged from main</span></span>
<span id="cb37-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git rebase <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-i</span> main</span></code></pre></div></div>
<p>Git opens an editor with one line per commit (oldest first):</p>
<pre><code>pick a1b2c3d Add login form
pick e4f5a6b Fix typo in login
pick 7c8d9e0 WIP: debugging
pick 1f2a3b4 Finalize login validation</code></pre>
<p>Commands you can use:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Command</th>
<th>Effect</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>pick</code></td>
<td>Keep the commit as-is</td>
</tr>
<tr class="even">
<td><code>reword</code></td>
<td>Keep the commit but edit its message</td>
</tr>
<tr class="odd">
<td><code>squash</code></td>
<td>Meld into the previous commit, combining messages</td>
</tr>
<tr class="even">
<td><code>fixup</code></td>
<td>Meld into the previous commit, discarding this message</td>
</tr>
<tr class="odd">
<td><code>edit</code></td>
<td>Pause after applying, letting you amend or split the commit</td>
</tr>
<tr class="even">
<td><code>drop</code></td>
<td>Remove the commit entirely</td>
</tr>
<tr class="odd">
<td><em>(reorder lines)</em></td>
<td>Change the order commits are applied</td>
</tr>
</tbody>
</table>
<p>A typical pre-merge cleanup: squash the “Fix typo” and “WIP” commits into their parent, reword the final message to be descriptive, and produce a clean, logical history.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Never Rewrite Published History
</div>
</div>
<div class="callout-body-container callout-body">
<p>Interactive rebase creates new commits with new hashes. If the original commits have been pushed to a shared branch (especially <code>main</code>), rewriting them forces everyone else to reconcile their divergent history - a painful and error-prone process. Only rebase commits that exist on your local feature branch and have not been shared. Once you merge to <code>main</code>, the history is permanent.</p>
</div>
</div>
</section>
<section id="cherry-pick" class="level3">
<h3 class="anchored" data-anchor-id="cherry-pick">6.4 Cherry-Pick</h3>
<p><code>git cherry-pick</code> takes one or more commits from anywhere in the history and replays them on the current branch, creating new commits with the same diffs but different hashes and parents.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb39" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb39-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pick a single commit</span></span>
<span id="cb39-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git cherry-pick a1b2c3d</span>
<span id="cb39-3"></span>
<span id="cb39-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Pick a range of commits</span></span>
<span id="cb39-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git cherry-pick main~3..main</span></code></pre></div></div>
<p>Common use case: you accidentally committed to the wrong branch. Cherry-pick the commits onto the correct branch, then reset the original branch to remove them.</p>
</section>
<section id="squashing-commits" class="level3">
<h3 class="anchored" data-anchor-id="squashing-commits">6.5 Squashing Commits</h3>
<p>Beyond interactive rebase, there is a quick way to squash the last N commits:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb40" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb40-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Squash the last 3 commits into one (keep changes staged)</span></span>
<span id="cb40-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git reset <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--soft</span> HEAD~3</span>
<span id="cb40-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git commit <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Implement login feature"</span></span></code></pre></div></div>
<p>This works because <code>--soft</code> moves the branch pointer back 3 commits but leaves the index and working directory untouched. All the changes from those 3 commits are now staged, ready for a single new commit.</p>
</section>
<section id="reverting-published-commits" class="level3">
<h3 class="anchored" data-anchor-id="reverting-published-commits">6.6 Reverting Published Commits</h3>
<p>All the history-rewriting tools above - amend, rebase, reset - create new commits and move branch pointers. This is fine on local feature branches, but what if a bad commit is already on <code>main</code> and shared with the team? You cannot rewrite published history without forcing everyone to reconcile.</p>
<p><strong><code>git revert</code></strong> solves this by creating a <em>new</em> commit that exactly undoes the changes from a previous commit. The original commit stays in the history - nothing is rewritten - but its effects are cancelled.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb41" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb41-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Revert the most recent commit</span></span>
<span id="cb41-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git revert HEAD</span>
<span id="cb41-3"></span>
<span id="cb41-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Revert a specific commit</span></span>
<span id="cb41-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git revert a1b2c3d</span>
<span id="cb41-6"></span>
<span id="cb41-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Revert without auto-committing (stage the inverse, let me inspect first)</span></span>
<span id="cb41-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git revert <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--no-commit</span> a1b2c3d</span></code></pre></div></div>
<p>Under the hood, <code>revert</code> computes the inverse diff of the target commit and applies it as a new commit. If the inverse conflicts with subsequent changes, Git pauses for conflict resolution - just like a merge.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Revert vs Reset
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>git reset</code> moves a branch pointer backward - it removes commits from the branch’s history. This is destructive to shared history. <code>git revert</code> moves <em>forward</em> - it adds a new commit that undoes an old one. Use <code>reset</code> on local/unpublished branches; use <code>revert</code> on shared/published branches. This is the safe complement to the “never rewrite published history” rule.</p>
</div>
</div>
</section>
<section id="git-reset-the-three-level-undo" class="level3">
<h3 class="anchored" data-anchor-id="git-reset-the-three-level-undo">6.7 git reset: The Three-Level Undo</h3>
<p><code>git reset</code> is the Swiss army knife of undoing changes. It operates on up to three levels, controlled by its flags:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 8%">
<col style="width: 28%">
<col style="width: 30%">
<col style="width: 32%">
</colgroup>
<thead>
<tr class="header">
<th>Flag</th>
<th>Moves branch pointer</th>
<th>Resets index (staging)</th>
<th>Resets working directory</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>--soft</code></td>
<td>Yes</td>
<td>No</td>
<td>No</td>
</tr>
<tr class="even">
<td><code>--mixed</code> (default)</td>
<td>Yes</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr class="odd">
<td><code>--hard</code></td>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
</tr>
</tbody>
</table>
<p>Think of it as three successive stages:</p>
<ol type="1">
<li><strong><code>--soft</code></strong>: move the branch pointer to the target commit. The index and working directory still reflect the old commit. All “removed” commits’ changes appear as staged. <em>Use case: squash commits.</em></li>
<li><strong><code>--mixed</code></strong> (default): move the branch pointer <em>and</em> reset the index to match. Changes appear as unstaged modifications. <em>Use case: unstage files.</em></li>
<li><strong><code>--hard</code></strong>: move everything - branch pointer, index, and working directory - to match the target commit. Uncommitted changes are <strong>permanently lost</strong>. <em>Use case: discard everything and start clean.</em></li>
</ol>
<p><strong>Path-specific reset:</strong></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb42" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb42-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Unstage a file (shorthand for git reset --mixed HEAD file)</span></span>
<span id="cb42-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git reset file.py</span>
<span id="cb42-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Modern equivalent:</span></span>
<span id="cb42-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git restore <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--staged</span> file.py</span></code></pre></div></div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>reset –hard Destroys Uncommitted Work
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>git reset --hard</code> is the only common Git command that can cause permanent data loss. It overwrites your working directory and staging area. If you had uncommitted changes, they are gone - <code>reflog</code> cannot help because those changes were never committed. Use with extreme care, and commit or stash your work first.</p>
</div>
</div>
<p><strong>checkout vs reset:</strong></p>
<p>These two commands appear similar but differ in a critical way: <code>git reset</code> moves what the branch <em>points to</em> (the branch itself advances or retreats). <code>git checkout</code> (or <code>git switch</code>) moves <em>HEAD</em> - it changes which branch you are on without moving any branch pointer. Additionally, <code>checkout</code> is working-directory-safe (it does a trivial merge and refuses to overwrite uncommitted changes), whereas <code>reset --hard</code> overwrites everything.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    subgraph soft ["--soft"]
        direction TB
        S1["Branch pointer ✓"] --&gt; S2["Index ✗"] --&gt; S3["Working dir ✗"]
    end
    subgraph mixed ["--mixed (default)"]
        direction TB
        M1["Branch pointer ✓"] --&gt; M2["Index ✓"] --&gt; M3["Working dir ✗"]
    end
    subgraph hard ["--hard ⚠️"]
        direction TB
        H1["Branch pointer ✓"] --&gt; H2["Index ✓"] --&gt; H3["Working dir ✓"]
    end

    soft ~~~ mixed ~~~ hard

    style S1 fill:#90c695,stroke:#333,color:#000
    style S2 fill:#ddd,stroke:#999,color:#666
    style S3 fill:#ddd,stroke:#999,color:#666
    style M1 fill:#90c695,stroke:#333,color:#000
    style M2 fill:#90c695,stroke:#333,color:#000
    style M3 fill:#ddd,stroke:#999,color:#666
    style H1 fill:#90c695,stroke:#333,color:#000
    style H2 fill:#90c695,stroke:#333,color:#000
    style H3 fill:#d9534f,stroke:#333,color:#fff
</pre>
</div>
<p></p><figcaption> <strong>Figure 6:</strong> The three levels of <code>git reset</code>. <code>--soft</code> moves only the branch pointer. <code>--mixed</code> also resets the index. <code>--hard</code> resets everything - including the working directory, permanently discarding uncommitted changes.</figcaption> </figure><p></p>
</div>
</div>
</div>
</section>
<section id="the-reflog-your-safety-net" class="level3">
<h3 class="anchored" data-anchor-id="the-reflog-your-safety-net">6.8 The Reflog: Your Safety Net</h3>
<p>The <strong>reflog</strong> (reference log) records every time a reference (HEAD, branch, etc.) is updated. It is a chronological diary of everything you have done in the repository - commits, checkouts, rebases, resets, merges - including intermediate states.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb43" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb43-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Show the reflog for HEAD</span></span>
<span id="cb43-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git reflog</span>
<span id="cb43-3"></span>
<span id="cb43-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Show the reflog for a specific branch</span></span>
<span id="cb43-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git reflog show main</span>
<span id="cb43-6"></span>
<span id="cb43-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Recover a "lost" commit after a bad reset</span></span>
<span id="cb43-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git reflog</span>
<span id="cb43-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Find the hash of the commit you want to recover</span></span>
<span id="cb43-10"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git reset <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--hard</span> abc123</span></code></pre></div></div>
<p>The reflog is <strong>local-only</strong> - it is not shared with remotes. It starts empty after a fresh clone. Entries expire after 90 days (reachable commits) or 30 days (unreachable) by default, after which <code>git gc</code> may remove the associated objects.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>When in Doubt, Check the Reflog
</div>
</div>
<div class="callout-body-container callout-body">
<p>If you think you have lost work - a bad rebase, an accidental <code>reset --hard</code>, a deleted branch - check <code>git reflog</code> before panicking. As long as the work was committed at some point, the reflog almost certainly has a reference to it.</p>
</div>
</div>
<p>History rewriting operates on commits that already exist. The next section zooms in on the step <em>before</em> committing - the staging area - and the tools for partial, selective operations.</p>
</section>
</section>
<section id="the-staging-area-and-partial-operations" class="level2">
<h2 class="anchored" data-anchor-id="the-staging-area-and-partial-operations">7. The Staging Area and Partial Operations</h2>
<p>The staging area (index) is one of Git’s most distinctive features - and one of its most misunderstood. Other version control systems commit directly from the working directory. Git inserts an intermediate step: the staging area, where you assemble the exact snapshot you want before committing it.</p>
<section id="the-index-file" class="level3">
<h3 class="anchored" data-anchor-id="the-index-file">7.1 The Index File</h3>
<p>The index is a binary file at <code>.git/index</code> that holds a sorted list of file paths, each with its blob hash, permissions, and timestamps. It represents the <em>proposed next commit</em>. When you run <code>git add</code>, you update the index; when you run <code>git commit</code>, Git builds trees from the index.</p>
<p>This design lets you do something powerful: <strong>commit a subset of your changes</strong>. You might have modified ten files, but only three are ready for this commit. Stage those three, commit, then continue working on the rest.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb44" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb44-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Stage specific files</span></span>
<span id="cb44-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git add file1.py file2.py</span>
<span id="cb44-3"></span>
<span id="cb44-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Stage all changes (tracked files only)</span></span>
<span id="cb44-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git add <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-u</span></span>
<span id="cb44-6"></span>
<span id="cb44-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Stage everything including untracked files</span></span>
<span id="cb44-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git add <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-A</span></span>
<span id="cb44-9"></span>
<span id="cb44-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># See what's staged vs unstaged</span></span>
<span id="cb44-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git diff <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--cached</span>     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># staged changes (index vs last commit)</span></span>
<span id="cb44-12"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git diff              <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># unstaged changes (working dir vs index)</span></span></code></pre></div></div>
</section>
<section id="interactive-staging-git-add-patch" class="level3">
<h3 class="anchored" data-anchor-id="interactive-staging-git-add-patch">7.2 Interactive Staging (git add –patch)</h3>
<p>When you have made multiple unrelated changes to the same file and want to split them into separate commits, <code>git add --patch</code> (or <code>-p</code>) lets you stage individual <em>hunks</em> - contiguous blocks of changes - interactively.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb45" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb45-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git add <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--patch</span> file.py</span></code></pre></div></div>
<p>Git presents each hunk and asks what to do:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Key</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>y</code></td>
<td>Stage this hunk</td>
</tr>
<tr class="even">
<td><code>n</code></td>
<td>Skip this hunk</td>
</tr>
<tr class="odd">
<td><code>s</code></td>
<td>Split into smaller hunks</td>
</tr>
<tr class="even">
<td><code>e</code></td>
<td>Manually edit the hunk</td>
</tr>
<tr class="odd">
<td><code>q</code></td>
<td>Quit (don’t stage remaining hunks)</td>
</tr>
</tbody>
</table>
<p>The same <code>--patch</code> flag works with other commands: <code>git checkout -p</code>, <code>git restore -p</code>, <code>git stash -p</code> - letting you selectively discard, restore, or stash parts of files.</p>
<p>This workflow is essential for maintaining clean, focused commits when you have been doing exploratory work across many areas of the codebase.</p>
</section>
<section id="stashing" class="level3">
<h3 class="anchored" data-anchor-id="stashing">7.3 Stashing</h3>
<p><strong>Stashing</strong> saves your uncommitted changes (both staged and unstaged) onto a stack and restores your working directory to the last commit. It is useful when you need to switch branches for an urgent fix but are not ready to commit your in-progress work.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb46" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb46-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Stash current changes</span></span>
<span id="cb46-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git stash</span>
<span id="cb46-3"></span>
<span id="cb46-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Include untracked files</span></span>
<span id="cb46-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git stash <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-u</span></span>
<span id="cb46-6"></span>
<span id="cb46-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># List all stashes</span></span>
<span id="cb46-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git stash list</span>
<span id="cb46-9"></span>
<span id="cb46-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Apply the most recent stash (keep it on the stack)</span></span>
<span id="cb46-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git stash apply</span>
<span id="cb46-12"></span>
<span id="cb46-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Apply and remove the most recent stash</span></span>
<span id="cb46-14"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git stash pop</span>
<span id="cb46-15"></span>
<span id="cb46-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Apply a specific stash</span></span>
<span id="cb46-17"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git stash apply stash@{2}</span>
<span id="cb46-18"></span>
<span id="cb46-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Drop a specific stash</span></span>
<span id="cb46-20"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git stash drop stash@{0}</span></code></pre></div></div>
<p>Stashes are portable across branches - you can stash on one branch and apply on another. If applying a stash would cause conflicts, Git reports them and you resolve them as you would a merge conflict.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>git stash branch: Conflict-Free Recovery
</div>
</div>
<div class="callout-body-container callout-body">
<p>If you are worried about conflicts when applying a stash, use <code>git stash branch new-branch-name</code>. This creates a new branch from the commit where you originally stashed, applies the stash, and drops it. Because you are replaying the stash on the exact commit it was created from, conflicts are impossible.</p>
</div>
</div>
</section>
<section id="removing-and-renaming-files" class="level3">
<h3 class="anchored" data-anchor-id="removing-and-renaming-files">7.4 Removing and Renaming Files</h3>
<p>Deleting or renaming a tracked file requires two steps: the filesystem operation and staging the change. Git provides commands that handle both in one step, keeping the index in sync with the working directory:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb47" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb47-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Remove a file from the working directory and stage the deletion</span></span>
<span id="cb47-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git rm file.py</span>
<span id="cb47-3"></span>
<span id="cb47-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Remove from tracking (staging area) but keep on disk</span></span>
<span id="cb47-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git rm <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--cached</span> file.py</span>
<span id="cb47-6"></span>
<span id="cb47-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Rename a file and stage the rename</span></span>
<span id="cb47-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git mv old-name.py new-name.py</span></code></pre></div></div>
<p><code>git rm --cached</code> is particularly useful when you accidentally tracked a file that should be in <code>.gitignore</code> - it stops tracking it without deleting it from your disk. After running it, add the file’s pattern to <code>.gitignore</code> and commit both changes.</p>
<p>Note that Git does not explicitly track renames. Internally, a rename is a delete + add. Git <em>detects</em> renames after the fact by comparing blob hashes between the old and new trees - if the content is identical (or nearly so), it infers a rename. This is why <code>git log --follow file.py</code> can track a file across renames.</p>
</section>
</section>
<section id="hooks-automating-git-events" class="level2">
<h2 class="anchored" data-anchor-id="hooks-automating-git-events">8. Hooks: Automating Git Events</h2>
<p>Git <strong>hooks</strong> are scripts that run automatically in response to specific Git events. They live in <code>.git/hooks/</code> and can be written in any language (Bash, Python, Ruby, etc.) as long as the file is executable and has no extension.</p>
<p>Every new repository comes pre-populated with example hooks (files ending in <code>.sample</code>). To activate one, remove the <code>.sample</code> extension. To create a custom hook, place an executable script with the right name in <code>.git/hooks/</code>.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Client-Side Hooks Are Not Shared
</div>
</div>
<div class="callout-body-container callout-body">
<p>Hooks in <code>.git/hooks/</code> are <strong>not</strong> copied when a repository is cloned. This means client-side hooks must be set up independently in each clone. Teams typically manage this by storing hooks in a <code>hooks/</code> directory within the project and using a setup script or Git’s <code>core.hooksPath</code> configuration to link them.</p>
</div>
</div>
<section id="client-side-hooks" class="level3">
<h3 class="anchored" data-anchor-id="client-side-hooks">8.1 Client-Side Hooks</h3>
<p>These run on your local machine in response to local operations:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 21%">
<col style="width: 32%">
<col style="width: 46%">
</colgroup>
<thead>
<tr class="header">
<th>Hook</th>
<th>Trigger</th>
<th>Typical use</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>pre-commit</code></td>
<td>Before commit message editor opens</td>
<td>Run linters, formatters, tests. Abort on non-zero exit.</td>
</tr>
<tr class="even">
<td><code>prepare-commit-msg</code></td>
<td>After default message created, before editor</td>
<td>Pre-populate commit messages (e.g., branch name prefix)</td>
</tr>
<tr class="odd">
<td><code>commit-msg</code></td>
<td>After message is written</td>
<td>Validate commit message format</td>
</tr>
<tr class="even">
<td><code>post-commit</code></td>
<td>After commit completes</td>
<td>Notifications, trigger CI</td>
</tr>
<tr class="odd">
<td><code>pre-rebase</code></td>
<td>Before rebase starts</td>
<td>Prevent rebase on certain branches</td>
</tr>
<tr class="even">
<td><code>post-merge</code></td>
<td>After merge completes</td>
<td>Restore dependencies (<code>npm install</code>)</td>
</tr>
</tbody>
</table>
</section>
<section id="server-side-hooks" class="level3">
<h3 class="anchored" data-anchor-id="server-side-hooks">8.2 Server-Side Hooks</h3>
<p>These run on the remote repository when receiving pushes:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 21%">
<col style="width: 32%">
<col style="width: 46%">
</colgroup>
<thead>
<tr class="header">
<th>Hook</th>
<th>Trigger</th>
<th>Typical use</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>pre-receive</code></td>
<td>Before any refs are updated</td>
<td>Access control, reject non-fast-forwards, validate code</td>
</tr>
<tr class="even">
<td><code>update</code></td>
<td>Like <code>pre-receive</code>, but runs once per branch</td>
<td>Per-branch policies</td>
</tr>
<tr class="odd">
<td><code>post-receive</code></td>
<td>After all refs are updated</td>
<td>Deploy, notify, update dashboards</td>
</tr>
</tbody>
</table>
<p>The <code>pre-receive</code> hook is the gatekeeper: if it exits non-zero, the entire push is rejected. This is how platforms like GitHub enforce branch protection rules.</p>
</section>
</section>
<section id="submodules-and-advanced-features" class="level2">
<h2 class="anchored" data-anchor-id="submodules-and-advanced-features">9. Submodules and Advanced Features</h2>
<p>As projects grow in scale and complexity, Git’s core model - objects, references, branches - remains the foundation, but additional tools become necessary. Submodules manage cross-repository dependencies. Worktrees let you work on multiple branches simultaneously without stashing. Sparse checkout and Git LFS address the performance challenges of large monorepos and binary files. And packfiles are the compression layer that keeps Git’s snapshot-based storage surprisingly compact.</p>
<section id="submodules" class="level3">
<h3 class="anchored" data-anchor-id="submodules">9.1 Submodules</h3>
<p><strong>Submodules</strong> let you embed one Git repository inside another while keeping their histories completely separate. Each submodule is a full Git repository in its own directory, with its own <code>.git</code>, tracked at a specific commit by the parent project.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb48" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb48-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add a submodule</span></span>
<span id="cb48-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git submodule add https://github.com/lib/dependency.git libs/dependency</span>
<span id="cb48-3"></span>
<span id="cb48-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Clone a project with submodules</span></span>
<span id="cb48-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git clone <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--recurse-submodules</span> https://github.com/user/project.git</span>
<span id="cb48-6"></span>
<span id="cb48-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Or initialize submodules after cloning</span></span>
<span id="cb48-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git submodule update <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--init</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--recursive</span></span>
<span id="cb48-9"></span>
<span id="cb48-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Update a submodule to its latest remote commit</span></span>
<span id="cb48-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git submodule update <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--remote</span> libs/dependency</span></code></pre></div></div>
<p>Adding a submodule creates two things: a directory containing the cloned repo, and a <code>.gitmodules</code> file mapping submodule paths to URLs. Both must be committed to track the submodule.</p>
<p>The parent repository tracks each submodule at a <em>specific commit hash</em> - not a branch. To update the submodule, you explicitly pull new commits and then commit the updated reference in the parent.</p>
</section>
<section id="worktrees" class="level3">
<h3 class="anchored" data-anchor-id="worktrees">9.2 Worktrees</h3>
<p><strong>Worktrees</strong> let you check out multiple branches of the same repository simultaneously, each in its own directory, sharing a single <code>.git</code> database. This avoids the need to stash, commit, or clone when you need to work on two branches at once.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb49" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb49-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Create a new worktree for a hotfix branch</span></span>
<span id="cb49-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git worktree add ../hotfix-branch hotfix/urgent-fix</span>
<span id="cb49-3"></span>
<span id="cb49-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># List all worktrees</span></span>
<span id="cb49-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git worktree list</span>
<span id="cb49-6"></span>
<span id="cb49-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Remove a worktree when done</span></span>
<span id="cb49-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git worktree remove ../hotfix-branch</span></code></pre></div></div>
<p>Worktrees are ideal for:</p>
<ul>
<li><strong>Parallel development</strong>: review a PR in one worktree while continuing feature work in another</li>
<li><strong>Long-running builds</strong>: keep building one branch while developing on another</li>
<li><strong>Bisecting</strong>: run <code>git bisect</code> in a separate worktree without disrupting your current work</li>
</ul>
<p>Unlike cloning the repo again, worktrees share the object store - no additional disk space for the history, and objects created in one worktree are immediately visible to others.</p>
</section>
<section id="sparse-checkout" class="level3">
<h3 class="anchored" data-anchor-id="sparse-checkout">9.3 Sparse Checkout</h3>
<p>For large monorepos where you only need a subset of the files, <strong>sparse checkout</strong> lets you check out just the directories you care about, significantly reducing disk usage and <code>git status</code> overhead.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb50" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb50-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Enable sparse checkout</span></span>
<span id="cb50-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git sparse-checkout init <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--cone</span></span>
<span id="cb50-3"></span>
<span id="cb50-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Check out only specific directories</span></span>
<span id="cb50-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git sparse-checkout set src/my-service tests/my-service</span>
<span id="cb50-6"></span>
<span id="cb50-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add more directories later</span></span>
<span id="cb50-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git sparse-checkout add docs/</span>
<span id="cb50-9"></span>
<span id="cb50-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Disable (check out everything again)</span></span>
<span id="cb50-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git sparse-checkout disable</span></code></pre></div></div>
<p>The <code>--cone</code> mode (recommended) restricts patterns to directory-level matching, which is much faster than arbitrary gitignore-style patterns. Sparse checkout works well in combination with shallow clones for CI/CD pipelines that only need to build one service in a monorepo.</p>
</section>
<section id="git-lfs-large-file-storage" class="level3">
<h3 class="anchored" data-anchor-id="git-lfs-large-file-storage">9.4 Git LFS (Large File Storage)</h3>
<p>Git’s content-addressable model stores every version of every file. For large binary files (datasets, images, videos, model weights), this causes repositories to balloon in size because binary diffs are inefficient. <strong>Git LFS</strong> solves this by storing large files on a separate server and replacing them with lightweight pointer files in the repository.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb51" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb51-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Install and initialize LFS</span></span>
<span id="cb51-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git lfs install</span>
<span id="cb51-3"></span>
<span id="cb51-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Track large file patterns</span></span>
<span id="cb51-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git lfs track <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"*.pth"</span></span>
<span id="cb51-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git lfs track <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data/*.parquet"</span></span>
<span id="cb51-7"></span>
<span id="cb51-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># This creates/updates .gitattributes - commit it</span></span>
<span id="cb51-9"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git add .gitattributes</span>
<span id="cb51-10"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git commit <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Track model weights and datasets with LFS"</span></span></code></pre></div></div>
<p>After setup, <code>git add</code>, <code>commit</code>, <code>push</code>, and <code>pull</code> work transparently - LFS intercepts operations on tracked files and handles the upload/download to the LFS server. The repository itself only stores small pointer files, keeping clones fast.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>When to Use LFS
</div>
</div>
<div class="callout-body-container callout-body">
<p>Use LFS for files that are (a) large (&gt; 1 MB), (b) binary (don’t benefit from Git’s delta compression), and (c) versioned (you need history). If you don’t need history for large files, consider <code>.gitignore</code> + external storage instead. Common LFS candidates: trained model weights, compiled binaries, large images, video files, and compressed datasets.</p>
</div>
</div>
</section>
<section id="packfiles" class="level3">
<h3 class="anchored" data-anchor-id="packfiles">9.5 Packfiles</h3>
<p>As a repository accumulates thousands of loose objects, Git periodically combines them into <strong>packfiles</strong> - single compressed files that store multiple objects with delta compression. A pack index file provides fast lookups by hash.</p>
<p>Packing happens automatically when there are too many loose objects, when you run <code>git gc</code> (garbage collection), or when pushing to a remote. You rarely need to think about packfiles, but understanding them explains why Git repositories are surprisingly compact despite storing full snapshots: packfiles use delta compression <em>between</em> similar objects, similar to how video codecs store keyframes and deltas.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb52" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb52-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Manually trigger garbage collection and packing</span></span>
<span id="cb52-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git gc</span>
<span id="cb52-3"></span>
<span id="cb52-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># See pack statistics</span></span>
<span id="cb52-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git count-objects <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-v</span></span></code></pre></div></div>
</section>
</section>
<section id="the-professional-workflow" class="level2">
<h2 class="anchored" data-anchor-id="the-professional-workflow">10. The Professional Workflow</h2>
<p>Understanding Git’s internals is necessary but not sufficient. A team of developers who all understand the object model but have no shared workflow will still produce a tangled history. This section describes a feature-branch workflow that produces clean, linear, reviewable history - the same workflow used by most teams.</p>
<section id="the-feature-branch-model" class="level3">
<h3 class="anchored" data-anchor-id="the-feature-branch-model">10.1 The Feature Branch Model</h3>
<p>The core rule: <strong>never commit directly to the main branch</strong>. Every change - no matter how small - starts on a feature branch.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb53" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb53-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Start a new feature</span></span>
<span id="cb53-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git switch <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> feature/add-search</span>
<span id="cb53-3"></span>
<span id="cb53-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Make small, focused commits</span></span>
<span id="cb53-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git add search.py</span>
<span id="cb53-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git commit <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Add search index builder"</span></span>
<span id="cb53-7"></span>
<span id="cb53-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Push the branch and set up tracking</span></span>
<span id="cb53-9"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git push <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-u</span> origin feature/add-search</span></code></pre></div></div>
<p>This ensures that <code>main</code> always contains reviewed, tested, production-ready code. Feature branches are disposable workspaces where experimentation, refactoring, and work-in-progress commits are welcome - they will be cleaned up before merging.</p>
</section>
<section id="pull-requests-context-is-everything" class="level3">
<h3 class="anchored" data-anchor-id="pull-requests-context-is-everything">10.2 Pull Requests: Context Is Everything</h3>
<p>Once your feature branch is pushed, open a pull request. The PR is not just a merge request - it is a <strong>communication artifact</strong>. A well-crafted PR description answers:</p>
<ul>
<li><strong>Why</strong> is this change needed? What problem does it solve?</li>
<li><strong>What</strong> approach did you take? Were alternatives considered?</li>
<li><strong>What assumptions</strong> were made? What are the risks?</li>
<li><strong>How</strong> should reviewers test or verify the change?</li>
</ul>
<p>For large features that cannot be broken into small PRs, use GitHub’s task lists to show progress so reviewers know not to do in-depth reviews until the feature is complete.</p>
<p>After receiving code review feedback, push additional commits to the feature branch - they are automatically included in the PR. Do not squash during review, as that makes it harder for reviewers to see what changed between rounds. Save the cleanup for the final step.</p>
</section>
<section id="the-merge-sequence-rebase-squash-fast-forward" class="level3">
<h3 class="anchored" data-anchor-id="the-merge-sequence-rebase-squash-fast-forward">10.3 The Merge Sequence: Rebase, Squash, Fast-Forward</h3>
<p>Before merging, prepare a clean history using this sequence:</p>
<p><strong>Step 1 - Update main and rebase:</strong></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb54" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb54-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fetch latest main and rebase your branch on top</span></span>
<span id="cb54-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git switch main <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;&amp;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> pull <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;&amp;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> switch <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-</span></span>
<span id="cb54-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git rebase main</span></code></pre></div></div>
<p>This ensures your feature branch’s commits sit <em>ahead</em> of main, making a fast-forward merge possible. If rebase produces conflicts, resolve them commit-by-commit as Git replays each one.</p>
<p><strong>Step 2 - Interactive rebase to clean up:</strong></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb55" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb55-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git rebase <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-i</span> main</span></code></pre></div></div>
<p>Squash WIP and fixup commits, reword messages to be descriptive, and ensure each remaining commit is a logical, self-contained unit. This is the time to craft commit messages that capture the “why” - they will be permanent history.</p>
<p><strong>Step 3 - Force push the cleaned-up branch:</strong></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb56" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb56-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git push <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--force-with-lease</span></span></code></pre></div></div>
<p>Use <code>--force-with-lease</code> instead of <code>--force</code> - it refuses to push if the remote has commits you have not seen, protecting against accidentally overwriting a colleague’s work.</p>
<p><strong>Step 4 - Fast-forward merge:</strong></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb57" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb57-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git switch main</span>
<span id="cb57-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git merge feature/add-search <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--ff-only</span></span>
<span id="cb57-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git push</span></code></pre></div></div>
<p><strong>Step 5 - Clean up:</strong></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb58" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb58-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git branch <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-d</span> feature/add-search           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># delete local branch</span></span>
<span id="cb58-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git push origin <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--delete</span> feature/add-search  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># delete remote branch</span></span></code></pre></div></div>
<p>GitHub auto-closes the PR when it detects that main contains the branch’s commits.</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    A["A"] --&gt; B["B"] --&gt; C["C&lt;br/&gt;&lt;small&gt;main&lt;/small&gt;"]
    C --&gt; D["D'"] --&gt; E["E'&lt;br/&gt;&lt;small&gt;feature (rebased)&lt;/small&gt;&lt;br/&gt;&lt;small&gt;main (after ff-merge)&lt;/small&gt;"]

    style A fill:#7eb8da,stroke:#333,color:#000
    style B fill:#7eb8da,stroke:#333,color:#000
    style C fill:#7eb8da,stroke:#333,color:#000
    style D fill:#90c695,stroke:#333,color:#000
    style E fill:#90c695,stroke:#333,color:#000
</pre>
</div>
<p></p><figcaption> <strong>Figure 4:</strong> The professional merge sequence. Feature commits are rebased onto main, cleaned up with interactive rebase, and fast-forward merged - producing a linear history with no merge commits.</figcaption> </figure><p></p>
</div>
</div>
</div>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Why Fast-Forward Only?
</div>
</div>
<div class="callout-body-container callout-body">
<p>With fast-forward merges, no merge commit is created on main. Every commit in the history was authored and reviewed on a feature branch before arriving on main. There are no “surprise” commits from Git’s merge algorithm. The history is linear, readable, and bisectable. This is one of the main benefits of the rebase-then-merge workflow: the permanent history on main consists entirely of curated, reviewed commits.</p>
</div>
</div>
</section>
<section id="commit-message-craft" class="level3">
<h3 class="anchored" data-anchor-id="commit-message-craft">10.4 Commit Message Craft</h3>
<p>Commit messages are documentation that lives forever in the history. A good message explains <em>why</em> a change was made, not <em>what</em> was changed (the diff shows the “what”). Follow these conventions:</p>
<p><strong>Format:</strong></p>
<pre><code>Short summary (50 chars or less)

Longer explanation wrapping at 72 characters. Explain the motivation
for the change, any trade-offs made, and anything a future reader
would need to understand the decision.

Refs: #123</code></pre>
<p><strong>Rules:</strong></p>
<ol type="1">
<li><strong>Separate subject from body with a blank line.</strong> Many tools (GitHub, <code>git log --oneline</code>, email patches) use only the first line.</li>
<li><strong>Limit the subject to 50 characters.</strong> Forces concision.</li>
<li><strong>Capitalize the subject, no trailing period.</strong></li>
<li><strong>Use imperative mood</strong> in the subject: “Add search feature” not “Added search feature” - it reads like a command, matching <code>git merge</code> and <code>git revert</code> output.</li>
<li><strong>Wrap the body at 72 characters.</strong> Terminals, <code>git log</code>, and email all look better with wrapped text.</li>
<li><strong>Explain why, not what.</strong> The diff shows what changed; the message should explain the decision.</li>
</ol>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Sign Your Commits
</div>
</div>
<div class="callout-body-container callout-body">
<p>For teams and open source projects, signed commits provide cryptographic proof of authorship. Git supports both GPG and SSH signing:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb60" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb60-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git config <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--global</span> commit.gpgsign true</span>
<span id="cb60-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git config <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--global</span> gpg.format ssh</span>
<span id="cb60-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git config <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--global</span> user.signingkey ~/.ssh/id_ed25519.pub</span></code></pre></div></div>
<p>GitHub displays a “Verified” badge on signed commits.</p>
</div>
</div>
</section>
</section>
<section id="configuration" class="level2">
<h2 class="anchored" data-anchor-id="configuration">11. Configuration</h2>
<p>Git’s configuration is read automatically before every command - no reloading needed. Configuration files are read in order, with later files overriding earlier ones:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 26%">
<col style="width: 23%">
<col style="width: 23%">
<col style="width: 26%">
</colgroup>
<thead>
<tr class="header">
<th>Level</th>
<th>File</th>
<th>Flag</th>
<th>Scope</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>System</td>
<td><code>/etc/gitconfig</code></td>
<td><code>--system</code></td>
<td>All users on the machine</td>
</tr>
<tr class="even">
<td>User</td>
<td><code>~/.gitconfig</code></td>
<td><code>--global</code></td>
<td>All repositories for the current user</td>
</tr>
<tr class="odd">
<td>Repository</td>
<td><code>.git/config</code></td>
<td><code>--local</code></td>
<td>This repository only</td>
</tr>
</tbody>
</table>
<section id="essential-settings" class="level3">
<h3 class="anchored" data-anchor-id="essential-settings">11.1 Essential Settings</h3>
<p>These three settings significantly improve the default Git experience:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb61" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb61-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Push only the current branch to its tracked upstream</span></span>
<span id="cb61-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git config <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--global</span> push.default upstream</span>
<span id="cb61-3"></span>
<span id="cb61-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Reject non-fast-forward merges (enforce linear history)</span></span>
<span id="cb61-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git config <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--global</span> merge.ff only</span>
<span id="cb61-6"></span>
<span id="cb61-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Auto-clean stale remote-tracking branches on fetch/pull</span></span>
<span id="cb61-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git config <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--global</span> fetch.prune true</span></code></pre></div></div>
</section>
<section id="aliases-and-subcommands" class="level3">
<h3 class="anchored" data-anchor-id="aliases-and-subcommands">11.2 Aliases and Subcommands</h3>
<p>Git aliases live in the <code>[alias]</code> section of your gitconfig. Single-command aliases are straightforward; multi-command aliases use a <code>!</code> prefix to invoke the shell:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb62" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb62-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Simple alias</span></span>
<span id="cb62-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git config <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--global</span> alias.co checkout</span>
<span id="cb62-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git config <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--global</span> alias.st status</span>
<span id="cb62-4"></span>
<span id="cb62-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Multi-command alias (note the ! prefix)</span></span>
<span id="cb62-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git config <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--global</span> alias.mup <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'!git checkout main &amp;&amp; git pull &amp;&amp; git checkout -'</span></span>
<span id="cb62-7"></span>
<span id="cb62-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Visual log</span></span>
<span id="cb62-9"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git config <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--global</span> alias.graph <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'log --all --decorate --graph --oneline'</span></span></code></pre></div></div>
<p><strong>Git subcommands</strong> are even more powerful: any executable on your <code>$PATH</code> named <code>git-&lt;name&gt;</code> becomes callable as <code>git &lt;name&gt;</code>. This lets you write complex tooling in any language:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb63" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb63-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#!/bin/bash</span></span>
<span id="cb63-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Save as 'git-cm' on your $PATH, make executable</span></span>
<span id="cb63-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Usage: git cm "message" OR git cm (opens editor)</span></span>
<span id="cb63-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">[[</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$#</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&gt;</span> 0 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">]];</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="cb63-5">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> commit <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">$@</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb63-6"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span></span>
<span id="cb63-7">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> commit <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-v</span></span>
<span id="cb63-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">fi</span></span></code></pre></div></div>
</section>
<section id="gitignore-patterns" class="level3">
<h3 class="anchored" data-anchor-id="gitignore-patterns">11.3 .gitignore Patterns</h3>
<p>The <code>.gitignore</code> file tells Git which files to ignore. It uses glob patterns and applies recursively from the directory where it is placed. You can have <code>.gitignore</code> files in subdirectories for directory-specific rules.</p>
<p><strong>Pattern rules:</strong></p>
<table class="caption-top table">
<colgroup>
<col style="width: 34%">
<col style="width: 30%">
<col style="width: 34%">
</colgroup>
<thead>
<tr class="header">
<th>Pattern</th>
<th>Effect</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>*.log</code></td>
<td>Ignore all files ending in <code>.log</code>, recursively</td>
<td>Build logs, app logs</td>
</tr>
<tr class="even">
<td><code>build/</code></td>
<td>Ignore any directory named <code>build</code></td>
<td>Compiled output</td>
</tr>
<tr class="odd">
<td><code>/TODO</code></td>
<td>Ignore <code>TODO</code> in the current directory only (no recursion)</td>
<td>Root-level notes</td>
</tr>
<tr class="even">
<td><code>doc/**/*.pdf</code></td>
<td>Ignore PDFs in <code>doc/</code> and all subdirectories</td>
<td>Generated docs</td>
</tr>
<tr class="odd">
<td><code>!important.log</code></td>
<td>Do <em>not</em> ignore this file (negation)</td>
<td>Exception to <code>*.log</code></td>
</tr>
<tr class="even">
<td><code>#</code></td>
<td>Comment line</td>
<td>-</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Global Gitignore
</div>
</div>
<div class="callout-body-container callout-body">
<p>For files that are personal to your setup (editor configs, OS files like <code>.DS_Store</code>), use a global gitignore rather than polluting project-level <code>.gitignore</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb64" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb64-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">$</span> git config <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--global</span> core.excludesfile ~/.gitignore_global</span></code></pre></div></div>
</div>
</div>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">12. Conclusion</h2>
<p>In this post, we built Git from the inside out - starting from the content-addressable object model, building through references and branches, exploring history inspection and rewriting tools, and arriving at the professional workflow that ties everything together.</p>
<p>Git’s power comes from a coherent set of design choices - each solving a specific problem with a specific mechanism.</p>
<section id="key-takeaways" class="level3">
<h3 class="anchored" data-anchor-id="key-takeaways">Key Takeaways</h3>
<ol type="1">
<li><p><strong>Git is a content-addressable filesystem, not a diff tracker.</strong> Every commit stores a complete snapshot. Unchanged files are deduplicated by hash. Diffs are computed on demand, not stored. This design makes branching and merging cheap pointer operations rather than expensive file copies.</p></li>
<li><p><strong>Three objects and three areas - that’s the whole model.</strong> Blobs (file contents), trees (directories), and commits (snapshots with context) are the only object types. The working directory, staging area (index), and object store are the only three areas. Every Git command is an operation on these six things.</p></li>
<li><p><strong>Branches are 41-byte files, not copies.</strong> A branch is a mutable pointer to a commit. Creating a branch creates a file. Merging moves or creates pointers. Understanding this is what makes branching feel lightweight rather than scary.</p></li>
<li><p><strong><code>reset</code>, <code>checkout</code>, and <code>rebase</code> are pointer operations.</strong> <code>reset</code> moves a branch pointer (and optionally resets the index and working directory). <code>checkout</code> moves HEAD. <code>rebase</code> replays commits with new parents. None of them destroy the original commits - the reflog keeps them recoverable.</p></li>
<li><p><strong>Clean history is a professional obligation.</strong> The rebase-then-fast-forward workflow produces linear, reviewable, bisectable history. Interactive rebase before merging turns messy development into clean permanent records. Commit messages should explain <em>why</em>, not <em>what</em>.</p></li>
<li><p><strong>Committed data is almost always recoverable; uncommitted data is not.</strong> The reflog tracks every reference update for 90 days. But changes that were never committed - unsaved edits, unstaged modifications - are gone when overwritten. Commit early, commit often.</p></li>
</ol>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Git’s Design Is Elegant
</div>
</div>
<div class="callout-body-container callout-body">
<p>Strip away the 150+ commands and Git is remarkably simple: a content-addressable object store (blobs, trees, commits), mutable pointers (branches, tags), and a single HEAD. Every feature - branching, merging, rebasing, bisecting, stashing - is built on this foundation. Understanding the foundation makes the entire surface area intuitive.</p>
</div>
</div>
</section>
</section>
<section id="references-resources" class="level2">
<h2 class="anchored" data-anchor-id="references-resources">References &amp; Resources</h2>
<ul>
<li><a href="https://git-scm.com/book/en/v2">Pro Git</a> by Scott Chacon and Ben Straub - the definitive, freely available Git book. Chapters 10 (Git Internals) and 7 (Git Tools) are particularly relevant to this post.</li>
<li><a href="http://gitready.com/">Git Ready</a> - practical how-to pages organized by difficulty: “learn a little, learn a lot.”</li>
<li><a href="https://github.com/pluralsight/git-internals-pdf">Git Internals PDF</a> - a deep dive into the object model with more detail than we could cover here.</li>
<li><a href="https://github.com/thoughtbot/guides/tree/main/">Thoughtbot Git Guides</a> - opinionated workflow guides from a well-respected consultancy.</li>
<li><a href="https://cli.github.com/">GitHub CLI (<code>gh</code>)</a> - interact with GitHub entirely from the command line: PRs, issues, actions, releases.</li>
<li><a href="https://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message">5 Rules for a Good Git Commit Message</a> - the widely-cited guide to commit message style.</li>
<li><a href="http://rakeroutes.com/blog/deliberate-git">Deliberate Git</a> by Stephen Ball - a talk on crafting intentional, meaningful commit history.</li>
<li><a href="http://confreaks.tv/videos/railsconf2015-implementing-a-strong-code-review-culture">Code Review Culture</a> by Derek Prior - implementing code review as a team practice.</li>
<li><a href="https://github.com/tpope/vim-fugitive">Fugitive</a> - the premier Vim plugin for Git integration.
<ul>
<li><a href="http://vimcasts.org/blog/2011/05/the-fugitive-series/">Fugitive Vimcasts Series</a> - five-part screencast series.</li>
</ul></li>
<li><a href="https://github.com/christoomey/vim-conflicted">vim-conflicted</a> - optimized merge conflict resolution in Vim.</li>
<li>Pro tip: add <code>autocmd Filetype gitcommit setlocal spell textwidth=72</code> to your Vim config for automatic spell-checking and line wrapping in commit messages.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Software Engineering</category>
  <guid>https://imaddabbura.github.io/posts/swe/Advanced-Git.html</guid>
  <pubDate>Fri, 22 Dec 2023 06:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/swe/images/git.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Building a Deep Learning Framework from Scratch</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/mlsys/dl-systems.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="why-build-a-deep-learning-framework-from-scratch" class="level2">
<h2 class="anchored" data-anchor-id="why-build-a-deep-learning-framework-from-scratch">Why Build a Deep Learning Framework from Scratch?</h2>
<p>Every deep learning practitioner eventually runs <code>loss.backward()</code> and watches gradients flow. But what <em>actually</em> happens inside that call? Where do the intermediate tensors live? Why does your GPU run out of memory on a model that “should” fit? And why does reshaping a tensor sometimes silently copy gigabytes of data?</p>
<p>I built <a href="https://github.com/ImadDabbura/tiny-pytorch"><code>tiny_pytorch</code></a> to answer these questions for myself. Along the way, I encountered nearly every foundational design decision that real frameworks like <a href="https://arxiv.org/abs/1912.01703">PyTorch</a>, <a href="https://arxiv.org/abs/1605.08695">TensorFlow</a>, and <a href="https://arxiv.org/abs/1408.5093">Caffe</a> had to make and learned <em>why</em> they made them.</p>
<p>This post distills everything I learned into a coherent narrative. We’ll start from the framework-level design philosophy, work our way down to how bytes are laid out in memory, and then zoom back out to distributed training across multiple GPUs. The goal is <strong>intuition</strong>: mental models you can carry with you when debugging real systems.</p>
</section>
<section id="the-evolution-of-dl-frameworks" class="level2">
<h2 class="anchored" data-anchor-id="the-evolution-of-dl-frameworks">The Evolution of DL Frameworks</h2>
<p>Before writing a single line of code, it helps to understand the three philosophies that shaped modern deep learning frameworks. Each solved a real problem and introduced new ones.</p>
<section id="caffe-everything-is-a-layer" class="level3">
<h3 class="anchored" data-anchor-id="caffe-everything-is-a-layer">Caffe: Everything Is a Layer</h3>
<p>Caffe (C++ only) was beautifully simple. You defined your computation as a stack of <strong>layers</strong>, each implementing a <code>forward()</code> and <code>backward()</code> method. The backward pass was a direct implementation of the <a href="https://www.nature.com/articles/323533a0">backpropagation algorithm</a>, where each layer knew how to compute its own gradients, and updates happened in-place.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Composable but Rigid
</div>
</div>
<div class="callout-body-container callout-body">
<p>Think of Caffe layers like a stack of Lego bricks. Each brick knows its own shape (forward) and how to “unstick” itself (backward). Simple, intuitive, but rigid: you can’t easily build non-linear architectures.</p>
</div>
</div>
</section>
<section id="tensorflow-1.x-the-static-graph" class="level3">
<h3 class="anchored" data-anchor-id="tensorflow-1.x-the-static-graph">TensorFlow 1.x: The Static Graph</h3>
<p>TensorFlow introduced a powerful idea: <strong>construct a static computation graph first</strong>, then execute it. This separation of <em>definition</em> and <em>execution</em> unlocked serious optimizations; the compiler could fuse operations, reuse memory, and skip unnecessary computations at run-time.</p>
<p>The cost? Debugging was painful. You couldn’t just print a tensor mid-computation. The graph had its own “programming language” that felt alien to Python developers. Experimentation slowed down because every change required rebuilding the graph.</p>
</section>
<section id="pytorch-define-by-run" class="level3">
<h3 class="anchored" data-anchor-id="pytorch-define-by-run">PyTorch: Define by Run</h3>
<p>PyTorch flipped the script with <strong>dynamic computation graphs</strong>: the graph is built on-the-fly as you execute operations. This is called <em>define by run</em>. You can mix Python control flow (if/else, loops) directly with tensor operations, set breakpoints anywhere, and inspect intermediate values trivially.</p>
<p>The trade-off? Dynamic graphs are typically harder to optimize ahead of time. You lose the global view that static compilation provides. Modern PyTorch addresses this with <code>torch.compile()</code> and JIT compilation, getting closer to static-graph performance while keeping the dynamic-graph developer experience.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Three Competing Goals
</div>
</div>
<div class="callout-body-container callout-body">
<p>Every DL framework navigates three competing goals: <strong>ease of debugging</strong>, <strong>optimization potential</strong>, and <strong>flexibility</strong>. Caffe optimized for simplicity, TensorFlow for optimization, and PyTorch for flexibility. No framework gets all three for free.</p>
</div>
</div>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    A["&lt;b&gt;Caffe&lt;/b&gt;&lt;br/&gt;Layers with forward/backward&lt;br/&gt;In-place updates&lt;br/&gt;C++ only"] --&gt; B["&lt;b&gt;TensorFlow 1.x&lt;/b&gt;&lt;br/&gt;Static graph&lt;br/&gt;Compile-then-run&lt;br/&gt;Hard to debug"]
    B --&gt; C["&lt;b&gt;PyTorch&lt;/b&gt;&lt;br/&gt;Dynamic graph&lt;br/&gt;Define-by-run&lt;br/&gt;Python-native"]
    C --&gt; D["&lt;b&gt;Modern PyTorch&lt;/b&gt;&lt;br/&gt;torch.compile / JIT&lt;br/&gt;Best of both worlds"]

</pre>
</div>
<p></p><figcaption> The evolution of DL framework design philosophies</figcaption> </figure><p></p>
</div>
</div>
</div>
<p><strong>Key takeaway:</strong> Framework design is fundamentally about <em>when</em> the computation graph is known. Know it early (static) and you can optimize aggressively. Know it late (dynamic) and you can iterate fast. Modern systems try to give you both.</p>
</section>
</section>
<section id="automatic-differentiation" class="level2">
<h2 class="anchored" data-anchor-id="automatic-differentiation">Automatic Differentiation</h2>
<p>Automatic differentiation (AD) is the core engine of every deep learning framework. It’s what makes <code>loss.backward()</code> work. But there are two fundamentally different approaches, and understanding <em>why</em> we use one over the other is essential. I dig into forward versus reverse mode, dual numbers, and the memory trade-off in a companion post, <a href="../../posts/mlsys/automatic-differentiation.html">Automatic Differentiation Demystified</a>.</p>
<section id="forward-mode-ad" class="level3">
<h3 class="anchored" data-anchor-id="forward-mode-ad">Forward Mode AD</h3>
<p>In forward mode, we walk from <strong>inputs to outputs</strong>. At each node, we compute the partial derivative of that node with respect to a <em>single</em> input variable. This means:</p>
<ul>
<li>For <strong>each input variable</strong>, we need a <em>full forward pass</em> through the graph.</li>
<li>If we have <img src="https://latex.codecogs.com/png.latex?n"> inputs, we need <img src="https://latex.codecogs.com/png.latex?n"> forward AD passes.</li>
</ul>
<p>For a typical deep learning loss function (a scalar output with millions of input parameters), this is catastrophically inefficient. We’d need millions of passes just to get one gradient update.</p>
</section>
<section id="reverse-mode-ad-backpropagation" class="level3">
<h3 class="anchored" data-anchor-id="reverse-mode-ad-backpropagation">Reverse Mode AD (Backpropagation)</h3>
<p>Reverse mode flips the direction. We walk from the <strong>output back to inputs</strong>, computing the gradient of the scalar output with respect to <em>all</em> input nodes in a <strong>single backward pass</strong>. This is why it’s the standard for deep learning: one output, millions of inputs, one pass. I implement both passes by hand, and check them against numerical gradients, in <a href="../../posts/coding-nn/fwd-bkwd-propagation/Coding-Neural-Network-Forwad-Back-Propagation.html">Coding Neural Network: Forward and Backward Propagation</a>.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart TD
    subgraph forward["Forward Mode (one pass per input)"]
        direction LR
        x1f["x₁"] --&gt; |"∂a/∂x₁"| af["a"] --&gt; |"∂b/∂x₁"| bf["b"] --&gt; |"∂L/∂x₁"| Lf["L"]
    end

    subgraph reverse["Reverse Mode (one pass for ALL inputs)"]
        direction RL
        Lr["L"] --&gt; |"∂L/∂b"| br["b"] --&gt; |"∂L/∂a"| ar["a"] --&gt; |"∂L/∂x₁&lt;br/&gt;∂L/∂x₂&lt;br/&gt;∂L/∂x₃"| xr["x₁, x₂, x₃"]
    end
</pre>
</div>
<p></p><figcaption> Forward vs.&nbsp;reverse mode AD: reverse mode computes all gradients in a single backward pass</figcaption> </figure><p></p>
</div>
</div>
</div>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>The Memory Cost of Reverse Mode
</div>
</div>
<div class="callout-body-container callout-body">
<p>Reverse mode has a catch: to compute gradients during the backward pass, we need the <strong>intermediate values from the forward pass</strong>. For each operation, we must store the input tensors and remember which operation created them. This is why training uses far more memory than inference: all those “saved tensors” accumulate on the graph.</p>
</div>
</div>
<p>Here’s what the autograd system actually tracks:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    x["Input x&lt;br/&gt;&lt;i&gt;leaf tensor&lt;/i&gt;"] --&gt; mul["Mul"]
    w["Weight W&lt;br/&gt;&lt;i&gt;leaf tensor&lt;/i&gt;"] --&gt; mul
    mul --&gt; |"z = W·x&lt;br/&gt;&lt;b&gt;saved: W, x&lt;/b&gt;"| act["ReLU"]
    act --&gt; |"a = relu(z)&lt;br/&gt;&lt;b&gt;saved: z&lt;/b&gt;"| loss_fn["MSELoss"]
    y["Target y"] --&gt; loss_fn
    loss_fn --&gt; |"L = loss(a, y)&lt;br/&gt;&lt;b&gt;saved: a, y&lt;/b&gt;"| L["Scalar Loss L"]

    L -.-&gt; |"backward()"| loss_fn
    loss_fn -.-&gt; act
    act -.-&gt; mul
    mul -.-&gt; x
    mul -.-&gt; w

</pre>
</div>
<p></p><figcaption> What the autograd engine saves during a forward pass: every intermediate result and its creator must be retained for backward</figcaption> </figure><p></p>
</div>
</div>
</div>
<p>The dashed arrows show the backward pass, which retraces the forward graph in reverse. At each node, the saved tensors are consumed to compute local gradients.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Gradients as Directional Information
</div>
</div>
<div class="callout-body-container callout-body">
<p>The gradient at each node tells you: <em>“In which direction would changing this value increase the loss most steeply?”</em> It points toward steepest <strong>ascent</strong>, which is the direction of maximum loss increase. To decrease the loss, we move in the <strong>negative</strong> gradient direction. This is why gradient descent subtracts the gradient from the parameters: <img src="https://latex.codecogs.com/png.latex?%5Ctheta%20%5Cleftarrow%20%5Ctheta%20-%20%5Calpha%20%5Cnabla_%5Ctheta%20L">.</p>
</div>
</div>
<p>One powerful consequence: the backward pass itself builds a computation graph for the gradients. This means you can compute <strong>gradients of gradients</strong> simply by adding more operations, which is exactly what second-order methods and some meta-learning approaches do.</p>
<p><strong>Key takeaway:</strong> Reverse mode AD gives us all gradients in one pass, but the price is memory: every intermediate tensor from the forward pass must be kept alive until it’s consumed by the backward pass.</p>
</section>
</section>
<section id="memory-layout-shapes-strides-and-the-viewcopy-divide" class="level2">
<h2 class="anchored" data-anchor-id="memory-layout-shapes-strides-and-the-viewcopy-divide">Memory Layout: Shapes, Strides, and the View/Copy Divide</h2>
<p>This is where the rubber meets the road. Understanding how tensors are stored in memory explains a surprising number of performance issues and subtle bugs.</p>
<section id="the-flat-array-reality" class="level3">
<h3 class="anchored" data-anchor-id="the-flat-array-reality">The Flat Array Reality</h3>
<p>Whether you’re on CPU or GPU, the hardware gives you a <strong>flat, contiguous block of memory</strong>. There are no “dimensions” at the hardware level, just consecutive slots. To create the <em>illusion</em> of an N-dimensional array, we need three pieces of metadata:</p>
<ul>
<li><strong>Shape</strong>: The logical dimensions (e.g., <code>[3, 4]</code> for a 3×4 matrix)</li>
<li><strong>Stride</strong>: How many elements to skip in the flat array to move one step along each dimension</li>
<li><strong>Offset</strong>: Where the data starts within the flat array</li>
</ul>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Row-Major vs.&nbsp;Column-Major via Strides
</div>
</div>
<div class="callout-body-container callout-body">
<p>For a 2D array <code>A</code> with shape <code>[R, C]</code>:</p>
<ul>
<li><strong>Row-major</strong> (C/NumPy/PyTorch default): <code>stride = [C, 1]</code>, rows are contiguous</li>
<li><strong>Column-major</strong> (Fortran/BLAS): <code>stride = [1, R]</code>, columns are contiguous</li>
</ul>
<p>Most BLAS libraries (the workhorses of linear algebra) are implemented in Fortran and expect column-major layout. This is why you sometimes see frameworks internally transposing data before calling into BLAS routines.</p>
</div>
</div>
</section>
<section id="views-same-memory-different-perspective" class="level3">
<h3 class="anchored" data-anchor-id="views-same-memory-different-perspective">Views: Same Memory, Different Perspective</h3>
<p>The stride mechanism enables something powerful: multiple tensor objects can <strong>share the same underlying memory</strong> with different shapes, strides, and offsets. These are called <em>views</em>. Three critical operations create views, not copies:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Operation</th>
<th>What Changes</th>
<th>Memory Cost</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Slice</strong></td>
<td>Offset + shape + stride</td>
<td>Zero (view)</td>
</tr>
<tr class="even">
<td><strong>Transpose</strong></td>
<td>Strides are swapped, shape changes</td>
<td>Zero (view)</td>
</tr>
<tr class="odd">
<td><strong>Broadcast</strong></td>
<td>Stride set to 0 along new dims</td>
<td>Zero (view)</td>
</tr>
<tr class="even">
<td><strong>Reshape/View</strong></td>
<td>Shape + stride (if compatible)</td>
<td>Zero <em>or</em> copy</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>When Reshape Becomes a Copy
</div>
</div>
<div class="callout-body-container callout-body">
<p><code>reshape</code> / <code>view</code> can create a view <em>only</em> when the new shape is compatible with existing strides (i.e., the data is already contiguous in the right order). If the tensor has been transposed or sliced in a way that makes the data non-contiguous, <code>reshape</code> must <strong>copy</strong> the data into a new contiguous block. This can silently allocate gigabytes of memory.</p>
<p><strong>How to detect it:</strong> In PyTorch, call <code>tensor.is_contiguous()</code> before reshaping. If it returns <code>False</code>, the reshape will trigger a copy. Use <code>tensor.contiguous()</code> explicitly to make the copy intentional and visible.</p>
</div>
</div>
</section>
<section id="the-contiguity-problem" class="level3">
<h3 class="anchored" data-anchor-id="the-contiguity-problem">The Contiguity Problem</h3>
<p>After operations like slicing or transposing, the logical tensor and the physical memory layout can diverge. The tensor is no longer <em>compact</em>, which means the offset isn’t 0 or the strides don’t correspond to row-major order.</p>
<p>This matters because many operations (especially matrix multiplication) require contiguous data for efficient memory access. The framework typically handles this by checking compactness before an operation and creating a contiguous copy if needed. But this implicit copy is a hidden performance cost.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart TD
    flat["Flat memory: [a b c d e f g h i j k l]"] --&gt; orig["Tensor A&lt;br/&gt;shape=[3,4], stride=[4,1], offset=0"]
    flat --&gt; slice["Slice A[0:2, 1:3]&lt;br/&gt;shape=[2,2], stride=[4,1], offset=1&lt;br/&gt;&lt;b&gt;VIEW (shared memory)&lt;/b&gt;"]
    flat --&gt; trans["A.T&lt;br/&gt;shape=[4,3], stride=[1,4], offset=0&lt;br/&gt;&lt;b&gt;VIEW (shared memory)&lt;/b&gt;"]

    trans --&gt; |"reshape(-1) on&lt;br/&gt;non-contiguous tensor"| copy["New flat memory&lt;br/&gt;&lt;b&gt;COPY (new allocation)&lt;/b&gt;"]

</pre>
</div>
<p></p><figcaption> View operations share memory; some operations force a copy when data is non-contiguous</figcaption> </figure><p></p>
</div>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Rule of Thumb
</div>
</div>
<div class="callout-body-container callout-body">
<p>If you chain <code>transpose</code> + <code>reshape</code>, you’re almost certainly triggering a copy. If you’re in a hot loop or a custom kernel, this matters. Profile with <code>torch.cuda.memory_allocated()</code> to catch surprise allocations.</p>
</div>
</div>
<p><strong>Key takeaway:</strong> Tensors are flat arrays dressed up with metadata. Operations that only change metadata (slice, transpose, broadcast) are free. Operations that need physically contiguous data may silently copy. Know which is which.</p>
</section>
</section>
<section id="broadcasting-and-its-gradient-implications" class="level2">
<h2 class="anchored" data-anchor-id="broadcasting-and-its-gradient-implications">Broadcasting and Its Gradient Implications</h2>
<p>Broadcasting is one of the most convenient features in numerical computing, and one of the most misunderstood when it comes to gradients.</p>
<section id="the-forward-pass-implicit-repetition" class="level3">
<h3 class="anchored" data-anchor-id="the-forward-pass-implicit-repetition">The Forward Pass: Implicit Repetition</h3>
<p>When you add a bias vector <code>b</code> of shape <code>[1, C]</code> to an activation matrix <code>A</code> of shape <code>[N, C]</code>, broadcasting logically <em>repeats</em> <code>b</code> along the batch dimension <code>N</code> times. But crucially, <strong>no data is copied</strong>. The framework simply sets the stride to 0 along the broadcast dimension, so the same values are read repeatedly.</p>
</section>
<section id="the-backward-pass-sum-reduce" class="level3">
<h3 class="anchored" data-anchor-id="the-backward-pass-sum-reduce">The Backward Pass: Sum-Reduce</h3>
<p>Here’s the subtle part. During the backward pass, if a value was broadcast (repeated) across a dimension, the gradients must be <strong>summed along that dimension</strong>. Why? Because the same parameter contributed to multiple outputs, so its total influence is the sum of all its partial effects.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    subgraph fwd["Forward: broadcast adds"]
        direction TB
        A_fwd["A: shape [N, C]"] --&gt; plus["+ (broadcast)"]
        b_fwd["b: shape [1, C]&lt;br/&gt;(stride 0 on dim 0)"] --&gt; plus
        plus --&gt; out_fwd["Output: shape [N, C]"]
    end

    subgraph bwd["Backward: sum-reduce"]
        direction TB
        grad_out["∂L/∂Output: shape [N, C]"] --&gt; sum_op["sum(dim=0)"]
        sum_op --&gt; grad_b["∂L/∂b: shape [1, C]"]
        grad_out --&gt; grad_A["∂L/∂A: shape [N, C]&lt;br/&gt;(passed through directly)"]
    end

    fwd --&gt; |"backward()"| bwd
</pre>
</div>
<p></p><figcaption> Broadcasting repeats values in the forward pass; gradients must sum-reduce along broadcast dimensions in the backward pass</figcaption> </figure><p></p>
</div>
</div>
</div>
<p><strong>Worked example:</strong></p>
<p>Suppose <code>A</code> has shape <code>[3, 2]</code> and <code>b</code> has shape <code>[1, 2]</code> with values <code>[0.5, -0.3]</code>. After broadcasting, every row of <code>A</code> gets the same bias added. If the upstream gradient <code>∂L/∂Output</code> is:</p>
<pre><code>[[1.0, 2.0],
 [0.5, 1.5],
 [0.3, 0.7]]</code></pre>
<p>Then <code>∂L/∂b = sum along dim 0 = [1.8, 4.2]</code>, because <code>b</code> influenced all three rows.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>General Rule
</div>
</div>
<div class="callout-body-container callout-body">
<p>For any operation in autograd: <strong>the gradient of a broadcast is a reduction, and the gradient of a reduction is a broadcast.</strong> This duality shows up everywhere: in loss functions, in normalization layers, and in attention mechanisms.</p>
</div>
</div>
<p><strong>Key takeaway:</strong> Broadcasting doesn’t copy data (strides handle it), but gradients must sum-reduce along every dimension that was broadcast. Forgetting this is a common source of shape mismatch bugs in custom autograd functions.</p>
</section>
</section>
<section id="hardware-acceleration-from-strides-to-silicon" class="level2">
<h2 class="anchored" data-anchor-id="hardware-acceleration-from-strides-to-silicon">Hardware Acceleration: From Strides to Silicon</h2>
<p>Understanding the hardware layer helps you write code that runs fast <em>by default</em> instead of fighting the machine.</p>
<section id="memory-alignment" class="level3">
<h3 class="anchored" data-anchor-id="memory-alignment">Memory Alignment</h3>
<p>Hardware loads data into caches in fixed-size chunks called <strong>cache lines</strong> (typically 64 bytes). If your data is aligned to cache line boundaries, a single load brings in exactly what you need. If it’s misaligned, you need <em>two</em> loads for data that spans a boundary, doubling the memory traffic for that access.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Practical Impact
</div>
</div>
<div class="callout-body-container callout-body">
<p>Memory alignment mostly matters for custom kernels and low-level code. High-level frameworks handle this for you. But if you’re writing CUDA kernels or using <code>ctypes</code> to interface with C libraries, ensure your allocations are aligned.</p>
</div>
</div>
</section>
<section id="parallelization-with-openmp" class="level3">
<h3 class="anchored" data-anchor-id="parallelization-with-openmp">Parallelization with OpenMP</h3>
<p>On CPU, the simplest form of parallelism is loop parallelization. Tools like <strong>OpenMP</strong> let you annotate a loop with <code>#pragma omp parallel for</code>, and the runtime splits iterations across CPU cores automatically.</p>
<p>This is the basis for CPU-accelerated tensor operations. Each core processes a different slice of the tensor, and the results are combined. The bottleneck shifts from compute to <strong>memory bandwidth</strong>: reading and writing large tensors becomes the limiting factor, not arithmetic.</p>
</section>
<section id="the-im2col-trick-convolution-as-matrix-multiplication" class="level3">
<h3 class="anchored" data-anchor-id="the-im2col-trick-convolution-as-matrix-multiplication">The im2col Trick: Convolution as Matrix Multiplication</h3>
<p>Convolution is the most compute-intensive operation in CNNs. The <strong>im2col</strong> (image-to-column) trick (<a href="https://arxiv.org/abs/1410.0759">Chetlur et al., 2014</a>) converts convolution into matrix multiplication, which lets us use heavily optimized BLAS routines.</p>
<p>The process for a batch of images (<code>N × H × W × Cᵢₙ</code>) with filters (<code>K × K × Cᵢₙ × Cₒᵤₜ</code>):</p>
<ol type="1">
<li>Create a 6D strided view: <code>N × H_out × W_out × K × K × Cᵢₙ</code></li>
<li>Reshape to a 2D im2col matrix: <code>(N·H_out·W_out) × (K·K·Cᵢₙ)</code></li>
<li>Reshape weights to 2D: <code>(K·K·Cᵢₙ) × Cₒᵤₜ</code></li>
<li>Matrix multiply: <code>im2col @ weights</code></li>
<li>Reshape result: <code>N × H_out × W_out × Cₒᵤₜ</code></li>
</ol>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>im2col Memory Overhead
</div>
</div>
<div class="callout-body-container callout-body">
<p>The im2col matrix is typically <strong>much larger</strong> than the original image tensor because filter patches overlap. Each input pixel appears in multiple rows of the im2col matrix. The reshape from the 6D strided view to 2D <em>cannot</em> be done as a view (the data isn’t contiguous in the right order), so it triggers a <strong>full copy</strong>. This is a significant memory cost: for large images with many channels, the im2col matrix can be several times the size of the input.</p>
<p><strong>When it helps:</strong> When your BLAS library is highly optimized (which it usually is). The speedup from using GEMM far outweighs the memory copy cost.</p>
<p><strong>When it hurts:</strong> When you’re memory-constrained. Alternative approaches like FFT-based convolution or Winograd transforms can reduce memory usage at the cost of implementation complexity.</p>
</div>
</div>
<p><strong>Key takeaway:</strong> The gap between “logical operations on tensors” and “what the hardware actually does” is large. Frameworks bridge it with tricks like im2col, cache-aware memory layout, and loop parallelization. When performance matters, understanding this layer is essential.</p>
</section>
</section>
<section id="weight-initialization-the-effects-that-persist" class="level2">
<h2 class="anchored" data-anchor-id="weight-initialization-the-effects-that-persist">Weight Initialization: The Effects That Persist</h2>
<p>Weight initialization might seem like a minor detail: just pick some random numbers and start training. But the evidence tells a more nuanced story. I implement several initialization schemes from scratch and watch their effect on training in <a href="../../posts/coding-nn/param-initialization/Coding-Neural-Network-Parameters-Initialization.html">Coding Neural Network: Parameters’ Initialization</a>.</p>
<section id="why-initialization-matters" class="level3">
<h3 class="anchored" data-anchor-id="why-initialization-matters">Why Initialization Matters</h3>
<p>Two observations that changed how I think about initialization:</p>
<ol type="1">
<li><p><strong>The effect of initialization persists throughout training.</strong> Bad initialization affects the relative norms of activations and gradients <em>at every step</em>. If you don’t initialize appropriately (e.g., using a standard deviation of <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7B%5Cfrac%7B2%7D%7Bn%7D%7D"> for ReLU networks, known as He initialization), the L2-norm of activations or gradients will drift: vanishing signals or exploding values.</p></li>
<li><p><strong>Weights don’t move far from their initial values.</strong> This is surprising. If you plot the variance of weights before and after training for each layer, you’ll see remarkably similar values. The weights shift in certain directions, but relative to their initial magnitude, the change is small especially for deep networks.</p></li>
</ol>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>The Implication
</div>
</div>
<div class="callout-body-container callout-body">
<p>Together, these observations mean initialization isn’t just “where you start”, it effectively defines the <em>neighborhood</em> of weight space you’ll explore during training. Proper initialization puts you in a good neighborhood. Bad initialization puts you somewhere the optimizer can’t easily escape.</p>
</div>
</div>
</section>
<section id="why-sqrt2n" class="level3">
<h3 class="anchored" data-anchor-id="why-sqrt2n">Why <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7B2/n%7D"></h3>
<p>Where does the <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7B2/n%7D"> come from? Take one layer <img src="https://latex.codecogs.com/png.latex?y%20=%20Wx"> with fan-in <img src="https://latex.codecogs.com/png.latex?n">, weights drawn i.i.d. with mean zero and variance <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2">, and an input <img src="https://latex.codecogs.com/png.latex?x"> whose components are i.i.d. Each output is a sum of <img src="https://latex.codecogs.com/png.latex?n"> independent products, so</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Coperatorname%7BVar%7D(y)%20=%20n%5C,%5Csigma%5E2%5C,%5Coperatorname%7BVar%7D(x)."></p>
<p>For the signal to neither grow nor shrink through the layer, we need <img src="https://latex.codecogs.com/png.latex?n%5Csigma%5E2%20=%201">, i.e.&nbsp;<img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2%20=%201/n">. That is <a href="http://proceedings.mlr.press/v9/glorot10a.html">Xavier initialization</a>, and it is right for a <em>linear</em> layer. A ReLU network is not linear: ReLU zeroes the negative half of a zero-mean pre-activation, halving the second moment of what passes through, <img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D%5B%5Coperatorname%7BReLU%7D(y)%5E2%5D%20=%20%5Ctfrac%7B1%7D%7B2%7D%5Cmathbb%7BE%7D%5By%5E2%5D">. Folding that factor into the balance condition gives <img src="https://latex.codecogs.com/png.latex?%5Ctfrac%7B1%7D%7B2%7Dn%5Csigma%5E2%20=%201">, so <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2%20=%202/n">. That is <a href="https://arxiv.org/abs/1502.01852">He initialization</a>, and the factor of 2 is the entire difference from Xavier: it compensates for the half of the signal ReLU discards. Under Xavier each ReLU layer loses a factor of <img src="https://latex.codecogs.com/png.latex?%5Csqrt2"> in activation scale, so after <img src="https://latex.codecogs.com/png.latex?L"> layers the signal is down by <img src="https://latex.codecogs.com/png.latex?(1/%5Csqrt2)%5E%7BL%7D">; by 40 layers that is about <img src="https://latex.codecogs.com/png.latex?10%5E%7B-6%7D">.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/init-signal-preservation.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure: Activation RMS by depth in a 40-layer ReLU network, averaged over 30 random inputs at width 512. He (\sigma=\sqrt{2/n}) holds the signal flat; Xavier (\sigma=\sqrt{1/n}) ignores the ReLU factor of 2 and decays by 1/\sqrt2 per layer; a naive \sigma=0.02 collapses within a few layers. The vanishing-gradient problem at initialization is the same curve read backward."><img src="https://imaddabbura.github.io/posts/mlsys/images/init-signal-preservation.svg" class="img-fluid figure-img" alt="Figure: Activation RMS by depth in a 40-layer ReLU network, averaged over 30 random inputs at width 512. He (\sigma=\sqrt{2/n}) holds the signal flat; Xavier (\sigma=\sqrt{1/n}) ignores the ReLU factor of 2 and decays by 1/\sqrt2 per layer; a naive \sigma=0.02 collapses within a few layers. The vanishing-gradient problem at initialization is the same curve read backward."></a></p>
<figcaption><strong>Figure:</strong> Activation RMS by depth in a 40-layer ReLU network, averaged over 30 random inputs at width 512. He (<img src="https://latex.codecogs.com/png.latex?%5Csigma=%5Csqrt%7B2/n%7D">) holds the signal flat; Xavier (<img src="https://latex.codecogs.com/png.latex?%5Csigma=%5Csqrt%7B1/n%7D">) ignores the ReLU factor of 2 and decays by <img src="https://latex.codecogs.com/png.latex?1/%5Csqrt2"> per layer; a naive <img src="https://latex.codecogs.com/png.latex?%5Csigma=0.02"> collapses within a few layers. The vanishing-gradient problem at initialization is the same curve read backward.</figcaption>
</figure>
</div>
</section>
<section id="how-to-diagnose-initialization-problems" class="level3">
<h3 class="anchored" data-anchor-id="how-to-diagnose-initialization-problems">How to Diagnose Initialization Problems</h3>
<p><strong>Monitor two metrics across layers over all training iterations:</strong></p>
<ul>
<li><strong>Norm of weights</strong> per layer</li>
<li><strong>Norm of gradients</strong> per layer</li>
</ul>
<p>If the weight norms explode or collapse across layers, or if gradient norms vary by orders of magnitude between early and late layers, your initialization is likely wrong. Proper initialization keeps these norms roughly stable across layers.</p>
<p><strong>Key takeaway:</strong> Proper weight initialization speeds up training and leads to lower final error rates. It defines the effective search region for your optimizer, and its influence persists throughout training.</p>
</section>
</section>
<section id="normalization-fixing-what-initialization-cant" class="level2">
<h2 class="anchored" data-anchor-id="normalization-fixing-what-initialization-cant">Normalization: Fixing What Initialization Can’t</h2>
<p>If we know that activation norms can drift during training (due to imperfect initialization or the dynamics of optimization itself), why not just <em>force</em> them to be well-behaved? That’s the idea behind normalization layers.</p>
<section id="batch-normalization" class="level3">
<h3 class="anchored" data-anchor-id="batch-normalization">Batch Normalization</h3>
<p><a href="https://arxiv.org/abs/1502.03167">Batch Normalization</a> normalizes activations <strong>across the batch dimension</strong> for each feature independently. For a given feature, it computes the mean and variance across all examples in the batch, then normalizes to zero mean and unit variance.</p>
<p><strong>When it helps:</strong></p>
<ul>
<li>Dramatically speeds up training by maintaining stable activation norms</li>
<li>Preserves the discriminative information <em>between features</em> within each layer (because normalization is per-feature, not per-example)</li>
</ul>
<p><strong>When it hurts:</strong></p>
<ul>
<li>Creates <strong>dependency between samples</strong> in a batch: each example’s normalized activation depends on the other examples in the batch</li>
<li><strong>Unstable with small batches</strong>: statistics become noisy, and with a batch of 1, the variance is undefined</li>
<li><strong>Doesn’t work well with RNNs</strong>: the hidden state has temporal dependencies across time steps, and computing batch statistics independently at each time step ignores this structure</li>
</ul>
</section>
<section id="layer-normalization" class="level3">
<h3 class="anchored" data-anchor-id="layer-normalization">Layer Normalization</h3>
<p><a href="https://arxiv.org/abs/1607.06450">Layer Normalization</a> normalizes <strong>across all features</strong> for each sample independently. No dependency on other samples in the batch.</p>
<p><strong>When it helps:</strong></p>
<ul>
<li>Works with <strong>any batch size</strong>, including batch size 1</li>
<li><strong>Perfect for RNNs and Transformers</strong>: it normalizes across the embedding dimension for each token in each example, respecting temporal structure</li>
<li>This is why it’s the standard in Transformer architectures</li>
</ul>
<p><strong>When it hurts:</strong></p>
<ul>
<li>For fully connected networks, forcing zero mean and unit variance <em>across features</em> can destroy the relative magnitude differences between activations for different examples. These magnitude differences can be an important discriminative signal.</li>
<li>This makes it harder to drive loss low on tasks where inter-example feature magnitude differences matter</li>
</ul>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Choosing Between Them
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Use BatchNorm</strong> for CNNs with reasonably large batches (≥32). <strong>Use LayerNorm</strong> for Transformers, RNNs, and any setting where batch size is small or variable.</p>
</div>
</div>
<p><strong>Key takeaway:</strong> Normalization layers fix the activation drift that initialization can only partially prevent. BatchNorm and LayerNorm make different trade-offs about <em>what to normalize over</em>, and the right choice depends on your architecture and batch size.</p>
</section>
</section>
<section id="regularization-controlling-complexity" class="level2">
<h2 class="anchored" data-anchor-id="regularization-controlling-complexity">Regularization: Controlling Complexity</h2>
<p>Regularization prevents models from memorizing the training data, forcing them to learn patterns that generalize to unseen examples.</p>
<section id="implicit-regularization" class="level3">
<h3 class="anchored" data-anchor-id="implicit-regularization">Implicit Regularization</h3>
<p>Before you add <em>any</em> explicit regularization, your training procedure already constrains the model. <strong>SGD with a particular initialization</strong> only explores a subset of all possible neural networks. The initialization defines the starting point, and the optimizer’s dynamics (step size, momentum, batch sampling) determine the trajectory through weight space. I derive SGD, momentum, and Adam from scratch in <a href="../../posts/optimization/gradient-descent.html">Gradient Descent and Its Variants</a>.</p>
<p>This is called <em>implicit regularization</em>, and it’s powerful. The fact that SGD-trained networks generalize well, even when they have enough capacity to memorize the training set, is partly due to these implicit biases of the optimization procedure.</p>
</section>
<section id="explicit-regularization" class="level3">
<h3 class="anchored" data-anchor-id="explicit-regularization">Explicit Regularization</h3>
<p>Explicit regularization directly limits the functions the model can learn:</p>
<p><strong>L2 Regularization</strong> adds a penalty proportional to the squared magnitude of the weights. The premise: smoother functions (which don’t change dramatically for small input changes) tend to have smaller weights. By penalizing large weights, we encourage smoother, simpler functions. I code L2 regularization from scratch in <a href="../../posts/coding-nn/regularization/Coding-Neural-Network-Regularization.html">Coding Neural Network: Regularization</a>.</p>
<p><strong><a href="https://jmlr.org/papers/v15/srivastava14a.html">Dropout</a></strong> randomly zeroes out activations with probability <img src="https://latex.codecogs.com/png.latex?p"> during training. A useful mental model: dropout is a <em>stochastic approximation</em> of each layer’s activations, similar to how SGD approximates the full gradient with a mini-batch sample. During inference, we multiply activations by <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B1%7D%7B1-p%7D"> (or equivalently, scale during training) to keep the expected value consistent. I implement dropout from scratch in <a href="../../posts/coding-nn/dropout/Coding-Neural-Network-Dropout.html">Coding Neural Network: Dropout</a>.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>L2 Regularization ≠ Weight Decay (for Adam!)
</div>
</div>
<div class="callout-body-container callout-body">
<p>For vanilla SGD, L2 regularization and weight decay are mathematically equivalent. But for adaptive optimizers like <strong>Adam</strong>, they are <em>not</em> the same.</p>
<p>Why? Adam computes first and second moments of the gradients. If you add the L2 penalty to the gradient (L2 regularization), the penalty gets scaled by Adam’s adaptive learning rate, making it <strong>less effective</strong> than intended. Weight decay, which adds the penalty directly to the parameter update step <em>without</em> modifying the gradient, avoids this issue.</p>
<p>This distinction was first identified in the <a href="https://arxiv.org/abs/1711.05101">“Decoupled Weight Decay” paper</a> (AdamW), which is why AdamW is preferred over Adam + L2 regularization in practice.</p>
</div>
</div>
<p><strong>Key takeaway:</strong> Regularization operates at two levels: the implicit biases of SGD and initialization, and explicit penalties like L2/weight decay and dropout. For Adam-family optimizers, always use weight decay (AdamW), not L2 regularization.</p>
</section>
</section>
<section id="scaling-up-when-one-gpu-isnt-enough" class="level2">
<h2 class="anchored" data-anchor-id="scaling-up-when-one-gpu-isnt-enough">Scaling Up: When One GPU Isn’t Enough</h2>
<p>Large datasets demand large models, and large models push hardware to its limits. Here’s how the systems community addresses this.</p>
<section id="the-memory-bottleneck" class="level3">
<h3 class="anchored" data-anchor-id="the-memory-bottleneck">The Memory Bottleneck</h3>
<p>The memory hierarchy tells the story:</p>
<ul>
<li><strong>Shared memory per core (GPU):</strong> ~64 KB, fast and tiny</li>
<li><strong>Global GPU memory:</strong> 10–80 GB depending on the device, the typical bottleneck</li>
<li><strong>CPU RAM:</strong> 64–512 GB, large but slow to access from GPU</li>
</ul>
<p>Most large models can’t fit entirely in GPU global memory during training, because we need to store: model parameters, optimizer state (2x or 3x model size for Adam), activations (saved for backward), and gradients.</p>
</section>
<section id="memory-saving-techniques" class="level3">
<h3 class="anchored" data-anchor-id="memory-saving-techniques">Memory-Saving Techniques</h3>
<section id="inference-buffer-reuse" class="level4">
<h4 class="anchored" data-anchor-id="inference-buffer-reuse">Inference: Buffer Reuse</h4>
<p>During inference, we don’t need to keep activations for backward. We can reuse a small set of buffers (2 or 3) across layers, writing each layer’s output into a buffer that a previous layer no longer needs. This reduces memory from <code>O(N)</code> to <code>O(1)</code> in the number of layers.</p>
</section>
<section id="training-activation-checkpointing" class="level4">
<h4 class="anchored" data-anchor-id="training-activation-checkpointing">Training: Activation Checkpointing</h4>
<p>During training, we normally keep <em>all</em> activations for the backward pass. Checkpointing trades memory for compute:</p>
<ol type="1">
<li>Divide the network into <strong>segments</strong> of roughly <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7BN%7D"> layers</li>
<li>Only store activations at <strong>segment boundaries</strong> (checkpoints)</li>
<li>During the backward pass, <strong>recompute</strong> the forward pass within each segment to recover the needed activations</li>
</ol>
<p>Why <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7BN%7D"> segments? Store a checkpoint every <img src="https://latex.codecogs.com/png.latex?k"> layers. During the backward pass two things sit in memory: the checkpoints themselves, <img src="https://latex.codecogs.com/png.latex?N/k"> of them, and, while recomputing one segment, up to <img src="https://latex.codecogs.com/png.latex?k"> activations inside it. Peak activation memory is therefore <img src="https://latex.codecogs.com/png.latex?M(k)%20=%20N/k%20+%20k">. Minimizing over <img src="https://latex.codecogs.com/png.latex?k"> gives <img src="https://latex.codecogs.com/png.latex?M'(k)%20=%20-N/k%5E2%20+%201%20=%200">, so <img src="https://latex.codecogs.com/png.latex?k%20=%20%5Csqrt%20N"> and the peak is <img src="https://latex.codecogs.com/png.latex?2%5Csqrt%20N">. Storing everything, <img src="https://latex.codecogs.com/png.latex?O(N)">, drops to <img src="https://latex.codecogs.com/png.latex?O(%5Csqrt%20N)"> for the price of recomputing each segment once, a single extra forward pass (<a href="https://arxiv.org/abs/1604.06174">Chen et al., 2016</a>).</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/checkpointing-tradeoff.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure: Peak activation memory N/k + k against segment length for a 64-layer network. More checkpoints (small k) trade off against a larger recompute buffer (large k); the sum bottoms out at k=\sqrt N = 8 with peak 2\sqrt N = 16, a 4x reduction from storing all 64."><img src="https://imaddabbura.github.io/posts/mlsys/images/checkpointing-tradeoff.svg" class="img-fluid figure-img" alt="Figure: Peak activation memory N/k + k against segment length for a 64-layer network. More checkpoints (small k) trade off against a larger recompute buffer (large k); the sum bottoms out at k=\sqrt N = 8 with peak 2\sqrt N = 16, a 4x reduction from storing all 64."></a></p>
<figcaption><strong>Figure:</strong> Peak activation memory <img src="https://latex.codecogs.com/png.latex?N/k%20+%20k"> against segment length for a 64-layer network. More checkpoints (small <img src="https://latex.codecogs.com/png.latex?k">) trade off against a larger recompute buffer (large <img src="https://latex.codecogs.com/png.latex?k">); the sum bottoms out at <img src="https://latex.codecogs.com/png.latex?k=%5Csqrt%20N%20=%208"> with peak <img src="https://latex.codecogs.com/png.latex?2%5Csqrt%20N%20=%2016">, a 4x reduction from storing all 64.</figcaption>
</figure>
</div>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    subgraph seg1["Segment 1"]
        L1["Layer 1"] --&gt; L2["Layer 2"] --&gt; L3["Layer 3"]
    end
    subgraph seg2["Segment 2"]
        L4["Layer 4"] --&gt; L5["Layer 5"] --&gt; L6["Layer 6"]
    end
    subgraph seg3["Segment 3"]
        L7["Layer 7"] --&gt; L8["Layer 8"] --&gt; L9["Layer 9"]
    end

    seg1 --&gt; |"✓ checkpoint"| seg2
    seg2 --&gt; |"✓ checkpoint"| seg3

</pre>
</div>
<p></p><figcaption> Activation checkpointing: store only segment boundaries, recompute the rest during backward</figcaption> </figure><p></p>
</div>
</div>
</div>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Approach</th>
<th>Memory</th>
<th>Compute Overhead</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>No checkpointing</td>
<td><code>O(N)</code> activations</td>
<td>None</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?%5Csqrt%7BN%7D"> checkpoints</td>
<td><code>O(√N)</code> activations</td>
<td>~1 extra forward pass</td>
</tr>
<tr class="odd">
<td>Aggressive checkpointing</td>
<td><code>O(1)</code> activations</td>
<td>Up to <code>N</code> extra forward passes</td>
</tr>
</tbody>
</table>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Smart Checkpoint Placement
</div>
</div>
<div class="callout-body-container callout-body">
<p>Choose checkpoints at layers with <strong>cheap recomputation</strong>. ReLU activations are trivial to recompute (just check sign). Convolution or attention layers are expensive. Checkpoint <em>after</em> cheap layers to minimize the recomputation cost.</p>
</div>
</div>
</section>
</section>
<section id="distributed-training-data-and-model-parallelism" class="level3">
<h3 class="anchored" data-anchor-id="distributed-training-data-and-model-parallelism">Distributed Training: Data and Model Parallelism</h3>
<p>When one GPU isn’t enough, we spread the work across multiple devices. There are two fundamental strategies:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart TD
    DT["Distributed Training"] --&gt; DP["&lt;b&gt;Data Parallelism&lt;/b&gt;&lt;br/&gt;Same model, different data"]
    DT --&gt; MP["&lt;b&gt;Model Parallelism&lt;/b&gt;&lt;br/&gt;Different parts of model"]

    DP --&gt; PS["Parameter Server&lt;br/&gt;Central coordinator"]
    DP --&gt; AR["AllReduce&lt;br/&gt;Peer-to-peer"]

    MP --&gt; TP["Tensor Parallelism&lt;br/&gt;Split layers across devices"]
    MP --&gt; PP["Pipeline Parallelism&lt;br/&gt;Different layers on different devices"]

</pre>
</div>
<p></p><figcaption> Taxonomy of distributed training approaches</figcaption> </figure><p></p>
</div>
</div>
</div>
<section id="data-parallelism" class="level4">
<h4 class="anchored" data-anchor-id="data-parallelism">Data Parallelism</h4>
<p>Every worker runs a <strong>full replica of the model</strong> on a different micro-batch. Since gradients are additive (they’re independent across examples), we just need to sum them across workers before performing the weight update.</p>
<p>Two coordination strategies:</p>
<ul>
<li><p><strong>Parameter Server:</strong> A central server collects gradients from all workers, sums them, performs the update, and broadcasts the new weights. Workers can start sending gradients as soon as they’re computed (layer by layer), overlapping communication with computation.</p>
<ul>
<li><strong>Bottleneck:</strong> The parameter server becomes a communication bottleneck as the number of workers grows. All traffic flows through one node.</li>
</ul></li>
<li><p><strong>AllReduce:</strong> A peer-to-peer approach where all workers collectively sum their gradients and each receives the result. No central bottleneck: communication scales more gracefully. Algorithms like <a href="https://arxiv.org/abs/1802.05799">Ring-AllReduce</a> distribute the bandwidth load evenly.</p>
<ul>
<li><strong>Bottleneck:</strong> Total communication volume still grows with model size. Network bandwidth between nodes becomes the limiting factor.</li>
</ul></li>
</ul>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>When Communication Dominates
</div>
</div>
<div class="callout-body-container callout-body">
<p>Communication overhead dominates training time when:</p>
<ul>
<li><strong>Model is large</strong> relative to batch computation time (small compute-to-communication ratio)</li>
<li><strong>Network bandwidth is low</strong> (especially across nodes vs.&nbsp;within a node with NVLink)</li>
<li><strong>Gradient compression</strong> isn’t used</li>
</ul>
<p>Rule of thumb: if your per-step compute time is less than 3x the gradient synchronization time, communication is your bottleneck. Scale batch size or use gradient compression/accumulation to amortize the cost.</p>
</div>
</div>
</section>
<section id="model-parallelism-pipeline-parallelism" class="level4">
<h4 class="anchored" data-anchor-id="model-parallelism-pipeline-parallelism">Model Parallelism (Pipeline Parallelism)</h4>
<p>When the model itself doesn’t fit on one device, we split the computation graph across devices (<a href="https://arxiv.org/abs/1811.06965">Huang et al., 2019</a>). Each device handles a different set of layers, and they <strong>pipeline</strong> the computation: while device 2 processes micro-batch 1, device 1 can start on micro-batch 2.</p>
<p>Communication happens at layer boundaries via <code>send</code>/<code>recv</code> operations. The challenge is minimizing <strong>pipeline bubbles</strong>, which is idle time when a device is waiting for input from the previous stage.</p>
<p><strong>Key takeaway:</strong> Scaling from one GPU to many introduces a new bottleneck: communication. Data parallelism is simpler and scales well when the model fits on one device. Model/pipeline parallelism is necessary when it doesn’t, but introduces pipeline bubbles and more complex communication patterns.</p>
</section>
</section>
</section>
<section id="neural-network-architectures-through-a-systems-lens" class="level2">
<h2 class="anchored" data-anchor-id="neural-network-architectures-through-a-systems-lens">Neural Network Architectures Through a Systems Lens</h2>
<p>The remaining sections cover architectures not as algorithmic curiosities, but as <em>systems design decisions</em>: what problem does each one solve, and what trade-off does it introduce?</p>
<section id="convolutional-neural-networks-cnns" class="level3">
<h3 class="anchored" data-anchor-id="convolutional-neural-networks-cnns">Convolutional Neural Networks (CNNs)</h3>
<p>CNNs exploit three structural priors about spatial data:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Property</th>
<th>What It Means</th>
<th>Systems Benefit</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Parameter sharing</strong></td>
<td>Same filter everywhere in the image</td>
<td>Massive reduction in parameters</td>
</tr>
<tr class="even">
<td><strong>Sparse connectivity</strong></td>
<td>Each output depends only on a local receptive field</td>
<td>Few computations per output pixel</td>
</tr>
<tr class="odd">
<td><strong>Translation equivariance</strong></td>
<td>Shifting input shifts output the same way</td>
<td>No need to learn position-specific detectors</td>
</tr>
</tbody>
</table>
<p><strong>Dilation</strong> increases the receptive field without increasing parameters: each filter element is spread out by a dilation factor, giving access to a larger spatial area. This is particularly useful for temporal problems where context matters.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Convolution as Matrix Multiplication
</div>
</div>
<div class="callout-body-container callout-body">
<p>We can express convolution as a matrix multiplication where the weight matrix has a specific sparsity pattern (filled with actual weights and zeros reflecting the filter structure). We don’t actually construct this matrix (it would be enormous), but this view explains why the backward pass of a convolution is a convolution with a flipped filter: multiplying by the transpose of the convolution matrix is equivalent to convolving with the spatially flipped kernel.</p>
</div>
</div>
</section>
<section id="recurrent-neural-networks-rnns" class="level3">
<h3 class="anchored" data-anchor-id="recurrent-neural-networks-rnns">Recurrent Neural Networks (RNNs)</h3>
<p>RNNs address temporal dependencies by maintaining a <strong>hidden state</strong> that gets updated at each time step as a function of the current input and the previous hidden state. In theory, the last hidden state captures the entire input history.</p>
<p>In practice, the hidden state is a bottleneck. The entire past is <em>compacted</em> into a single vector, and information from early time steps (<img src="https://latex.codecogs.com/png.latex?x_1">) gets diluted compared to recent ones (<img src="https://latex.codecogs.com/png.latex?x_t">).</p>
<p><strong>Backpropagation Through Time (BPTT):</strong> Because weights are shared across time steps, gradients must flow through the entire unrolled sequence. If the dominant eigenvalue of the weight matrix is less than 1, gradients <strong>vanish</strong> exponentially with sequence length. Greater than 1, they <strong>explode</strong>.</p>
<p>Concretely, the gradient reaching an early hidden state is a product of one per-step Jacobian for every step it travels back, so along the repeated direction it scales like <img src="https://latex.codecogs.com/png.latex?%5Crho%5E%7B%5C,t%7D">, where <img src="https://latex.codecogs.com/png.latex?%5Crho"> is that dominant eigenvalue magnitude and <img src="https://latex.codecogs.com/png.latex?t"> is the number of steps. Any <img src="https://latex.codecogs.com/png.latex?%5Crho%20%5Cneq%201"> is unstable over long sequences, and no single value is safe at every horizon.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/bptt-gradient-norm.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure: Gradient norm against how many steps it propagates back, for three spectral radii. Only \rho=1 is stable; \rho=0.9 has lost three orders of magnitude by 70 steps and \rho=1.1 has gained three. This is why a vanilla RNN cannot learn long-range dependencies, and why the fix is architectural rather than a matter of tuning."><img src="https://imaddabbura.github.io/posts/mlsys/images/bptt-gradient-norm.svg" class="img-fluid figure-img" alt="Figure: Gradient norm against how many steps it propagates back, for three spectral radii. Only \rho=1 is stable; \rho=0.9 has lost three orders of magnitude by 70 steps and \rho=1.1 has gained three. This is why a vanilla RNN cannot learn long-range dependencies, and why the fix is architectural rather than a matter of tuning."></a></p>
<figcaption><strong>Figure:</strong> Gradient norm against how many steps it propagates back, for three spectral radii. Only <img src="https://latex.codecogs.com/png.latex?%5Crho=1"> is stable; <img src="https://latex.codecogs.com/png.latex?%5Crho=0.9"> has lost three orders of magnitude by 70 steps and <img src="https://latex.codecogs.com/png.latex?%5Crho=1.1"> has gained three. This is why a vanilla RNN cannot learn long-range dependencies, and why the fix is architectural rather than a matter of tuning.</figcaption>
</figure>
</div>
</section>
<section id="lstm-gating-the-information-flow" class="level3">
<h3 class="anchored" data-anchor-id="lstm-gating-the-information-flow">LSTM: Gating the Information Flow</h3>
<p><a href="https://doi.org/10.1162/neco.1997.9.8.1735">LSTMs</a> address vanishing gradients by separating the hidden state into two components:</p>
<ul>
<li><strong>Cell state</strong>: A “highway” for long-range information flow</li>
<li><strong>Hidden state</strong>: The working memory exposed to the next layer</li>
</ul>
<p>Four gates (learned transformations) control information flow at each step:</p>
<ol type="1">
<li><strong>Forget gate</strong>: What information from the cell state to discard</li>
<li><strong>Input gate</strong>: What new information to add to the cell state</li>
<li><strong>Cell update</strong>: The candidate new information</li>
<li><strong>Output gate</strong>: What to expose as the hidden state</li>
</ol>
<p>I build an LSTM cell from scratch, gate by gate, and profile where the time actually goes in <a href="../../posts/nlp/LSTM-Annotated-Implementation.html">Inside LSTMs</a>.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>LSTMs Don’t Fully Solve Long-Range Dependencies
</div>
</div>
<div class="callout-body-container callout-body">
<p>Despite the gating mechanism, both RNNs and LSTMs struggle with information far in the past. Recent tokens have a much more direct connection to the current hidden state. The cell state highway helps, but it’s not a complete solution for very long sequences. This is the fundamental motivation for attention mechanisms.</p>
</div>
</div>
</section>
<section id="transformers-global-receptive-field-via-attention" class="level3">
<h3 class="anchored" data-anchor-id="transformers-global-receptive-field-via-attention">Transformers: Global Receptive Field via Attention</h3>
<p><a href="https://arxiv.org/abs/1706.03762">Transformers</a> replace recurrence with <strong>attention</strong>, which gives every position direct access to every other position, a global receptive field.</p>
<p>However, the attention mechanism is inherently <strong>order-invariant</strong>: permuting the input tokens permutes the output in the same way. There’s no notion of “first” or “last.” This is why <strong>positional encodings</strong> are essential: they inject order information that attention alone cannot capture.</p>
<p>For <strong>autoregressive tasks</strong> (language modeling, text generation), a causal mask restricts each position to attend only to current and previous positions, preserving the left-to-right generation constraint. I walk through attention, positional encodings, and masking in <a href="../../posts/nlp/Transformer-Architecture-Explained.html">The Transformer Architecture: A Deep Dive</a>, then assemble them into a working model in <a href="../../posts/nlp/GPT2-From-Scratch.html">Building GPT from Scratch</a>.</p>
</section>
<section id="gans-adversarial-generation" class="level3">
<h3 class="anchored" data-anchor-id="gans-adversarial-generation">GANs: Adversarial Generation</h3>
<p><a href="https://arxiv.org/abs/1406.2661">GANs</a> learn to generate data by pitting two networks against each other:</p>
<ul>
<li><strong>Generator</strong>: Takes a random noise vector and tries to produce realistic images. Its objective is to <em>maximize</em> the discriminator’s error, making the discriminator believe the fake images are real.</li>
<li><strong>Discriminator</strong>: Receives both real and generated images and tries to classify them correctly. It <em>minimizes</em> its classification loss.</li>
</ul>
<p>The discriminator acts as a learned loss function that guides the generator toward producing increasingly realistic outputs. The “adversarial” aspect refers to the generator learning to exploit subtle distributional differences that are imperceptible to humans.</p>
<p><strong>Conv2dTranspose (Deconvolution):</strong> The generator typically needs to upsample from a small latent vector to a full-resolution image. Transposed convolution reverses the spatial dimension change of convolution: taking a small spatial input and producing a larger spatial output.</p>
<p><strong>Key takeaway:</strong> Each architecture encodes different assumptions about data structure. CNNs assume spatial locality. RNNs assume temporal ordering. Transformers assume that global relationships matter and let attention learn what to focus on. GANs assume that the best loss function is a learned one.</p>
</section>
</section>
<section id="model-deployment-considerations" class="level2">
<h2 class="anchored" data-anchor-id="model-deployment-considerations">Model Deployment Considerations</h2>
<p>Training a model is only half the battle. Deploying it introduces a different set of constraints:</p>
<ul>
<li><strong>Application environment restrictions</strong>: Model size limits, no Python runtime available (embedded/mobile)</li>
<li><strong>Hardware acceleration</strong>: Leveraging mobile GPUs, NPUs, or specialized CPU instructions (AVX, NEON)</li>
<li><strong>Integration</strong>: Fitting into existing application architectures and serving infrastructure</li>
</ul>
<p>These constraints often drive post-training optimizations like quantization, pruning, distillation, and conversion to inference-specific formats (ONNX, TensorRT, Core ML).</p>
</section>
<section id="how-the-layers-interact" class="level2">
<h2 class="anchored" data-anchor-id="how-the-layers-interact">How the Layers Interact</h2>
<p>If you’ve made it this far, you’ve traced the full stack of a deep learning system:</p>
<ol type="1">
<li><strong>Framework design</strong> determines your development experience and optimization ceiling</li>
<li><strong>Autograd</strong> gives you gradients but demands memory for saved tensors</li>
<li><strong>Memory layout</strong> (strides, views, contiguity) determines whether operations are free or expensive</li>
<li><strong>Hardware acceleration</strong> turns logical operations into physical memory accesses and arithmetic</li>
<li><strong>Initialization and normalization</strong> keep training stable from start to finish</li>
<li><strong>Regularization</strong> prevents overfitting at both implicit and explicit levels</li>
<li><strong>Scaling</strong> trades communication overhead for the ability to train larger models</li>
<li><strong>Architecture choices</strong> encode structural assumptions about your data</li>
</ol>
<p>These layers interact. Autograd’s saved tensors create memory pressure, which motivates checkpointing, which trades memory for recomputation. Initialization determines activation norms, which normalization layers can stabilize, which affects gradient flow, which determines whether training converges. Strides determine memory access patterns, which determine kernel performance, which determines whether you’re compute-bound or memory-bound.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Why Building One Helps
</div>
</div>
<div class="callout-body-container callout-body">
<p>The next time training is slow, memory is exploding, or loss isn’t decreasing, you’ll have a mental model of the full stack to reason about where the problem might be. That’s the real value of building a framework from scratch.</p>
</div>
</div>
</section>
<section id="references" class="level2">
<h2 class="anchored" data-anchor-id="references">References</h2>
<ul>
<li><strong>Rumelhart, D., Hinton, G., &amp; Williams, R.</strong> (1986). <a href="https://www.nature.com/articles/323533a0">Learning Representations by Back-Propagating Errors</a>. <em>Nature</em>, 323.</li>
<li><strong>Hochreiter, S. &amp; Schmidhuber, J.</strong> (1997). <a href="https://doi.org/10.1162/neco.1997.9.8.1735">Long Short-Term Memory</a>. <em>Neural Computation</em>, 9(8).</li>
<li><strong>Glorot, X. &amp; Bengio, Y.</strong> (2010). <a href="http://proceedings.mlr.press/v9/glorot10a.html">Understanding the Difficulty of Training Deep Feedforward Neural Networks</a>. <em>AISTATS</em>.</li>
<li><strong>Jia, Y. et al.</strong> (2014). <a href="https://arxiv.org/abs/1408.5093">Caffe: Convolutional Architecture for Fast Feature Embedding</a>. <em>ACM MM</em>.</li>
<li><strong>Goodfellow, I. et al.</strong> (2014). <a href="https://arxiv.org/abs/1406.2661">Generative Adversarial Networks</a>. <em>NeurIPS</em>.</li>
<li><strong>Chetlur, S. et al.</strong> (2014). <a href="https://arxiv.org/abs/1410.0759">cuDNN: Efficient Primitives for Deep Learning</a>.</li>
<li><strong>Srivastava, N. et al.</strong> (2014). <a href="https://jmlr.org/papers/v15/srivastava14a.html">Dropout: A Simple Way to Prevent Neural Networks from Overfitting</a>. <em>JMLR</em>, 15.</li>
<li><strong>He, K. et al.</strong> (2015). <a href="https://arxiv.org/abs/1502.01852">Delving Deep into Rectifiers</a>. <em>ICCV</em>.</li>
<li><strong>Ioffe, S. &amp; Szegedy, C.</strong> (2015). <a href="https://arxiv.org/abs/1502.03167">Batch Normalization</a>. <em>ICML</em>.</li>
<li><strong>Abadi, M. et al.</strong> (2016). <a href="https://arxiv.org/abs/1605.08695">TensorFlow: A System for Large-Scale Machine Learning</a>. <em>OSDI</em>.</li>
<li><strong>Ba, J., Kiros, J., &amp; Hinton, G.</strong> (2016). <a href="https://arxiv.org/abs/1607.06450">Layer Normalization</a>.</li>
<li><strong>Chen, T. et al.</strong> (2016). <a href="https://arxiv.org/abs/1604.06174">Training Deep Nets with Sublinear Memory Cost</a>.</li>
<li><strong>Vaswani, A. et al.</strong> (2017). <a href="https://arxiv.org/abs/1706.03762">Attention Is All You Need</a>. <em>NeurIPS</em>.</li>
<li><strong>Sergeev, A. &amp; Del Balso, M.</strong> (2018). <a href="https://arxiv.org/abs/1802.05799">Horovod: Fast and Easy Distributed Deep Learning</a>.</li>
<li><strong>Huang, Y. et al.</strong> (2019). <a href="https://arxiv.org/abs/1811.06965">GPipe: Efficient Training of Giant Neural Networks Using Pipeline Parallelism</a>. <em>NeurIPS</em>.</li>
<li><strong>Loshchilov, I. &amp; Hutter, F.</strong> (2019). <a href="https://arxiv.org/abs/1711.05101">Decoupled Weight Decay Regularization</a>. <em>ICLR</em>.</li>
<li><strong>Paszke, A. et al.</strong> (2019). <a href="https://arxiv.org/abs/1912.01703">PyTorch: An Imperative Style, High-Performance Deep Learning Library</a>. <em>NeurIPS</em>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>ML Systems</category>
  <guid>https://imaddabbura.github.io/posts/mlsys/dl-systems.html</guid>
  <pubDate>Wed, 20 Dec 2023 06:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/mlsys/images/dl-system-image.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Breaking Text Apart (The Smart Way)</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/nlp/Tokenization-Strategies.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>Tokenization sits at the foundation of every NLP system, and it’s where more bugs, performance failures, and cross-lingual headaches originate than most practitioners expect.</p>
<p>The core problem: neural networks can’t consume raw text. They need numbers. Tokenization is the bridge: converting a string into a sequence of integer IDs that the model can embed and process. But <em>how</em> you make that conversion has enormous downstream consequences: for vocabulary size, sequence length, out-of-vocabulary handling, and multilingual generalization.</p>
<p>There are three fundamental strategies, sitting on a spectrum from fine-grained to coarse:</p>
<ul>
<li><strong>Character tokenization</strong>: split at every character: maximum granularity, minimum vocabulary</li>
<li><strong>Word tokenization</strong>: split at word boundaries: minimum granularity, maximum vocabulary</li>
<li><strong>Subword tokenization</strong>: split rules learned from corpus statistics: the practical sweet spot used by every modern LLM</li>
</ul>
<p>We’ll work through each in turn with concrete code, then zoom in on the two subword algorithms that dominate modern NLP: <strong>WordPiece</strong> (BERT, DistilBERT) and <strong>BPE via SentencePiece</strong> (XLM-R, LLaMA, GPT-family models).</p>
</section>
<section id="tokenization-process" class="level2">
<h2 class="anchored" data-anchor-id="tokenization-process">Tokenization Process</h2>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/tokenization-pipeline.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure 1: The four-stage tokenization pipeline: normalization, pretokenization, the (trained) tokenizer model, and postprocessing."><img src="https://imaddabbura.github.io/posts/nlp/images/tokenization-pipeline.png" class="img-fluid figure-img" alt="Figure 1: The four-stage tokenization pipeline: normalization, pretokenization, the (trained) tokenizer model, and postprocessing."></a></p>
<figcaption><strong>Figure 1:</strong> The four-stage tokenization pipeline: normalization, pretokenization, the (trained) tokenizer model, and postprocessing.</figcaption>
</figure>
</div>
<p>The tokenization pipeline has four stages, each with a distinct job:</p>
<ul>
<li><p><strong>Normalization</strong>: Clean the raw text before any splitting. Common operations include Unicode normalization (collapsing different byte representations of the same character), lowercasing, and accent stripping. Critically, what gets normalized here is permanent: the model never sees the original form.</p></li>
<li><p><strong>Pretokenization</strong>: Split the normalized text into coarse units, typically words or word-like chunks. For English and German, splitting on whitespace and punctuation works well. For languages like Japanese or Chinese (which have no whitespace), language-specific rules or character-level splits are used instead.</p></li>
<li><p><strong>Tokenizer model</strong>: Apply the learned subword splitting algorithm (WordPiece, BPE, Unigram, etc.) to each pretokenized chunk. This is the only <em>trained</em> stage; everything else is rule-based. The vocabulary and merge rules come from the pretraining corpus.</p></li>
<li><p><strong>Postprocessing</strong>: Wrap the token sequence with any model-specific special tokens. BERT prepends <code>[CLS]</code> and inserts <code>[SEP]</code> between sequences. XLM-R uses <code>&lt;s&gt;</code> and <code>&lt;/s&gt;</code>. These tokens have specific learned representations and must be consistent between pretraining and fine-tuning.</p></li>
</ul>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>The Pipeline Is Framework-Agnostic
</div>
</div>
<div class="callout-body-container callout-body">
<p>This four-stage structure underpins Hugging Face <code>tokenizers</code>, SentencePiece, and most production tokenizer implementations. Most unexpected token outputs trace back to either normalization (e.g., surprise lowercasing or accent stripping) or postprocessing (missing or double-added special tokens).</p>
</div>
</div>
</section>
<section id="tokenization-strategies" class="level2">
<h2 class="anchored" data-anchor-id="tokenization-strategies">Tokenization Strategies</h2>
<p>There are three core tokenization schemes. Before diving in, here’s a preview of the trade-offs that motivate the progression from characters to subwords:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Strategy</th>
<th>Vocab size</th>
<th>Sequence length</th>
<th>OOV handling</th>
<th>Multilingual</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Character</td>
<td>Tiny (~100s)</td>
<td>Very long</td>
<td>None</td>
<td>Natural</td>
</tr>
<tr class="even">
<td>Word</td>
<td>Huge (millions)</td>
<td>Short</td>
<td>UNK collapse</td>
<td>Poor</td>
</tr>
<tr class="odd">
<td>Subword</td>
<td>Medium (10K–100K)</td>
<td>Medium</td>
<td>Decompose</td>
<td>Good</td>
</tr>
</tbody>
</table>
<p>The pattern is clear: characters and words are opposite extremes, each with a disqualifying flaw. Subword tokenization is the engineered middle ground, and why every modern LLM uses it.</p>
<section id="character-tokenization" class="level3">
<h3 class="anchored" data-anchor-id="character-tokenization">Character Tokenization</h3>
<p>Character tokenization is the simplest possible approach: split the input string into individual characters and treat each one as a token. No learned vocabulary, no language-specific rules: just <code>list(text)</code>. It’s the floor of the granularity spectrum.</p>
<div id="92ba1e07-4dff-4f3a-9b31-0daf88028379" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1">text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"I love NLP!"</span></span>
<span id="cb1-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(text)</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="3">
<pre><code>['I', ' ', 'l', 'o', 'v', 'e', ' ', 'N', 'L', 'P', '!']</code></pre>
</div>
</div>
<p>From here, it is easy to convert each character into integers that would be fed to the model. This step is called <em>numericalization</em>. We can numericalize the above text by first building the vocabulary, and then convert each character to its corresponding index as follows:</p>
<div id="88aa86ee-95da-4173-82e0-20dd5f5c3fc6" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">vocab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {char: idx <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> idx, char <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(text)))}</span>
<span id="cb3-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(vocab)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>{' ': 0, '!': 1, 'I': 2, 'L': 3, 'N': 4, 'P': 5, 'e': 6, 'l': 7, 'o': 8, 'v': 9}</code></pre>
</div>
</div>
<p>Now we can simply map each token (character in this case) to its own corresponding index:</p>
<div id="cabb3953-a7e5-4bab-9ae6-5f3f3c319e55" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">[vocab[char] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> char <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> text]</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="5">
<pre><code>[2, 0, 7, 8, 9, 6, 0, 4, 3, 5, 1]</code></pre>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Why Character Tokenization Is Appealing
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>No out-of-vocabulary problem</strong>: every possible input (misspellings, code, emojis, neologisms) is representable from the same small fixed alphabet</li>
<li><strong>Tiny vocabulary</strong>: ~100 characters for English. The embedding matrix and output projection stay small, which reduces parameter count and memory</li>
</ul>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Why Character Tokenization Fails in Practice
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>Sequences become extremely long</strong>: “I love NLP!” becomes 11 tokens. A typical 512-word document becomes several thousand characters. For <a href="../../posts/nlp/Transformer-Architecture-Explained.html">Transformers</a> with quadratic attention cost, this is prohibitively expensive</li>
<li><strong>No free linguistic priors</strong>: the model has no prior knowledge that <code>l</code>, <code>o</code>, <code>v</code>, <code>e</code> together constitute a meaningful unit. Recovering word-level and phrase-level structure from raw characters requires far more data, compute, and model depth than most tasks justify</li>
<li><strong>Context window exhaustion</strong>: with fixed-length context windows, very long character sequences mean the model can attend to only a small slice of a document at a time, losing long-range dependencies that often carry the most important signal</li>
</ul>
</div>
</div>
</section>
<section id="word-tokenization" class="level3">
<h3 class="anchored" data-anchor-id="word-tokenization">Word Tokenization</h3>
<p>Word tokenization takes the opposite approach: split on whitespace (and often punctuation) and treat each word as an atomic token. Sequences stay short and tokens carry recognizable meaning, but the vocabulary problem quickly becomes unmanageable at scale.</p>
<div id="1b6e7b4b-99d2-4ce4-a3d7-3176cdf65250" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">text.split()</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="6">
<pre><code>['I', 'love', 'NLP!']</code></pre>
</div>
</div>
<div id="c5daf75d-a025-42d4-a21a-35b3b96391b3" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">vocab <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {char: idx <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> idx, char <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(text.split())))}</span>
<span id="cb9-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(vocab)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>{'I': 0, 'NLP!': 1, 'love': 2}</code></pre>
</div>
</div>
<div id="7af9c548-5537-4a74-aee4-904ddfd45184" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">[vocab[word] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> word <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> text.split()]</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="9">
<pre><code>[0, 2, 1]</code></pre>
</div>
</div>
<p>Most production word tokenizers go beyond whitespace splitting and include language-specific heuristics: for example, separating contractions like “doesn’t” into “does” and “n’t”, or splitting punctuation from adjacent words. These rules improve coverage but don’t solve the fundamental vocabulary size and OOV problems.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Why Word Tokenization Seems Appealing
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>Short sequences</strong>: “I love NLP!” is 3 tokens. The model attends to far more context within the same fixed-length window</li>
<li><strong>Tokens carry meaning directly</strong>: each token maps to a recognizable linguistic unit, giving the model useful priors without learning from scratch</li>
</ul>
</div>
</div>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>Why Word Tokenization Breaks Down
</div>
</div>
<div class="callout-body-container callout-body">
<ul>
<li><strong>Vocabulary explosion</strong>: a large corpus contains millions of distinct word forms: declinations, misspellings, punctuation variants, domain-specific terms. An embedding table with 1M entries at dimension 512 requires ~500M parameters for the embedding layer alone. Truncating to the top-N words forces everything else to <code>[UNK]</code>, which destroys information silently: the model has no way to recover what word was there</li>
<li><strong>Under-trained embeddings</strong>: rare words appear too infrequently to accumulate meaningful gradient signal. They occupy slots in the vocabulary without learning useful representations: wasted capacity</li>
<li><strong>Language boundary failures</strong>: languages without clear word boundaries (Japanese, Chinese, Thai) have no natural whitespace to split on. Word tokenization either silently fails or requires expensive language-specific preprocessing at training and inference time</li>
</ul>
</div>
</div>
</section>
<section id="subword-tokenization" class="level3">
<h3 class="anchored" data-anchor-id="subword-tokenization">Subword Tokenization</h3>
<p>Subword tokenization is the engineered middle ground between the two extremes. The core insight: most words in any language are built from a small set of recurring morphemes: prefixes, roots, suffixes. “tokenization”, “tokenizer”, “tokenized” all share the root “token”. Word tokenization throws that structure away by treating each form as an unrelated atomic entry. Character tokenization preserves the raw signal but forces the model to discover linguistic structure from scratch, without any priors.</p>
<p>Subword algorithms exploit this structure directly. They learn a vocabulary of high-frequency subword units from a large pretraining corpus. Common words like “love” stay as single tokens. Rare or novel words get decomposed into familiar pieces: “tokenization” → <code>["token", "##ization"]</code> in WordPiece, or <code>["▁token", "ization"]</code> in SentencePiece. The model has seen “token” thousands of times and has a rich representation for it, and that representation is now available even when encountering “detokenization” for the first time.</p>
<p>This also handles misspellings and out-of-domain terms gracefully. “GPT-4o” doesn’t need to be in the vocabulary: it gets decomposed into known subwords rather than collapsing to <code>[UNK]</code>.</p>
<p>Two algorithms dominate modern NLP: <strong>WordPiece</strong> (BERT, DistilBERT) and <strong>BPE via SentencePiece</strong> (XLM-R, LLaMA, GPT-family models). Both learn subword vocabularies from corpus statistics, but they use different objectives and produce different tokenization behavior, differences that matter when debugging cross-lingual failures or unexpected token splits. I build BPE from scratch, byte by byte, in <a href="../../posts/nlp/BPE-Tokenizer.html">Byte Pair Encoding from Scratch</a>.</p>
<section id="wordpiece" class="level4">
<h4 class="anchored" data-anchor-id="wordpiece">WordPiece</h4>
<p><a href="https://arxiv.org/abs/1609.08144v2">WordPiece</a> is the subword algorithm behind BERT and DistilBERT. Like BPE, it starts with a character-level vocabulary and iteratively merges pairs, but the key difference is in <em>how</em> it chooses which pair to merge next.</p>
<p>BPE picks the most frequent pair. WordPiece picks the pair that <strong>maximizes the likelihood of the training corpus</strong> when merged. Concretely, for a candidate pair <img src="https://latex.codecogs.com/png.latex?(u,%20v)">, it evaluates:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bscore%7D(u,%20v)%20=%20%5Cfrac%7B%5Ctext%7Bcount%7D(uv)%7D%7B%5Ctext%7Bcount%7D(u)%20%5Ctimes%20%5Ctext%7Bcount%7D(v)%7D"></p>
<p>This is a pointwise mutual information criterion: it rewards pairs that appear together more than their individual frequencies would predict. Merging “##iz” with “##ation” scores high not just because the bigram is frequent, but because seeing “##iz” almost always predicts “##ation”: the merge buys maximum information.</p>
<p>The training process:</p>
<ol type="1">
<li>Initialize the vocabulary with all characters in the corpus, prepending <code>##</code> to all characters that don’t start a word</li>
<li>Score every adjacent pair using the PMI formula above</li>
<li>Merge the highest-scoring pair and add it to the vocabulary</li>
<li>Repeat until the vocabulary reaches the target size (BERT uses 30,000)</li>
</ol>
<p>The <code>##</code> prefix is the signature of WordPiece. It marks continuation subwords: pieces that are <em>not</em> at the start of a word boundary. So <code>["nl", "##p"]</code> means: “nl” starts a word, “##p” continues it. Reconstructing the original word means stripping <code>##</code> and concatenating.</p>
<div id="99b30f6a-2857-4850-9b6d-7ccfbd2ec75b" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> transformers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> DistilBertTokenizer</span>
<span id="cb13-2"></span>
<span id="cb13-3">tokenizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> DistilBertTokenizer.from_pretrained(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"distilbert-base-uncased"</span>)</span>
<span id="cb13-4">encoded_text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tokenizer(text)</span>
<span id="cb13-5">encoded_text</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="8">
<pre><code>{'input_ids': [101, 1045, 2293, 17953, 2361, 999, 102], 'attention_mask': [1, 1, 1, 1, 1, 1, 1]}</code></pre>
</div>
</div>
<div id="f0dbfc78-fdec-485e-84ac-795cb9ea3be3" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1">tokenizer.convert_ids_to_tokens(encoded_text[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input_ids"</span>])</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="9">
<pre><code>['[CLS]', 'i', 'love', 'nl', '##p', '!', '[SEP]']</code></pre>
</div>
</div>
<p>Reading the DistilBERT output token by token:</p>
<ul>
<li><code>[CLS]</code>: a special classification token prepended to every sequence. Its final hidden state is used as the aggregate sequence representation for classification tasks</li>
<li><code>i</code>: “I” was lowercased (DistilBERT uses <code>distilbert-base-**uncased**</code>)</li>
<li><code>love</code>: a common English word; gets its own token</li>
<li><code>nl</code>: the first subword of “NLP”. “NLP” is rare enough in BERT’s training corpus that it was never merged into a single token</li>
<li><code>##p</code>: continues from “nl”. The <code>##</code> prefix signals “this piece is not at a word boundary, attach it to the previous token”</li>
<li><code>!</code>: punctuation gets its own token</li>
<li><code>[SEP]</code>: marks the end of a sequence (or the boundary between two sequences in sentence-pair tasks)</li>
</ul>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Decoding the <code>##</code> Prefix
</div>
</div>
<div class="callout-body-container callout-body">
<p>When you see <code>##</code> in WordPiece output, it means: strip the <code>##</code> and concatenate directly to the previous token. <code>["nl", "##p"]</code> → <code>"nlp"</code>. <code>["un", "##believ", "##able"]</code> → <code>"unbelievable"</code>. The <code>##</code> is how WordPiece encodes which subwords are word-internal vs.&nbsp;word-initial, which is critical for reconstructing the original string.</p>
</div>
</div>
<div id="211d4098-2821-4fd3-9a0c-c404f4ac3ec9" class="cell" data-execution_count="12">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1">tokenizer.convert_tokens_to_string(</span>
<span id="cb17-2">    tokenizer.convert_ids_to_tokens(encoded_text[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input_ids"</span>])</span>
<span id="cb17-3">)</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="12">
<pre><code>'[CLS] i love nlp ! [SEP]'</code></pre>
</div>
</div>
</section>
<section id="sentencepiece" class="level4">
<h4 class="anchored" data-anchor-id="sentencepiece">SentencePiece</h4>
<p><a href="https://arxiv.org/abs/1808.06226">SentencePiece</a> is a language-agnostic tokenization library that implements both BPE and unigram language model algorithms. Two properties make it the dominant choice for multilingual models.</p>
<p><strong>First: it treats the input as a raw Unicode character stream</strong>: no language-specific pretokenization required. It never assumes whitespace marks word boundaries, which means it works equally well on English, Chinese, Japanese, Arabic, and any language mixture. This is why XLM-R, mT5, and LLaMA all use SentencePiece.</p>
<p><strong>Second: it uses <code>▁</code> (U+2581, lower one-eighth block) to encode the start of a new word.</strong> Rather than marking continuation pieces like WordPiece does with <code>##</code>, SentencePiece marks word-<em>starts</em>. A <code>▁</code> at the beginning of a token means “there was a space before this character in the original text.” Absence of <code>▁</code> means “this token is a continuation.”</p>
<p>The BPE algorithm it implements:</p>
<ol type="1">
<li>Initialize the vocabulary with individual Unicode characters plus an end-of-word marker</li>
<li>Count all adjacent character pairs across the corpus</li>
<li>Merge the most frequent pair into a new subword unit</li>
<li>Repeat until the vocabulary reaches the target size</li>
</ol>
<p>Unlike WordPiece’s PMI-based selection, BPE uses raw frequency. It’s simpler but produces similar results in practice: both algorithms converge on vocabularies dominated by common morphemes.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>BPE vs.&nbsp;Unigram in SentencePiece
</div>
</div>
<div class="callout-body-container callout-body">
<p>SentencePiece supports two algorithms. BPE builds the vocabulary bottom-up by merging. Unigram starts with a large candidate vocabulary and prunes it by removing tokens that minimally reduce the likelihood of the training corpus, a top-down approach. Unigram is used by XLNet and some multilingual models; BPE is more common. Both are interchangeable in the SentencePiece API.</p>
</div>
</div>
<div id="12596fb8-0a5a-439c-aa79-5bc0ab181f9c" class="cell" data-execution_count="13">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb19-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> transformers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> XLMRobertaTokenizer</span>
<span id="cb19-2"></span>
<span id="cb19-3">tokenizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> XLMRobertaTokenizer.from_pretrained(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"xlm-roberta-base"</span>)</span>
<span id="cb19-4">encoded_text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tokenizer(text)</span>
<span id="cb19-5">encoded_text</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="13">
<pre><code>{'input_ids': [0, 87, 5161, 541, 37352, 38, 2], 'attention_mask': [1, 1, 1, 1, 1, 1, 1]}</code></pre>
</div>
</div>
<div id="d1d33cbc-139d-49fd-9b80-d1815ff7d60e" class="cell" data-execution_count="14">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb21-1">tokenizer.convert_ids_to_tokens(encoded_text[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input_ids"</span>])</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="14">
<pre><code>['&lt;s&gt;', '▁I', '▁love', '▁N', 'LP', '!', '&lt;/s&gt;']</code></pre>
</div>
</div>
<p>Reading the XLM-R output token by token:</p>
<ul>
<li><code>&lt;s&gt;</code>: sequence start token (XLM-R’s equivalent of <code>[CLS]</code>)</li>
<li><code>▁I</code>: the <code>▁</code> prefix means “there was a space before this character.” Since “I” starts the sentence (treated as if preceded by whitespace), it gets <code>▁</code></li>
<li><code>▁love</code>: common word, single token; <code>▁</code> marks it as word-initial</li>
<li><code>▁N</code>: “NLP” is split; <code>▁N</code> is the word-initial piece</li>
<li><code>LP</code>: continues from <code>▁N</code>, no <code>▁</code> prefix (it’s a word-internal continuation)</li>
<li><code>!</code>: punctuation token</li>
<li><code>&lt;/s&gt;</code>: sequence end token (XLM-R’s equivalent of <code>[SEP]</code>)</li>
</ul>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>WordPiece <code>##</code> vs.&nbsp;SentencePiece <code>▁</code>: Same Information, Opposite Markers
</div>
</div>
<div class="callout-body-container callout-body">
<p>These two prefixes encode word boundary information in opposite ways:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Tokenizer</th>
<th>Marker</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>WordPiece (BERT)</td>
<td><code>##token</code></td>
<td>This piece continues the previous word</td>
</tr>
<tr class="even">
<td>SentencePiece (XLM-R, LLaMA)</td>
<td><code>▁token</code></td>
<td>A space preceded this character - new word starts here</td>
</tr>
</tbody>
</table>
<p>Both fully encode the original whitespace and allow perfect string reconstruction. The difference is convention, not capability. But you need to know which convention a tokenizer uses when writing postprocessing code to detokenize outputs.</p>
</div>
</div>
<div id="75c7acd6-1fc1-4b12-9e38-a39ca5ceeca8" class="cell" data-execution_count="15">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb23-1">tokenizer.convert_tokens_to_string(</span>
<span id="cb23-2">    tokenizer.convert_ids_to_tokens(encoded_text[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input_ids"</span>])</span>
<span id="cb23-3">)</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="15">
<pre><code>'&lt;s&gt; I love NLP!&lt;/s&gt;'</code></pre>
</div>
</div>
</section>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>The three tokenization strategies form a clear hierarchy in practice:</p>
<ul>
<li><p><strong>Character tokenization</strong> is essentially unused in production NLP. Sequence lengths become prohibitively long for Transformer attention, and the model must learn linguistic structure entirely from scratch. It survives in niche applications: <a href="../../posts/nlp/Character-LeveL-Language-Model.html">character-level language models</a>, certain byte-level models (GPT-2 uses byte-level BPE as a starting point), and as a fallback for extremely small vocabularies.</p></li>
<li><p><strong>Word tokenization</strong> appears in legacy systems and simple bag-of-words pipelines, but fails at scale. Vocabulary explosion, <code>[UNK]</code> collapse, and multilingual brittleness make it unsuitable for anything pretrained on broad corpora.</p></li>
<li><p><strong>Subword tokenization</strong> is the universal standard for pretrained language models. WordPiece and SentencePiece BPE both solve the core trade-offs: bounded vocabulary, graceful OOV handling, multilingual coverage, and sequences short enough for Transformer attention.</p></li>
</ul>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>Always Use the Tokenizer the Model Was Trained With
</div>
</div>
<div class="callout-body-container callout-body">
<p>When fine-tuning a pretrained model, you must use the <strong>exact same tokenizer</strong>, not just the same algorithm, but the same vocabulary file. The model’s embedding matrix maps token ID 1045 to a learned vector for the word “i” (in DistilBERT). Swap in a different tokenizer and ID 1045 now refers to something else entirely. The embeddings become noise, the model is unrecoverable, and fine-tuning won’t fix it. This applies to vocabulary size, normalization rules, and special token placements: all of it must match pretraining exactly.</p>
</div>
</div>
<p>Most practical work doesn’t require building tokenizers from scratch: Hugging Face <code>tokenizers</code> and SentencePiece handle it. What matters operationally is understanding the output: recognizing <code>##</code> vs <code>▁</code> markers, knowing which special tokens a model expects and in what order, and catching normalization surprises (casing, accent stripping) before they cause silent failures downstream.</p>
</section>
<section id="references-resources" class="level2">
<h2 class="anchored" data-anchor-id="references-resources">References &amp; Resources</h2>
<ul>
<li><strong>Sennrich, R. et al.</strong> (2016). <a href="https://arxiv.org/abs/1508.07909">Neural Machine Translation of Rare Words with Subword Units</a>. <em>ACL</em>.</li>
<li><strong>Wu, Y. et al.</strong> (2016). <a href="https://arxiv.org/abs/1609.08144">Google’s Neural Machine Translation System</a>.</li>
<li><strong>Kudo, T.</strong> (2018). <a href="https://arxiv.org/abs/1804.10959">Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates</a>. <em>ACL</em>.</li>
<li><strong>Kudo, T. &amp; Richardson, J.</strong> (2018). <a href="https://arxiv.org/abs/1808.06226">SentencePiece: A Simple and Language Independent Subword Tokenizer and Detokenizer for Neural Text Processing</a>. <em>EMNLP</em>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>NLP</category>
  <guid>https://imaddabbura.github.io/posts/nlp/Tokenization-Strategies.html</guid>
  <pubDate>Sat, 14 Jan 2023 06:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/nlp/images/tokenization.png" medium="image" type="image/png" height="65" width="144"/>
</item>
<item>
  <title>C Program Startup</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/swe/program-startup-notes.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge growing">growing</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>In this post, I will try to write down the steps of C program execution on x86. I used to believe that all C programs start execution at <code>main</code>, or at least this was my understanding from different books/courses until my best friend <code>gdb</code> debugger showed the symbol for <code>_start</code>. This is how I got curious until I got to the bottom of it. Below are my notes that I took during my learning.</p>
</section>
<section id="execution-steps" class="level2">
<h2 class="anchored" data-anchor-id="execution-steps">Execution Steps</h2>
<ol type="1">
<li>The linker inject <code>_start</code> which is called in the process of loading.
<ul>
<li>It is written in assembly language</li>
<li>Always placed at the beginning of the <code>.text</code> section -&gt; Always guaranteed to run before anything else</li>
<li>It sets up some registers and arguments and calls <code>__start</code> which is called <code>__libc_start_main</code></li>
</ul></li>
<li><code>__libc_start_main</code> is written in C that:
<ul>
<li>function prototype:</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb1-1">__libc_start_main <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(*</span>main<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**),</span></span>
<span id="cb1-2">                   <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span> argc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-3">                   <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>argv<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-4">                   <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span>  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(*</span>init<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**,</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**),</span></span>
<span id="cb1-5">                   <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(*</span>fini<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-6">                   <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(*</span>rtld_fini<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">),</span></span>
<span id="cb1-7">                   <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">void</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>stack_end</span>
<span id="cb1-8">                  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div></div>
<ul>
<li>Define <code>environ</code> global variable using <code>ps_string</code>: <code>environ = ps_strings-&gt;ps_envstr</code>
<ul>
<li>Below are some details about <code>ps_strings</code> structure:</li>
</ul></li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/*</span></span>
<span id="cb2-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> * The following structure is found at the top of the user stack of each</span></span>
<span id="cb2-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> * user process. The ps program uses it to locate argv and environment</span></span>
<span id="cb2-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> * strings. Programs that wish ps to display other information may modify</span></span>
<span id="cb2-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> * it; normally ps_argvstr points to argv[0], and ps_nargvstr is the same</span></span>
<span id="cb2-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> * as the program's argc. The fields ps_envstr and ps_nenvstr are the</span></span>
<span id="cb2-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> * equivalent for the environment.</span></span>
<span id="cb2-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> */</span></span>
<span id="cb2-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">struct</span> ps_strings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb2-10">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span>    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>ps_argvstr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* first of 0 or more argument strings */</span></span>
<span id="cb2-11">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span>       ps_nargvstr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* the number of argument strings */</span></span>
<span id="cb2-12">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">char</span>    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>ps_envstr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* first of 0 or more environment strings */</span></span>
<span id="cb2-13">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">int</span>       ps_nenvstr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span>       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* the number of environment strings */</span></span>
<span id="cb2-14"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">};</span></span></code></pre></div></div>
<ul>
<li>It is typically defined as char <code>envp = argv[argc + 1]</code> in <code>libc_init_first</code></li>
<li>It also registers cleanup and exit handlers</li>
<li>It define <code>init</code> &amp; <code>fini</code> that defines function prolog and epilogue which means defining what happens when calling a function and when returning from a function. They also align the stack to be multiple of 16 bytes so it is more efficient and cache friendly. They are written in assembly language</li>
<li>It sets %rbp to zero because <code>main</code> would be the outermost frame</li>
<li>Finally it calls:</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb3-1">    exit<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>main<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>ps_strings<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>ps_nargvstr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> ps_strings<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span>ps_argvstr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> environ<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">));</span></span></code></pre></div></div>
<ul>
<li>After the NULL of <code>envp</code>, there is ELF auxiliary vector that the loader uses to provide information to the process such as user id and page size etc.</li>
<li>Therefore, <code>__libc_start_main</code> in general does the following:
<ul>
<li>Set up argv and envp</li>
<li>Initialize the thread local storage by calling <code>__pthread_initialize_minimal</code> (which only calls <code>__libc_setup_tls</code>). <code>__libc_setup_tls</code> will initialize Thread Control Block and Dynamic Thread Vector.</li>
<li>Set up the thread stack guard</li>
<li>Register the destructor (i.e.&nbsp;the rtld_fini argument passed to <code>__libc_start_main</code>) of the dynamic linker (by calling <code>__cxa_atexit</code>) if there is any</li>
<li>Initialize Glibc itself by calling <code>__libc_init_first</code></li>
<li>Register <code>__libc_csu_fini</code> (i.e.&nbsp;the fini argument passed to <code>__libc_start_main</code>) using <code>__cxa_atexit</code></li>
<li>Call <code>__libc_csu_init</code> (i.e.&nbsp;the init argument passed to <code>__libc_start_main</code>). <code>__libc_csu_init</code> execute them in the following order:
<ul>
<li>Function pointers in .preinit_array section</li>
<li>Functions marked as <code>__attribute__ ((constructor))</code>, via <code>_init</code></li>
<li>Function pointers in <code>.init_array</code> section</li>
</ul></li>
<li>Set up data structures needed for thread unwinding/cancellation</li>
<li>Call main of user’s program.</li>
<li>Call <code>exit</code>
<ul>
<li>In reverse order, functions registered via <code>atexit</code> or <code>on_exit</code></li>
<li>Function pointers in <code>.fini_array</code> section, via <code>__libc_csu_fini</code></li>
<li>Functions marked as <code>__attribute__ ((destructor))</code>, via <code>__libc_csu_fini</code> (which calls <code>_fini</code> after Step 2)</li>
<li>stdio cleanup functions</li>
<li>The <code>.fini_array</code> section must also contain function pointers and the prototype is like the destructor, i.e.&nbsp;taking no arguments and returning void. If the program exits normally, then the exit function (Glibc source file stdlib/exit.c)</li>
</ul></li>
</ul></li>
</ul></li>
</ol>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>So starting program will call <code>execve</code> that starts the loader that at some point pass control to <code>_start</code>, which calls <code>__libc_start_main</code> which calls <code>__libc_csu_init</code> which calls <code>_init</code>.</p>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Software Engineering</category>
  <guid>https://imaddabbura.github.io/posts/swe/program-startup-notes.html</guid>
  <pubDate>Fri, 21 Oct 2022 05:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/swe/images/linux-prog-startup.png" medium="image" type="image/png" height="123" width="144"/>
</item>
<item>
  <title>The Transformer Architecture: A Deep Dive</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/nlp/Transformer-Architecture-Explained.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>If you’ve called <code>from transformers import BertModel</code> or prompted GPT-4, you’ve used a Transformer. But <em>what actually happens</em> when it processes text? Why does attention use three separate projections: Q, K, and V? Why does the decoder need a causal mask?</p>
<p>The Transformer displaced a decade of sequence modelling research not because it was more complex, but because it was more general: the same architecture, with minimal changes, now handles text, images, protein structures, and audio. Understanding <em>why</em> it generalises is what separates someone who can use these models from someone who can reason about them.</p>
<p>This post builds one from scratch, understanding the motivation behind every design choice before any code. By the end, you will be able to:</p>
<ol type="1">
<li>Explain <em>why</em> each component exists, not just what it does</li>
<li>Trace a forward pass through the full encoder-decoder architecture, step by step</li>
<li>Understand the three architecture variants (encoder-only, decoder-only, encoder-decoder) and when to use each</li>
<li>Read modern Transformer papers and recognise the improvements they describe</li>
</ol>
<p>We start with the problem that motivated the Transformer (sequential bottlenecks in RNNs), build the attention mechanism from scratch, implement each component in PyTorch with annotated shapes, and assemble all three architecture variants, using the architecture diagram below as our map throughout.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/transformer-arch.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure 1: The encoder-decoder Transformer (Vaswani et al., 2017), the architecture we’ll build in this post. Left stack (Encoder): reads the full source sequence; every token attends to every other token with no masking. Right stack (Decoder): generates the target sequence one token at a time; each layer has three sublayers: ① masked self-attention (tokens attend only to past positions), ② cross-attention (Q from decoder, K and V from the encoder output; the arrow connecting the two stacks), and ③ a feed-forward network. “N×” means the layer block repeats N times (typically 6–12). Add &amp; Norm is a residual connection followed by LayerNorm. The Linear + Softmax at the top projects the decoder’s final representation to a probability distribution over the vocabulary. Every component labelled here has its own section below. (source)"><img src="https://imaddabbura.github.io/posts/nlp/images/transformer-arch.png" class="img-fluid quarto-figure quarto-figure-center figure-img" width="600" alt="Figure 1: The encoder-decoder Transformer (Vaswani et al., 2017), the architecture we’ll build in this post. Left stack (Encoder): reads the full source sequence; every token attends to every other token with no masking. Right stack (Decoder): generates the target sequence one token at a time; each layer has three sublayers: ① masked self-attention (tokens attend only to past positions), ② cross-attention (Q from decoder, K and V from the encoder output; the arrow connecting the two stacks), and ③ a feed-forward network. “N×” means the layer block repeats N times (typically 6–12). Add &amp; Norm is a residual connection followed by LayerNorm. The Linear + Softmax at the top projects the decoder’s final representation to a probability distribution over the vocabulary. Every component labelled here has its own section below. (source)"></a></p>
</figure>
</div>
<figcaption><strong>Figure 1:</strong> The encoder-decoder Transformer (Vaswani et al., 2017), the architecture we’ll build in this post. <strong>Left stack (Encoder):</strong> reads the full source sequence; every token attends to every other token with no masking. <strong>Right stack (Decoder):</strong> generates the target sequence one token at a time; each layer has three sublayers: ① masked self-attention (tokens attend only to past positions), ② <strong>cross-attention</strong> (Q from decoder, K and V from the encoder output; the arrow connecting the two stacks), and ③ a feed-forward network. <strong>“N×”</strong> means the layer block repeats N times (typically 6–12). <strong>Add &amp; Norm</strong> is a residual connection followed by LayerNorm. The <strong>Linear + Softmax</strong> at the top projects the decoder’s final representation to a probability distribution over the vocabulary. Every component labelled here has its own section below. (<a href="https://arxiv.org/abs/1706.03762">source</a>)</figcaption>
</figure>
</div>
</section>
<section id="the-problem-why-not-rnns" class="level2">
<h2 class="anchored" data-anchor-id="the-problem-why-not-rnns">The Problem: Why Not RNNs?</h2>
<p>To understand <em>why</em> the Transformer is designed the way it is, you first need to understand what it replaced, and what was broken about it.</p>
<section id="the-rnn-mental-model" class="level3">
<h3 class="anchored" data-anchor-id="the-rnn-mental-model">The RNN Mental Model</h3>
<p>A Recurrent Neural Network processes a sequence one token at a time. After seeing each token <img src="https://latex.codecogs.com/png.latex?w_t">, it updates a fixed-size <strong>hidden state</strong> <img src="https://latex.codecogs.com/png.latex?h_t"> that is supposed to summarize everything the model has seen so far:</p>
<p><img src="https://latex.codecogs.com/png.latex?h_t%20=%20f(h_%7Bt-1%7D,%5C,%20w_t)"></p>
<p>The hidden state is then passed to the next step. Think of it as a single notepad that a reader carries through a book, rewriting one paragraph of notes after each page. By the time they reach page 500, that notepad contains almost nothing from page 1: there wasn’t room to preserve it through 499 rewrites.</p>
<p>The notepad is not just an illustration: the RNN must compress all prior context into a fixed-size vector, and that compression is lossy by design.</p>
</section>
<section id="the-long-range-dependency-problem" class="level3">
<h3 class="anchored" data-anchor-id="the-long-range-dependency-problem">The Long-Range Dependency Problem</h3>
<p>Language is full of dependencies that span many tokens. Consider:</p>
<blockquote class="blockquote">
<p><em>“The trophy didn’t fit in the suitcase because <strong>it</strong> was too large.”</em></p>
</blockquote>
<p>To resolve what “it” refers to, a model must connect a pronoun near the end of the sentence back to a noun near the beginning. In an RNN, that connection must survive through every intermediate hidden state update. Each update potentially overwrites or dilutes the earlier information. The longer the sequence, the worse this gets.</p>
</section>
<section id="the-vanishing-gradient-problem" class="level3">
<h3 class="anchored" data-anchor-id="the-vanishing-gradient-problem">The Vanishing Gradient Problem</h3>
<p>The training-time failure mirrors the inference-time failure. When we backpropagate through an RNN, the gradient of the loss with respect to early hidden states is a product of Jacobians, one per time step:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20h_0%7D%20=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20h_T%7D%20%5Cprod_%7Bt=1%7D%5E%7BT%7D%20%5Cfrac%7B%5Cpartial%20h_t%7D%7B%5Cpartial%20h_%7Bt-1%7D%7D"></p>
<p>If the entries of those Jacobians are consistently less than 1 (common with bounded activations like tanh), the product shrinks exponentially with <img src="https://latex.codecogs.com/png.latex?T">. Gradients from the loss signal barely reach the early time steps, so the model cannot learn from long-range dependencies.</p>
<p>LSTMs and GRUs mitigate this with gating mechanisms, but they don’t eliminate it; they just slow the decay. (For a full treatment of LSTMs and their gating solution, see the <a href="../../posts/nlp/LSTM-Annotated-Implementation.html">Inside LSTMs</a> post.)</p>
</section>
<section id="the-sequential-processing-bottleneck" class="level3">
<h3 class="anchored" data-anchor-id="the-sequential-processing-bottleneck">The Sequential Processing Bottleneck</h3>
<p>RNNs are inherently sequential: you cannot compute <img src="https://latex.codecogs.com/png.latex?h_t"> until you have <img src="https://latex.codecogs.com/png.latex?h_%7Bt-1%7D">. This makes it impossible to parallelize across the time dimension. For a sequence of length <img src="https://latex.codecogs.com/png.latex?T">, the forward pass requires <img src="https://latex.codecogs.com/png.latex?T"> sequential steps regardless of how many GPUs you have.</p>
<p>Modern GPUs are massively parallel processors: they shine on matrix multiplications that can be batched across thousands of operations simultaneously. RNNs waste almost all of that capacity.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>The Three Failure Modes
</div>
</div>
<div class="callout-body-container callout-body">
<p>RNNs fail in three compounding ways: (1) the hidden state <strong>bottleneck</strong> loses information over long sequences; (2) <strong>vanishing gradients</strong> prevent learning long-range relationships from the training signal; (3) <strong>sequential computation</strong> prevents parallelization, making training slow regardless of hardware. The Transformer addresses all three, not with patches, but by replacing sequential recurrence with a different mechanism.</p>
</div>
</div>
</section>
</section>
<section id="the-big-idea-attention-as-direct-communication" class="level2">
<h2 class="anchored" data-anchor-id="the-big-idea-attention-as-direct-communication">The Big Idea: Attention as Direct Communication</h2>
<p>The central insight of the Transformer is simple: throw out sequential processing entirely and let every token communicate directly with every other token, in a single parallel operation.</p>
<section id="from-sequential-relay-to-direct-access" class="level3">
<h3 class="anchored" data-anchor-id="from-sequential-relay-to-direct-access">From Sequential Relay to Direct Access</h3>
<p>With an RNN, every relationship between tokens must be mediated through the hidden state. Information travels through a long chain before it reaches its destination. With attention, every token asks every other token directly: <em>“How relevant are you to me?”</em> The answer shapes what information each token receives.</p>
<p>This is a different kind of computation: instead of routing information through a bottleneck, we create a <strong>direct, differentiable communication channel</strong> between all pairs of tokens simultaneously. The attention matrix for a sequence of length <img src="https://latex.codecogs.com/png.latex?T"> is <img src="https://latex.codecogs.com/png.latex?T%20%5Ctimes%20T">. Every pair gets its own weight.</p>
</section>
<section id="sec-library" class="level3">
<h3 class="anchored" data-anchor-id="sec-library">The Library Analogy: Query, Key, Value</h3>
<p>The attention mechanism is most naturally understood as a <strong>soft database lookup</strong>.</p>
<p>Imagine walking into a library. You have a <strong>query</strong> in mind: say, you’re looking for books about long-range dependencies in sequences. Every book in the library has a <strong>key</strong> on its spine: a short descriptor of what’s inside. You compare your query against every key, computing a relevance score for each book. Then you retrieve the <strong>values</strong> (the actual content) weighted by those relevance scores. The most relevant books contribute the most to what you walk away knowing.</p>
<p>This is exactly what the Transformer’s attention mechanism does at every layer, for every token:</p>
<ul>
<li><strong>Query (<img src="https://latex.codecogs.com/png.latex?Q">)</strong>: what this token is looking for</li>
<li><strong>Key (<img src="https://latex.codecogs.com/png.latex?K">)</strong>: what this token offers to match against</li>
<li><strong>Value (<img src="https://latex.codecogs.com/png.latex?V">)</strong>: what this token actually communicates if attended to</li>
</ul>
<p>The attended output for each token is a weighted mixture of all value vectors, where the weights are determined by the similarity between that token’s query and all other tokens’ keys.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Attention as a Differentiable Lookup
</div>
</div>
<div class="callout-body-container callout-body">
<p>Attention is not a neural network layer in the traditional sense; it is a <strong>soft, differentiable database lookup</strong>. It is differentiable because the retrieval weights are produced by a smooth function (softmax), so gradients flow through the lookup operation during backpropagation. The queries, keys, and values are all learned: the model learns <em>what to look for</em>, <em>what to advertise</em>, and <em>what to say</em>.</p>
</div>
</div>
<p>We’ll return to this library analogy throughout: it explains why Q, K, and V need to be separate projections, and what the attention weights actually represent numerically.</p>
</section>
<section id="why-this-architecture-generalizes-beyond-language" class="level3">
<h3 class="anchored" data-anchor-id="why-this-architecture-generalizes-beyond-language">Why This Architecture Generalizes Beyond Language</h3>
<p>One property explains why Vision Transformers, AlphaFold2, audio Transformers, and point cloud Transformers all use the same architecture as BERT and GPT, often with almost no modification.</p>
<p><strong>The Transformer has almost no structural inductive bias.</strong> CNNs assume that nearby pixels are related: they bake in locality and translation equivariance as a prior. RNNs assume sequential order: they process left-to-right by construction. The Transformer assumes <em>nothing</em> about the structure of its input beyond what the positional encoding tells it. Every pair of positions is treated symmetrically by the attention mechanism until the training data says otherwise.</p>
<p>This is simultaneously the weakness and the strength:</p>
<ul>
<li><strong>Weakness</strong>: Without structural priors, the model needs more data to learn relationships that CNNs or RNNs would pick up for free. A CNN learns “adjacent pixels tend to be related” from very few examples; a Transformer must discover this from data.</li>
<li><strong>Superpower</strong>: Any domain with a set of elements you want to relate to each other can be modeled by a Transformer. Images? Treat patches as tokens, inject 2D positional encodings (ViT). Proteins? Treat amino acids as tokens, use pairwise distances as positional information (AlphaFold2). Audio? Treat spectrogram frames as tokens. Graphs? Treat nodes as tokens.</li>
</ul>
<p>Across domains, only the positional encoding changes. The attention mechanism, FFN, LayerNorm, and residual connections are entirely domain-agnostic. Swap the positional encoding and the same architecture processes any structured data. This is why the Transformer became the universal architecture, not because it is uniquely suited to language, but because it is uniquely <em>generic</em>.</p>
</section>
</section>
<section id="tokenization-from-text-to-numbers" class="level2">
<h2 class="anchored" data-anchor-id="tokenization-from-text-to-numbers">Tokenization: From Text to Numbers</h2>
<p>Before anything else, raw text must be converted into numbers that the model can process. This conversion (<strong>tokenization</strong>) splits text into a vocabulary of subword units and maps each unit to an integer ID. The Transformer receives a <code>B × T</code> matrix of integers as input, where <code>B</code> is the batch size and <code>T</code> is the sequence length.</p>
<p>There are three families of tokenization strategy (character-level, word-level, and subword), each with distinct tradeoffs in vocabulary size, sequence length, and out-of-vocabulary handling. Modern language models universally use <strong>subword tokenization</strong> (BPE or WordPiece), which offers a vocabulary of tens of thousands of tokens while gracefully handling rare and novel words by decomposing them into known pieces.</p>
<p>This post focuses on the Transformer architecture that consumes tokenized sequences, not on tokenization itself. For a detailed treatment of how tokenization works:</p>
<ul>
<li><a href="../../posts/nlp/Tokenization-Strategies.html"><strong>Breaking Text Apart (The Smart Way)</strong></a>: the four-stage pipeline, plus WordPiece (BERT) and SentencePiece (LLaMA, XLM-R)</li>
<li><a href="../../posts/nlp/BPE-Tokenizer.html"><strong>Byte Pair Encoding from Scratch</strong></a>: the training vs.&nbsp;encoding asymmetry, vocabulary-size tradeoffs, and GPT-2’s regex pre-tokenization refinement</li>
</ul>
</section>
<section id="embedding-layer" class="level2">
<h2 class="anchored" data-anchor-id="embedding-layer">Embedding Layer</h2>
<p>The embedding layer is the first thing the model does with the token IDs it receives. It has two jobs: turn integers into meaningful vectors, and inject positional information so the model knows where each token sits in the sequence.</p>
<section id="token-embeddings" class="level3">
<h3 class="anchored" data-anchor-id="token-embeddings">Token Embeddings</h3>
<p>An integer ID has no geometric structure. The number 42 is not “close to” 41 in any meaningful sense for language. The token at position 42 in the vocabulary might be completely unrelated to token 41. Neural networks need continuous-valued vectors they can do math on: compute dot products, measure distances, apply linear transformations.</p>
<p>A token embedding is a <strong>lookup table</strong>: a matrix of shape <code>vocab_sz × embed_dim</code> where each row is a learnable vector associated with one token. When the model sees token ID <img src="https://latex.codecogs.com/png.latex?i">, it looks up row <img src="https://latex.codecogs.com/png.latex?i"> and uses that vector downstream.</p>
<p>What makes embeddings powerful is that training forces semantically similar tokens into nearby regions of this vector space. After training on enough text, the embedding for “king” minus the embedding for “man” plus the embedding for “woman” lands close to “queen”, not because we encoded this relationship by hand, but because the training signal shaped the space that way.</p>
<blockquote class="blockquote">
<p><em>An embedding turns a name tag into a GPS coordinate: suddenly you can measure distance, find neighbors, and do arithmetic.</em></p>
</blockquote>
<p><strong>Shape:</strong> <code>B × T</code> (integer IDs) → <code>B × T × embed_dim</code> (float vectors)</p>
<p><strong>Weight tying.</strong> In most language models, the embedding matrix is <em>reused</em> as the output projection at the end of the network: the final linear layer that maps from <code>d_model</code> back to <code>vocab_sz</code> uses the same weights, transposed. This is called <strong>weight tying</strong>. The core intuition: if two tokens have similar embeddings (i.e., they are semantically close), they should also receive similar probabilities when the model generates the next-token distribution. Since the LM head scores each candidate token by taking the dot product of the model’s output vector with that token’s embedding row, tokens whose embedding vectors are close to the output vector will score similarly, producing nearby probabilities. Weight tying enforces this consistency directly: the same geometry that groups similar tokens together in the input space also determines their relative scores in the output distribution. As a bonus, it halves the parameter count for the vocabulary components (often 30–100K tokens × 768 dims = a significant share of total parameters) and keeps input and output representations aligned throughout training.</p>
</section>
<section id="positional-encodings" class="level3">
<h3 class="anchored" data-anchor-id="positional-encodings">Positional Encodings</h3>
<section id="why-position-carries-meaning" class="level4">
<h4 class="anchored" data-anchor-id="why-position-carries-meaning">Why position carries meaning</h4>
<p>Word order is one of the primary mechanisms through which human languages encode meaning. Consider how much information is carried purely by where a word sits in a sentence:</p>
<p><strong>Order determines who does what to whom.</strong> “The dog bit the man” and “The man bit the dog” contain identical tokens. The meaning is completely reversed. Without positional information, a model sees the same set of embeddings for both; it cannot distinguish them.</p>
<p><strong>Agreement and dependency span long distances.</strong> In <em>“The cats that live in the house <strong>are</strong> noisy”</em>, the verb “are” must agree with “cats” (plural), not “house” (singular). Correctly resolving this requires knowing that “cats” appears before the relative clause, and “house” is inside it, a structural relationship determined entirely by position.</p>
<p><strong>Negation scope is positional.</strong> <em>“I <strong>never</strong> said she stole the money”</em> and <em>“I said she <strong>never</strong> stole the money”</em> contain the same words. The position of “never” determines the scope of negation: whether the speaker denies making the claim or denies the theft itself.</p>
<p><strong>Modifier attachment is determined by proximity.</strong> <em>“I photographed the man with a telescope”</em> is ambiguous in isolation. In context, positional proximity to either “man” or “photographed” is the primary cue for whether the telescope was used for photographing or was held by the man.</p>
<p>In short: token embeddings capture <em>what</em> each word means in isolation; positional encodings capture <em>where</em> each word sits, which encodes its grammatical role, its relationships to surrounding words, and its structural function in the sentence.</p>
</section>
<section id="the-permutation-equivariance-problem" class="level4">
<h4 class="anchored" data-anchor-id="the-permutation-equivariance-problem">The permutation equivariance problem</h4>
<p>Here is the technical issue: attention is <strong>permutation equivariant</strong>. If you reorder the input tokens, the output tokens reorder identically: the attention mechanism has no internal sense of sequence order. From the model’s perspective, “the cat sat on the mat” and “the mat sat on the cat” produce the same set of output vectors (just shuffled). Position is invisible.</p>
<p>To fix this, we add <strong>positional encodings</strong> to the token embeddings before feeding them into the Transformer. The result: two otherwise identical tokens at different positions get different combined representations, making order visible to every downstream layer.</p>
<p>There are three main strategies:</p>
</section>
<section id="strategy-1-sinusoidal-encodings-original-paper" class="level4">
<h4 class="anchored" data-anchor-id="strategy-1-sinusoidal-encodings-original-paper">Strategy 1: Sinusoidal Encodings (Original Paper)</h4>
<p>The original Transformer paper uses fixed, non-learned positional encodings based on sine and cosine functions at different frequencies:</p>
<p><img src="https://latex.codecogs.com/png.latex?PE_%7B(pos,%5C,%202i)%7D%20=%20%5Csin%5C!%5Cleft(%5Cfrac%7Bpos%7D%7B10000%5E%7B2i/d%7D%7D%5Cright)"> <img src="https://latex.codecogs.com/png.latex?PE_%7B(pos,%5C,%202i+1)%7D%20=%20%5Ccos%5C!%5Cleft(%5Cfrac%7Bpos%7D%7B10000%5E%7B2i/d%7D%7D%5Cright)"></p>
<p><strong>Why low dimensions oscillate fast and high dimensions oscillate slowly</strong> comes directly from the formula. The denominator <img src="https://latex.codecogs.com/png.latex?10000%5E%7B2i/d%7D"> is the key: it grows exponentially with the dimension index <img src="https://latex.codecogs.com/png.latex?i">. Dividing by a larger number slows the wave down:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Dimension <img src="https://latex.codecogs.com/png.latex?i"></th>
<th>Denominator <img src="https://latex.codecogs.com/png.latex?10000%5E%7B2i/d%7D"></th>
<th>Wave period (positions for one full cycle)</th>
<th>What it encodes</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?i%20=%200"></td>
<td><img src="https://latex.codecogs.com/png.latex?1"></td>
<td>~6 positions</td>
<td>Very fine: distinguishes adjacent tokens</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?i%20=%20d/8"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Capprox%2018"></td>
<td>~110 positions</td>
<td>Phrase-level distance</td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?i%20=%20d/4"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Capprox%20316"></td>
<td>~2,000 positions</td>
<td>Sentence-level distance</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?i%20=%20d/2"></td>
<td><img src="https://latex.codecogs.com/png.latex?10%7B,%7D000"></td>
<td>~62,800 positions</td>
<td>Barely changes: encodes very coarse, document-level position</td>
</tr>
</tbody>
</table>
<p>Think of it as an odometer: the rightmost digit (low dimension) flips every meter, the leftmost digit (high dimension) barely moves over a typical journey. Each digit alone is ambiguous: the rightmost digit of “7” could be position 7, 17, 27, or 107. But all digits together uniquely identify every position.</p>
<p>This multi-scale design is intentional. <strong>Low dimensions</strong> give the model a fine-grained signal that changes every few positions, useful for detecting whether two tokens are immediate neighbors. <strong>High dimensions</strong> give a coarse signal that changes only over long distances, useful for detecting whether two tokens are in the same half of the document. Together, the full vector is unique for every position from 0 to the maximum.</p>
<p>The advantage of sinusoidal encodings is that they can generalize to sequence lengths longer than those seen during training: the functions extend naturally to any position.</p>
</section>
<section id="strategy-2-learned-absolute-encodings" class="level4">
<h4 class="anchored" data-anchor-id="strategy-2-learned-absolute-encodings">Strategy 2: Learned Absolute Encodings</h4>
<p>Instead of fixing the positional encoding by formula, we can make it a learned parameter: another <code>nn.Embedding</code> table of shape <code>max_seq_len × embed_dim</code>. Each position from 0 to <code>max_seq_len-1</code> gets its own learnable row, updated via backpropagation just like token embeddings.</p>
<p>This is what BERT and GPT use. The model learns what positional fingerprints work best for its task. The downside: sequences longer than <code>max_seq_len</code> seen during training have no positional encoding; the model has never learned what those positions mean.</p>
</section>
<section id="strategy-3-rotary-positional-encoding-rope" class="level4">
<h4 class="anchored" data-anchor-id="strategy-3-rotary-positional-encoding-rope">Strategy 3: Rotary Positional Encoding (RoPE)</h4>
<p>RoPE, introduced by Su et al.&nbsp;(2021) and used in LLaMA, Mistral, and GPT-NeoX, takes a different approach: instead of <em>adding</em> a fixed vector to the embeddings, it <em>rotates</em> the query and key vectors by an angle proportional to their absolute position before computing the attention dot product.</p>
<p>The key property: when you rotate <img src="https://latex.codecogs.com/png.latex?Q"> at position <img src="https://latex.codecogs.com/png.latex?m"> and <img src="https://latex.codecogs.com/png.latex?K"> at position <img src="https://latex.codecogs.com/png.latex?n">, their dot product becomes a function of only the <em>relative distance</em> <img src="https://latex.codecogs.com/png.latex?m%20-%20n">:</p>
<p><img src="https://latex.codecogs.com/png.latex?Q_m%20%5Ccdot%20K_n%20=%20f(m%20-%20n)"></p>
<p>This is highly desirable. Relative position (how far apart two tokens are) is often more informative than absolute position. Whether “cat” is token 5 or token 50 in the sentence matters less than how far it sits from the verb it modifies. Syntactic dependencies (subject → verb, adjective → noun) tend to hold over short distances regardless of where the sentence begins. RoPE bakes this directly into the attention computation at every layer, without requiring separate positional embedding vectors.</p>
<p>RoPE also generalizes better to longer sequences than the model was trained on, making it the dominant choice in modern open-source LLMs.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Implementation Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>The code below uses learned absolute positional embeddings, the simplest approach and standard for BERT-style encoder models. The embedding layer adds the token embedding and positional embedding, normalizes with LayerNorm, and applies dropout.</p>
</div>
</div>
<div id="9a092f68-2275-4456-acfa-39f02a2ffe24" class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dataclasses <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> dataclass</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch.nn <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> nn</span>
<span id="cb1-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch.nn.functional <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> F</span></code></pre></div></div>
</details>
</div>
<div id="5a6d2afd-e157-421f-bfe6-d4dc0f73af5f" class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span></span>
<span id="cb2-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> TransformerConfig:</span>
<span id="cb2-3">    vocab_sz: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span></span>
<span id="cb2-4">    block_sz: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span></span>
<span id="cb2-5">    hidden_dropout_prob: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span></span>
<span id="cb2-6">    num_attention_heads: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span></span>
<span id="cb2-7">    num_hidden_layers: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span></span>
<span id="cb2-8">    embed_dim: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">768</span></span>
<span id="cb2-9">    num_classes: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb2-10">    layer_norm_eps: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-12</span></span>
<span id="cb2-11">    intermediate_sz: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># set to 4 * embed_dim in __post_init__</span></span>
<span id="cb2-12"></span>
<span id="cb2-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> __post_init__(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb2-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.intermediate_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb2-15">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.intermediate_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.embed_dim</span>
<span id="cb2-16"></span>
<span id="cb2-17">config <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> TransformerConfig()</span></code></pre></div></div>
</details>
</div>
<div id="51562c60-9883-4e62-b7c0-6dd0091ef203" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Embeddings(nn.Module):</span>
<span id="cb3-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config):</span>
<span id="cb3-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb3-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.token_embedding <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Embedding(config.vocab_sz, config.embed_dim)</span>
<span id="cb3-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.position_embedding <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Embedding(config.block_sz, config.embed_dim)</span>
<span id="cb3-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(config.embed_dim, eps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>config.layer_norm_eps)</span>
<span id="cb3-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dropout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Dropout(p<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>)</span>
<span id="cb3-8"></span>
<span id="cb3-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb3-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x:              B x T  (integer token IDs)</span></span>
<span id="cb3-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## token_emb:      B x T x embed_dim</span></span>
<span id="cb3-12">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## position_emb:   T x embed_dim  (broadcast over batch)</span></span>
<span id="cb3-13">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## output:         B x T x embed_dim</span></span>
<span id="cb3-14">        seq_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb3-15">        positions <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.arange(seq_len, device<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>x.device)</span>
<span id="cb3-16">        embeddings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.token_embedding(x) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.position_embedding(positions)</span>
<span id="cb3-17">        embeddings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm(embeddings)</span>
<span id="cb3-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dropout(embeddings)</span></code></pre></div></div>
</div>
<div id="aa000001-pe00-heat-map0-000000000001" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb4-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb4-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib</span>
<span id="cb4-4"></span>
<span id="cb4-5">matplotlib.rcParams[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"figure.dpi"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">150</span></span>
<span id="cb4-6"></span>
<span id="cb4-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> sinusoidal_pe(seq_len, d_model):</span>
<span id="cb4-8">    pe  <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((seq_len, d_model))</span>
<span id="cb4-9">    pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(seq_len)[:, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>]</span>
<span id="cb4-10">    i   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.arange(d_model)[<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, :]</span>
<span id="cb4-11">    div <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10000</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> d_model)</span>
<span id="cb4-12">    pe[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>::<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.sin(pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> div[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>::<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span>
<span id="cb4-13">    pe[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>::<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.cos(pos <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> div[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>::<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span>
<span id="cb4-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> pe</span>
<span id="cb4-15"></span>
<span id="cb4-16">pe <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sinusoidal_pe(seq_len<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span>, d_model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">128</span>)</span>
<span id="cb4-17"></span>
<span id="cb4-18">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">3.5</span>))</span>
<span id="cb4-19">img <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ax.imshow(pe.T, cmap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"RdBu_r"</span>, aspect<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"auto"</span>, vmin<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, vmax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb4-20">ax.set_xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Position in sequence"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>)</span>
<span id="cb4-21">ax.set_ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Embedding dimension"</span>, fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>)</span>
<span id="cb4-22">ax.set_title(</span>
<span id="cb4-23">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Sinusoidal Positional Encoding: each column is a unique position fingerprint"</span>,</span>
<span id="cb4-24">    fontsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span></span>
<span id="cb4-25">)</span>
<span id="cb4-26">plt.colorbar(img, ax<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ax, fraction<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.015</span>, pad<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Encoding value"</span>)</span>
<span id="cb4-27">plt.tight_layout()</span>
<span id="cb4-28">plt.savefig(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"positional-encoding-heatmap.png"</span>, dpi<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">150</span>, bbox_inches<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tight"</span>)</span>
<span id="cb4-29">plt.show()</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="Transformer-Architecture-Explained_files/figure-html/cell-5-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure 2: Sinusoidal positional encoding. Each column is a unique fingerprint for one position. Low-frequency components (bottom rows) vary slowly, encoding coarse, sentence-level position. High-frequency components (top rows) vary quickly, encoding fine, word-level position. Together, every position from 0 to max_seq_len gets a unique vector."><img src="https://imaddabbura.github.io/posts/nlp/Transformer-Architecture-Explained_files/figure-html/cell-5-output-1.png" class="img-fluid figure-img" alt="Figure 2: Sinusoidal positional encoding. Each column is a unique fingerprint for one position. Low-frequency components (bottom rows) vary slowly, encoding coarse, sentence-level position. High-frequency components (top rows) vary quickly, encoding fine, word-level position. Together, every position from 0 to max_seq_len gets a unique vector."></a></p>
<figcaption><strong>Figure 2:</strong> Sinusoidal positional encoding. Each column is a unique fingerprint for one position. Low-frequency components (bottom rows) vary slowly, encoding coarse, sentence-level position. High-frequency components (top rows) vary quickly, encoding fine, word-level position. Together, every position from 0 to max_seq_len gets a unique vector.</figcaption>
</figure>
</div>
</div>
</div>
</section>
</section>
</section>
<section id="scaled-dot-product-attention" class="level2">
<h2 class="anchored" data-anchor-id="scaled-dot-product-attention">Scaled Dot-Product Attention</h2>
<p>Attention is the core computation that makes everything else in the Transformer work. Everything up to this point (embeddings, positional encodings) has been preprocessing. <em>This</em> is the operation that enables direct token-to-token communication.</p>
<p>This section builds it up step by step: from the Q/K/V projections, through the dot-product similarity, scaling, softmax, and masking. By the end, the library analogy will have a precise mathematical form.</p>
<section id="the-three-projections-query-key-value" class="level3">
<h3 class="anchored" data-anchor-id="the-three-projections-query-key-value">The Three Projections: Query, Key, Value</h3>
<p>Given an input sequence <img src="https://latex.codecogs.com/png.latex?x"> of shape <code>B × T × embed_dim</code>, we produce three separate linear projections:</p>
<p><img src="https://latex.codecogs.com/png.latex?Q%20=%20xW_Q,%20%5Cquad%20K%20=%20xW_K,%20%5Cquad%20V%20=%20xW_V"></p>
<p>Each weight matrix (<img src="https://latex.codecogs.com/png.latex?W_Q">, <img src="https://latex.codecogs.com/png.latex?W_K">, <img src="https://latex.codecogs.com/png.latex?W_V">) has shape <code>embed_dim × head_dim</code>. These are learned parameters: different projection matrices produce different “perspectives” on the same input.</p>
<p><strong>Why three separate projections instead of one?</strong> Because what a token <em>wants</em> (its query), what it <em>offers to match against</em> (its key), and what it <em>actually communicates</em> (its value) are three genuinely different things. Consider how a search engine works: your search query text (Q) is compared against the indexed keywords of a web page (K), but what you actually receive when you click is the full page content (V), which may be organized completely differently from the index terms. Separating these three roles gives the model the flexibility to learn very different relationships for each.</p>
</section>
<section id="computing-attention-weights-a-worked-example" class="level3">
<h3 class="anchored" data-anchor-id="computing-attention-weights-a-worked-example">Computing Attention Weights: A Worked Example</h3>
<p>With Q, K, V in hand, the attention weights are computed as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bweights%7D%20=%20%5Ctext%7Bsoftmax%7D%5C!%5Cleft(%5Cfrac%7BQK%5ET%7D%7B%5Csqrt%7Bd_k%7D%7D%5Cright)"></p>
<p>Let’s trace through this with a concrete 3-token example. Suppose our sequence is [“the”, “cat”, “sat”], with <code>head_dim = 4</code>. After the Q and K projections, imagine we have:</p>
<p><img src="https://latex.codecogs.com/png.latex?Q%20=%20%5Cbegin%7Bbmatrix%7D%201%20&amp;%200%20&amp;%201%20&amp;%200%20%5C%5C%200%20&amp;%201%20&amp;%200%20&amp;%201%20%5C%5C%201%20&amp;%201%20&amp;%200%20&amp;%200%20%5Cend%7Bbmatrix%7D,%5Cquad%20K%20=%20%5Cbegin%7Bbmatrix%7D%201%20&amp;%200%20&amp;%200%20&amp;%201%20%5C%5C%200%20&amp;%201%20&amp;%201%20&amp;%200%20%5C%5C%201%20&amp;%201%20&amp;%200%20&amp;%200%20%5Cend%7Bbmatrix%7D"></p>
<p><strong>Step 1. Dot products <img src="https://latex.codecogs.com/png.latex?QK%5ET"></strong> (shape <code>3 × 3</code>): Every token’s query is dotted with every token’s key. The <img src="https://latex.codecogs.com/png.latex?(i,j)"> entry measures how much token <img src="https://latex.codecogs.com/png.latex?i"> “wants” to attend to token <img src="https://latex.codecogs.com/png.latex?j">.</p>
<p><img src="https://latex.codecogs.com/png.latex?QK%5ET%20=%20%5Cbegin%7Bbmatrix%7D%201%20&amp;%201%20&amp;%202%20%5C%5C%201%20&amp;%201%20&amp;%200%20%5C%5C%201%20&amp;%202%20&amp;%202%20%5Cend%7Bbmatrix%7D"></p>
<p>Why dot products? Geometrically, the dot product of two vectors is large when they point in similar directions (small angle) and small when they are orthogonal. If a query and key are aligned (the token is “looking for” exactly what the other token “offers”), the dot product is high, and that token will receive a large attention weight.</p>
<p><strong>Step 2. Scale by <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B1%7D%7B%5Csqrt%7Bd_k%7D%7D%20=%20%5Cfrac%7B1%7D%7B2%7D">:</strong></p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cfrac%7BQK%5ET%7D%7B%5Csqrt%7Bd_k%7D%7D%20=%20%5Cbegin%7Bbmatrix%7D%200.5%20&amp;%200.5%20&amp;%201.0%20%5C%5C%200.5%20&amp;%200.5%20&amp;%200.0%20%5C%5C%200.5%20&amp;%201.0%20&amp;%201.0%20%5Cend%7Bbmatrix%7D"></p>
<p><strong>Step 3. Softmax row-wise</strong> (each row sums to 1):</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bweights%7D%20=%20%5Cbegin%7Bbmatrix%7D%200.27%20&amp;%200.27%20&amp;%200.46%20%5C%5C%200.33%20&amp;%200.33%20&amp;%200.33%20%5C%5C%200.21%20&amp;%200.39%20&amp;%200.39%20%5Cend%7Bbmatrix%7D"></p>
<p>Row 1 (token “the”): attends most strongly to “sat” (0.46). Row 2 (“cat”): distributes evenly. Row 3 (“sat”): attends most to “cat” and itself.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/attention-weights-matrix.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure 3: Attention weights for the [‘the’, ‘cat’, ‘sat’] example. Rows are query tokens; columns are keys. Each row is a probability distribution: how much each token attends to every other token."><img src="https://imaddabbura.github.io/posts/nlp/images/attention-weights-matrix.png" class="quarto-figure quarto-figure-center figure-img" width="400" height="400" alt="Figure 3: Attention weights for the [‘the’, ‘cat’, ‘sat’] example. Rows are query tokens; columns are keys. Each row is a probability distribution: how much each token attends to every other token."></a></p>
</figure>
</div>
<figcaption><strong>Figure 3:</strong> Attention weights for the [‘the’, ‘cat’, ‘sat’] example. Rows are query tokens; columns are keys. Each row is a probability distribution: how much each token attends to every other token.</figcaption>
</figure>
</div>
<p><strong>Step 4. Multiply by V</strong> (shape <code>3 × head_dim</code>): The output for each token is a weighted combination of all value vectors, with weights from the softmax step. Token “the” will receive a mix of all three value vectors, weighted 27%/27%/46%. The output is a <strong>contextualized representation</strong>: the same token in a different sentence would produce different weights and therefore a different output vector.</p>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>The Departure from Static Embeddings
</div>
</div>
<div class="callout-body-container callout-body">
<p>Notice what just happened: the token “the”, which starts with a fixed embedding vector identical in every sentence, now has a representation shaped by the presence of “cat” and “sat.” Run the same token through a different sentence (“the table broke”), and it emerges from attention with a different output vector.</p>
<p>This is the fundamental departure from static word embeddings like word2vec or GloVe: those give every token a single, context-free vector that never changes. A Transformer gives every token a <em>contextual</em> representation, numerically different depending on what surrounds it. “bank” in “river bank” and “bank” in “bank account” start with the same embedding but diverge after attention. This is why Transformer-based representations are so dramatically better at tasks requiring word sense disambiguation, coreference resolution, and syntactic parsing.</p>
</div>
</div>
</section>
<section id="why-scale-by-sqrtd_k" class="level3">
<h3 class="anchored" data-anchor-id="why-scale-by-sqrtd_k">Why Scale by <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7Bd_k%7D">?</h3>
<p>This design choice looks arbitrary until you see the numerical reason.</p>
<p>Q and K are both initialized as approximately unit-variance random vectors. When you compute their dot product across <code>d_k</code> dimensions, the result has <strong>variance equal to <img src="https://latex.codecogs.com/png.latex?d_k"></strong> (sum of <code>d_k</code> independent unit-variance terms). For a typical <code>head_dim</code> of 64, the raw dot products have standard deviation 8. For <code>head_dim = 768</code>, standard deviation 27.</p>
<p>Large-magnitude inputs to softmax cause a saturation problem. When one logit is much larger than the others, softmax approaches a one-hot distribution: almost all weight goes to one token, and gradients for every other position become negligibly small. The model can only learn from the one token it attends to, and ignores all the rest.</p>
<p>Dividing by <img src="https://latex.codecogs.com/png.latex?%5Csqrt%7Bd_k%7D"> rescales the dot products back to approximately unit variance, regardless of <code>head_dim</code>. Softmax then produces a diffuse distribution (not too concentrated, not too uniform), and gradients flow to all positions during training.</p>
<blockquote class="blockquote">
<p><em>Without scaling, the softmax saturates: one token captures nearly all the weight and the rest are ignored. Scaling keeps the distribution spread out, so every token can contribute to the output.</em></p>
</blockquote>
</section>
<section id="softmax-competition-not-independence" class="level3">
<h3 class="anchored" data-anchor-id="softmax-competition-not-independence">Softmax: Competition, Not Independence</h3>
<p>Why use softmax and not sigmoid (or any other normalization)?</p>
<p>Sigmoid applied to each attention logit independently would allow a token to “attend highly to everyone” at the same time, with no trade-off. But attention should be selective: attending more to one token means attending less to others.</p>
<p>Softmax is a <strong>competitive normalization</strong>: its outputs sum to 1, so the weights form a probability distribution over the context window. Increasing attention to one token necessarily decreases attention to all others. This forces the model to make decisions about what is relevant rather than attending indiscriminately to everything.</p>
<p><strong>The exponential creates sparsity, not just competition.</strong> Softmax uses <img src="https://latex.codecogs.com/png.latex?e%5Ex">, not a simple linear normalization like dividing by the sum. The exponential amplifies differences: if one logit is 2 points higher than another, it receives <img src="https://latex.codecogs.com/png.latex?e%5E2%20%5Capprox%207%5Ctimes"> more weight, not just 2× more. In practice this means attention patterns are often <em>peaky</em>: a small number of tokens receive the vast majority of the weight, and the rest are nearly zero. This emergent sparsity is what makes attention heads interpretable: a head that attends sharply to the syntactic subject has learned a crisp, readable pattern, not a diffuse smear. It also means that a single highly-relevant token can dominate the output almost entirely, which is the mechanism behind induction heads and other sharp attention circuits found in mechanistic interpretability research.</p>
</section>
<section id="causal-masking-decoder-only" class="level3">
<h3 class="anchored" data-anchor-id="causal-masking-decoder-only">Causal Masking (Decoder Only)</h3>
<p>In a language model, the task is to predict the next token from all previous tokens. If the model can see token <img src="https://latex.codecogs.com/png.latex?t+1"> while predicting token <img src="https://latex.codecogs.com/png.latex?t">, that is data leakage: the model would just copy the future token rather than learning to predict it.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/causal-mask-matrix.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Figure 4: Causal mask for a 5-token sequence. Green cells are positions the query token is allowed to attend to; red cells (−∞) are masked out and become 0 after softmax. Each row is a query token; each column is a key token."><img src="https://imaddabbura.github.io/posts/nlp/images/causal-mask-matrix.png" class="img-fluid quarto-figure quarto-figure-center figure-img" alt="Figure 4: Causal mask for a 5-token sequence. Green cells are positions the query token is allowed to attend to; red cells (−∞) are masked out and become 0 after softmax. Each row is a query token; each column is a key token."></a></p>
</figure>
</div>
<figcaption><strong>Figure 4:</strong> Causal mask for a 5-token sequence. Green cells are positions the query token is allowed to attend to; red cells (−∞) are masked out and become 0 after softmax. Each row is a query token; each column is a key token.</figcaption>
</figure>
</div>
<p>After softmax, <img src="https://latex.codecogs.com/png.latex?-%5Cinfty"> becomes exactly 0. Token 1 can only attend to itself. Token 3 can attend to tokens 1, 2, and 3 but not 4. The mask enforces a strict information asymmetry: <strong>you can read anything in the past, but nothing in the future</strong>.</p>
<p>This is implemented by registering a lower-triangular buffer in the <code>AttentionHead</code> and calling <code>masked_fill</code> before softmax.</p>
</section>
<section id="self-attention-vs.-cross-attention" class="level3">
<h3 class="anchored" data-anchor-id="self-attention-vs.-cross-attention">Self-Attention vs.&nbsp;Cross-Attention</h3>
<p><strong>Self-attention</strong>: Q, K, and V all come from the same input sequence <img src="https://latex.codecogs.com/png.latex?x">. Every token attends to every other token within the same sequence. This is what the encoder uses (bidirectional) and the decoder uses for its first sublayer (causal).</p>
<p><strong>Cross-attention</strong>: Q comes from one sequence (the decoder’s hidden state), while K and V come from a different sequence (the encoder’s output). The decoder “reads” the encoder’s representation of the source sequence. This is the mechanism that connects the two halves of an encoder-decoder model.</p>
<p>The generalization is worth stating explicitly: <strong>any two sequences can be related through cross-attention</strong>, simply by using one as the source of Q and the other as the source of K and V. This is the same operation that connects modalities in vision-language models (text queries attend to image patch keys/values), that lets perceiver architectures compress long inputs (a small set of learned query vectors attends to a large input), and that underlies virtually all multi-modal conditioning. Cross-attention is not a feature of encoder-decoder models; it is a universal conditioning primitive.</p>
<p>The full attention equation:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7BAttention%7D(Q,%20K,%20V)%20=%20%5Ctext%7Bsoftmax%7D%5C!%5Cleft(%5Cfrac%7BQK%5ET%7D%7B%5Csqrt%7Bd_k%7D%7D%5Cright)V"></p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/scaled-dot-product-attention.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Figure 5: Scaled Dot-Product Attention (source)"><img src="https://imaddabbura.github.io/posts/nlp/images/scaled-dot-product-attention.png" class="quarto-figure quarto-figure-center figure-img" height="400" alt="Figure 5: Scaled Dot-Product Attention (source)"></a></p>
</figure>
</div>
<figcaption><strong>Figure 5:</strong> Scaled Dot-Product Attention (<a href="https://arxiv.org/abs/1706.03762">source</a>)</figcaption>
</figure>
</div>
</section>
<section id="the-quadratic-cost-attentions-fundamental-bottleneck" class="level3">
<h3 class="anchored" data-anchor-id="the-quadratic-cost-attentions-fundamental-bottleneck">The Quadratic Cost: Attention’s Fundamental Bottleneck</h3>
<p>Computing attention requires forming the full <img src="https://latex.codecogs.com/png.latex?T%20%5Ctimes%20T"> weight matrix: every token’s query dotted against every token’s key. This is <img src="https://latex.codecogs.com/png.latex?O(T%5E2%20%5Ccdot%20d_k)"> time and <img src="https://latex.codecogs.com/png.latex?O(T%5E2)"> memory. For most sentences this is fine. For long documents, it becomes the dominant constraint:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Sequence length</th>
<th>Attention matrix</th>
<th>Memory (fp16, 1 head)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>512 tokens</td>
<td>512 × 512 = 262K</td>
<td>~0.5 MB</td>
</tr>
<tr class="even">
<td>4,096 tokens</td>
<td>4K × 4K = 16.8M</td>
<td>~32 MB</td>
</tr>
<tr class="odd">
<td>128K tokens</td>
<td>128K × 128K = 16.4B</td>
<td>~31 GB</td>
</tr>
</tbody>
</table>
<p>This quadratic growth is why early BERT was capped at 512 tokens, why getting GPT-3 to handle long documents required tricks, and why an entire subfield of <strong>efficient attention</strong> exists: sliding-window attention (Longformer), linear attention, sparse attention (BigBird), and state-space models like Mamba are all attempts to approximate or restructure the <img src="https://latex.codecogs.com/png.latex?T%20%5Ctimes%20T"> computation to grow linearly with sequence length.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>FlashAttention Changes the Hardware Utilization, Not the Complexity
</div>
</div>
<div class="callout-body-container callout-body">
<p>FlashAttention (Dao et al., 2022) is often described as “making attention faster.” What it actually does: reorders the computation to tile through the attention matrix in blocks that fit in GPU SRAM (fast memory), avoiding slow round-trips to HBM (GPU global memory). The FLOPs are identical to standard attention; the memory bandwidth cost drops dramatically: 2–4x wall-clock speedup with numerically identical outputs. It also reduces peak memory from <img src="https://latex.codecogs.com/png.latex?O(T%5E2)"> to <img src="https://latex.codecogs.com/png.latex?O(T)"> by never materializing the full attention matrix. This is why FlashAttention is the standard in every modern training stack, but it does not fix the fundamental quadratic scaling problem for very long contexts.</p>
</div>
</div>
<div id="0540e571-f6de-49d2-9535-e91755a7a78f" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> AttentionHead(nn.Module):</span>
<span id="cb5-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config, head_dim, is_decoder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb5-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb5-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.embed_dim, head_dim, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb5-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.embed_dim, head_dim, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb5-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.embed_dim, head_dim, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb5-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.is_decoder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> is_decoder</span>
<span id="cb5-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.is_decoder:</span>
<span id="cb5-9">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.register_buffer(</span>
<span id="cb5-10">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mask"</span>, torch.tril(torch.ones(config.block_sz, config.block_sz))</span>
<span id="cb5-11">            )</span>
<span id="cb5-12"></span>
<span id="cb5-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, query, key, value):</span>
<span id="cb5-14">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## query: B x T_q x embed_dim  (source of queries)</span></span>
<span id="cb5-15">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## key:   B x T_k x embed_dim  (source of keys)</span></span>
<span id="cb5-16">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## value: B x T_k x embed_dim  (source of values)</span></span>
<span id="cb5-17">        q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.q(query)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T_q x head_dim</span></span>
<span id="cb5-18">        k <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.k(key)    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T_k x head_dim</span></span>
<span id="cb5-19">        v <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.v(value)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T_k x head_dim</span></span>
<span id="cb5-20">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## w: B x T_q x T_k  (pairwise similarity between every query and every key)</span></span>
<span id="cb5-21">        w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> k.transpose(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (k.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>)</span>
<span id="cb5-22">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.is_decoder:</span>
<span id="cb5-23">            T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> w.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb5-24">            w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> w.masked_fill(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.mask[:T, :T] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"inf"</span>))</span>
<span id="cb5-25">        w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> F.softmax(w, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-26">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## output: B x T_q x head_dim</span></span>
<span id="cb5-27">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> w <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> v</span></code></pre></div></div>
</div>
</section>
</section>
<section id="multi-head-attention" class="level2">
<h2 class="anchored" data-anchor-id="multi-head-attention">Multi-Head Attention</h2>
<section id="why-multiple-heads" class="level3">
<h3 class="anchored" data-anchor-id="why-multiple-heads">Why Multiple Heads?</h3>
<p>A single attention head learns one type of relationship between tokens. For example, it might learn to focus on the syntactic subject of a sentence whenever any token is processed, a subject-finding head. But language has many simultaneous relationship types that are all relevant at once:</p>
<ul>
<li><em>Syntactic</em>: subject-verb agreement, noun-adjective agreement</li>
<li><em>Semantic</em>: coreference (“it” → “the trophy”), negation scope</li>
<li><em>Structural</em>: attending to nearby tokens for local context</li>
<li><em>Task-specific</em>: attending to sentiment-bearing words for classification</li>
</ul>
<p>Multiple heads allow the model to learn all of these in parallel. Each head has its own independent weight matrices <img src="https://latex.codecogs.com/png.latex?W_Q%5Eh">, <img src="https://latex.codecogs.com/png.latex?W_K%5Eh">, <img src="https://latex.codecogs.com/png.latex?W_V%5Eh"> that project the same input <img src="https://latex.codecogs.com/png.latex?x"> into a different lower-dimensional subspace:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bhead%5C_dim%7D%20=%20%5Cfrac%7B%5Ctext%7Bembed%5C_dim%7D%7D%7B%5Ctext%7Bnum%5C_heads%7D%7D"></p>
<p>This subspace separation is the mechanism that makes specialization both possible and stable. Head 3’s attention weights are determined by <img src="https://latex.codecogs.com/png.latex?W_Q%5E3%20%5Ccdot%20W_K%5E3"> inner products, which have nothing to do with what <img src="https://latex.codecogs.com/png.latex?W_Q%5E7%20%5Ccdot%20W_K%5E7"> computes for head 7. Because they project into orthogonal subspaces of the embedding, heads don’t interfere with each other: a coreference head and a subject-finding head can coexist without one corrupting the other.</p>
<p>Empirical findings from BERTology (Clark et al., 2019) confirm that this specialization emerges after training: some heads consistently track syntactic dependencies across the entire network; others attend primarily to adjacent tokens, effectively implementing a local sliding window; some heads in BERT-style models attend heavily to the <code>[SEP]</code> token, a kind of “no-op” head that routes excess attention somewhere harmless when no strong relationship exists.</p>
<p>Importantly, this specialization is <strong>not designed in</strong>. It arises entirely from the training signal. The architecture only provides the capacity for parallel, independent subspace projections; training discovers what each subspace should track.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/multi-head-attention.png" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="Figure 6: Multi-Head Attention with several attention layers running in parallel (source)"><img src="https://imaddabbura.github.io/posts/nlp/images/multi-head-attention.png" class="quarto-figure quarto-figure-center figure-img" height="400" alt="Figure 6: Multi-Head Attention with several attention layers running in parallel (source)"></a></p>
</figure>
</div>
<figcaption><strong>Figure 6:</strong> Multi-Head Attention with several attention layers running in parallel (<a href="https://arxiv.org/abs/1706.03762">source</a>)</figcaption>
</figure>
</div>
</section>
<section id="implementation-parallel-heads-final-projection" class="level3">
<h3 class="anchored" data-anchor-id="implementation-parallel-heads-final-projection">Implementation: Parallel Heads, Final Projection</h3>
<p>Each head produces an output of shape <code>B × T × head_dim</code>. All heads run entirely in parallel: there is <strong>no communication between heads</strong> during the forward pass. The outputs of all heads are concatenated along the last dimension: <code>num_heads × head_dim = embed_dim</code>. The concatenated tensor then passes through a final linear projection <img src="https://latex.codecogs.com/png.latex?W_O"> of shape <code>embed_dim × embed_dim</code>.</p>
<p><strong>Why the final projection?</strong> The heads operated in isolation: each found something different in its own subspace. The <img src="https://latex.codecogs.com/png.latex?W_O"> projection is the first opportunity for the model to mix information <em>across</em> heads: to combine what the coreference head found with what the subject-finding head found into a single coherent output vector. But <img src="https://latex.codecogs.com/png.latex?W_O"> does more than concatenate: it <em>filters and compresses</em>. The 12 concatenated head outputs may contain redundant information, conflicting signals, or noise from heads that found nothing relevant. <img src="https://latex.codecogs.com/png.latex?W_O"> is a learned projection that selects which cross-head combinations to amplify and which to suppress. Think of it as the editor who takes 12 reporters’ raw notes and synthesises them into a single coherent paragraph; not every detail makes it through.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Pedagogical vs.&nbsp;Efficient Implementation
</div>
</div>
<div class="callout-body-container callout-body">
<p>The implementation below uses a Python loop over heads for clarity. In practice, all heads are computed in a single batched matrix multiply by reshaping the input to <code>B × T × num_heads × head_dim</code> and transposing. This is the approach used in production (and in <code>torch.nn.MultiheadAttention</code>). The pedagogical loop is equivalent but slower.</p>
</div>
</div>
<p>However, there is still a problem: multi-head attention is a weighted <em>averaging</em> operation; it is linear in V. Stacking multiple attention layers with nothing in between collapses to a single linear transformation. The network needs nonlinearity. That is the feed-forward network’s job.</p>
<div id="cf91afcc-af53-4bd9-84c6-bd3dd69bd49f" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> MultiHeadAttention(nn.Module):</span>
<span id="cb6-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config, is_decoder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb6-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb6-4">        head_dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> config.embed_dim <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> config.num_attention_heads</span>
<span id="cb6-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.heads <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.ModuleList(</span>
<span id="cb6-6">            [</span>
<span id="cb6-7">                AttentionHead(config, head_dim, is_decoder)</span>
<span id="cb6-8">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(config.num_attention_heads)</span>
<span id="cb6-9">            ]</span>
<span id="cb6-10">        )</span>
<span id="cb6-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Final projection mixes information across heads: embed_dim -&gt; embed_dim</span></span>
<span id="cb6-12">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.output_proj <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.embed_dim, config.embed_dim)</span>
<span id="cb6-13"></span>
<span id="cb6-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, query, key, value):</span>
<span id="cb6-15">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## query: B x T_q x embed_dim</span></span>
<span id="cb6-16">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## key:   B x T_k x embed_dim</span></span>
<span id="cb6-17">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## value: B x T_k x embed_dim</span></span>
<span id="cb6-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Each head produces B x T_q x head_dim; cat gives B x T_q x embed_dim</span></span>
<span id="cb6-19">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.cat([head(query, key, value) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> head <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.heads], dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb6-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.output_proj(x)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T_q x embed_dim</span></span></code></pre></div></div>
</div>
</section>
</section>
<section id="feed-forward-network" class="level2">
<h2 class="anchored" data-anchor-id="feed-forward-network">Feed-Forward Network</h2>
<section id="why-is-it-needed" class="level3">
<h3 class="anchored" data-anchor-id="why-is-it-needed">Why Is It Needed?</h3>
<p>Attention is a weighted averaging operation. It is <strong>linear in V</strong>: the output for each position is a linear combination of value vectors, where the combination weights come from the attention scores. If we stacked multiple attention layers with no nonlinearity in between, the composition of linear operations would remain linear, effectively equivalent to a single layer.</p>
<p>This is the same reason we use activation functions between layers in any neural network: without them, depth buys us nothing.</p>
<p>The feed-forward network (FFN) adds the essential nonlinearity. It processes each token’s representation <strong>independently</strong> after the attention layer. There is no mixing of tokens in the FFN; that is attention’s job. The clean separation of concerns is intentional:</p>
<ul>
<li><strong>Attention</strong>: mixes information across positions (who talks to whom)</li>
<li><strong>FFN</strong>: transforms each position’s representation non-linearly (what to say)</li>
</ul>
<p><strong>The FFN as a knowledge store.</strong> Research by Geva et al.&nbsp;(2021) provides a compelling interpretation: FFN layers function as associative memories. The first linear layer acts as a set of keys that pattern-match against the input; the second linear layer acts as the corresponding values that are retrieved and output. Most of a Transformer’s factual knowledge (the associations between entities, relations, and attributes) is hypothesized to live in FFN weights, not in the attention matrices.</p>
<blockquote class="blockquote">
<p><em>Attention is the routing system. The FFN is the knowledge store.</em></p>
</blockquote>
</section>
<section id="architecture-details" class="level3">
<h3 class="anchored" data-anchor-id="architecture-details">Architecture Details</h3>
<p>The FFN has a characteristic structure: expand, activate, contract.</p>
<ol type="1">
<li><strong>Expand</strong>: Linear projection from <code>embed_dim</code> → <code>4 × embed_dim</code>. The 4x factor is empirical, found to work well across a range of model sizes. The expanded intermediate dimension is where most of the model’s representational capacity lives, and it is the dimension that is typically scaled up when making larger models.</li>
<li><strong>Activate</strong>: GELU (Gaussian Error Linear Unit) nonlinearity. Unlike ReLU, GELU applies a smooth, probabilistic gate proportional to the Gaussian CDF. Empirically, GELU consistently outperforms ReLU in Transformer training. Modern models (LLaMA, PaLM) use SwiGLU, a gated variant, which further improves performance.</li>
<li><strong>Contract</strong>: Linear projection from <code>4 × embed_dim</code> → <code>embed_dim</code>, restoring the original dimension for the residual connection.</li>
</ol>
<p><strong>Why position-wise?</strong> The FFN applies the same learned transformation to every position independently and in parallel. There is no weight-sharing across positions within a layer, but the same weight matrices process every position. This is sometimes called a “position-wise” or “point-wise” feed-forward layer.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Most of the parameters live here.</strong> Each attention layer has four weight matrices (Q, K, V, O), each of size <img src="https://latex.codecogs.com/png.latex?d_%7B%5Ctext%7Bmodel%7D%7D%20%5Ctimes%20d_%7B%5Ctext%7Bmodel%7D%7D">, totalling <img src="https://latex.codecogs.com/png.latex?4d_%7B%5Ctext%7Bmodel%7D%7D%5E2"> parameters. The FFN has two matrices of size <img src="https://latex.codecogs.com/png.latex?d_%7B%5Ctext%7Bmodel%7D%7D%20%5Ctimes%204d_%7B%5Ctext%7Bmodel%7D%7D">, totalling <img src="https://latex.codecogs.com/png.latex?8d_%7B%5Ctext%7Bmodel%7D%7D%5E2"> parameters: <strong>twice as many as attention</strong>. Across a full model, the FFN accounts for roughly two-thirds of all trainable parameters. When people talk about “scaling” a Transformer, they mostly mean growing <img src="https://latex.codecogs.com/png.latex?d_%7B%5Ctext%7Bmodel%7D%7D"> and <img src="https://latex.codecogs.com/png.latex?d_%7B%5Ctext%7Bff%7D%7D">, which expands this majority share.</p>
</div>
</div>
<div id="c7ceda3b-1880-495c-8a32-04005afe4260" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> FeedForwardNN(nn.Module):</span>
<span id="cb7-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config):</span>
<span id="cb7-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb7-4">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Expand to 4x hidden dim, then contract back; most capacity lives here</span></span>
<span id="cb7-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.embed_dim, config.intermediate_sz)</span>
<span id="cb7-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.intermediate_sz, config.embed_dim)</span>
<span id="cb7-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dropout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Dropout(config.hidden_dropout_prob)</span>
<span id="cb7-8"></span>
<span id="cb7-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb7-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x:        B x T x embed_dim</span></span>
<span id="cb7-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## after l1: B x T x intermediate_sz  (expand)</span></span>
<span id="cb7-12">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## after l2: B x T x embed_dim        (contract)</span></span>
<span id="cb7-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dropout(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l2(F.gelu(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l1(x))))</span></code></pre></div></div>
</div>
</section>
</section>
<section id="layer-normalization" class="level2">
<h2 class="anchored" data-anchor-id="layer-normalization">Layer Normalization</h2>
<section id="why-normalize-at-all" class="level3">
<h3 class="anchored" data-anchor-id="why-normalize-at-all">Why Normalize at All?</h3>
<p>Deep networks have a training stability problem: as signals propagate through many layers, the distribution of activations tends to shift and grow, a phenomenon called <strong>internal covariate shift</strong>. Layers that receive wildly varying input distributions must constantly adjust their weights just to track the shifting scale, not to learn meaningful transformations. This wastes capacity and slows training.</p>
<blockquote class="blockquote">
<p><em>Think of it as keeping the working range of each layer consistent. Without normalization, earlier layers can produce outputs 100x larger than what later layers expect: the later layers waste capacity on a bookkeeping problem rather than learning anything about language.</em></p>
</blockquote>
<p>Normalization is the engineering fix: explicitly constrain activation distributions to zero mean and unit variance at key points in the network, keeping signals in a regime where gradients are well-behaved throughout training.</p>
</section>
<section id="batch-normalization-vs.-layer-normalization" class="level3">
<h3 class="anchored" data-anchor-id="batch-normalization-vs.-layer-normalization">Batch Normalization vs.&nbsp;Layer Normalization</h3>
<p>Batch Normalization (Ioffe &amp; Szegedy, 2015) normalizes each feature across the batch dimension. This works well for CNNs on images but has two critical failure modes for sequence models:</p>
<ol type="1">
<li><strong>Small batches</strong>: with batch size 1, the batch mean and variance are undefined (or estimated from a single sample). Transformers are often trained with small batch sizes per GPU.</li>
<li><strong>Variable-length sequences</strong>: different positions in a batch may have very different activation statistics. Normalizing across a mixed batch conflates these.</li>
</ol>
<p>Layer Normalization (Ba et al., 2016) normalizes across the <strong>feature dimension</strong> instead of the batch dimension:</p>
<p><img src="https://latex.codecogs.com/png.latex?y%20=%20%5Cfrac%7Bx%20-%20%5Cmathbb%7BE%7D%5Bx%5D%7D%7B%5Csqrt%7B%5Ctext%7BVar%7D%5Bx%5D%20+%20%5Cepsilon%7D%7D%20%5Ccdot%20%5Cgamma%20+%20%5Cbeta"></p>
<p>The mean and variance are computed independently for each example, over all features of that example. This makes LayerNorm completely independent of batch size: it works identically whether batch size is 1 or 1000.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th></th>
<th>Batch Norm</th>
<th>Layer Norm</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Normalizes over</td>
<td>Batch dimension</td>
<td>Feature dimension</td>
</tr>
<tr class="even">
<td>Running statistics for inference</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr class="odd">
<td>Breaks for batch_size = 1</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr class="even">
<td>Variable-length sequences</td>
<td>Awkward</td>
<td>Natural</td>
</tr>
<tr class="odd">
<td>Common in</td>
<td>CNNs, image models</td>
<td>Transformers, RNNs</td>
</tr>
</tbody>
</table>
<p><strong>The learnable parameters <img src="https://latex.codecogs.com/png.latex?%5Cgamma"> and <img src="https://latex.codecogs.com/png.latex?%5Cbeta"></strong>: After normalization, every layer’s output would have zero mean and unit variance, too rigid. The learned scale (<img src="https://latex.codecogs.com/png.latex?%5Cgamma">) and shift (<img src="https://latex.codecogs.com/png.latex?%5Cbeta">) let each layer restore whatever distribution works best for its downstream computation. Without them, normalization would over-constrain the model.</p>
</section>
<section id="pre-norm-vs.-post-norm-a-critical-implementation-choice" class="level3">
<h3 class="anchored" data-anchor-id="pre-norm-vs.-post-norm-a-critical-implementation-choice">Pre-Norm vs.&nbsp;Post-Norm: A Critical Implementation Choice</h3>
<p>The original Transformer paper placed LayerNorm <em>after</em> the residual addition (Post-LayerNorm). GPT-2 and virtually every modern large model places it <em>before</em> (Pre-LayerNorm). This seemingly minor change has significant consequences for training stability.</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    subgraph PostLN["Post-LN (original paper)"]
        A1[x] --&gt; B1[Sublayer]
        A1 --&gt; C1[+]
        B1 --&gt; C1
        C1 --&gt; D1[LayerNorm]
        D1 --&gt; E1[output]
    end
    subgraph PreLN["Pre-LN (GPT-2, modern default)"]
        A2[x] --&gt; B2[LayerNorm]
        B2 --&gt; C2[Sublayer]
        A2 --&gt; D2[+]
        C2 --&gt; D2
        D2 --&gt; E2[output]
    end
</pre>
</div>
<p></p><figcaption> <strong>Figure 7:</strong> Post-LayerNorm (left) vs Pre-LayerNorm (right). Modern models use Pre-LN.</figcaption> </figure><p></p>
</div>
</div>
</div>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Post-LN: <code>LN(x + sublayer(x))</code></th>
<th>Pre-LN: <code>x + sublayer(LN(x))</code></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Gradient path</td>
<td>Normalization sits outside the residual (gradients must pass through it)</td>
<td>Normalization is inside (clean gradient highway through the residual)</td>
</tr>
<tr class="even">
<td>Training stability</td>
<td>Sensitive; requires careful learning rate warm-up; can diverge</td>
<td>More stable; trains without warm-up</td>
</tr>
<tr class="odd">
<td>Final performance</td>
<td>Marginally better with enough tuning</td>
<td>Slightly lower ceiling, but much easier to train</td>
</tr>
</tbody>
</table>
<p>Modern practice defaults to Pre-LN: training stability at scale is worth more than marginal final performance differences. If you are building a new model, use Pre-LN.</p>
</section>
</section>
<section id="skip-residual-connections" class="level2">
<h2 class="anchored" data-anchor-id="skip-residual-connections">Skip (Residual) Connections</h2>
<section id="the-residual-stream-mental-model" class="level3">
<h3 class="anchored" data-anchor-id="the-residual-stream-mental-model">The Residual Stream Mental Model</h3>
<p>Think of a Transformer as a <strong>residual stream</strong>: a river of information that flows from the input through all the layers to the output. Each layer (attention + FFN) reads from the stream and writes a correction back to it via addition:</p>
<p><img src="https://latex.codecogs.com/png.latex?x_%7B%5Ctext%7Bout%7D%7D%20=%20x_%7B%5Ctext%7Bin%7D%7D%20+%20%5Ctext%7Bsublayer%7D(x_%7B%5Ctext%7Bin%7D%7D)"></p>
<p>No single layer “owns” the representation. Each layer adds its contribution to a shared river. The residual stream at any point contains the sum of everything all previous layers have written.</p>
<p>This framing, developed in mechanistic interpretability research, makes it immediately clear why attention heads can specialize: each head contributes independently and additively to the stream. They don’t compete or overwrite each other; they contribute independently, and the stream accumulates all contributions.</p>
</section>
<section id="why-residual-connections-work" class="level3">
<h3 class="anchored" data-anchor-id="why-residual-connections-work">Why Residual Connections Work</h3>
<p><strong>Gradient highways.</strong> When backpropagating through <img src="https://latex.codecogs.com/png.latex?y%20=%20x%20+%20F(x)">:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20x%7D%20=%20%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20y%7D%20%5Ccdot%20%5Cleft(1%20+%20%5Cfrac%7B%5Cpartial%20F%7D%7B%5Cpartial%20x%7D%5Cright)"></p>
<p>The term <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%20L%7D%7B%5Cpartial%20y%7D"> reaches <img src="https://latex.codecogs.com/png.latex?x"> directly, through the identity path, regardless of what <img src="https://latex.codecogs.com/png.latex?F(x)"> does. Even if <img src="https://latex.codecogs.com/png.latex?F"> has saturated activations or near-zero gradients, the loss signal still flows back to earlier layers. This is why ResNets with skip connections can be trained to hundreds of layers while the same architecture without them fails beyond a dozen.</p>
<p><strong>Loss landscape smoothing.</strong> He et al.&nbsp;(2016) visualized the loss surfaces of deep networks with and without skip connections. Without them: chaotic, sharp, with many high-curvature local minima that trap gradient descent. With them: smooth, convex, much more navigable.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/loss-landscape-with-skip-connections.png" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="Figure 8: Loss surfaces of ResNet-56 with/without skip connections (source)"><img src="https://imaddabbura.github.io/posts/nlp/images/loss-landscape-with-skip-connections.png" class="img-fluid figure-img" alt="Figure 8: Loss surfaces of ResNet-56 with/without skip connections (source)"></a></p>
<figcaption><strong>Figure 8:</strong> Loss surfaces of ResNet-56 with/without skip connections (<a href="https://arxiv.labs.arxiv.org/html/1712.09913">source</a>)</figcaption>
</figure>
</div>
<p><strong>The forgetting argument.</strong> Without skip connections, each layer must preserve all useful information from its input in its output: if the layer wants to pass something unchanged, it must learn to do so explicitly. With skip connections, the <strong>default is identity</strong>: the layer only needs to learn what to <em>add</em>, not what to keep. This dramatically reduces the effective depth that the gradient must overcome.</p>
<p>However, training deep networks reliably requires one more ingredient beyond gradient highways: preventing the network from memorizing noise. That is dropout’s job.</p>
</section>
</section>
<section id="dropout" class="level2">
<h2 class="anchored" data-anchor-id="dropout">Dropout</h2>
<p>Dropout (Srivastava et al., 2014) randomly zeros a fraction <code>p</code> of activations during training. Each training step uses a different random mask, forcing the model not to rely on any particular activation path, a phenomenon called <strong>co-adaptation prevention</strong>.</p>
<p>The regularization effect comes from two mechanisms:</p>
<ol type="1">
<li><strong>Network size reduction</strong>: Dropping units creates a smaller effective network per step. A smaller network has fewer parameters to overfit.</li>
<li><strong>Implicit ensembling</strong>: Each step trains a different subnetwork. At inference, the full network approximates averaging over all these subnetworks, equivalent to a cheap bagging ensemble.</li>
</ol>
<p>In Transformers, dropout is applied after the embedding layer (after adding token + positional embeddings), after each attention sublayer, and after each FFN sublayer.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Modern Large Models Often Skip Dropout
</div>
</div>
<div class="callout-body-container callout-body">
<p>LLaMA, Mistral, and other recent large models use no dropout at all. At sufficient scale with enough data, the regularization effect of dropout is less necessary, and it slows training. Dropout remains important for smaller models trained on limited data, and for fine-tuning where overfitting is a risk.</p>
</div>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/dropout.png" class="lightbox" data-gallery="quarto-lightbox-gallery-8" title="Figure 9: Left: standard neural net. Right: thinned net after applying dropout; crossed units are dropped. (source)"><img src="https://imaddabbura.github.io/posts/nlp/images/dropout.png" class="img-fluid figure-img" alt="Figure 9: Left: standard neural net. Right: thinned net after applying dropout; crossed units are dropped. (source)"></a></p>
<figcaption><strong>Figure 9:</strong> Left: standard neural net. Right: thinned net after applying dropout; crossed units are dropped. (<a href="https://www.cs.toronto.edu/~rsalakhu/papers/srivastava14a.pdf">source</a>)</figcaption>
</figure>
</div>
<p>With all the individual components understood (attention, FFN, LayerNorm, skip connections, dropout), it’s time to see how they snap together into a complete layer.</p>
</section>
<section id="assembling-the-encoder-layer" class="level2">
<h2 class="anchored" data-anchor-id="assembling-the-encoder-layer">Assembling the Encoder Layer</h2>
<p>Now that we have all the building blocks, let us see how they snap together into a single encoder layer: the repeated unit that makes up the encoder stack.</p>
<p>An encoder layer applies two sublayers in sequence, each wrapped in a residual connection and LayerNorm. Tracing the shapes at every step (using Pre-LN convention):</p>
<table class="caption-top table">
<colgroup>
<col style="width: 15%">
<col style="width: 28%">
<col style="width: 18%">
<col style="width: 36%">
</colgroup>
<thead>
<tr class="header">
<th>Step</th>
<th>Operation</th>
<th>Shape</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td>Input Embeddings</td>
<td><code>(B, T, d_model)</code></td>
<td>Token IDs → dense vectors</td>
</tr>
<tr class="even">
<td>2</td>
<td>+ Positional Encoding</td>
<td><code>(B, T, d_model)</code></td>
<td>Inject position information</td>
</tr>
<tr class="odd">
<td>3</td>
<td>Self-Attention (xh heads)</td>
<td><code>(B, T, d_model)</code></td>
<td>Each token attends to all others</td>
</tr>
<tr class="even">
<td>4</td>
<td>Add &amp; Norm</td>
<td><code>(B, T, d_model)</code></td>
<td>Residual connection + layer norm</td>
</tr>
<tr class="odd">
<td>5</td>
<td>Feed-Forward</td>
<td><code>(B, T, d_ff)</code> → <code>(B, T, d_model)</code></td>
<td>Non-linear transformation</td>
</tr>
<tr class="even">
<td>6</td>
<td>Add &amp; Norm</td>
<td><code>(B, T, d_model)</code></td>
<td>Residual connection + layer norm</td>
</tr>
<tr class="odd">
<td>7</td>
<td>[Repeat × N layers]</td>
<td><code>(B, T, d_model)</code></td>
<td>Stack N encoder layers</td>
</tr>
<tr class="even">
<td>8</td>
<td>Encoder Output</td>
<td><code>(B, T, d_model)</code></td>
<td>Rich contextual representations</td>
</tr>
</tbody>
</table>
<p><code>B = batch size, T = sequence length, d_model = model dimension</code></p>
<p><strong>Why every row says <code>d_model</code>.</strong> Residual connections require that the sublayer output has exactly the same shape as its input; otherwise you cannot add them together. This is a hard architectural constraint: every sublayer (attention, FFN, LayerNorm) must consume and produce tensors of shape <code>(B, T, d_model)</code>. It is the reason <code>head_dim = d_model / num_heads</code> (the concatenation of all heads must restore <code>d_model</code>), and why the FFN contracts back from <code>d_ff</code> → <code>d_model</code> at the end. The entire Transformer is shaped around this single number.</p>
<p>Every token’s representation enters with shape <code>d_model</code>. After the attention sublayer, it has been updated by attending to all other tokens: information has been mixed across positions. After the FFN sublayer, each position’s representation has been transformed nonlinearly, independently from all other positions.</p>
<blockquote class="blockquote">
<p><em>An encoder layer does two things: (1) let tokens talk to each other via attention, then (2) let each token digest what it heard via the FFN.</em></p>
</blockquote>
<p>A full encoder stacks <img src="https://latex.codecogs.com/png.latex?N"> of these layers (typically 6–24). Each layer refines the representations further: early layers tend to capture surface-level patterns, later layers capture increasingly abstract semantic relationships.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Post-LN in the Code
</div>
</div>
<div class="callout-body-container callout-body">
<p>The implementation below uses the Post-LayerNorm arrangement from the original paper: <code>LN(x + sublayer(x))</code>. The Pre-LN alternative is shown in comments. For new models, prefer Pre-LN.</p>
</div>
</div>
<div id="b25d08fb-7099-40a1-9175-f53442e43b89" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> EncoderLayer(nn.Module):</span>
<span id="cb8-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config):</span>
<span id="cb8-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb8-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.attn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> MultiHeadAttention(config)</span>
<span id="cb8-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> FeedForwardNN(config)</span>
<span id="cb8-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(config.embed_dim)</span>
<span id="cb8-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(config.embed_dim)</span>
<span id="cb8-8"></span>
<span id="cb8-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb8-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x: B x T x embed_dim  (input and output shape are identical)</span></span>
<span id="cb8-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">##</span></span>
<span id="cb8-12">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Post-LayerNorm arrangement (original Transformer paper):</span></span>
<span id="cb8-13">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_1(x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.attn(x, x, x))  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## bidirectional self-attention</span></span>
<span id="cb8-14">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_2(x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ff(x))</span>
<span id="cb8-15">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">##</span></span>
<span id="cb8-16">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Pre-LayerNorm alternative (GPT-2+, more stable, recommended for new models):</span></span>
<span id="cb8-17">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x = x + self.attn(self.layer_norm_1(x), self.layer_norm_1(x), self.layer_norm_1(x))</span></span>
<span id="cb8-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x = x + self.ff(self.layer_norm_2(x))</span></span>
<span id="cb8-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T x embed_dim</span></span></code></pre></div></div>
</div>
<div id="af565905-5b13-4773-b44f-d0a2e78a3831" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> TransformerEncoder(nn.Module):</span>
<span id="cb9-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb9-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb9-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.embeddings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Embeddings(config)</span>
<span id="cb9-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder_blocks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Sequential(</span>
<span id="cb9-6">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>[EncoderLayer(config) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(config.num_hidden_layers)]</span>
<span id="cb9-7">        )</span>
<span id="cb9-8"></span>
<span id="cb9-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb9-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x:    B x T  (integer token IDs)</span></span>
<span id="cb9-11">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.embeddings(x)        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T x embed_dim</span></span>
<span id="cb9-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder_blocks(x)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T x embed_dim</span></span></code></pre></div></div>
</div>
</section>
<section id="assembling-the-decoder-layer" class="level2">
<h2 class="anchored" data-anchor-id="assembling-the-decoder-layer">Assembling the Decoder Layer</h2>
<p>The decoder layer differs from the encoder in one critical way: it adds a <strong>cross-attention sublayer</strong> between the masked self-attention and the FFN. This is the mechanism that lets the decoder read the encoder’s output.</p>
<p>A decoder layer applies three sublayers:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 15%">
<col style="width: 28%">
<col style="width: 18%">
<col style="width: 36%">
</colgroup>
<thead>
<tr class="header">
<th>Step</th>
<th>Operation</th>
<th>Shape</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td>Target Embeddings</td>
<td><code>(B, T_tgt, d_model)</code></td>
<td>Target token IDs → dense vectors</td>
</tr>
<tr class="even">
<td>2</td>
<td>+ Positional Encoding</td>
<td><code>(B, T_tgt, d_model)</code></td>
<td>Inject position information</td>
</tr>
<tr class="odd">
<td>3</td>
<td>Masked Self-Attention (xh)</td>
<td><code>(B, T_tgt, d_model)</code></td>
<td>Attends only to past positions (causal mask)</td>
</tr>
<tr class="even">
<td>4</td>
<td>Add &amp; Norm</td>
<td><code>(B, T_tgt, d_model)</code></td>
<td>Residual connection + layer norm</td>
</tr>
<tr class="odd">
<td>5</td>
<td>Cross-Attention (×h)</td>
<td><code>(B, T_tgt, d_model)</code></td>
<td>Q from decoder; K, V from encoder output</td>
</tr>
<tr class="even">
<td>6</td>
<td>Add &amp; Norm</td>
<td><code>(B, T_tgt, d_model)</code></td>
<td>Residual connection + layer norm</td>
</tr>
<tr class="odd">
<td>7</td>
<td>Feed-Forward</td>
<td><code>(B, T_tgt, d_ff)</code> → <code>(B, T_tgt, d_model)</code></td>
<td>Non-linear transformation</td>
</tr>
<tr class="even">
<td>8</td>
<td>Add &amp; Norm</td>
<td><code>(B, T_tgt, d_model)</code></td>
<td>Residual connection + layer norm</td>
</tr>
<tr class="odd">
<td>9</td>
<td>[Repeat × N layers]</td>
<td><code>(B, T_tgt, d_model)</code></td>
<td>Stack N decoder layers</td>
</tr>
<tr class="even">
<td>10</td>
<td>Linear + Softmax</td>
<td><code>(B, T_tgt, vocab_size)</code></td>
<td>Project to vocabulary probabilities</td>
</tr>
</tbody>
</table>
<p><code>B = batch size, T_tgt = target sequence length</code></p>
<p><strong>Sublayer 1 (masked self-attention)</strong>: Decoder tokens attend to each other, but only to past and current positions (causal mask). This builds a contextualized representation of the target sequence generated so far.</p>
<p><strong>Sublayer 2 (cross-attention)</strong>: The decoder’s hidden state becomes the query. The encoder’s final output provides the keys and values. Every decoder position can attend to all encoder positions: this is how the decoder “reads” the full source sequence at every generation step.</p>
<p><strong>Sublayer 3 (FFN)</strong>: Same position-wise transformation as in the encoder.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Note on the Code Below
</div>
</div>
<div class="callout-body-container callout-body">
<p>The DecoderLayer shown uses only masked self-attention (no cross-attention sublayer). It is therefore suited for the decoder-only (GPT-style) architecture. Cross-attention is addressed in the Encoder-Decoder section.</p>
</div>
</div>
<div id="d684880a-3132-487a-ab51-fc3b20978e5f" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> DecoderLayer(nn.Module):</span>
<span id="cb10-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config):</span>
<span id="cb10-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb10-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.attn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> MultiHeadAttention(config, is_decoder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb10-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ff <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> FeedForwardNN(config)</span>
<span id="cb10-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(config.embed_dim)</span>
<span id="cb10-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(config.embed_dim)</span>
<span id="cb10-8"></span>
<span id="cb10-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb10-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x: B x T x embed_dim</span></span>
<span id="cb10-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Masked self-attention: each token only attends to past and current positions</span></span>
<span id="cb10-12">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_1(x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.attn(x, x, x))</span>
<span id="cb10-13">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_2(x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ff(x))</span>
<span id="cb10-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T x embed_dim</span></span></code></pre></div></div>
</div>
<div id="89f920e8-82b9-4975-841d-94613c5bfa7d" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> TransformerDecoder(nn.Module):</span>
<span id="cb11-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb11-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb11-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.embeddings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Embeddings(config)</span>
<span id="cb11-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.decoder_blocks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Sequential(</span>
<span id="cb11-6">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>[DecoderLayer(config) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(config.num_hidden_layers)]</span>
<span id="cb11-7">        )</span>
<span id="cb11-8"></span>
<span id="cb11-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb11-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x:    B x T  (integer token IDs)</span></span>
<span id="cb11-11">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.embeddings(x)         <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T x embed_dim</span></span>
<span id="cb11-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.decoder_blocks(x)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T x embed_dim</span></span></code></pre></div></div>
</div>
</section>
<section id="architecture-variants" class="level2">
<h2 class="anchored" data-anchor-id="architecture-variants">Architecture Variants</h2>
<p>The same building blocks support three distinct architectures, differing only in which sublayers are present and whether attention is masked. Here is the full comparison before diving into each:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Encoder-Only</th>
<th>Decoder-Only</th>
<th>Encoder-Decoder</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Attention masking</strong></td>
<td>Bidirectional</td>
<td>Causal</td>
<td>Causal in decoder; bidirectional in encoder</td>
</tr>
<tr class="even">
<td><strong>Cross-attention</strong></td>
<td>No</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr class="odd">
<td><strong>Input → Output</strong></td>
<td>Text → hidden states</td>
<td>Text → next token</td>
<td>Source text → target text</td>
</tr>
<tr class="even">
<td><strong>Canonical task</strong></td>
<td>Classification, NER, embeddings</td>
<td>Text generation, LM</td>
<td>Translation, summarization</td>
</tr>
<tr class="odd">
<td><strong>Examples</strong></td>
<td>BERT, RoBERTa, DistilBERT</td>
<td>GPT, LLaMA, Mistral</td>
<td>T5, BART, mT5</td>
</tr>
</tbody>
</table>
<section id="encoder-only-architecture" class="level3">
<h3 class="anchored" data-anchor-id="encoder-only-architecture">Encoder-Only Architecture</h3>
<p>Encoder-only models use bidirectional self-attention: every token attends to every other token with no masking. This means the representation of each token is conditioned on the full context: tokens to the left <em>and</em> the right. Bidirectional context makes encoder-only models excellent at understanding tasks: text classification, named entity recognition, extractive question answering, and computing sentence embeddings.</p>
<p><strong>Why bidirectional?</strong> Classification does not require generating new tokens; it requires understanding the full input. A model that sees the entire sentence simultaneously can build richer representations than one forced to read left-to-right.</p>
<p><strong>How is it trained?</strong> BERT-style models are trained with <strong>Masked Language Modeling (MLM)</strong>: 15% of tokens are randomly masked (<code>[MASK]</code>), and the model must predict the original token at each masked position. Because the model can see all tokens to the left <em>and</em> right of the mask, this forces it to build bidirectional representations.</p>
<p><strong>Classification head.</strong> A special <code>[CLS]</code> token is prepended to every sequence before the encoder. The encoder’s output at the <code>[CLS]</code> position (<code>encoder_output[:, 0, :]</code>) serves as an aggregate representation of the full sequence. This vector is passed through a linear classification head to produce logits.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Why [CLS] and Not Mean Pooling?
</div>
</div>
<div class="callout-body-container callout-body">
<p>BERT uses <code>[CLS]</code> because it is trained to aggregate sequence-level information during pretraining (next sentence prediction task). In practice, mean pooling over all token representations often performs equally well or better for downstream tasks. Modern models trained without NSP use mean pooling as the default.</p>
</div>
</div>
<div id="df92c42f-8650-4ef6-9865-686d183d61cc" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> TransformerForSequenceClassification(nn.Module):</span>
<span id="cb12-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config):</span>
<span id="cb12-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb12-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> TransformerEncoder(config)</span>
<span id="cb12-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dropout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Dropout(config.hidden_dropout_prob)</span>
<span id="cb12-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.classifier <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.embed_dim, config.num_classes)</span>
<span id="cb12-7"></span>
<span id="cb12-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb12-9">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x:              B x T  (integer token IDs)</span></span>
<span id="cb12-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## encoder output: B x T x embed_dim</span></span>
<span id="cb12-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## [CLS] vector:   B x embed_dim  (position 0 aggregates sequence meaning)</span></span>
<span id="cb12-12">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## logits:         B x num_classes</span></span>
<span id="cb12-13">        cls_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder(x)[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, :]</span>
<span id="cb12-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.classifier(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dropout(cls_output))</span></code></pre></div></div>
</div>
</section>
<section id="decoder-only-architecture" class="level3">
<h3 class="anchored" data-anchor-id="decoder-only-architecture">Decoder-Only Architecture</h3>
<p>Decoder-only models use causal self-attention: each token can only attend to itself and previous tokens. This is the natural architecture for <strong>language modeling</strong>: predicting the next token given all previous tokens.</p>
<p><strong>Why causal?</strong> Generating text requires predicting one token at a time. If the model could see future tokens while predicting token <img src="https://latex.codecogs.com/png.latex?t">, it would copy them. The causal mask enforces the constraint that prediction at position <img src="https://latex.codecogs.com/png.latex?t"> uses only information from positions <img src="https://latex.codecogs.com/png.latex?0,%201,%20%5Cldots,%20t">.</p>
<p><strong>The training objective: Causal Language Modeling (CLM).</strong> Decoder-only models are trained by next-token prediction: given a sequence of tokens, predict the next one at every position simultaneously. The loss is the average cross-entropy over all positions. Because the causal mask prevents each position from seeing future tokens, a single forward pass generates <img src="https://latex.codecogs.com/png.latex?T"> training examples from one sequence: every position is simultaneously a training target. This is why CLM scales so efficiently: a 2048-token document yields 2048 gradient signals per forward pass. The training objective directly shapes what the model learns: because it must predict the next token from all preceding context, the model is forced to compress everything useful about the past into each position’s representation, which is why later layers hold increasingly abstract, predictive features.</p>
<p><strong>Autoregressive generation.</strong> At inference, the decoder generates text by repeating:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    A["Input tokens&lt;br/&gt;[BOS, t₁, t₂]"] --&gt; B["Decoder&lt;br/&gt;(causal attention)"]
    B --&gt; C["LM head&lt;br/&gt;(linear + softmax)"]
    C --&gt; D["Next token&lt;br/&gt;t₃"]
    D --&gt; A
</pre>
</div>
<p></p><figcaption> <strong>Figure 10:</strong> Autoregressive generation loop in decoder-only models.</figcaption> </figure><p></p>
</div>
</div>
</div>
<ol type="1">
<li>Feed current token sequence through the decoder</li>
<li>Take the output at the last position → pass through the LM head (linear projection to <code>vocab_sz</code>, then softmax)</li>
<li>Sample the next token from the resulting distribution</li>
<li>Append the sampled token to the sequence and repeat</li>
</ol>
<p><strong>Sampling strategies</strong> control how token <img src="https://latex.codecogs.com/png.latex?t+1"> is chosen from the distribution:</p>
<ul>
<li><strong>Greedy</strong>: always pick the highest-probability token. Fast but repetitive.</li>
<li><strong>Top-k</strong>: sample from the top-<img src="https://latex.codecogs.com/png.latex?k"> tokens by probability. Controls diversity.</li>
<li><strong>Top-p (nucleus)</strong>: sample from the smallest set of tokens whose cumulative probability exceeds <img src="https://latex.codecogs.com/png.latex?p">. Adaptive: uses fewer options when one token is dominant.</li>
<li><strong>Temperature</strong>: divide all logits by temperature <img src="https://latex.codecogs.com/png.latex?%5Ctau"> before softmax. <img src="https://latex.codecogs.com/png.latex?%5Ctau%20%3C%201"> sharpens the distribution (more confident); <img src="https://latex.codecogs.com/png.latex?%5Ctau%20%3E%201"> flattens it (more random).</li>
</ul>
<p><strong>KV caching: why inference is efficient.</strong> The loop as described implies re-computing attention over the full growing sequence at every step, which would scale as <img src="https://latex.codecogs.com/png.latex?O(T%5E2)"> for a <img src="https://latex.codecogs.com/png.latex?T">-token generation. Production systems avoid this with a <strong>KV cache</strong>: the K and V tensors for all past positions are stored after their first computation and reused on every subsequent step. Only the new token’s Q needs to be computed; it attends to the cached K/V from all prior positions. Each generation step then costs <img src="https://latex.codecogs.com/png.latex?O(T%20%5Ccdot%20d)"> instead of <img src="https://latex.codecogs.com/png.latex?O(T%5E2%20%5Ccdot%20d)">.</p>
<p>The KV cache is a first-class engineering constraint in LLM deployment. For a model with <img src="https://latex.codecogs.com/png.latex?L"> layers, <img src="https://latex.codecogs.com/png.latex?H"> heads, head dimension <img src="https://latex.codecogs.com/png.latex?d_k">, and current sequence length <img src="https://latex.codecogs.com/png.latex?T">, the cache requires <img src="https://latex.codecogs.com/png.latex?2%20%5Ccdot%20L%20%5Ccdot%20H%20%5Ccdot%20d_k%20%5Ccdot%20T"> values; for LLaMA-3 70B at 4K context in fp16, that is roughly 5 GB. This is precisely why <strong>Grouped Query Attention (GQA)</strong> exists: by sharing a single K/V head across multiple Q heads, the cache shrinks by a factor of <code>num_heads / num_kv_heads</code>, often 8x. Every major modern model (LLaMA 2/3, Mistral, Gemma) uses GQA for exactly this reason.</p>
<div id="8d2737a7-c025-4578-882e-0352341d7e95" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> GPT(nn.Module):</span>
<span id="cb13-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config):</span>
<span id="cb13-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb13-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.decoder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> TransformerDecoder(config)</span>
<span id="cb13-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dropout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Dropout(config.hidden_dropout_prob)</span>
<span id="cb13-6">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Project from embed_dim to vocab_sz to get next-token logits</span></span>
<span id="cb13-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lm_head <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.embed_dim, config.vocab_sz, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb13-8"></span>
<span id="cb13-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb13-10">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x:       B x T  (integer token IDs)</span></span>
<span id="cb13-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## decoded: B x T x embed_dim</span></span>
<span id="cb13-12">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## logits:  B x T x vocab_sz  (next-token distribution at every position)</span></span>
<span id="cb13-13">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.dropout(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.decoder(x))</span>
<span id="cb13-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lm_head(x)</span></code></pre></div></div>
</div>
<div id="b3e1f2a0-seq2-seq0-0000-000000000001" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> CrossAttentionDecoderLayer(nn.Module):</span>
<span id="cb14-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Decoder layer with three sublayers:</span></span>
<span id="cb14-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    (1) masked causal self-attention, (2) cross-attention to encoder, (3) FFN.</span></span>
<span id="cb14-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb14-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config):</span>
<span id="cb14-6">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb14-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.self_attn    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> MultiHeadAttention(config, is_decoder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb14-8">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.cross_attn   <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> MultiHeadAttention(config, is_decoder<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb14-9">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ff           <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> FeedForwardNN(config)</span>
<span id="cb14-10">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(config.embed_dim)</span>
<span id="cb14-11">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(config.embed_dim)</span>
<span id="cb14-12">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LayerNorm(config.embed_dim)</span>
<span id="cb14-13"></span>
<span id="cb14-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, encoder_output):</span>
<span id="cb14-15">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x:              B x T_dec x embed_dim</span></span>
<span id="cb14-16">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## encoder_output: B x T_enc x embed_dim</span></span>
<span id="cb14-17">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">##</span></span>
<span id="cb14-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## 1. Masked self-attention: decoder tokens attend to each other causally</span></span>
<span id="cb14-19">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_1(x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.self_attn(x, x, x))</span>
<span id="cb14-20">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## 2. Cross-attention: Q from decoder, K and V from encoder</span></span>
<span id="cb14-21">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">##    Every decoder position can attend to all encoder positions</span></span>
<span id="cb14-22">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_2(x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.cross_attn(x, encoder_output, encoder_output))</span>
<span id="cb14-23">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## 3. Position-wise FFN</span></span>
<span id="cb14-24">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.layer_norm_3(x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ff(x))</span>
<span id="cb14-25">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T_dec x embed_dim</span></span>
<span id="cb14-26"></span>
<span id="cb14-27"></span>
<span id="cb14-28"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Seq2SeqTransformer(nn.Module):</span>
<span id="cb14-29">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Encoder-decoder Transformer for sequence-to-sequence tasks</span></span>
<span id="cb14-30"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    such as machine translation and summarization.</span></span>
<span id="cb14-31"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb14-32">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, config):</span>
<span id="cb14-33">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb14-34">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder_embeddings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Embeddings(config)</span>
<span id="cb14-35">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.decoder_embeddings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Embeddings(config)</span>
<span id="cb14-36">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder_blocks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.ModuleList(</span>
<span id="cb14-37">            [EncoderLayer(config) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(config.num_hidden_layers)]</span>
<span id="cb14-38">        )</span>
<span id="cb14-39">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.decoder_blocks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.ModuleList(</span>
<span id="cb14-40">            [CrossAttentionDecoderLayer(config) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(config.num_hidden_layers)]</span>
<span id="cb14-41">        )</span>
<span id="cb14-42">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lm_head <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(config.embed_dim, config.vocab_sz, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb14-43"></span>
<span id="cb14-44">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> encode(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, src):</span>
<span id="cb14-45">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## src: B x T_enc  →  B x T_enc x embed_dim</span></span>
<span id="cb14-46">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder_embeddings(src)</span>
<span id="cb14-47">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encoder_blocks:</span>
<span id="cb14-48">            x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> block(x)</span>
<span id="cb14-49">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T_enc x embed_dim</span></span>
<span id="cb14-50"></span>
<span id="cb14-51">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> decode(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, tgt, encoder_output):</span>
<span id="cb14-52">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## tgt:            B x T_dec</span></span>
<span id="cb14-53">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## encoder_output: B x T_enc x embed_dim</span></span>
<span id="cb14-54">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.decoder_embeddings(tgt)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T_dec x embed_dim</span></span>
<span id="cb14-55">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> block <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.decoder_blocks:</span>
<span id="cb14-56">            x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> block(x, encoder_output)</span>
<span id="cb14-57">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T_dec x embed_dim</span></span>
<span id="cb14-58"></span>
<span id="cb14-59">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, src, tgt):</span>
<span id="cb14-60">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## src: B x T_enc  (source token IDs, e.g. English)</span></span>
<span id="cb14-61">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## tgt: B x T_dec  (target token IDs, e.g. German; teacher-forced during training)</span></span>
<span id="cb14-62">        encoder_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.encode(src)                    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T_enc x embed_dim</span></span>
<span id="cb14-63">        decoder_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.decode(tgt, encoder_output)    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T_dec x embed_dim</span></span>
<span id="cb14-64">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.lm_head(decoder_output)                  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x T_dec x vocab_sz</span></span></code></pre></div></div>
</div>
</section>
<section id="encoder-decoder-architecture" class="level3">
<h3 class="anchored" data-anchor-id="encoder-decoder-architecture">Encoder-Decoder Architecture</h3>
<p>The encoder-decoder (or “sequence-to-sequence”) architecture is the original Transformer from Vaswani et al.&nbsp;(2017). It is designed for tasks where both input and output are text sequences, particularly tasks where the input and output are structurally different, like machine translation or summarization.</p>
<p><strong>The two-phase interpretation:</strong></p>
<ul>
<li><strong>Encoder</strong>: reads the full source sequence with bidirectional attention and produces a rich, contextualized representation. Think of this as “understanding the source.”</li>
<li><strong>Decoder</strong>: generates the target sequence token by token, conditioned on the encoder’s representation at every step. Think of this as “generating the target given the understanding.”</li>
</ul>
<p><strong>How cross-attention implements conditioning.</strong> At every decoder step, the cross-attention sublayer takes:</p>
<ul>
<li>Queries from the decoder’s current hidden state: <em>“What do I need from the source?”</em></li>
<li>Keys and Values from the encoder’s final output: <em>“Here is everything in the source.”</em></li>
</ul>
<p>Every decoder position attends to all encoder positions simultaneously. The model learns which parts of the source to focus on when generating each target token: the alignment between source and target.</p>
<p>Note that unlike self-attention (where the <img src="https://latex.codecogs.com/png.latex?T%20%5Ctimes%20T"> weight matrix is square), cross-attention produces a <strong>rectangular</strong> weight matrix of shape <img src="https://latex.codecogs.com/png.latex?T_%7B%5Ctext%7Btgt%7D%7D%20%5Ctimes%20T_%7B%5Ctext%7Bsrc%7D%7D">: one row per decoder query position, one column per encoder key position. During early generation when only a few target tokens exist, this matrix might be <img src="https://latex.codecogs.com/png.latex?3%20%5Ctimes%2050">: three decoder positions each attending over fifty source positions. The asymmetry is intentional: the decoder decides what to ask (Q), the encoder provides the full library of keys and values (K, V), and the weight matrix records what each decoder step borrows from each source position.</p>
<p><strong>When encoder-decoder vs.&nbsp;decoder-only?</strong> Encoder-decoder models are preferred when source and target are structurally different (e.g., English → German, document → summary). For tasks where both input and output are similar in format (e.g., open-domain conversation, code completion), decoder-only models have largely taken over: they are simpler to train and scale, and can handle both input and output within a single sequence by formatting the task as a text completion problem.</p>
<p>Notable encoder-decoder models: <strong>T5</strong> (Text-to-Text Transfer Transformer), <strong>BART</strong>, <strong>mT5</strong>, <strong>NLLB</strong>.</p>
</section>
</section>
<section id="end-to-end-forward-pass-walkthrough" class="level2">
<h2 class="anchored" data-anchor-id="end-to-end-forward-pass-walkthrough">End-to-End Forward Pass Walkthrough</h2>
<p>Let’s trace a complete forward pass through an encoder-decoder Transformer to see how all the pieces compose. We’ll use a small example: translating the English sentence “The cat sat” into German.</p>
<p><strong>Setup:</strong> batch size <img src="https://latex.codecogs.com/png.latex?B%20=%201">, source length <img src="https://latex.codecogs.com/png.latex?T_%7Benc%7D%20=%203">, <code>embed_dim = 768</code>, <code>num_heads = 12</code>, <code>head_dim = 64</code>.</p>
<hr>
<p><strong>Step 1. Tokenize the source.</strong></p>
<p>“The cat sat” → subword tokenizer → <code>[2, 47, 193]</code> (integer IDs)</p>
<p>Shape: <code>1 × 3</code> (integers)</p>
<hr>
<p><strong>Step 2. Token embedding lookup.</strong></p>
<p>Each integer is mapped to a 768-dimensional vector via the embedding table.</p>
<p>Shape: <code>1 × 3</code> → <code>1 × 3 × 768</code></p>
<hr>
<p><strong>Step 3. Add positional encodings.</strong></p>
<p>A positional encoding vector is added to each token’s embedding. The result encodes both <em>what</em> the token is (token embedding) and <em>where</em> it sits (positional encoding).</p>
<p>Shape: <code>1 × 3 × 768</code> (unchanged)</p>
<hr>
<p><strong>Step 4. N encoder layers.</strong></p>
<p>Each encoder layer applies two sublayers:</p>
<ul>
<li><strong>Multi-head self-attention</strong>: All 3 tokens attend to all 3 tokens. The <img src="https://latex.codecogs.com/png.latex?3%20%C3%97%203"> attention weight matrix (12 heads, each with its own <img src="https://latex.codecogs.com/png.latex?3%20%C3%97%203"> weights) is computed, and each token’s representation is updated as a weighted mix of all token values.</li>
<li><strong>FFN</strong>: Each token’s updated representation passes through the 2-layer FFN independently.</li>
</ul>
<p>Shape at every encoder layer: <code>1 × 3 × 768</code> (unchanged throughout)</p>
<p>After <img src="https://latex.codecogs.com/png.latex?N"> encoder layers, each of the 3 token positions holds a deeply <strong>contextualized representation</strong>: the meaning of “cat” is now informed by the presence of “The” and “sat” in context.</p>
<p><strong>Encoder output:</strong> <code>1 × 3 × 768</code>, which is what the decoder will attend to.</p>
<hr>
<p><strong>Step 5. Decoder receives the start token.</strong></p>
<p>Decoder input starts with a start-of-sequence token <code>[BOS]</code>.</p>
<p>Shape: <code>1 × 1</code> → (after embedding) <code>1 × 1 × 768</code></p>
<hr>
<p><strong>Step 6. N decoder layers.</strong></p>
<p>Each decoder layer applies three sublayers:</p>
<ol type="1">
<li><p><strong>Masked self-attention</strong>: Only 1 token so far, so the <img src="https://latex.codecogs.com/png.latex?1%20%C3%97%201"> causal attention matrix is trivially “attend to self.” Shape: <code>1 × 1 × 768</code>.</p></li>
<li><p><strong>Cross-attention</strong>: Q comes from the decoder hidden state (<code>1 × 1 × 768</code>). K and V come from the encoder output (<code>1 × 3 × 768</code>). Attention weights have shape <code>1 × 1 × 3</code>: the single decoder position attends to all 3 encoder positions. Output: <code>1 × 1 × 768</code>.</p></li>
<li><p><strong>FFN</strong>: <code>1 × 1 × 768</code> processed position-wise.</p></li>
</ol>
<hr>
<p><strong>Step 7. LM head.</strong></p>
<p>The decoder output at the final position (<code>1 × 1 × 768</code>) is projected to <code>vocab_sz</code> via a linear layer, then softmax gives a probability distribution over the vocabulary.</p>
<p>Shape: <code>1 × 1 × 768</code> → <code>1 × 1 × vocab_sz</code> → sample token → e.g., <code>"Die"</code> (German “The”)</p>
<hr>
<p><strong>Step 8. Autoregressive loop.</strong></p>
<p>Append <code>"Die"</code> to the decoder input. Repeat Steps 6–7 with decoder input <code>[BOS, "Die"]</code> to generate the next token. Continue until <code>[EOS]</code> is sampled or the maximum length is reached.</p>
<hr>
<p>The core asymmetry: <strong>the encoder runs once</strong> for the full source sequence. The <strong>decoder runs once per generated token</strong>, attending to the full encoder output (which never changes) at every step via cross-attention.</p>
</section>
<section id="what-transformers-actually-learn" class="level2">
<h2 class="anchored" data-anchor-id="what-transformers-actually-learn">What Transformers Actually Learn</h2>
<p>Understanding the architecture is one thing; understanding what trained Transformers actually compute is another. Here is a brief map of empirical findings.</p>
<section id="attention-head-specialization" class="level3">
<h3 class="anchored" data-anchor-id="attention-head-specialization">Attention Head Specialization</h3>
<p>Clark et al.&nbsp;(2019) systematically analyzed BERT’s attention patterns across all layers and heads and found striking specialization:</p>
<ul>
<li><strong>Syntactic dependency heads</strong>: Certain heads consistently attend from a token to its syntactic governor (the word it depends on), recovering dependency parse relationships with high accuracy, without ever being trained on parse labels.</li>
<li><strong>Positional heads</strong>: Some heads attend predominantly to adjacent tokens (the previous or next token), implementing local sliding-window attention.</li>
<li><strong><code>[SEP]</code> heads</strong>: Many heads in middle layers attend heavily to <code>[SEP]</code> tokens. The interpretation: when no strong relationship exists, these heads use <code>[SEP]</code> as a “garbage collector”, routing excess attention somewhere harmless.</li>
</ul>
<p>This specialization is <strong>emergent</strong>, not designed. It arises purely from the training signal on downstream tasks.</p>
</section>
<section id="ffn-layers-as-factual-memories" class="level3">
<h3 class="anchored" data-anchor-id="ffn-layers-as-factual-memories">FFN Layers as Factual Memories</h3>
<p>Geva et al.&nbsp;(2021) showed that FFN sublayers act as key-value memories. The first linear layer’s weight rows act as “keys” that activate on specific input patterns; the second linear layer’s corresponding columns act as “values” that are retrieved and output.</p>
<p>This framing explains where factual knowledge lives in a language model. When a model correctly completes “The Eiffel Tower is located in ___“, the relevant association (Eiffel Tower → Paris) is likely stored as a key-value pair in the FFN weights of one or more layers, not in the attention matrices.</p>
</section>
<section id="layer-depth-and-abstraction" class="level3">
<h3 class="anchored" data-anchor-id="layer-depth-and-abstraction">Layer Depth and Abstraction</h3>
<p>Probing classifiers (small models trained to predict linguistic properties from internal representations) consistently find that:</p>
<ul>
<li><strong>Early layers</strong> (1–4): Surface-level features (part-of-speech tags, token identity, local syntax).</li>
<li><strong>Middle layers</strong> (5–12): Syntactic structure, phrase-level groupings, coreference.</li>
<li><strong>Later layers</strong>: Task-specific, abstract semantic features.</li>
</ul>
<p>The architecture explains <em>why</em> this gradient exists. Early layers receive representations that have undergone very little contextualization: essentially just the token and positional embeddings. They can only access local, surface-level patterns. Later layers, on the other hand, are reading from a residual stream that has already accumulated many rounds of attention and FFN processing. Each layer builds on the contextualized representations produced by all previous layers, enabling increasingly abstract structures to emerge. The depth gradient is not a design choice; it is a direct consequence of how information accumulates through residual connections.</p>
</section>
</section>
<section id="modern-improvements" class="level2">
<h2 class="anchored" data-anchor-id="modern-improvements">Modern Improvements</h2>
<p>The original Transformer (2017) has been refined substantially. Here are the key improvements that appear in modern LLMs, with brief explanations of why each was adopted:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Improvement</th>
<th>What changes</th>
<th>Why</th>
<th>Used in</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Pre-LayerNorm</strong></td>
<td>LN moves inside the residual branch</td>
<td>Training stability at scale; no warm-up required</td>
<td>GPT-2, LLaMA, Mistral</td>
</tr>
<tr class="even">
<td><strong>Rotary Position Embedding (RoPE)</strong></td>
<td>Replaces absolute pos. embeddings with rotation of Q and K</td>
<td>Better length generalization; relative position naturally encoded at every layer</td>
<td>LLaMA, Mistral, GPT-NeoX, Qwen</td>
</tr>
<tr class="odd">
<td><strong>Grouped Query Attention (GQA)</strong></td>
<td>Multiple Q heads share a single K and V head</td>
<td>Reduces KV cache memory at inference without meaningful accuracy loss</td>
<td>LLaMA 2/3, Mistral</td>
</tr>
<tr class="even">
<td><strong>SwiGLU activation</strong></td>
<td>Replaces GELU in FFN with a gated linear unit: <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BSwiGLU%7D(x)%20=%20%5Ctext%7BSwish%7D(xW_1)%20%5Codot%20xW_2"></td>
<td>Consistently higher benchmark performance at equivalent parameter counts</td>
<td>LLaMA, PaLM, Gemma</td>
</tr>
<tr class="odd">
<td><strong>FlashAttention</strong></td>
<td>Reorders attention computation to minimize memory bandwidth</td>
<td><img src="https://latex.codecogs.com/png.latex?O(N)"> memory instead of <img src="https://latex.codecogs.com/png.latex?O(N%5E2)">; 2–4x faster; identical numerical outputs</td>
<td>Used in most modern training stacks</td>
</tr>
<tr class="even">
<td><strong>RMSNorm</strong></td>
<td>Replaces LayerNorm with root-mean-square normalization (no mean subtraction)</td>
<td>Simpler, ~10% faster, equivalent quality</td>
<td>LLaMA, Mistral, Gemma</td>
</tr>
</tbody>
</table>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>In this post, we built the Transformer architecture from scratch, starting from the failure modes of RNNs, building the attention mechanism step by step, implementing each component in PyTorch with annotated shapes, and assembling the encoder-only, decoder-only, and encoder-decoder variants. We also traced a complete end-to-end forward pass and surveyed what trained Transformers empirically learn.</p>
<p>The architecture’s dominance across language, vision, speech, and biology stems from a coherent set of design choices, each solving a specific problem with a specific mechanism.</p>
<section id="key-takeaways" class="level3">
<h3 class="anchored" data-anchor-id="key-takeaways">Key Takeaways</h3>
<ol type="1">
<li><p><strong>Attention replaces sequential recurrence with parallel direct communication.</strong> Every token attends to every other token in a single matrix operation. No hidden state bottleneck, no sequential dependency, no vanishing gradient through time: the fundamental failures of RNNs are eliminated at the architectural level, not patched over.</p></li>
<li><p><strong>Q, K, V separation is intentional, not arbitrary.</strong> What a token <em>wants</em> (query), what it <em>offers</em> (key), and what it <em>says</em> (value) are three genuinely different roles. Separating them, as in the library lookup analogy, gives the model the flexibility to learn very different relationships for each. A single projection would conflate all three.</p></li>
<li><p><strong>Multi-head attention gives the model multiple simultaneous perspectives.</strong> Each head operates in its own lower-dimensional subspace and learns to track different relationship types: one head for syntax, one for coreference, one for local context. This specialization is emergent: it arises from the training signal, not from any explicit design constraint.</p></li>
<li><p><strong>The FFN is the knowledge store; attention is the routing system.</strong> Attention decides which tokens talk to which and mixes their representations. The FFN then transforms each token’s representation independently and nonlinearly: this is where factual associations are stored. Without the FFN, stacked attention layers collapse to a single linear transformation.</p></li>
<li><p><strong>Skip connections and LayerNorm make depth trainable.</strong> Residual connections create gradient highways that bypass each sublayer entirely, making it possible to train networks dozens of layers deep. Pre-LayerNorm (inside the residual branch) stabilizes training at scale without requiring learning rate warm-up.</p></li>
<li><p><strong>Architecture determines what tokens can see; everything else is shared.</strong> The only fundamental difference between an encoder and a decoder is the causal mask. The same attention mechanism, FFN, LayerNorm, and residual structure underlies all three variants (encoder-only, decoder-only, and encoder-decoder), differing only in which tokens each position is allowed to attend to.</p></li>
</ol>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>The Core Architecture Is Stable
</div>
</div>
<div class="callout-body-container callout-body">
<p>Despite years of improvements (RoPE, GQA, SwiGLU, FlashAttention, RMSNorm), the fundamental architecture described in this post has not changed since 2017. The overall structure (attention + FFN + residual + norm, stacked <img src="https://latex.codecogs.com/png.latex?N"> times) is the same in GPT-4, LLaMA 3, and Gemini as it was in the original “Attention Is All You Need.” If you understand this post, you understand the backbone of essentially all modern AI.</p>
</div>
</div>
<p><strong>What to explore next:</strong></p>
<ul>
<li><a href="../../posts/nlp/GPT2-From-Scratch.html"><strong>Building GPT-2 from Scratch</strong></a>: takes the decoder-only architecture from this post and implements a full GPT-2 training run, including mixed precision, Flash Attention, and distributed training</li>
<li><a href="../../posts/nlp/BPE-Tokenizer.html"><strong>BPE Tokenizer from Scratch</strong></a>: implements the tokenizer that sits upstream of everything in this post</li>
<li><a href="../../posts/nlp/Tokenization-Strategies.html"><strong>Tokenization Strategies</strong></a>: compares character, word, and subword tokenization with code examples and real model outputs</li>
</ul>
</section>
</section>
<section id="references-resources" class="level2">
<h2 class="anchored" data-anchor-id="references-resources">References &amp; Resources</h2>
<ul>
<li><strong>Srivastava, N. et al.</strong> (2014). <a href="https://www.cs.toronto.edu/~rsalakhu/papers/srivastava14a.pdf">Dropout: A Simple Way to Prevent Neural Networks from Overfitting</a>. <em>JMLR</em>.</li>
<li><strong>Ba, J. et al.</strong> (2016). <a href="https://arxiv.org/abs/1607.06450">Layer Normalization</a>.</li>
<li><strong>He, K. et al.</strong> (2016). <a href="https://arxiv.org/abs/1512.03385">Deep Residual Learning for Image Recognition</a>. <em>CVPR</em>.</li>
<li><strong>Vaswani, A. et al.</strong> (2017). <a href="https://arxiv.org/abs/1706.03762">Attention Is All You Need</a>. <em>NeurIPS</em>.</li>
<li><strong>Devlin, J. et al.</strong> (2018). <a href="https://arxiv.org/abs/1810.04805">BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding</a>. <em>NAACL</em>.</li>
<li><strong>Clark, K. et al.</strong> (2019). <a href="https://arxiv.org/abs/1906.04341">What Does BERT Look At? An Analysis of BERT’s Attention</a>. <em>ACL BlackboxNLP</em>.</li>
<li><strong>Geva, M. et al.</strong> (2021). <a href="https://arxiv.org/abs/2012.14913">Transformer Feed-Forward Layers Are Key-Value Memories</a>. <em>EMNLP</em>.</li>
<li><strong>Su, J. et al.</strong> (2021). <a href="https://arxiv.org/abs/2104.09864">RoFormer: Enhanced Transformer with Rotary Position Embedding</a>.</li>
<li><strong>Dao, T. et al.</strong> (2022). <a href="https://arxiv.org/abs/2205.14135">FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness</a>. <em>NeurIPS</em>.</li>
<li><a href="https://nlp.seas.harvard.edu/2018/04/03/attention.html">The Annotated Transformer</a>.</li>
<li><a href="https://github.com/karpathy/nanoGPT">Andrej Karpathy’s NanoGPT</a>.</li>
<li><a href="https://lilianweng.github.io/posts/2023-01-27-the-transformer-family-v2/">Lilian Weng’s The Transformer Family v2.0</a>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>NLP</category>
  <guid>https://imaddabbura.github.io/posts/nlp/Transformer-Architecture-Explained.html</guid>
  <pubDate>Mon, 14 Feb 2022 06:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/nlp/images/transformer-arch.png" medium="image" type="image/png" height="96" width="144"/>
</item>
<item>
  <title>Inside LSTMs: Implementing and Optimizing Sequential Models from First Principles</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/nlp/LSTM-Annotated-Implementation.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>A plain RNN cannot learn long-range dependencies. During backpropagation, the gradient that links a distant pair of tokens shrinks geometrically with the distance between them: across 100 steps, with a dominant eigenvalue of 0.9, by a factor of about <img src="https://latex.codecogs.com/png.latex?0.9%5E%7B100%7D%20%5Capprox%203%20%5Ctimes%2010%5E%7B-5%7D">. The influence of early tokens is gone, and training cannot recover it. This is the vanishing gradient problem.</p>
<p>The LSTM (<a href="https://www.bioinf.jku.at/publications/older/2604.pdf">Hochreiter and Schmidhuber, 1997</a>) fixes it by adding a separate memory channel that is updated additively rather than overwritten, so the gradient can pass through it without decaying. This is the residual connection from ResNets, applied across time instead of depth, and the same idea underlies the residual stream in Transformers. The cost is more machinery: two state vectors and four gates in place of one state and none.</p>
<p>This post implements <code>LSTMCell</code> and a multi-layer <code>LSTM</code> from scratch and verifies both against PyTorch to floating-point precision. We start from the vanishing gradient, use it to explain the two states and four gates, then write and check the code.</p>
</section>
<section id="the-vanishing-gradient-problem" class="level2">
<h2 class="anchored" data-anchor-id="the-vanishing-gradient-problem">The Vanishing Gradient Problem</h2>
<p>The decay comes from repeated multiplication by the same weight. In a vanilla RNN, the hidden state is <em>completely overwritten</em> at every time step:</p>
<p><img src="https://latex.codecogs.com/png.latex?h_t%20=%20%5Ctanh(W_%7Bhh%7D%20%5Ccdot%20h_%7Bt-1%7D%20+%20W_%7Bxh%7D%20%5Ccdot%20x_t%20+%20b)"></p>
<p>During backpropagation, the gradient of the loss with respect to an early hidden state <img src="https://latex.codecogs.com/png.latex?h_1"> must pass through the <img src="https://latex.codecogs.com/png.latex?%5Ctanh"> nonlinearity and the weight matrix <img src="https://latex.codecogs.com/png.latex?W_%7Bhh%7D"> at <em>every single time step</em> between <img src="https://latex.codecogs.com/png.latex?h_T"> and <img src="https://latex.codecogs.com/png.latex?h_1">. If the sequence has 100 tokens, the gradient is multiplied by <img src="https://latex.codecogs.com/png.latex?W_%7Bhh%7D"> roughly 100 times. If the dominant eigenvalue of <img src="https://latex.codecogs.com/png.latex?W_%7Bhh%7D"> is even slightly less than 1 (say 0.9), the gradient shrinks by a factor of <img src="https://latex.codecogs.com/png.latex?0.9%5E%7B100%7D%20%5Capprox%200.00003">. The signal from early tokens effectively disappears. I take the systems view of this decay, and the memory cost of training RNNs, in <a href="../../posts/mlsys/dl-systems.html">Building a Deep Learning Framework from Scratch</a>.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/vanishing-gradient-decay.svg" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure 1: Repeated multiplication by the recurrent weight. Back-propagating through n time steps multiplies the gradient by W_{hh} roughly n times, so the decay is governed by its dominant eigenvalue \lambda. At \lambda = 0.9 the signal from 100 steps back is scaled by 0.9^{100} \approx 3 \times 10^{-5} and vanishes (blue); at \lambda = 1.1 it explodes (red). The LSTM holds the effective factor near 1 (green), which the next section explains."><img src="https://imaddabbura.github.io/posts/nlp/images/vanishing-gradient-decay.svg" class="img-fluid figure-img" alt="Figure 1: Repeated multiplication by the recurrent weight. Back-propagating through n time steps multiplies the gradient by W_{hh} roughly n times, so the decay is governed by its dominant eigenvalue \lambda. At \lambda = 0.9 the signal from 100 steps back is scaled by 0.9^{100} \approx 3 \times 10^{-5} and vanishes (blue); at \lambda = 1.1 it explodes (red). The LSTM holds the effective factor near 1 (green), which the next section explains."></a></p>
<figcaption><strong>Figure 1:</strong> Repeated multiplication by the recurrent weight. Back-propagating through <img src="https://latex.codecogs.com/png.latex?n"> time steps multiplies the gradient by <img src="https://latex.codecogs.com/png.latex?W_%7Bhh%7D"> roughly <img src="https://latex.codecogs.com/png.latex?n"> times, so the decay is governed by its dominant eigenvalue <img src="https://latex.codecogs.com/png.latex?%5Clambda">. At <img src="https://latex.codecogs.com/png.latex?%5Clambda%20=%200.9"> the signal from 100 steps back is scaled by <img src="https://latex.codecogs.com/png.latex?0.9%5E%7B100%7D%20%5Capprox%203%20%5Ctimes%2010%5E%7B-5%7D"> and vanishes (blue); at <img src="https://latex.codecogs.com/png.latex?%5Clambda%20=%201.1"> it explodes (red). The LSTM holds the effective factor near 1 (green), which the next section explains.</figcaption>
</figure>
</div>
<div class="callout callout-style-default callout-important callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Important</span>The Fundamental Issue
</div>
</div>
<div class="callout-body-container callout-body">
<p>The problem isn’t just mathematical. It has a concrete consequence: vanilla RNNs can’t learn long-range dependencies. If the answer to a question depends on a word 50 tokens earlier in the sentence, the gradient signal connecting them is essentially zero. The model can’t learn that relationship, no matter how long you train.</p>
</div>
</div>
</section>
<section id="how-lstms-fix-it" class="level2">
<h2 class="anchored" data-anchor-id="how-lstms-fix-it">How LSTMs Fix It</h2>
<p>The LSTM introduces a <strong>cell state</strong> <img src="https://latex.codecogs.com/png.latex?c_t">: a separate memory channel that runs parallel to the hidden state. The critical difference is in <em>how</em> it gets updated:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Vanilla RNN</th>
<th>LSTM Cell State</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Update rule</strong></td>
<td><img src="https://latex.codecogs.com/png.latex?h_t%20=%20%5Ctanh(W%20%5Ccdot%20h_%7Bt-1%7D%20+%20%5Cldots)"></td>
<td><img src="https://latex.codecogs.com/png.latex?c_t%20=%20f_t%20%5Codot%20c_%7Bt-1%7D%20+%20i_t%20%5Codot%20g_t"></td>
</tr>
<tr class="even">
<td><strong>Mechanism</strong></td>
<td>Complete <em>replacement</em> through nonlinearity</td>
<td>Selective <em>modification</em> via additive gating</td>
</tr>
<tr class="odd">
<td><strong>Gradient flow</strong></td>
<td>Must pass through <img src="https://latex.codecogs.com/png.latex?%5Ctanh"> and <img src="https://latex.codecogs.com/png.latex?W"> at every step</td>
<td>Can flow <em>directly</em> through the forget gate <img src="https://latex.codecogs.com/png.latex?f_t"></td>
</tr>
<tr class="even">
<td><strong>Long-range memory</strong></td>
<td>Exponential decay</td>
<td>Controlled retention</td>
</tr>
</tbody>
</table>
<p>The cell state update is <strong>additive</strong>: when the forget gate <img src="https://latex.codecogs.com/png.latex?f_t"> is close to 1 and the input gate <img src="https://latex.codecogs.com/png.latex?i_t"> is close to 0, the cell state passes through <em>unchanged</em>: <img src="https://latex.codecogs.com/png.latex?c_t%20%5Capprox%20c_%7Bt-1%7D">. Gradients flow backward through time with minimal decay: no weight matrix or nonlinearity in the way.</p>
<p>If this looks familiar, it should. It’s the same principle behind <strong>residual connections</strong> in ResNets. In a ResNet, each layer computes <img src="https://latex.codecogs.com/png.latex?y%20=%20F(x)%20+%20x">: the input passes through unchanged, and the layer only learns the <em>residual</em>. The LSTM cell state works the same way, but across <strong>time instead of depth</strong>: the previous cell state passes through (scaled by <img src="https://latex.codecogs.com/png.latex?f_t">), and the network adds a residual update (<img src="https://latex.codecogs.com/png.latex?i_t%20%5Codot%20g_t">). Both create a gradient highway. ResNets made it possible to train 100+ layer networks; the LSTM cell state makes it possible to learn dependencies across 100+ time steps. Same insight, different axis.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/lstm-cell.jpeg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure 2: The LSTM cell. The horizontal line along the top is the cell state, the “highway” through time. The four gates (\sigma, \sigma, \tanh, \sigma) are the forget, input, cell, and output gates. The cell state is updated additively (the \oplus node), while the gates use element-wise multiplication (\otimes) to control information flow."><img src="https://imaddabbura.github.io/posts/nlp/images/lstm-cell.jpeg" class="img-fluid figure-img" width="500" alt="Figure 2: The LSTM cell. The horizontal line along the top is the cell state, the “highway” through time. The four gates (\sigma, \sigma, \tanh, \sigma) are the forget, input, cell, and output gates. The cell state is updated additively (the \oplus node), while the gates use element-wise multiplication (\otimes) to control information flow."></a></p>
<figcaption><strong>Figure 2:</strong> The LSTM cell. The horizontal line along the top is the cell state, the “highway” through time. The four gates (<img src="https://latex.codecogs.com/png.latex?%5Csigma,%20%5Csigma,%20%5Ctanh,%20%5Csigma">) are the forget, input, cell, and output gates. The cell state is updated additively (the <img src="https://latex.codecogs.com/png.latex?%5Coplus"> node), while the gates use element-wise multiplication (<img src="https://latex.codecogs.com/png.latex?%5Cotimes">) to control information flow.</figcaption>
</figure>
</div>
<section id="why-two-states" class="level3">
<h3 class="anchored" data-anchor-id="why-two-states">Why Two States?</h3>
<p>A vanilla RNN has a single hidden state that must do <em>everything</em>: store long-term memory, carry short-term context, and produce the output that downstream layers consume. That’s too many jobs for one vector: optimizing the hidden state for the current prediction destroys the long-term information stored in it.</p>
<p>LSTMs split this into two specialized roles:</p>
<ul>
<li><p><strong>Cell state (<img src="https://latex.codecogs.com/png.latex?c_t">): the long-term internal memory.</strong> The cell state is the LSTM’s private memory, never directly exposed to the rest of the network. Its job is to <em>retain information across long distances</em> without interference. Because it’s updated additively, gradients can flow through it across hundreds of time steps. Think of it as a notebook that the LSTM writes to and reads from, but never shows to anyone directly.</p></li>
<li><p><strong>Hidden state (<img src="https://latex.codecogs.com/png.latex?h_t">): the short-term working output.</strong> The hidden state is what the LSTM <em>exposes</em> to the outside world (the input to the next layer, the softmax, or whatever comes next). It’s computed by selectively reading from the cell state via the output gate: <img src="https://latex.codecogs.com/png.latex?h_t%20=%20o_t%20%5Codot%20%5Ctanh(c_t)">. The output gate decides: <em>“Given everything I know and the current context, what’s relevant right now?”</em></p></li>
</ul>
<p>This separation is what makes the long-range memory work: the cell state can hold information like “the subject is plural” or “we’re inside a quotation” for as long as needed, without being distorted by the demands of predicting intermediate tokens. When it <em>is</em> needed, the output gate reads it out at exactly the right moment.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>Cell State (<img src="https://latex.codecogs.com/png.latex?c_t">)</th>
<th>Hidden State (<img src="https://latex.codecogs.com/png.latex?h_t">)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Role</strong></td>
<td>Long-term memory</td>
<td>Short-term working output</td>
</tr>
<tr class="even">
<td><strong>Visible to</strong></td>
<td>Only the LSTM itself (internal)</td>
<td>Next layer, softmax, classifier (external)</td>
</tr>
<tr class="odd">
<td><strong>Updated by</strong></td>
<td>Forget gate (erase) + input gate (write)</td>
<td>Output gate reading from cell state</td>
</tr>
<tr class="even">
<td><strong>Gradient flow</strong></td>
<td>Additive (gradients pass through cleanly)</td>
<td>Through tanh and output gate (more lossy)</td>
</tr>
<tr class="odd">
<td><strong>Analogy</strong></td>
<td>A notebook you write in privately</td>
<td>The answer you speak aloud when asked</td>
</tr>
</tbody>
</table>
</section>
<section id="a-concrete-example" class="level3">
<h3 class="anchored" data-anchor-id="a-concrete-example">A Concrete Example</h3>
<p>Consider: <em>“The cat, which sat on the mat in the living room near the window overlooking the garden, <strong>was</strong> sleeping.”</em> The verb “was” must agree with “cat” (singular), not “garden” or “window”, a dependency spanning ~15 tokens. A vanilla RNN’s gradient signal from “was” back to “cat” would be multiplied by <img src="https://latex.codecogs.com/png.latex?W_%7Bhh%7D"> fifteen times, likely vanishing. An LSTM can keep “cat = singular noun” in its cell state with the forget gate near 1, preserving the information until it’s needed at “was.”</p>
<p>One important constraint: RNNs and LSTMs are <strong>sequential models</strong>. The output at time <img src="https://latex.codecogs.com/png.latex?t"> depends on the hidden state from <img src="https://latex.codecogs.com/png.latex?t-1">. We cannot parallelize across time steps; we must iterate one token at a time. This is the limitation that the Transformer (<a href="https://arxiv.org/abs/1706.03762">Vaswani et al., 2017</a>) later addressed with self-attention. I build that self-attention from scratch in <a href="../../posts/nlp/Transformer-Architecture-Explained.html">The Transformer Architecture: A Deep Dive</a>.</p>
</section>
</section>
<section id="inside-the-lstm-cell" class="level2">
<h2 class="anchored" data-anchor-id="inside-the-lstm-cell">Inside the LSTM Cell</h2>
<p>An <code>LSTMCell</code> computes four gates, then uses them to update the cell and hidden states. Each gate has the same dimension as the hidden state:</p>
<img src="https://latex.codecogs.com/png.latex?%5Cbegin%7Barray%7D%7Bll%7D%20%5C%5C%0Ai_t%20=%20%5Csigma(W_%7Bii%7D%20x_t%20+%20b_%7Bii%7D%20+%20W_%7Bih%7D%20h_%7Bt-1%7D%20+%20b_%7Bhi%7D)%20%5C%5C%0Af_t%20=%20%5Csigma(W_%7Bif%7D%20x_t%20+%20b_%7Bif%7D%20+%20W_%7Bhf%7D%20h_%7Bt-1%7D%20+%20b_%7Bhf%7D)%20%5C%5C%0Ag_t%20=%20%5Ctanh(W_%7Big%7D%20x_t%20+%20b_%7Big%7D%20+%20W_%7Bhg%7D%20h_%7Bt-1%7D%20+%20b_%7Bhg%7D)%20%5C%5C%0Ao_t%20=%20%5Csigma(W_%7Bio%7D%20x_t%20+%20b_%7Bio%7D%20+%20W_%7Bho%7D%20h_%7Bt-1%7D%20+%20b_%7Bho%7D)%20%5C%5C%0Ac_t%20=%20f_t%20%5Codot%20c_%7Bt-1%7D%20+%20i_t%20%5Codot%20g_t%20%5C%5C%0Ah_t%20=%20o_t%20%5Codot%20%5Ctanh(c_t)%20%5C%5C%0A%5Cend%7Barray%7D">
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Gate</th>
<th>Name</th>
<th>Activation</th>
<th>What It Does</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?i_t"></td>
<td><strong>Input gate</strong></td>
<td>Sigmoid (0–1)</td>
<td>How much of the <em>new</em> candidate values to write into the cell</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?f_t"></td>
<td><strong>Forget gate</strong></td>
<td>Sigmoid (0–1)</td>
<td>How much of the <em>old</em> cell state to keep (1 = remember everything, 0 = forget everything)</td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?g_t"></td>
<td><strong>Cell gate</strong></td>
<td>Tanh (-1 to 1)</td>
<td>The candidate new values to potentially add to the cell state</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?o_t"></td>
<td><strong>Output gate</strong></td>
<td>Sigmoid (0–1)</td>
<td>How much of the cell state to expose as the hidden state output</td>
</tr>
</tbody>
</table>
<p>Notice the activation functions: three gates use <strong>sigmoid</strong>, but the cell gate uses <strong>tanh</strong>. The choice of the activation function reflects their different roles. The sigmoid gates (<img src="https://latex.codecogs.com/png.latex?i_t,%20f_t,%20o_t">) answer <em>“how much?”</em> questions: how much to write, how much to keep, how much to expose. Sigmoid squashes values to (0, 1), making each gate a dimmer switch that scales its input between “fully off” and “fully on.” The cell gate <img src="https://latex.codecogs.com/png.latex?g_t"> answers a different question: <em>“what values?”</em> It proposes candidate content to write into the cell state. Tanh maps to (-1, 1), which lets the cell state both <strong>increase and decrease</strong>. If <img src="https://latex.codecogs.com/png.latex?g_t"> used sigmoid (0, 1), the additive update <img src="https://latex.codecogs.com/png.latex?i_t%20%5Codot%20g_t"> could only ever push the cell state upward, and it would grow without bound. Tanh lets the network write negative corrections, keeping the cell state centered and bounded.</p>
<section id="independent-gates-four-operating-modes" class="level3">
<h3 class="anchored" data-anchor-id="independent-gates-four-operating-modes">Independent Gates: Four Operating Modes</h3>
<p>A critical design choice is that the input gate and forget gate are <strong>completely independent</strong>: computed from separate weight matrices and biases, with nothing constraining them to sum to 1. The network is free to set both high, both low, or any combination.</p>
<p>Contrast this with the GRU (Gated Recurrent Unit), where the equivalent gates <em>are</em> complementary: a single update gate <img src="https://latex.codecogs.com/png.latex?z_t"> weights new content by <img src="https://latex.codecogs.com/png.latex?z_t"> and old content by <img src="https://latex.codecogs.com/png.latex?(1%20-%20z_t)">, forcing a trade-off. The GRU is more parameter-efficient, but less expressive: it can only interpolate between “keep old” and “write new.”</p>
<p>The LSTM’s independence gives it four distinct operating modes:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Forget <img src="https://latex.codecogs.com/png.latex?f_t"></th>
<th>Input <img src="https://latex.codecogs.com/png.latex?i_t"></th>
<th>Mode</th>
<th>Effect</th>
<th>When It’s Useful</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?%5Capprox%201"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Capprox%201"></td>
<td><strong>Accumulate</strong></td>
<td>Keep old state <em>and</em> write new info</td>
<td>Building up a running representation (e.g., accumulating features of a described entity)</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?%5Capprox%200"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Capprox%201"></td>
<td><strong>Replace</strong></td>
<td>Flush old state, write new info</td>
<td>Topic change or sentence boundary (start fresh with new content)</td>
</tr>
<tr class="odd">
<td><img src="https://latex.codecogs.com/png.latex?%5Capprox%201"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Capprox%200"></td>
<td><strong>Preserve</strong></td>
<td>Keep old state, ignore current input</td>
<td>Carrying information across irrelevant tokens (e.g., remembering subject across a parenthetical)</td>
</tr>
<tr class="even">
<td><img src="https://latex.codecogs.com/png.latex?%5Capprox%200"></td>
<td><img src="https://latex.codecogs.com/png.latex?%5Capprox%200"></td>
<td><strong>Reset</strong></td>
<td>Forget old state <em>and</em> ignore input</td>
<td>Clearing a dimension that’s no longer needed</td>
</tr>
</tbody>
</table>
<p>The GRU can only express the diagonal of this table. This is why LSTMs tend to outperform GRUs on tasks requiring long-range memory: the accumulate mode lets information persist indefinitely while still absorbing new inputs, and the reset mode provides a clean mechanism for freeing capacity.</p>
</section>
<section id="gates-as-learned-pattern-detectors" class="level3">
<h3 class="anchored" data-anchor-id="gates-as-learned-pattern-detectors">Gates as Learned Pattern Detectors</h3>
<p>It’s tempting to think of gates as simple switches, but each gate is a <strong>learned pattern detector</strong>: analogous to how a CNN filter activates on specific visual patterns, a gate’s weight matrix learns to activate on specific <em>contextual patterns</em> in the input and hidden state. A CNN filter produces a high activation when the input patch matches its learned pattern; a gate weight matrix produces a high activation (close to 1 after sigmoid) when the combination of <img src="https://latex.codecogs.com/png.latex?x_t"> and <img src="https://latex.codecogs.com/png.latex?h_%7Bt-1%7D"> matches <em>its</em> learned pattern. CNN filters detect <em>spatial</em> patterns in pixel neighborhoods; gate weights detect <em>contextual</em> patterns across the current token and sequence history.</p>
<p>Consider the forget gate: <img src="https://latex.codecogs.com/png.latex?f_t%20=%20%5Csigma(W_%7Bif%7D%20%5Ccdot%20x_t%20+%20W_%7Bhf%7D%20%5Ccdot%20h_%7Bt-1%7D%20+%20b_f)">. After training, specific rows of these weight matrices become specialized detectors:</p>
<ul>
<li>Some rows might detect <strong>“end of clause”</strong> patterns (a period, “but”), signaling that old context should be flushed</li>
<li>Other rows might detect <strong>“continuation”</strong> patterns (a comma, “which”), signaling that existing context should be preserved</li>
<li>Rows in the input gate might detect <strong>“salient new information”</strong> patterns (a named entity, a negation word), signaling that this input should be written into memory</li>
</ul>
<p>This happens <strong>per dimension</strong> of the hidden state. The gate output is a vector, not a scalar: dimension 42 of the forget gate might be close to 0 (forget) while dimension 73 is close to 1 (keep), because each dimension stores different information and each gate dimension detects different patterns.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>The Single-Matrix Trick
</div>
</div>
<div class="callout-body-container callout-body">
<p>Even though we describe four separate gates, in practice we compute them all in <strong>one matrix multiplication</strong> by concatenating the four weight matrices into a single <code>4 * hidden_size</code> matrix. We then split the result into four chunks. This is much faster because it replaces four small matmuls with one large one, better using GPU parallelism and memory bandwidth.</p>
</div>
</div>
</section>
</section>
<section id="implementation" class="level2">
<h2 class="anchored" data-anchor-id="implementation">Implementation</h2>
<p>With the conceptual foundation in place, let’s turn these equations into code. We’ll build two modules, <code>LSTMCell</code> (one time step) and <code>LSTM</code> (full sequences with multiple layers), verifying each against PyTorch’s official implementation.</p>
<section id="lstmcell" class="level3">
<h3 class="anchored" data-anchor-id="lstmcell"><code>LSTMCell</code></h3>
<p>We implement two versions: a verbose one that makes every operation explicit (separate weight matrices for each gate), and a compact one using <code>nn.Linear</code> with the single-matrix trick. Both produce identical results. The compact version is what you’d use in practice.</p>
<div id="4f325555-fdab-47c3-94a6-38a4a2e6dd11" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch.nn <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> nn</span>
<span id="cb1-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch.nn.functional <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> F</span></code></pre></div></div>
</details>
</div>
<div id="e7aa6509-79db-4a54-9d47-e8c656fa28d7" class="cell" data-execution_count="2">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Long version</span></span>
<span id="cb2-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> LSTMCellNew(nn.Module):</span>
<span id="cb2-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, input_sz, hidden_sz, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>):</span>
<span id="cb2-4">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb2-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.weight_ih <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Parameter(torch.randn((input_sz, hidden_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)))</span>
<span id="cb2-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.weight_hh <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Parameter(torch.randn((hidden_sz, hidden_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>)))</span>
<span id="cb2-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bias_ih <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Parameter(torch.zeros(hidden_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb2-8">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bias_hh <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Parameter(torch.zeros(hidden_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>))</span>
<span id="cb2-9"></span>
<span id="cb2-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, h, c):</span>
<span id="cb2-11">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## B x hidden_sz</span></span>
<span id="cb2-12">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.weight_ih <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> h <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.weight_hh <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bias_ih <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.bias_hh</span>
<span id="cb2-13">        i, f, g, o <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.split(out, out.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb2-14">        i, f, o <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.sigmoid(i), torch.sigmoid(f), torch.sigmoid(o)</span>
<span id="cb2-15">        g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tanh(g)</span>
<span id="cb2-16">        c_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> g</span>
<span id="cb2-17">        h_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> o <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> torch.tanh(c_t)</span>
<span id="cb2-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> h_t, c_t</span></code></pre></div></div>
</div>
<div id="d5630f35-d6a3-435a-8a8d-0ae5c2b87601" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Short version using linear layer module</span></span>
<span id="cb3-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> LSTMCellNew(nn.Module):</span>
<span id="cb3-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, input_sz, hidden_sz, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>):</span>
<span id="cb3-4">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb3-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ih <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(input_sz, hidden_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>bias)</span>
<span id="cb3-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.hh <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(hidden_sz, hidden_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>bias)</span>
<span id="cb3-7"></span>
<span id="cb3-8">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, h, c):</span>
<span id="cb3-9">        out <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.ih(x) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.hh(h)</span>
<span id="cb3-10">        i, f, g, o <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.split(out, out.shape[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb3-11">        i, f, o <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.sigmoid(i), torch.sigmoid(f), torch.sigmoid(o)</span>
<span id="cb3-12">        g <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.tanh(g)</span>
<span id="cb3-13">        c_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> f <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> g</span>
<span id="cb3-14">        h_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> o <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> torch.tanh(c_t)</span>
<span id="cb3-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> h_t, c_t</span></code></pre></div></div>
</div>
<div id="2c9e3a64-ba74-4c22-84ad-77d212fe2f31" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1">batch_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span></span>
<span id="cb4-2">seq_len <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span></span>
<span id="cb4-3">input_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span></span>
<span id="cb4-4">hidden_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span></span>
<span id="cb4-5">num_layers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span></code></pre></div></div>
</div>
<div id="18f4dcb4-49b0-4f90-bf15-961483ed0471" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.randn(seq_len, batch_sz, input_sz, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float32)</span>
<span id="cb5-2">c_0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.randn(num_layers, batch_sz, hidden_sz, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float32)</span>
<span id="cb5-3">h_0 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.randn(num_layers, batch_sz, hidden_sz, dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>torch.float32)</span></code></pre></div></div>
</div>
<div id="13915d7b-7746-480b-8bfe-85d2000c21a1" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1">pytorch_cell <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LSTMCell(input_sz, hidden_sz, bias<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb6-2">(</span>
<span id="cb6-3">    pytorch_cell.weight_hh.shape,</span>
<span id="cb6-4">    pytorch_cell.weight_ih.shape,</span>
<span id="cb6-5">    pytorch_cell.bias_ih.shape,</span>
<span id="cb6-6">    pytorch_cell.bias_hh.shape,</span>
<span id="cb6-7">)</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="6">
<pre><code>(torch.Size([400, 100]),
 torch.Size([400, 20]),
 torch.Size([400]),
 torch.Size([400]))</code></pre>
</div>
</div>
<div id="36dbbe52-f7c4-4dff-9361-6757dffbd917" class="cell" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## h: B x hidden_sz</span></span>
<span id="cb8-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## c: B x hidden_sz</span></span>
<span id="cb8-3">pytorch_h, pytorch_c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pytorch_cell(X[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], (h_0[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], c_0[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]))</span></code></pre></div></div>
</div>
<div id="9f7dd706-59f6-4162-b00c-8c3610c573d3" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">cell <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> LSTMCellNew(input_sz, hidden_sz)</span>
<span id="cb9-2"></span>
<span id="cb9-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## To make sure pytorch and our implementation both</span></span>
<span id="cb9-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## have the same weights so we can compare them</span></span>
<span id="cb9-5">cell.ih.weight.data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pytorch_cell.weight_ih.data</span>
<span id="cb9-6">cell.hh.weight.data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pytorch_cell.weight_hh.data</span>
<span id="cb9-7">cell.ih.bias.data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pytorch_cell.bias_ih.data</span>
<span id="cb9-8">cell.hh.bias.data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pytorch_cell.bias_hh.data</span></code></pre></div></div>
</div>
<div id="1a03238d-994b-427e-92bd-7fb1abff8b62" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1">h_t, c_t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cell(X[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], h_0[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], c_0[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span></code></pre></div></div>
</div>
<div id="0bc1356c-0d3e-4e79-a174-0b717407882c" class="cell" data-execution_count="10">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb11-2">    np.linalg.norm(pytorch_h.detach().numpy() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> h_t.detach().numpy()),</span>
<span id="cb11-3">    np.linalg.norm(pytorch_c.detach().numpy() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> c_t.detach().numpy()),</span>
<span id="cb11-4">)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>0.0 0.0</code></pre>
</div>
</div>
</section>
<section id="from-cell-to-sequence-the-full-lstm" class="level3">
<h3 class="anchored" data-anchor-id="from-cell-to-sequence-the-full-lstm">From Cell to Sequence: The Full <code>LSTM</code></h3>
<p>With <code>LSTMCell</code> verified, let’s build the full <code>LSTM</code> module that handles entire sequences and optionally stacks multiple layers.</p>
<p>There are several important design decisions in a production LSTM implementation:</p>
<p><strong>Memory layout: sequence-first (<code>T × B × D</code>).</strong> We use the sequence length as the first dimension instead of batch-first. Why? We iterate over time steps in the inner loop, and we want each <code>x[t]</code> to be a contiguous slice of memory. If batch were first, each time step’s data would be non-contiguous, requiring a copy on every iteration.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Warning</span>The Contiguity Trap
</div>
</div>
<div class="callout-body-container callout-body">
<p>If you pass batch-first tensors (<code>B × T × D</code>) to an LSTM that expects sequence-first, it will still “work”, but each time step access triggers an implicit copy because the memory isn’t contiguous along the time dimension. This can silently slow down training. PyTorch’s <code>nn.LSTM</code> has a <code>batch_first</code> flag that handles the transpose for you, but internally it still processes sequence-first.</p>
</div>
</div>
<p><strong>Truncated Backpropagation Through Time (TBPTT).</strong> Since weights are shared across all time steps within a layer, backpropagating through very long sequences causes severe vanishing/exploding gradients <em>and</em> extreme memory usage (all intermediate activations must be stored). The standard solution: <strong>detach</strong> the hidden and cell states from the computation graph after each batch. Gradients can flow within a batch’s time steps but not across batch boundaries.</p>
<p><strong>Multi-layer stacking.</strong> We can stack LSTMs by feeding the hidden state output of layer <img src="https://latex.codecogs.com/png.latex?l"> as the input to layer <img src="https://latex.codecogs.com/png.latex?l+1">. Each layer has its own <code>LSTMCell</code> with independent weights. The first layer’s cell takes input of size <code>input_sz</code>; all subsequent layers take input of size <code>hidden_sz</code>. This increases model capacity: deeper layers can learn more abstract representations.</p>
<p><strong>Layer iteration order.</strong> With multiple layers, there are two valid iteration orders: (1) iterate all time steps for layer 0, then all time steps for layer 1, etc., or (2) at each time step, iterate through all layers before moving to the next time step. Our implementation uses option (1), which is simpler and matches PyTorch’s behavior.</p>
<p><strong>Handling variable-length sequences.</strong> Not all sequences have the same length. Two approaches:</p>
<ol type="1">
<li><strong>Padding</strong>: pad shorter sequences to the longest length with zeros (pre- or post-padding). Simple but wasteful; the model does unnecessary computation on padding tokens.</li>
<li><strong>Packed sequences</strong>: combine all sequences together with index metadata marking boundaries. More efficient but more complex to implement. PyTorch provides <code>pack_padded_sequence</code> and <code>pad_packed_sequence</code> utilities for this.</li>
</ol>
<div id="d7691b4e-235e-434c-b22f-a5130c6ad864" class="cell" data-execution_count="11">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> LSTMNew(nn.Module):</span>
<span id="cb13-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, input_sz, hidden_sz, num_layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb13-3">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb13-4">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.num_layers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> num_layers</span>
<span id="cb13-5">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.hidden_sz <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> hidden_sz</span>
<span id="cb13-6">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.cells <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.ModuleList(</span>
<span id="cb13-7">            [</span>
<span id="cb13-8">                LSTMCellNew(input_sz, hidden_sz)</span>
<span id="cb13-9">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb13-10">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> LSTMCellNew(hidden_sz, hidden_sz)</span>
<span id="cb13-11">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.num_layers)</span>
<span id="cb13-12">            ]</span>
<span id="cb13-13">        )</span>
<span id="cb13-14"></span>
<span id="cb13-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x, h_t, c_t):</span>
<span id="cb13-16">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## x  :      T     x B x hidden_sz</span></span>
<span id="cb13-17">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## h_t: num_layers x B x hidden_sz</span></span>
<span id="cb13-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## c_t: num_layers x B x hidden_sz</span></span>
<span id="cb13-19">        T, B, _ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x.shape</span>
<span id="cb13-20">        H <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.zeros(T, B, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.hidden_sz)</span>
<span id="cb13-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, cell <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.cells):</span>
<span id="cb13-22">            h, c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> h_t[i], c_t[i]</span>
<span id="cb13-23">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb13-24">                x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> H</span>
<span id="cb13-25">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> t <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(T):</span>
<span id="cb13-26">                h, c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cell(x[t], h, c)</span>
<span id="cb13-27">                H[t] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> h</span>
<span id="cb13-28">            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## last hidden state for each layer</span></span>
<span id="cb13-29">            h_t[i], c_t[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> h, c</span>
<span id="cb13-30">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">## Truncated BPTT</span></span>
<span id="cb13-31">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> H, (h_t.detach(), c_t.detach())</span></code></pre></div></div>
</div>
<div id="d2c608d9-11f0-49a6-bcbf-e8a7387cb205" class="cell" data-execution_count="12">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb14-1">pytorch_lstm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.LSTM(input_sz, hidden_sz, num_layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>num_layers)</span>
<span id="cb14-2">pytorch_H, (pytorch_h, pytorch_c) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pytorch_lstm(X, (h_0, c_0))</span></code></pre></div></div>
</div>
<div id="0966232f-132c-4e2f-b97c-8fa2fab3ba56" class="cell" data-execution_count="13">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1">lstm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> LSTMNew(input_sz, hidden_sz, num_layers<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>num_layers)</span>
<span id="cb15-2"></span>
<span id="cb15-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(num_layers):</span>
<span id="cb15-4">    lstm.cells[i].ih.weight.data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">getattr</span>(pytorch_lstm, <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"weight_ih_l</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>).data</span>
<span id="cb15-5">    lstm.cells[i].hh.weight.data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">getattr</span>(pytorch_lstm, <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"weight_hh_l</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>).data</span>
<span id="cb15-6">    lstm.cells[i].ih.bias.data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">getattr</span>(pytorch_lstm, <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"bias_ih_l</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>).data</span>
<span id="cb15-7">    lstm.cells[i].hh.bias.data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">getattr</span>(pytorch_lstm, <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"bias_hh_l</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>).data</span>
<span id="cb15-8"></span>
<span id="cb15-9">H, (h_t, c_t) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lstm(X, h_0, c_0)</span></code></pre></div></div>
</div>
<div id="652fc3bb-7371-489b-85c4-8cd2e97fd60f" class="cell" data-execution_count="14">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb16-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb16-2">    np.linalg.norm(pytorch_H.detach().numpy() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> H.detach().numpy()),</span>
<span id="cb16-3">    np.linalg.norm(pytorch_h.detach().numpy() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> h_t.detach().numpy()),</span>
<span id="cb16-4">    np.linalg.norm(pytorch_c.detach().numpy() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> c_t.detach().numpy()),</span>
<span id="cb16-5">)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>0.0 0.0 0.0</code></pre>
</div>
</div>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>LSTMs were the dominant architecture for sequence modeling in NLP for years, powering machine translation, text classification, language modeling, and speech recognition before Transformers took over. In this post, we implemented both <code>LSTMCell</code> and a multi-layer <code>LSTM</code> from scratch, verified them against PyTorch’s official implementation, and discussed the performance decisions that go into a production implementation.</p>
<section id="key-takeaways" class="level3">
<h3 class="anchored" data-anchor-id="key-takeaways">Key Takeaways</h3>
<ol type="1">
<li><p><strong>LSTMs solve vanishing gradients through additive cell state updates.</strong> The forget gate can stay close to 1, so gradients flow through many time steps without exponential decay. This is fundamentally different from vanilla RNNs, where the hidden state is completely overwritten at each step.</p></li>
<li><p><strong>Four gates, one matrix multiplication.</strong> The input, forget, cell, and output gates are computed together in a single fused operation, then split, a practical optimization that improves throughput by better using hardware parallelism.</p></li>
<li><p><strong>Sequential processing is the fundamental bottleneck.</strong> The output at time <img src="https://latex.codecogs.com/png.latex?t"> depends on the hidden state from <img src="https://latex.codecogs.com/png.latex?t-1">, so parallelization across time steps is impossible. This is the limitation that motivated the Transformer’s self-attention mechanism.</p></li>
<li><p><strong>Truncated BPTT is essential for long sequences.</strong> Detaching hidden states between batches prevents gradient computation from spanning the entire sequence, reducing both memory usage and gradient instability.</p></li>
<li><p><strong>Memory layout matters.</strong> Using sequence-first tensors (<code>T × B × D</code>) ensures contiguous memory access at each time step, avoiding hidden performance penalties from implicit copies.</p></li>
</ol>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>LSTMs in the Transformer Era
</div>
</div>
<div class="callout-body-container callout-body">
<p>While Transformers have largely replaced LSTMs for most NLP tasks, understanding LSTMs remains valuable. They’re still used in streaming/online settings where you process one token at a time, in resource-constrained environments where the <img src="https://latex.codecogs.com/png.latex?O(n%5E2)"> attention cost is prohibitive, and as components in hybrid architectures. More importantly, the concepts (gating, cell states, truncated BPTT) appear in many modern architectures in different forms.</p>
</div>
</div>
</section>
</section>
<section id="references-resources" class="level2">
<h2 class="anchored" data-anchor-id="references-resources">References &amp; Resources</h2>
<ul>
<li><strong>Hochreiter, S. &amp; Schmidhuber, J.</strong> (1997). <a href="https://www.bioinf.jku.at/publications/older/2604.pdf">Long Short-Term Memory</a>. <em>Neural Computation</em>, 9(8), 1735–1780.</li>
<li><strong>Olah, C.</strong> (2015). <a href="https://colah.github.io/posts/2015-08-Understanding-LSTMs/">Understanding LSTM Networks</a>.</li>
<li><strong>Greff, K. et al.</strong> (2017). <a href="https://arxiv.org/abs/1503.04069">LSTM: A Search Space Odyssey</a>. <em>IEEE TNNLS</em>.</li>
<li><strong>Vaswani, A. et al.</strong> (2017). <a href="https://arxiv.org/abs/1706.03762">Attention Is All You Need</a>. <em>NeurIPS 2017</em>.</li>
<li><strong>Merity, S. et al.</strong> (2018). <a href="https://arxiv.org/abs/1708.02182">Regularizing and Optimizing LSTM Language Models</a> (AWD-LSTM). <em>ICLR 2018</em>.</li>
<li><strong>PyTorch Documentation</strong>. <a href="https://pytorch.org/docs/stable/generated/torch.nn.LSTM.html">nn.LSTM</a> and <a href="https://pytorch.org/docs/stable/generated/torch.nn.LSTMCell.html">nn.LSTMCell</a>.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>NLP</category>
  <guid>https://imaddabbura.github.io/posts/nlp/LSTM-Annotated-Implementation.html</guid>
  <pubDate>Tue, 10 Mar 2020 05:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/nlp/images/lstm-cell.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Anomaly Detection</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/anomaly-detection/Anomaly-Detection.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge growing">growing</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p>Anomaly Detection is the identification of examples or events that don’t confront to an expected pattern or the majority of examples. Roughly speaking, it’s the process of identifying an example that is not <em>normal (outlier)</em> given the distribution of the data. <strong>Outlier</strong> is an example that deviates so much from the other examples that arouse suspicions that it was generated by different data generating process. Mainly, such outliers would have a very low probability (on the very end of both left and right tails of the probability density function) that they belong to the same data generating process.</p>
<p>The algorithm works as follows: 1. Fit a <em>Gaussian Probability Density Function (PDF)</em> for each feature in the training dataset. 1. Calculate the mean and the variance of each feature: <img src="https://latex.codecogs.com/png.latex?%5Cmu_j%20=%20%5Cfrac%7B1%7D%7Bm%7D%5Csum_%7Bi%20=%201%7D%5Emx_j%5Ei%5C%5C%7B%7D"> <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2_j%20=%20%5Cfrac%7B1%7D%7Bm%7D%5Csum_%7Bi%20=%201%7D%5Em(x_j%5Ei%20-%20%5Cmu_j)%5E2%5C%5C%7B%7D"> Where <img src="https://latex.codecogs.com/png.latex?%5Cmu"> is the mean and <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2"> is the variance that controls the shape of the density function. 2. Compute the density function for each feature using the following formula:<br>
<img src="https://latex.codecogs.com/png.latex?p(x;%20%5Cmu,%20%5Csigma%5E2)%20=%20%5Cfrac%7B1%7D%7B%5Csqrt%7B2%5Cpi%7D%5Csigma%7De%5E%7B-%5Cfrac%7B(x%20-%20%5Cmu)%5E2%7D%7B2%5Csigma%5E2%7D%7D%5C%5C%7B%7D"> Since the mean and the variance are sensitive to outliers, we use training dataset that has only normal examples to fit the model and calculate both the mean vector and the covariance matrix. 2. Compute the gaussian density by taking the product of all features’ density functions. 3. If <img src="https://latex.codecogs.com/png.latex?p(x)%20%3C%20%5Cepsilon"> then anomaly; otherwise, normal. Epsilon controls how sensitive the detection algorithm is. If <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"> is large <img src="https://latex.codecogs.com/png.latex?%5Crightarrow"> flag a lot of the examples as anomalous and that would increase the <em>False Positives</em>. However, If <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"> is small <img src="https://latex.codecogs.com/png.latex?%5Crightarrow"> very small portion of the examples will be flagged as anomalous and that would increase the <em>False Negatives</em>. 4. Use <em>Cross Validation</em> for tuning the hyper-parameter <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"> that yields the best performance metrics value. F1 score is commonly used: <img src="https://latex.codecogs.com/png.latex?F_1%20=%202%20%5Cfrac%7Bprecision%20*%20recall%7D%7Bprecision%20+%20recall%7D%5C%5C%7B%7D"> Where:<img src="https://latex.codecogs.com/png.latex?precision%20=%20%5Cfrac%7Btp%7D%7Btp%20+%20fp%7D%5C%5C%7B%7D"> <img src="https://latex.codecogs.com/png.latex?recall%20=%20%5Cfrac%7Btp%7D%7Btp%20+%20fn%7D%5C%5C%7B%7D"> <em>tp: True Positive, fp: False Positive, fn: False Negative</em>.</p>
<p>We have two kinds of anomaly detection algorithms: 1. <strong>Univariate Gaussian Density Function</strong> <img src="https://latex.codecogs.com/png.latex?p(x)%20=%20%5Cprod_%7Bj%20=%201%7D%5E%7Bn%7Dp(x_j;%20%5Cmu_j,%20%5Csigma_j%5E2)%5C%5C%7B%7D"> <img src="https://latex.codecogs.com/png.latex?%20=%20p(x_1;%20%5Cmu_1,%20%5Csigma_1%5E2)*p(x_2;%20%5Cmu_2,%20%5Csigma_2%5E2)*%20...%20*%20p(x_n;%20%5Cmu_n,%20%5Csigma_j%5En)%5C%5C%7B%7D"> * It assumes that all features are independent. Therefore, the covariance between all pairs of features is zero. * It’s computationally faster and more efficient. * Use it if we have very large number of features. * Make sure to add features manually that captures unusual values for combination of features; such as <img src="https://latex.codecogs.com/png.latex?x_3%20=%20%5Cfrac%20%7Bx_2%7D%7Bx_1%7D">. Otherwise, the algorithm may fail to detect anomalies that takes values that are considered normal when looked at each feature separately but are unusual when looking at values of all features together such as having high value for feature 2 compared to low value for feature 1.</p>
<ol start="2" type="1">
<li><strong>Multivariate Gaussian Density Function</strong> <img src="https://latex.codecogs.com/png.latex?p(x)%20=%20%5Cprod_%7Bj%20=%201%7D%5E%7Bn%7Dp(x_j;%20%5Cmu_j,%20%5Csigma_j%5E2)%5C%5C%7B%7D"> <img src="https://latex.codecogs.com/png.latex?p(x;%20%5Cmu,%20%5Csigma%5E2)%20=%20%5Cfrac%7B1%7D%7B(2%5Cpi)%5E%7B(n%20/%202)%7D(%5Cdet%5Csum)%5E%7B1%20/%202%7D%7De%5E%7B%5Cfrac%7B-1%7D%7B2%7D(x%20-%20%5Cmu)%5ET%5Csum%5E%7B-1%7D(x%20-%20%5Cmu)%7D%5C%5C%7B%7D"> Where <img src="https://latex.codecogs.com/png.latex?%5Csum"> is n x n covariance matrix: <img src="https://latex.codecogs.com/png.latex?%5Csum%20=%20%5Cbegin%7Bbmatrix%7D%0A%5Csigma_1%5E2&amp;%5Csigma_%7B12%7D&amp;%5Ccdots&amp;%5Csigma_%7B1n%7D%5C%5C%0A%5Csigma_%7B21%7D&amp;%5Csigma_2%5E2&amp;%5Ccdots&amp;0%5C%5C%0A%5Cvdots%20&amp;%20%5Cvdots%20&amp;%20%5Cddots%20&amp;%20%5Cvdots%20%5C%5C%0A%5Csigma_%7Bn1%7D%20&amp;%200%20&amp;%200%20&amp;%20%5Csigma_n%5E2%0A%5Cend%7Bbmatrix%7D"> Where <img src="https://latex.codecogs.com/png.latex?%5Csigma_%7B12%7D%20=%20%5Csigma_%7B21%7D"> is the covariance between features 1&amp;2. Therefore, the covariance matrix is <em>symmetric positive (semi) definite</em>.
<ul>
<li>Computationally expensive</li>
<li>Use it when number of examples <img src="https://latex.codecogs.com/png.latex?%5Cgeq"> 10 times number of features, i.e.&nbsp;<img src="https://latex.codecogs.com/png.latex?m%20%5Cgeq%2010n"></li>
<li>If some features are linearly dependent or number of examples is less than number of features <img src="https://latex.codecogs.com/png.latex?%5Crightarrow"> covariance matrix won’t be invertible</li>
<li>No need to add more features to capture unusual values of combination of features because it captures that through covariances of all pairs of features</li>
<li>Univariate density function can be derived from Multivariate density function where covariance matrix would be a diagonal matrix. Therefore, <img src="https://latex.codecogs.com/png.latex?%5Csigma_%7Bij%7D%20=%200"> for all <img src="https://latex.codecogs.com/png.latex?i%20%5Cneq%20j"></li>
</ul></li>
</ol>
<p>There are some assumptions made implicitly here: - For each feature, <img src="https://latex.codecogs.com/png.latex?X_i">’s are IID (independently and identically distributed). - Using Central Theorem (CLT): the distribution of sum of iid random variable are approximately normal. Therefore, this would allow us to fit normal distribution that’s parameterized by <img src="https://latex.codecogs.com/png.latex?%5Cmu"> and <img src="https://latex.codecogs.com/png.latex?%5Csigma%5E2">. - <img src="https://latex.codecogs.com/png.latex?%5Cmu"> and <img src="https://latex.codecogs.com/png.latex?%5Csum"> will be estimated using maximum-likelihood estimation method.</p>
<p>When fitting multivariate probability distribution using the above assumptions, we’ll use that pdf to estimate the probability that each example from the validation/test set was generated by this pdf. If the probability is smaller that <img src="https://latex.codecogs.com/png.latex?%5Cepsilon">, then we believe that such example was generated by different mutlivariate PDF and, therefor, classified as <em>anomaly</em> (outlier).</p>
<p>In this exercise, we’ll implement an anomaly detection algorithm to detect anomalous behavior in server computers. The features measure the throughput (mb/s) and latency (ms) of response of each server. While servers were operating, <img src="https://latex.codecogs.com/png.latex?m%20=%20307"> examples of how they were behaving were captured. We suspect that the vast majority of them are normal (non-anomalous) examples of the servers operating normally.</p>
<p>Let’s first load and plot the data:</p>
<div id="cell-4" class="cell" data-code_folding="[0]" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> numpy.linalg <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pinv, det</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb1-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> matplotlib.pyplot <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> plt</span>
<span id="cb1-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> scipy.io <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> loadmat, whosmat</span>
<span id="cb1-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> scipy.optimize <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> opt</span>
<span id="cb1-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> seaborn <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> sns</span>
<span id="cb1-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> warnings <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> filterwarnings</span>
<span id="cb1-9"></span>
<span id="cb1-10"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%</span>matplotlib inline</span>
<span id="cb1-11">sns.set_context(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'notebook'</span>)</span>
<span id="cb1-12">plt.style.use(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'fivethirtyeight'</span>)</span>
<span id="cb1-13">filterwarnings(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'ignore'</span>)</span></code></pre></div></div>
</details>
</div>
</section>
<section id="functions" class="level2">
<h2 class="anchored" data-anchor-id="functions">Functions</h2>
<div id="cell-6" class="cell" data-code_folding="[1,46]" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Compute guassian distribution fn</span></span>
<span id="cb2-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> gaussian_estimate(X_train, X_val, gaussian_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'univariate'</span>):</span>
<span id="cb2-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">'''</span></span>
<span id="cb2-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    parameters</span></span>
<span id="cb2-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    ----------</span></span>
<span id="cb2-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    X_train: array-like</span></span>
<span id="cb2-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        training features matrix m x n that has only normal examples.</span></span>
<span id="cb2-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    X_val: array-like</span></span>
<span id="cb2-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        cross validation features matrix that has anomalous and normal</span></span>
<span id="cb2-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        examples.</span></span>
<span id="cb2-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    gussian_type: str</span></span>
<span id="cb2-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        univariate or multivariate.</span></span>
<span id="cb2-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    </span></span>
<span id="cb2-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns</span></span>
<span id="cb2-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    -------</span></span>
<span id="cb2-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    pdf: array-like</span></span>
<span id="cb2-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        multivariate pdf vector of n x 1</span></span>
<span id="cb2-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    '''</span></span>
<span id="cb2-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># number of training examples and features</span></span>
<span id="cb2-20">    m, n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X_train.shape</span>
<span id="cb2-21">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># number of cv examples</span></span>
<span id="cb2-22">    mval <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X_val.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb2-23"></span>
<span id="cb2-24">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># compute mean and covariance matrix</span></span>
<span id="cb2-25">    mu <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X_train.mean(axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb2-26">    cov <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (m)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (X_train <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu).T.dot(X_train <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu)</span>
<span id="cb2-27"></span>
<span id="cb2-28">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># convert the covariance matrix to diagonal if it's a univariate</span></span>
<span id="cb2-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> gaussian_type <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'univariate'</span>:</span>
<span id="cb2-30">        z <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros_like(cov)</span>
<span id="cb2-31">        np.fill_diagonal(z, np.diagonal(cov))</span>
<span id="cb2-32">        cov <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> z</span>
<span id="cb2-33"></span>
<span id="cb2-34">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># compute determinant and inverse of covariance matrix</span></span>
<span id="cb2-35">    cov_det <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> det(cov)</span>
<span id="cb2-36">    cov_inv <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pinv(cov)</span>
<span id="cb2-37"></span>
<span id="cb2-38">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># compute pdf vector</span></span>
<span id="cb2-39">    pdf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.pi) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> (<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (cov_det <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span> (<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*\</span></span>
<span id="cb2-40">        np.exp(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(np.multiply((X_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu).dot(cov_inv),</span>
<span id="cb2-41">                                         (X_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> mu)), axis<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb2-42"></span>
<span id="cb2-43">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> pdf</span>
<span id="cb2-44"></span>
<span id="cb2-45"></span>
<span id="cb2-46"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Hyperparameter tuning of epsilon using cv dataset</span></span>
<span id="cb2-47"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> select_threshold(y_val, p_val):</span>
<span id="cb2-48">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">'''</span></span>
<span id="cb2-49"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    parameters</span></span>
<span id="cb2-50"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    ----------</span></span>
<span id="cb2-51"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    y_val: array-like</span></span>
<span id="cb2-52"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        label whether a validation example is normal (0) or anomaly (1).</span></span>
<span id="cb2-53"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    p_val: array-like</span></span>
<span id="cb2-54"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        pdf for validated examples.</span></span>
<span id="cb2-55"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    </span></span>
<span id="cb2-56"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Returns</span></span>
<span id="cb2-57"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    -------</span></span>
<span id="cb2-58"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    eplsion : float</span></span>
<span id="cb2-59"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        best epsilon value tuned on validation data.</span></span>
<span id="cb2-60"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    F1_score : float</span></span>
<span id="cb2-61"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        F1 score using epsilon tuned on validation data.</span></span>
<span id="cb2-62"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    '''</span></span>
<span id="cb2-63">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># initialize epsilon and F1 score values</span></span>
<span id="cb2-64">    best_epsilon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb2-65">    best_F1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb2-66"></span>
<span id="cb2-67">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># compute stepsize for each iteration</span></span>
<span id="cb2-68">    epsilon_stepsize <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (p_val.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> p_val.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>()) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span></span>
<span id="cb2-69"></span>
<span id="cb2-70">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> epsilon <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> np.arange(p_val.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(), p_val.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">max</span>(), epsilon_stepsize):</span>
<span id="cb2-71">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># get predictions vector</span></span>
<span id="cb2-72">        pred <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ((p_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> epsilon) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>).reshape(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb2-73"></span>
<span id="cb2-74">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># compute true positives, false positives, false negatives</span></span>
<span id="cb2-75">        tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((pred <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (y_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb2-76">        fp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((pred <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (y_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))</span>
<span id="cb2-77">        fn <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>((pred <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> (y_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb2-78"></span>
<span id="cb2-79">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># compute precision and recall</span></span>
<span id="cb2-80">        precision_ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> fp)</span>
<span id="cb2-81">        recall_ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> fn)</span>
<span id="cb2-82"></span>
<span id="cb2-83">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># compute F1 score</span></span>
<span id="cb2-84">        F1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> ((precision_ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> recall_) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (precision_ <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> recall_))</span>
<span id="cb2-85">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># if F1 score &gt; best_F1, set best_F1 = F1</span></span>
<span id="cb2-86">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> F1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> best_F1:</span>
<span id="cb2-87">            best_F1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> F1</span>
<span id="cb2-88">            best_epsilon <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> epsilon</span>
<span id="cb2-89"></span>
<span id="cb2-90">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> best_epsilon, best_F1</span></code></pre></div></div>
</details>
</div>
<div id="cell-7" class="cell" data-code_folding="[0]" data-tags="[]" data-execution_count="6">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Load data</span></span>
<span id="cb3-2">data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> loadmat(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'../data/servers_anomaly_detection.mat'</span>)</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Training data</span></span>
<span id="cb3-5">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'X'</span>]</span>
<span id="cb3-6"></span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Cross validation data</span></span>
<span id="cb3-8">X_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Xval'</span>]</span>
<span id="cb3-9">y_val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'yval'</span>]</span>
<span id="cb3-10"></span>
<span id="cb3-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Plot data</span></span>
<span id="cb3-12">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>))</span>
<span id="cb3-13">plt.scatter(X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], s <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span>, c <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'blue'</span>)</span>
<span id="cb3-14">plt.axis([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>])</span>
<span id="cb3-15">plt.xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Latency (ms)'</span>)</span>
<span id="cb3-16">plt.ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Throughput (mb/s)'</span>)</span>
<span id="cb3-17">plt.gca().set_aspect(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'equal'</span>)</span>
<span id="cb3-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># plt.title('Scatter plot of the first dataset');</span></span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><a href="Anomaly-Detection_files/figure-html/cell-4-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://imaddabbura.github.io/posts/anomaly-detection/Anomaly-Detection_files/figure-html/cell-4-output-1.png" class="img-fluid figure-img"></a></p>
</figure>
</div>
</div>
</div>
<div id="cell-8" class="cell" data-execution_count="7">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># plt.subplots(1, 2, 1)</span></span>
<span id="cb4-2">sns.kdeplot(X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb4-3">sns.kdeplot(X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><a href="Anomaly-Detection_files/figure-html/cell-5-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img src="https://imaddabbura.github.io/posts/anomaly-detection/Anomaly-Detection_files/figure-html/cell-5-output-1.png" class="img-fluid figure-img"></a></p>
</figure>
</div>
</div>
</div>
<p>Now, we’ll first estimate the Gaussian distribution for both the training and cross validation sets. Note that we use training dataset that has ONLY normal examples when computing mean and covariance and then use cross validation that has both normal and anomalous examples to know the best epsilon.</p>
<div id="cell-10" class="cell" data-code_folding="[0]" data-execution_count="10">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fit guassian distribution on both training and CV examples</span></span>
<span id="cb5-2">ptrain <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gaussian_estimate(X, X)</span>
<span id="cb5-3">pval <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gaussian_estimate(X, X_val, gaussian_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'multivariate'</span>)</span>
<span id="cb5-4"></span>
<span id="cb5-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Tune epsilon</span></span>
<span id="cb5-6">epsilon, F1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> select_threshold(y_val, pval)</span>
<span id="cb5-7"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'The best epsilon tuned using CV that yielded the best'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb5-8">      <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'F1-score </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>F1<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> is: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>epsilon<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">.'</span>)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>The best epsilon tuned using CV that yielded the bestF1-score 0.875 is: 9.065769728392737e-05.</code></pre>
</div>
</div>
<p>We’ll use the value of epsilon that we tuned using CV to see what examples were anomalous based on our algorithm. Below is the scatter plot of the training data where red points are anomalous examples.</p>
<div id="cell-12" class="cell" data-code_folding="[0]" data-tags="[]" data-execution_count="11">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get the index of the outlier</span></span>
<span id="cb7-2">outliers <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.where(ptrain <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> epsilon)</span>
<span id="cb7-3"></span>
<span id="cb7-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Plot data</span></span>
<span id="cb7-5">fig, ax <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> plt.subplots(figsize<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6</span>))</span>
<span id="cb7-6">plt.scatter(X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], X[:, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span>, c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'blue'</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Normal Examples'</span>)</span>
<span id="cb7-7">plt.scatter(X[outliers[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], X[outliers[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>, c<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'red'</span>, label<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Anomalous Examples'</span>)</span>
<span id="cb7-8">plt.axis([<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>])</span>
<span id="cb7-9">plt.xlabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Latency (ms)'</span>)</span>
<span id="cb7-10">plt.ylabel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Throughput (mb/s)'</span>)</span>
<span id="cb7-11">plt.legend(loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'upper right'</span>)</span>
<span id="cb7-12">plt.title(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Scatter plot of the training dataset'</span>)<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span></span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><a href="Anomaly-Detection_files/figure-html/cell-7-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img src="https://imaddabbura.github.io/posts/anomaly-detection/Anomaly-Detection_files/figure-html/cell-7-output-1.png" class="img-fluid figure-img"></a></p>
</figure>
</div>
</div>
</div>
<p>Finally, we’ll try to fit Gaussian distribution on training dataset that has 1000 examples and 11 features. Note that in both examples we used <em>Multivariate</em> not <em>Univariate</em> Gaussian distribution.</p>
<div id="cell-14" class="cell" data-code_folding="[]" data-execution_count="12">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Load data</span></span>
<span id="cb8-2">data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> loadmat(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'../data/ex8data2.mat'</span>)</span>
<span id="cb8-3"></span>
<span id="cb8-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Training data</span></span>
<span id="cb8-5">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'X'</span>]</span>
<span id="cb8-6"></span>
<span id="cb8-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Cross validation data</span></span>
<span id="cb8-8">Xval <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Xval'</span>]</span>
<span id="cb8-9">yval <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'yval'</span>]</span>
<span id="cb8-10"></span>
<span id="cb8-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fit guassian distribution on both training and CV examples</span></span>
<span id="cb8-12">ptrain <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gaussian_estimate(X, X, gaussian_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'multivariate'</span>)</span>
<span id="cb8-13">pval <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gaussian_estimate(X, Xval, gaussian_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'multivariate'</span>)</span>
<span id="cb8-14"></span>
<span id="cb8-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Tune epsilon</span></span>
<span id="cb8-16">epsilon, F1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> select_threshold(yval, pval)</span>
<span id="cb8-17"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'The best epsilon tuned using CV that yielded the best'</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb8-18">      <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'F1-score </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{F1:.3f}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> is: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{epsilon}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">.'</span>)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-error">
<div class="ansi-escaped-output">
<pre><span class="ansi-red-fg">---------------------------------------------------------------------------</span>
<span class="ansi-red-fg">FileNotFoundError</span>                         Traceback (most recent call last)
File <span class="ansi-green-fg">~/anaconda3/envs/dl/lib/python3.10/site-packages/scipy/io/matlab/mio.py:39</span>, in <span class="ansi-cyan-fg">_open_file</span><span class="ansi-blue-fg">(file_like, appendmat, mode)</span>
<span class="ansi-green-fg ansi-bold">     38</span> <span style="font-weight:bold;color:rgb(0,135,0)">try</span>:
<span class="ansi-green-fg">---&gt; 39</span>     <span style="font-weight:bold;color:rgb(0,135,0)">return</span> <span style="color:rgb(0,135,0)" class="ansi-yellow-bg">open</span><span class="ansi-yellow-bg">(</span><span class="ansi-yellow-bg">file_like</span><span class="ansi-yellow-bg">,</span><span class="ansi-yellow-bg"> </span><span class="ansi-yellow-bg">mode</span><span class="ansi-yellow-bg">)</span>, <span style="font-weight:bold;color:rgb(0,135,0)">True</span>
<span class="ansi-green-fg ansi-bold">     40</span> <span style="font-weight:bold;color:rgb(0,135,0)">except</span> <span style="font-weight:bold;color:rgb(215,95,95)">IOError</span> <span style="font-weight:bold;color:rgb(0,135,0)">as</span> e:
<span class="ansi-green-fg ansi-bold">     41</span>     <span style="font-style:italic;color:rgb(95,135,135)"># Probably "not found"</span>

<span class="ansi-red-fg">FileNotFoundError</span>: [Errno 2] No such file or directory: '../data/ex8data2.mat'

During handling of the above exception, another exception occurred:

<span class="ansi-red-fg">FileNotFoundError</span>                         Traceback (most recent call last)
Input <span class="ansi-green-fg">In [12]</span>, in <span class="ansi-cyan-fg">&lt;cell line: 2&gt;</span><span class="ansi-blue-fg">()</span>
<span class="ansi-green-fg ansi-bold">      1</span> <span style="font-style:italic;color:rgb(95,135,135)"># Load data</span>
<span class="ansi-green-fg">----&gt; 2</span> data <span style="color:rgb(98,98,98)">=</span> <span class="ansi-yellow-bg">loadmat</span><span class="ansi-yellow-bg">(</span><span style="color:rgb(175,0,0)" class="ansi-yellow-bg">'</span><span style="color:rgb(175,0,0)" class="ansi-yellow-bg">../data/ex8data2.mat</span><span style="color:rgb(175,0,0)" class="ansi-yellow-bg">'</span><span class="ansi-yellow-bg">)</span>
<span class="ansi-green-fg ansi-bold">      4</span> <span style="font-style:italic;color:rgb(95,135,135)"># Training data</span>
<span class="ansi-green-fg ansi-bold">      5</span> X <span style="color:rgb(98,98,98)">=</span> data[<span style="color:rgb(175,0,0)">'</span><span style="color:rgb(175,0,0)">X</span><span style="color:rgb(175,0,0)">'</span>]

File <span class="ansi-green-fg">~/anaconda3/envs/dl/lib/python3.10/site-packages/scipy/io/matlab/mio.py:224</span>, in <span class="ansi-cyan-fg">loadmat</span><span class="ansi-blue-fg">(file_name, mdict, appendmat, **kwargs)</span>
<span class="ansi-green-fg ansi-bold">     87</span> <span style="font-style:italic;color:rgb(175,0,0)">"""</span>
<span class="ansi-green-fg ansi-bold">     88</span> <span style="font-style:italic;color:rgb(175,0,0)">Load MATLAB file.</span>
<span class="ansi-green-fg ansi-bold">     89</span> 
<span class="ansi-green-fg">   (...)</span>
<span class="ansi-green-fg ansi-bold">    221</span> <span style="font-style:italic;color:rgb(175,0,0)">    3.14159265+3.14159265j])</span>
<span class="ansi-green-fg ansi-bold">    222</span> <span style="font-style:italic;color:rgb(175,0,0)">"""</span>
<span class="ansi-green-fg ansi-bold">    223</span> variable_names <span style="color:rgb(98,98,98)">=</span> kwargs<span style="color:rgb(98,98,98)">.</span>pop(<span style="color:rgb(175,0,0)">'</span><span style="color:rgb(175,0,0)">variable_names</span><span style="color:rgb(175,0,0)">'</span>, <span style="font-weight:bold;color:rgb(0,135,0)">None</span>)
<span class="ansi-green-fg">--&gt; 224</span> <span style="font-weight:bold;color:rgb(0,135,0)">with</span> _open_file_context(file_name, appendmat) <span style="font-weight:bold;color:rgb(0,135,0)">as</span> f:
<span class="ansi-green-fg ansi-bold">    225</span>     MR, _ <span style="color:rgb(98,98,98)">=</span> mat_reader_factory(f, <span style="color:rgb(98,98,98)">*</span><span style="color:rgb(98,98,98)">*</span>kwargs)
<span class="ansi-green-fg ansi-bold">    226</span>     matfile_dict <span style="color:rgb(98,98,98)">=</span> MR<span style="color:rgb(98,98,98)">.</span>get_variables(variable_names)

File <span class="ansi-green-fg">~/anaconda3/envs/dl/lib/python3.10/contextlib.py:135</span>, in <span class="ansi-cyan-fg">_GeneratorContextManager.__enter__</span><span class="ansi-blue-fg">(self)</span>
<span class="ansi-green-fg ansi-bold">    133</span> <span style="font-weight:bold;color:rgb(0,135,0)">del</span> <span style="color:rgb(0,135,0)">self</span><span style="color:rgb(98,98,98)">.</span>args, <span style="color:rgb(0,135,0)">self</span><span style="color:rgb(98,98,98)">.</span>kwds, <span style="color:rgb(0,135,0)">self</span><span style="color:rgb(98,98,98)">.</span>func
<span class="ansi-green-fg ansi-bold">    134</span> <span style="font-weight:bold;color:rgb(0,135,0)">try</span>:
<span class="ansi-green-fg">--&gt; 135</span>     <span style="font-weight:bold;color:rgb(0,135,0)">return</span> <span style="color:rgb(0,135,0)" class="ansi-yellow-bg">next</span><span class="ansi-yellow-bg">(</span><span style="color:rgb(0,135,0)" class="ansi-yellow-bg">self</span><span style="color:rgb(98,98,98)" class="ansi-yellow-bg">.</span><span class="ansi-yellow-bg">gen</span><span class="ansi-yellow-bg">)</span>
<span class="ansi-green-fg ansi-bold">    136</span> <span style="font-weight:bold;color:rgb(0,135,0)">except</span> <span style="font-weight:bold;color:rgb(215,95,95)">StopIteration</span>:
<span class="ansi-green-fg ansi-bold">    137</span>     <span style="font-weight:bold;color:rgb(0,135,0)">raise</span> <span style="font-weight:bold;color:rgb(215,95,95)">RuntimeError</span>(<span style="color:rgb(175,0,0)">"</span><span style="color:rgb(175,0,0)">generator didn</span><span style="color:rgb(175,0,0)">'</span><span style="color:rgb(175,0,0)">t yield</span><span style="color:rgb(175,0,0)">"</span>) <span style="font-weight:bold;color:rgb(0,135,0)">from</span> <span style="color:rgb(0,135,0)">None</span>

File <span class="ansi-green-fg">~/anaconda3/envs/dl/lib/python3.10/site-packages/scipy/io/matlab/mio.py:17</span>, in <span class="ansi-cyan-fg">_open_file_context</span><span class="ansi-blue-fg">(file_like, appendmat, mode)</span>
<span class="ansi-green-fg ansi-bold">     15</span> <span style="color:rgb(175,0,255)">@contextmanager</span>
<span class="ansi-green-fg ansi-bold">     16</span> <span style="font-weight:bold;color:rgb(0,135,0)">def</span> <span style="color:rgb(0,0,255)">_open_file_context</span>(file_like, appendmat, mode<span style="color:rgb(98,98,98)">=</span><span style="color:rgb(175,0,0)">'</span><span style="color:rgb(175,0,0)">rb</span><span style="color:rgb(175,0,0)">'</span>):
<span class="ansi-green-fg">---&gt; 17</span>     f, opened <span style="color:rgb(98,98,98)">=</span> <span class="ansi-yellow-bg">_open_file</span><span class="ansi-yellow-bg">(</span><span class="ansi-yellow-bg">file_like</span><span class="ansi-yellow-bg">,</span><span class="ansi-yellow-bg"> </span><span class="ansi-yellow-bg">appendmat</span><span class="ansi-yellow-bg">,</span><span class="ansi-yellow-bg"> </span><span class="ansi-yellow-bg">mode</span><span class="ansi-yellow-bg">)</span>
<span class="ansi-green-fg ansi-bold">     18</span>     <span style="font-weight:bold;color:rgb(0,135,0)">try</span>:
<span class="ansi-green-fg ansi-bold">     19</span>         <span style="font-weight:bold;color:rgb(0,135,0)">yield</span> f

File <span class="ansi-green-fg">~/anaconda3/envs/dl/lib/python3.10/site-packages/scipy/io/matlab/mio.py:45</span>, in <span class="ansi-cyan-fg">_open_file</span><span class="ansi-blue-fg">(file_like, appendmat, mode)</span>
<span class="ansi-green-fg ansi-bold">     43</span>     <span style="font-weight:bold;color:rgb(0,135,0)">if</span> appendmat <span style="font-weight:bold;color:rgb(175,0,255)">and</span> <span style="font-weight:bold;color:rgb(175,0,255)">not</span> file_like<span style="color:rgb(98,98,98)">.</span>endswith(<span style="color:rgb(175,0,0)">'</span><span style="color:rgb(175,0,0)">.mat</span><span style="color:rgb(175,0,0)">'</span>):
<span class="ansi-green-fg ansi-bold">     44</span>         file_like <span style="color:rgb(98,98,98)">+</span><span style="color:rgb(98,98,98)">=</span> <span style="color:rgb(175,0,0)">'</span><span style="color:rgb(175,0,0)">.mat</span><span style="color:rgb(175,0,0)">'</span>
<span class="ansi-green-fg">---&gt; 45</span>     <span style="font-weight:bold;color:rgb(0,135,0)">return</span> <span style="color:rgb(0,135,0)" class="ansi-yellow-bg">open</span><span class="ansi-yellow-bg">(</span><span class="ansi-yellow-bg">file_like</span><span class="ansi-yellow-bg">,</span><span class="ansi-yellow-bg"> </span><span class="ansi-yellow-bg">mode</span><span class="ansi-yellow-bg">)</span>, <span style="font-weight:bold;color:rgb(0,135,0)">True</span>
<span class="ansi-green-fg ansi-bold">     46</span> <span style="font-weight:bold;color:rgb(0,135,0)">else</span>:
<span class="ansi-green-fg ansi-bold">     47</span>     <span style="font-weight:bold;color:rgb(0,135,0)">raise</span> <span style="font-weight:bold;color:rgb(215,95,95)">IOError</span>(
<span class="ansi-green-fg ansi-bold">     48</span>         <span style="color:rgb(175,0,0)">'</span><span style="color:rgb(175,0,0)">Reader needs file name or open file-like object</span><span style="color:rgb(175,0,0)">'</span>
<span class="ansi-green-fg ansi-bold">     49</span>     ) <span style="font-weight:bold;color:rgb(0,135,0)">from</span> <span style="font-weight:bold;color:rgb(0,0,255)">e</span>

<span class="ansi-red-fg">FileNotFoundError</span>: [Errno 2] No such file or directory: '../data/ex8data2.mat'</pre>
</div>
</div>
</div>
<p>Using the best-epsilon value we got above, we can then classify any example as anomaly if <img src="https://latex.codecogs.com/png.latex?p(x)%20%3C%20%5Cepsilon">; otherwise, it’s normal.</p>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<ul>
<li>The implementation of the variance/covariance in the detection algorithms has <img src="https://latex.codecogs.com/png.latex?m"> in the denominator not <img src="https://latex.codecogs.com/png.latex?(m%20-%201)"> because with large datasets this doesn’t make a difference. However, the unbiased estimator of the variance should have <img src="https://latex.codecogs.com/png.latex?(m%20-%201)"> in the denominator not <img src="https://latex.codecogs.com/png.latex?m">.</li>
<li>Anomaly detection vs Supervised learning:
<ul>
<li>Use Anomaly Detection when you have large number of negative examples and very small number of positive examples. The reason is because the supervised learning algorithm wouldn’t be able to have enough examples to learn about the scene especially if the future anomalies are nothing like training anomalies</li>
<li>Use Supervised Learning algorithms such as logistic regression if you have enough positive examples that make the learning easy on the algorithm and probably it would outperform Anomaly Detection algorithms.<br>
</li>
</ul></li>
<li>Univariate PDF performs well most of the times compared to Multivariate PDF and scale really well.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Machine Learning</category>
  <guid>https://imaddabbura.github.io/posts/anomaly-detection/Anomaly-Detection.html</guid>
  <pubDate>Wed, 11 Sep 2019 05:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/anomaly-detection/feature.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Gradient Descent Algorithm and Its Variants</title>
  <dc:creator>Imad Dabbura</dc:creator>
  <link>https://imaddabbura.github.io/posts/optimization/gradient-descent.html</link>
  <description><![CDATA[ 






<div class="status-badge-container" style="margin-bottom: 1rem;"><span class="status-badge evergreen">evergreen</span></div>
<section id="introduction" class="level2">
<h2 class="anchored" data-anchor-id="introduction">Introduction</h2>
<p><strong>Optimization</strong> refers to the task of minimizing/maximizing an objective function <img src="https://latex.codecogs.com/png.latex?f(x)"> parameterized by <img src="https://latex.codecogs.com/png.latex?x">. In machine/deep learning terminology, it’s the task of minimizing the cost/loss function <img src="https://latex.codecogs.com/png.latex?J(w)"> parameterized by the model’s parameters <img src="https://latex.codecogs.com/png.latex?w%20%5Cin%20%5Cmathbb%7BR%7D%5Ed">. Optimization algorithms (in case of minimization) have one of the following goals: - Find the global minimum of the objective function. This is feasible if the objective function is convex, i.e.&nbsp;any local minimum is a global minimum. - Find the lowest possible value of the objective function within its neighbor. That’s usually the case if the objective function is not convex as the case in most deep learning problems.</p>
<p>There are three kinds of optimization algorithms:</p>
<ul>
<li>Optimization algorithm that is not iterative and simply solves for one point.</li>
<li>Optimization algorithm that is iterative in nature and converges to acceptable solution regardless of the parameters initialization such as gradient descent applied to logistic regression.</li>
<li>Optimization algorithm that is iterative in nature and applied to a set of problems that have non-convex cost functions such as neural networks. Therefore, parameters’ initialization plays a critical role in speeding up convergence and achieving lower error rates.</li>
</ul>
<p><strong>Gradient Descent</strong> is the most common optimization algorithm in <em>machine learning</em> and <em>deep learning</em>. It is a first-order optimization algorithm. This means it only takes into account the first derivative when performing the updates on the parameters. On each iteration, we update the parameters in the opposite direction of the gradient of the objective function <img src="https://latex.codecogs.com/png.latex?J(w)"> w.r.t to the parameters where the gradient gives the direction of the steepest ascent. The size of the step we take on each iteration to reach the local minimum is determined by the learning rate <img src="https://latex.codecogs.com/png.latex?%5Calpha">. Therefore, we follow the direction of the slope downhill until we reach a local minimum.</p>
<p>In this notebook, we’ll cover gradient descent algorithm and its variants: <em>Batch Gradient Descent, Mini-batch Gradient Descent, and Stochastic Gradient Descent</em>.</p>
<p>Let’s first see how gradient descent and its associated steps works on logistic regression before going into the details of its variants. For the sake of simplicity, let’s assume that the logistic regression model has only two parameters: weight <img src="https://latex.codecogs.com/png.latex?w"> and bias <img src="https://latex.codecogs.com/png.latex?b">.</p>
<ol type="1">
<li>Initialize weight <img src="https://latex.codecogs.com/png.latex?w"> and bias <img src="https://latex.codecogs.com/png.latex?b"> to any random numbers.</li>
<li>Pick a value for the learning rate <img src="https://latex.codecogs.com/png.latex?%5Calpha">. The learning rate determines how big the step would be on each iteration.</li>
</ol>
<ul>
<li>If <img src="https://latex.codecogs.com/png.latex?%5Calpha"> is very small, it would take long time to converge and become computationally expensive.</li>
<li>IF <img src="https://latex.codecogs.com/png.latex?%5Calpha"> is large, it may fail to converge and overshoot the minimum.</li>
</ul>
<p>Therefore, plot the cost function against different values of <img src="https://latex.codecogs.com/png.latex?%5Calpha"> and pick the value of <img src="https://latex.codecogs.com/png.latex?%5Calpha"> that is right before the first value that didn’t converge so that we would have a very fast learning algorithm that converges (see figure 1).</p>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><a href="images/learning_rate.PNG" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure 1: Gradient descent with different learning rates Source"><img src="https://imaddabbura.github.io/posts/optimization/images/learning_rate.PNG" class="quarto-figure quarto-figure-left figure-img" width="600" height="400" alt="Figure 1: Gradient descent with different learning rates Source"></a></p>
</figure>
</div>
<figcaption><strong>Figure 1</strong>: Gradient descent with different learning rates <a href="http://cs231n.github.io/neural-networks-3/">Source</a></figcaption>
</figure>
</div>
<ul>
<li>The most commonly used rates are : <em>0.001, 0.003, 0.01, 0.03, 0.1, 0.3</em>.</li>
</ul>
<ol start="3" type="1">
<li>Make sure to scale the data if it’s on very different scales. If we don’t scale the data, the level curves (contours) would be narrower and taller which means it would take longer time to converge (see figure 2).</li>
</ol>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><a href="images/normalized-vs-unnormalized.PNG" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure 2: Gradient descent: normalized versus unnormalized level curves"><img src="https://imaddabbura.github.io/posts/optimization/images/normalized-vs-unnormalized.PNG" class="quarto-figure quarto-figure-left figure-img" width="800" height="300" alt="Figure 2: Gradient descent: normalized versus unnormalized level curves"></a></p>
</figure>
</div>
<figcaption><strong>Figure 2</strong>: Gradient descent: normalized versus unnormalized level curves</figcaption>
</figure>
</div>
<p>Scale the data to have <img src="https://latex.codecogs.com/png.latex?%5Cmu%20=%200"> and <img src="https://latex.codecogs.com/png.latex?%5Csigma%20=%201">. Below is the formula for scaling each example: <img src="https://latex.codecogs.com/png.latex?%5C%5C%7B%7D%5Cfrac%7Bx_i%20-%20%5Cmu%7D%7B%5Csigma%7D%5Ctag%7B1%7D%5C%5C%7B%7D%20"> 4. On each iteration, take the partial derivative of the cost function <img src="https://latex.codecogs.com/png.latex?J(w)"> w.r.t each parameter (gradient): <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%7D%7B%5Cpartial%20w%7DJ(w)%20=%20%5Cnabla_w%20J%5Ctag%7B2%7D%5C%5C%7B%7D"> <img src="https://latex.codecogs.com/png.latex?%5Cfrac%7B%5Cpartial%7D%7B%5Cpartial%20b%7DJ(w)%20=%20%5Cnabla_b%20J%5Ctag%7B3%7D%5C%5C%7B%7D"> The update equations are: <img src="https://latex.codecogs.com/png.latex?w%20=%20w%20-%20%5Calpha%20%5Cnabla_w%20J%5Ctag%7B4%7D%5C%5C%7B%7D"> <img src="https://latex.codecogs.com/png.latex?b%20=%20b%20-%20%5Calpha%20%5Cnabla_b%20J%5Ctag%7B5%7D%5C%5C%7B%7D"> * For the sake of illustration, assume we don’t have bias. If the slope of the current values of <img src="https://latex.codecogs.com/png.latex?w%20%3E%200">, this means that we are to the right of optimal <img src="https://latex.codecogs.com/png.latex?w%5E*">. Therefore, the update will be negative, and will start getting close to the optimal values of <img src="https://latex.codecogs.com/png.latex?w%5E*">. However, if it’s negative, the update will be positive and will increase the current values of <img src="https://latex.codecogs.com/png.latex?w"> to converge to the optimal values of <img src="https://latex.codecogs.com/png.latex?w%5E*"> (see figure 3):</p>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><a href="images/gradients.PNG" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure 3: Gradient descent. An illustration of how gradient descent algorithm uses the first derivative of the loss function to follow downhill it’s minimum."><img src="https://imaddabbura.github.io/posts/optimization/images/gradients.PNG" class="quarto-figure quarto-figure-left figure-img" width="600" height="400" alt="Figure 3: Gradient descent. An illustration of how gradient descent algorithm uses the first derivative of the loss function to follow downhill it’s minimum."></a></p>
</figure>
</div>
<figcaption><strong>Figure 3</strong>: Gradient descent. An illustration of how gradient descent algorithm uses the first derivative of the loss function to follow downhill it’s minimum.</figcaption>
</figure>
</div>
<ul>
<li>Continue the process until the cost function converges. That is, until the error curve becomes flat and doesn’t change.</li>
<li>In addition, on each iteration, the step would be in the direction that gives the maximum change since it’s perpendicular to level curves at each step.</li>
</ul>
<p>Now let’s discuss the three variants of gradient descent algorithm. The main difference between them is the amount of data we use when computing the gradients for each learning step. The trade-off between them is the accuracy of the gradient versus the time complexity to perform each parameter’s update (learning step).</p>
</section>
<section id="batch-gradient-descent" class="level2">
<h2 class="anchored" data-anchor-id="batch-gradient-descent">Batch Gradient Descent</h2>
<p>Batch Gradient Descent is when we sum up over all examples on each iteration when performing the updates to the parameters. Therefore, for each update, we have to sum over all examples: <img src="https://latex.codecogs.com/png.latex?w%20=%20w%20-%20%5Calpha%20%5Cnabla_w%20J(w)%5Ctag%7B6%7D"></p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(num_epochs):</span>
<span id="cb1-2">grad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_gradient(data, params)</span>
<span id="cb1-3">params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> learning_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> grad</span></code></pre></div></div>
<p>The main advantages:</p>
<ul>
<li>We can use fixed learning rate during training without worrying about learning rate decay.</li>
<li>It has straight trajectory towards the minimum and it is guaranteed to converge in theory to the global minimum if the loss function is convex and to a local minimum if the loss function is not convex.</li>
<li>It has unbiased estimate of gradients. The more the examples, the lower the standard error.</li>
</ul>
<p>The main disadvantages:</p>
<ul>
<li>Even though we can use vectorized implementation, it may still be slow to go over all examples especially when we have large datasets.</li>
<li>Each step of learning happens after going over all examples where some examples may be redundant and don’t contribute much to the update.</li>
</ul>
</section>
<section id="mini-batch-gradient-descent" class="level2">
<h2 class="anchored" data-anchor-id="mini-batch-gradient-descent">Mini-Batch Gradient Descent</h2>
<p>Instead of going over all examples, Mini-batch Gradient Descent sums up over lower number of examples based on batch size. Therefore, learning happens on each mini-batch of <img src="https://latex.codecogs.com/png.latex?b"> examples:</p>
<p><img src="https://latex.codecogs.com/png.latex?w%20=%20w%20-%20%5Calpha%20%5Cnabla_w%20J(x%5E%7B%5C%7Bi:i%20+%20b%5C%7D%7D,%20y%5E%7B%5C%7Bi:%20i%20+%20b%5C%7D%7D;%20w)%5Ctag%7B7%7D%5C%5C%7B%7D"></p>
<ul>
<li>Shuffle the training dataset to avoid pre-existing order of examples.</li>
<li>Partition the training dataset into <img src="https://latex.codecogs.com/png.latex?b"> mini-batches based on the batch size. If the training set size is not divisible by batch size, the remaining will be its own batch.</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(num_epochs):</span>
<span id="cb2-2">np.random.shuffle(data)</span>
<span id="cb2-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> batch <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> radom_minibatches(data, batch_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>):</span>
<span id="cb2-4">    grad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_gradient(batch, params)</span>
<span id="cb2-5">    params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> learning_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> grad</span></code></pre></div></div>
<p>The batch size is something we can tune. It is usually chosen as power of 2 such as 32, 64, 128, 256, 512, etc. The reason behind it is because some hardware such as GPUs achieve better runtime with common batch sizes such as power of 2.</p>
<p>The main advantages:</p>
<ul>
<li>Faster than Batch version because it goes through a lot less examples than Batch (all examples).</li>
<li>Randomly selecting examples will help avoid redundant examples or examples that are very similar that don’t contribute much to the learning.</li>
<li>With batch size &lt; size of training set, it adds noise to the learning process that helps improving generalization error.</li>
<li>Even though with more examples the estimate would have lower standard error, the return is less than linear compared to the computational burden we incur.</li>
</ul>
<p>The main disadvantages:</p>
<ul>
<li>It won’t converge. On each iteration, the learning step may go back and forth due to the noise. Therefore, it wanders around the minimum region but never converges.</li>
<li>Due to the noise, the learning steps have more oscillations (see figure 4) and requires adding learning-decay to decrease the learning rate as we become closer to the minimum.</li>
</ul>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><a href="images/batch-vs-minibatch.PNG" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Figure 4: Gradient descent: batch versus mini-batch loss function"><img src="https://imaddabbura.github.io/posts/optimization/images/batch-vs-minibatch.PNG" class="quarto-figure quarto-figure-left figure-img" width="800" height="300" alt="Figure 4: Gradient descent: batch versus mini-batch loss function"></a></p>
</figure>
</div>
<figcaption><strong>Figure 4</strong>: Gradient descent: batch versus mini-batch loss function</figcaption>
</figure>
</div>
<p>With large training datasets, we don’t usually need more than 2-10 passes over all training examples (epochs). Note: with batch size <img src="https://latex.codecogs.com/png.latex?b%20=%20m">, we get the Batch Gradient Descent.</p>
</section>
<section id="stochastic-gradient-descent" class="level2">
<h2 class="anchored" data-anchor-id="stochastic-gradient-descent">Stochastic Gradient Descent</h2>
<p>Instead of going through all examples, Stochastic Gradient Descent (SGD) performs the parameters update on each example <img src="https://latex.codecogs.com/png.latex?(x%5Ei,%20y%5Ei)">. Therefore, learning happens on every example:</p>
<p><img src="https://latex.codecogs.com/png.latex?w%20=%20w%20-%20%5Calpha%20%5Cnabla_w%20J(x%5Ei,%20y%5Ei;%20w)%5Ctag%7B7%7D"></p>
<ul>
<li>Shuffle the training dataset to avoid pre-existing order of examples.</li>
<li>Partition the training dataset into <img src="https://latex.codecogs.com/png.latex?m"> examples.</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(num_epochs):</span>
<span id="cb3-2">    np.random.shuffle(data)</span>
<span id="cb3-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> example <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> data:</span>
<span id="cb3-4">        grad <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> compute_gradient(example, params)</span>
<span id="cb3-5">        params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> params <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> learning_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> grad</span></code></pre></div></div>
<p>It shares most of the advantages and the disadvantages with mini-batch version. Below are the ones that are specific to SGD:</p>
<ul>
<li>It adds even more noise to the learning process than mini-batch that helps improving generalization error. However, this would increase the run time.</li>
<li>We can’t utilize vectorization over 1 example and becomes very slow. Also, the variance becomes large since we only use 1 example for each learning step.</li>
</ul>
<p>Below is a graph that shows the gradient descent’s variants and their direction towards the minimum:</p>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><a href="images/batch-vs-minibatch-vs-stochastic.PNG" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Figure 5: Gradient descent variants’ trajectory towards minimum"><img src="https://imaddabbura.github.io/posts/optimization/images/batch-vs-minibatch-vs-stochastic.PNG" class="quarto-figure quarto-figure-left figure-img" width="600" height="300" alt="Figure 5: Gradient descent variants’ trajectory towards minimum"></a></p>
</figure>
</div>
<figcaption><strong>Figure 5</strong>: Gradient descent variants’ trajectory towards minimum</figcaption>
</figure>
</div>
<p>As the figure above shows, SGD direction is very noisy compared to mini-batch.</p>
</section>
<section id="challenges" class="level2">
<h2 class="anchored" data-anchor-id="challenges">Challenges</h2>
<p>Below are some challenges regarding gradient descent algorithm in general as well as its variants - mainly batch and mini-batch:</p>
<ul>
<li>Gradient descent is a first-order optimization algorithm, which means it doesn’t take into account the second derivatives of the cost function. However, the curvature of the function affects the size of each learning step. The gradient measures the steepness of the curve but the second derivative measures the curvature of the curve. Therefore, if:</li>
<li>Second derivative = 0 <img src="https://latex.codecogs.com/png.latex?%5Crightarrow"> the curvature is linear. Therefore, the step size = the learning rate <img src="https://latex.codecogs.com/png.latex?%5Calpha">.</li>
<li>Second derivative &gt; 0 <img src="https://latex.codecogs.com/png.latex?%5Crightarrow"> the curvature is going upward. Therefore, the step size &lt; the learning rate <img src="https://latex.codecogs.com/png.latex?%5Calpha"> and may lead to divergence.</li>
<li>Second derivative &lt; 0 <img src="https://latex.codecogs.com/png.latex?%5Crightarrow"> the curvature is going downward. Therefore, the step size &gt; the learning rate <img src="https://latex.codecogs.com/png.latex?%5Calpha">.</li>
</ul>
<p>As a result, the direction that looks promising to the gradient may not be so and may lead to slow the learning process or even diverge. - If Hessian matrix has poor conditioning number, i.e.&nbsp;the direction of the most curvature has much more curvature than the direction of the lowest curvature. This will lead the cost function to be very sensitive in some directions and insensitive in other directions. As a result, it will make it harder on the gradient because the direction that looks promising for the gradient may not lead to big changes in the cost function (see figure 7).</p>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><a href="images/curvature.PNG" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="Figure 6: Gradient descent fails to exploit the curvature information contained in the Hessian matrix. Source"><img src="https://imaddabbura.github.io/posts/optimization/images/curvature.PNG" class="quarto-figure quarto-figure-left figure-img" height="400" alt="Figure 6: Gradient descent fails to exploit the curvature information contained in the Hessian matrix. Source"></a></p>
</figure>
</div>
<figcaption><strong>Figure 6</strong>: Gradient descent fails to exploit the curvature information contained in the Hessian matrix. <a href="http://www.deeplearningbook.org/contents/numerical.html">Source</a></figcaption>
</figure>
</div>
<ul>
<li>The norm of the gradient <img src="https://latex.codecogs.com/png.latex?g%5ETg"> is supposed to decrease slowly with each learning step because the curve is getting flatter and steepness of the curve will decrease. However, we see that the norm of the gradient is increasing, because of the curvature of the curve. Nonetheless, even though the gradients’ norm is increasing, we’re able to achieve a very low error rates (see figure 8).</li>
</ul>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><a href="images/gradient_norm.PNG" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="Figure 7: Gradient norm. Source"><img src="https://imaddabbura.github.io/posts/optimization/images/gradient_norm.PNG" class="quarto-figure quarto-figure-left figure-img" width="600" height="300" alt="Figure 7: Gradient norm. Source"></a></p>
</figure>
</div>
<figcaption><strong>Figure 7</strong>: Gradient norm. <a href="http://www.deeplearningbook.org/contents/optimization.html">Source</a></figcaption>
</figure>
</div>
<ul>
<li>In small dimensions, local minimum is common; however, in large dimensions, saddle points are more common. Saddle point is when the function curves up in some directions and curves down in other directions. In other words, saddle point looks a minimum from one direction and a maximum from other direction (see figure 9). This happens when at least one eigenvalue of the hessian matrix is negative and the rest of eigenvalues are positive.</li>
</ul>
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<div class="quarto-figure quarto-figure-left">
<figure class="figure">
<p><a href="images/saddle.PNG" class="lightbox" data-gallery="quarto-lightbox-gallery-8" title="Figure 8: Saddle point"><img src="https://imaddabbura.github.io/posts/optimization/images/saddle.PNG" class="quarto-figure quarto-figure-left figure-img" width="600" height="300" alt="Figure 8: Saddle point"></a></p>
</figure>
</div>
<figcaption><strong>Figure 8</strong>: Saddle point</figcaption>
</figure>
</div>
<ul>
<li>As discussed previously, choosing a proper learning rate is hard. Also, for mini-batch gradient descent, we have to adjust the learning rate during the training process to make sure it converges to the local minimum and not wander around it. Figuring out the decay rate of the learning rate is also hard and changes with different datasets.</li>
<li>All parameter updates have the same learning rate; however, we may want to perform larger updates to some parameters that have their directional derivatives more inline with the trajectory towards the minimum than other parameters.</li>
</ul>


</section>

<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Deep Learning</category>
  <category>Machine Learning</category>
  <category>Optimization</category>
  <guid>https://imaddabbura.github.io/posts/optimization/gradient-descent.html</guid>
  <pubDate>Mon, 18 Feb 2019 06:00:00 GMT</pubDate>
  <media:content url="https://imaddabbura.github.io/posts/optimization/images/gradient_cover.PNG" medium="image"/>
</item>
</channel>
</rss>
